8 ms·
A Shader Trick
- gfd 5y agoI think I've read that minecraft behaves differently depending on how far away you are standing from the origin for similar reasons: [1]https://minecraft.fandom.com/wiki/Bedrock_Edition_distance_effects https://minecraft.fandom.com/wiki/Bedrock_Edition_distance_e... [2]https://minecraft.fandom.com/wiki/Java_Edition_distance_effects https://minecraft.fandom.com/wiki/Java_Edition_distance_effe...
- bemmu 5y agoIn Roblox this happens if you get thrown too far: https://www.youtube.com/watch?v=Fw1GZLodmtw https://www.youtube.com/watch?v=Fw1GZLodmtw
- SquareWheel 5y agoYes, the Minecraft Farlands are caused by precision issues of floating point numbers. The game becomes jittery as it tries snapping to fewer and fewer precise digits. This is most obvious at first in the selection box around blocks, but the terrain also becomes unstable after a certain point. Note that one Youtuber KurtJMac has been walking to the Farlands since 2011. He's been raising money for charity as part of those videos/streams. He's roughly 40% of the way there. https://www.farlandsorbust.com/ https://www.farlandsorbust.com/ https://www.youtube.com/user/kurtjmac https://www.youtube.com/user/kurtjmac
- skykooler 5y agoHuh, I remember seeing some of his videos about five years ago and wondering if he'd ever make it. Glad to see he's still going!
- kylewlacy 5y agoAnother YouTuber beat him to the punch about 2 years ago actually! Here's the moment from his stream where he finally reached the Farlands [volume warning]: https://youtu.be/VAvQ_kT73W4?t=25696 https://youtu.be/VAvQ_kT73W4?t=25696 (at the 7:08:17 mark)
- lostmsu 5y agoGoogle for Minecraft Farlands took off half an hour of my life. Amazing floating-point-related bug.
- initplus 5y agoNote these are seperate issues, caused by the same underlying issues with float precision. Some game engines solve the jittery rendering issues by moving the world relative to the player/camera, rather than the opposite (though it may be more intuitive). This way all your shaders work in nice accurate low floats relative to the camera at the origin, no matter the player location in the world. But to implement worldgen with relative coordinates would be much more complex.
- bicarbonato 5y agoI believe that every game have this problem if you are far enough away from the origin. 3kliksphilip did a great video on this subject: https://www.youtube.com/watch?v=eK7eNgiQfhk https://www.youtube.com/watch?v=eK7eNgiQfhk
- sgtnoodle 5y agoI first encountered this in Active Worlds around 2000 or so, as a teenager. I managed to get the head developer to check it out, and he seemed surprised by it.
- rezmason 5y agoI've encountered that, too, in my Excel 97 easter egg reproduction. My terrain loops in the shader, but camera position is governed by the JavaScript, so it can get weird far from origin if I don't sanitize. Here's how the terrain normally looks, zoomed out with the grid on: https://rezmason.github.io/excel_97_egg/?o=bq https://rezmason.github.io/excel_97_egg/?o=bq And here's how it looks 8589990 units from the origin, with sanitizing off: https://rezmason.github.io/excel_97_egg/?o=bq&sanitizePosition=false&l=8589900,0 https://rezmason.github.io/excel_97_egg/?o=bq&sanitizePositi... There's two weird phenomena happening there: the lack of camera position precision causes the terrain to shift left and right, but a little bit further, you can see that all the terrain quads collapse in one dimension for some reason. Someone also made a game out of this, called Floating Point Leviathan: https://yanknoopdev.itch.io/fpl https://yanknoopdev.itch.io/fpl
- Scene_Cast2 5y agoCould you use a non-float integer instead? Do GPUs have cheap int % float -> float operations?
- aqme28 5y agoI'm confused why you need a global total game-time anyways, or if you have one why it has to be precise. Could you use your precise time in float, and then increment a larger but less-precise total gametime value every 10 minutes?
- zamadatix 5y agoIt doesn't have to be game time, as the article says it could be time since level or some other trigger. It's basically about any time based counter you need for a time based effect, such as a predefined cyclical wind effect on trees or waves in water. The article also explains why it has to be precise, looping effects will behave oddly if the loop is not occurring precisely. Splitting the number into a large part and a precise part doesn't actually solve for anything, it just moves the problem into "how do I make arbitrary effects precisely loop based on the 2 parts of the time" instead of "how do I make arbitrary effects precisely loop based on the time".
- dataangel 5y agoIf it moves the problem far enough forward in practice it won't matter though. If somebody runs The Witness for 100 days that's on them
- zamadatix 5y agoIt doesn't move it to 100 days, it'd move the problem to 10 minutes. Shaders can't just take a large number as a large piece and small piece unmodified, solving for this in the logic is the same as solving for the original cycle matching problem.
- hwillis 5y ago1. Integers would make the situation easier, but still overflow after ~50 days. You can't just increment delta every frame; frame times vary significantly and and if you aren't precise to the millisecond things will jump around. 2. It's not exactly cheap, and I don't think compilers put much effort into making sure you actually retain that precision. I have only really used floats in shaders though, and don't know what would happen. 3. I'm pretty sure that in practice you'd lose out on a lot of hardware-accelerated functions, doing trig and interpolations with multiple messy conversions. It's also possible you'd fuck up some compiler optimizations.
- GrantS 5y agoAha! This explains a 20-year-old mystery bug! In undergrad, one of my side projects was a procedural 3D world from scratch (kind of Myst meets Halo), including physics, graphics (OpenGL), and the raw synthesized sound of ocean waves and plasma grenade explosions from sine waves via granular synthesis. After about 10 minutes exploring the world (or running around throwing tiny spheres down hills and scattering them with plasma grenades) my relaxing synthesized ocean wave sounds started to sound garbled and distorted so I always had to restart the program — now I know why :)
- webkike 5y agoI think that instead of this, my solution would be to have each function that depends on dt to accumulate the dt themselves, and reset it at their individual periods. A Rust trait with an associated const would help with this: trait TimeDependentFn { const PERIOD: f32; type Output; fn call(&self, dt: f32) -> Self::Output; }
- nmilo 5y agoBut then you would need a local dt for each time-dependent function, and shaders don't really have packaged-up member variables in structs like you think they would if you come from Rust. That means you would have to maintain a massive list of dt's for each time-dependent function your shader calls, and then change this list everytime you introduce or remove a time-dependent function to the shader code. It's a solution that only works in a language that can hide the complexity from the programmer like Rust.
- ruined 5y agothis is about shaders, which have no persistent memory even pixel-to-pixel on the same frame. there is no way to accumulate over time. and calculating outside of the shader would miss the point. so you're stuck doing it from scratch every pixel. that's fine, shaders are fast. at most it might make sense to calculate a truncated time globally per frame and provide that as a uniform.
- Waterluvian 5y agoThe blog suggests that you need to use integers to describe time because floats have problems. Granted. Why limit one’s self to absolutely having to describe it in a single integer value? Why not some wrapper around a handful of integer values that can handle a much bigger max? Would this have a meaningful performance cost?
- ruined 5y agoyou'd still end up either losing precision or using a truncated (cycling) part when you wanted to use it in a float calculation. it's effectively the same.
- sgtnoodle 5y agoYou could simply use a single 64 bit integer in, say, nanoseconds. That would give you 584 years of range. Just convert the integer into a float before passing it into the shader. For periodic effects, apply the appropriate modulo. Fog doesn't change very rapidly, so if wrapping is a pain, you could just accept the loss in precision. You could round the value as part of the conversion so that the precision doesn't change over the range. With 1 second precision, you should be good for a few months with a 32 bit float.
- merlincorey 5y agoYou can't put a 64 bit integer into a 32 or even 64 bit floating point number since at least some of those bits aren't available even in the 64 bit floating point number.
- sgtnoodle 5y agoI'm specifically suggesting transforming the integer in an intelligent way to map well to the mantissa of the floating point representation, based on the intended usage. High frequency animations can chop off the most significant bits, while low frequency animations can chop off the bottom bits. If you aren't able to constrain the design or make assumptions about frequencies, then it seems like you have to instead parameterize time as some sort of tuple so that you have more bits, such as the sum of a course absolute and a fine offset, and write the shader to cope with it. That's how time APIs in many operating systems work, where there's integer seconds and integer micro or nanoseconds.
- ruined 5y agoi have been doing this for a while playing around with shadertoy. i don't think i learned it from anywhere, i just wanted some things to match up and repeat over hours and it was obvious that sin/cos are periodic, and cycling the time value would improve the imprecision at the seam. seeing it delivered as a clever trick in a blog post makes me wonder if i'm more competent than i thought, or if everyone else is generally less competent than i thought.
- EveYoung 5y agoYou can use a similar approach for adding time features to a ML model. In this context, I think it is a quite common technique.
- sgtnoodle 5y agoIt's a little bit of both, probably?
- josephg 5y agoEvery now and then I have to remind myself that things that are obvious to me aren't obvious to everyone else. And thus, sometimes its worth explaining "obvious" things. This is probably true for everyone.
- Etherlord87 5y agoThe trick isn't so much about looping the sine/cosine. It's about setting synchronizing the maximum time value passed to a shader, with maximum precision of an arbitrary multiplicand used in the shader. > How do we ensure that, easily, in a way that people don't have to think about? The relation is that the time should switch from 10^x to 0, and in the shader the number of digits after a dot should be no more than x.
- ChrisLomont 5y agoI've used a similar trick for decades: for example I put it in the code for my Hypnocube (www.hypnocube.com) product in 2005 (Hypnocube). I used it in games and digital art projects before (and after) that. For embedded or low resource computing, sin/cos may be expensive, so I use a table based fixed point version. I pick the table to have size power of 2, making lots of things easier. Then to make time wrap, I use a large power of 2, which is exactly the same as this trick, with base 10 replaced with base 2 (and using fixed point math). You also hit problems where delta times can go negative, so those also need to be max time aware. In short, I always make a timing module, it tracks time (and stretches it as needed), and doles out a few things used everywhere: a delta frame time, a large time (say 64 bits as ns for 584 year wraparound), and a capped time (say 16 or 24 bits) to use in places where you know the wrap amount and still give space for computations not to overflow. As far as I know the Hypnocube never repeats nor does anything flicker at any time due to bad wraparounds. But that took work to ensure.
- jimmygrapes 5y agoI remember seeing Hypnocube references and your site probably around 2005 or 2006, but it was so far beyond me at the time I just wrote it off as "cool OK". YouTube wasn't quite what it is today so I never did get to see what it looks like in motion. Today I assume similar things can be done extraordinarily cheap and easily but I still have no idea how. I'm not motivated enough right now to ask, but I just wanted to thank you for giving younger-me's friends something exciting to talk about for a short while.
- ChrisLomont 5y agoCool. Glad you liked it. It's been a fun gadget to make and sell.
- hwers 5y agoCan't believe it's almost as long since this game was released as the time we waited for it.
- hedgehog 5y agoThe Patriot range gate bug is a relatively famous issue with a similar cause related to numeric precision and timers: https://cs.nyu.edu/exact/resource/mirror/patriot.htm https://cs.nyu.edu/exact/resource/mirror/patriot.htm
- Const-me 5y agoWhen I need something like that, I usually passing [ 0 .. 1 ] float phase to the shaders, and when updating the number on CPU I wrap the value into that range after the increment.
- jstanley 5y agoI've been playing The Witness recently. I've enjoyed it a lot, and had a lot of "aha moments", but I wouldn't say I altogether understand the game. It has a very elusive nature. I'm working through (what I believe to be) the "ending" at the moment, but there is still a lot of the pre-ending stuff that I haven't figured out yet. At first glance The Witness is a puzzle game, made annoying by the fact that you have to walk everywhere instead of clicking "Next Puzzle". But then you occasionally see things in the environment that are just really pleasing. An example that springs to mind is a bunch of broken metal in a window[0], and some branches on a tree, that line up with the sun (which doesn't move) to cast a shadow of a woman sitting underneath a tree[1]. And there's loads of this stuff. And noticing it doesn't contribute anything at all to the apparent objective of the game (except where it does!), but it adds so much that you just wouldn't get if it were the simple puzzle game that it initially appears to be. I really like this game. [0] https://img.incoherency.co.uk/3706 https://img.incoherency.co.uk/3706 [1] https://img.incoherency.co.uk/3705 https://img.incoherency.co.uk/3705
- nurbl 5y agoI think it's the game I've played that comes closest to feeling like a genuine mystery. Even though I think I've finished it, there are many aspects I still don't "get". It may just be that it's good at simulating this experience (sort of like a Lynch movie) but I don't think it matters. It's a great puzzle game wrapped in an enigma. And a piece of art too.
- greggman3 5y agoThat game has a moment when you realize it's not quite about what you thought it was about. I hope this isn't a spoiler but there are black obelisks around the world. If you know what they are for then you've found this secondary goal
- somehnacct3757 5y agoI think one of the key flourishes of the game has to do with intrinsic vs extrinsic motivation. The game never presents a locked door where there is some key elsewhere, and the key goes into your inventory, and now you can open the door. At all times, all doors are openable. But you may not know the rules to the puzzle on the door yet. Once you learn them, the 'key' is knowledge and the 'inventory' is your mind. If you're keen to this then you will find it surprising how few games work this way.
- brundolf 5y agoThis is a brand-new post. I'm surprised he's still blogging on the site for The Witness, given that it came out six years ago and I don't think it's seen any activity in terms of ports or content updates since the iOS port in 2017. Is it even still getting patches?
- mleonhard 5y agoWould it be useful to have a 'fraction' type that represents values in [0,1] with uniform precision? Then instead of time deltas, we could work with fractions of the animation period. When switching from float to fraction, we can remove code that handles float's edge cases: +inf, -inf, NaN, -0.0, non-canonical encodings, and loss of precision.
- addaon 5y agoWe have various integer types, and e.g. a 0.32 or 0.16 fixed point representation is trivial on top of integer types; and is also the only efficient uniform representation on [0, 1) (which is probably more valuable than [0, 1]).
- thenoblesunfish 5y agoI didn't see an obvious mention of it on the page, but this site has an RSS feed at http://the-witness.net/news/feed http://the-witness.net/news/feed