Friday, November 11, 2005

Oh yes, they float, Georgie, they all float!


I'm just about finished with floating objects using Novodex so I figured I'd share what I came up with.

The buoyant force on an object in a fluid, according to Archimedes' Principle, is F = M * G, where M is the mass of liquid displaced by the object and G is the acceleration due to gravity. To simulate buoyancy then, we need to figure out how much water the floating object displaces at any given time. For this we take our model (of a marshmallow peep) and "voxelize" it, generating sample points throughout the peep.


Each point is assigned a fraction of the peep's total volume, and when under water, each point receives a fraction of the buoyant force. Gravity is constantly pulling the peep under water and the peep floats when the buoyant forces from the submerged sample points cancel out the gravitational force. The mass of the water displaced is found by taking the volume of dispacement times the density of water (1 gram per cubic centimeter). That times the negative gravity vector gives us the buoyant force at any given sample point.

Sounds simple and straight-forward, and it works great with enough sample points and a high framerate. Problems arise, however, when realtime physics simulation rears it's ghetto head. The biggest problem I had to battle was framerate. If the simulation framerate is too low, in one frame the peep can be given enough force to toss it straight out of the water. Once entirely out of the water in the next frame, the peep now has no submerged sample points, and gravity will pull it deep under water once again, and the cycle repeats. This causes the peep to jump up and down in the water, sometimes several feet into the air!

To account for this sort of crap (which shows up all over the place in physics simulation), Novodex uses a constant time-step. In a given frame, Novodex calls it's solver function a variable number of times. If a frame lasts 0.1 seconds and the time-step is set to 60 Hertz, the solver is run 6 times. This allows for more consistent, framerate independent results.

Again, sounds great in theory, but there are still issues with our water. Ideally, you would want to check for the number of submerged points and apply the appropriate buoyant forces at every time-step. The Novodex trigger callback will tell you all about the submerged points, but will not let you apply any forces between time-steps. Apparently there are some threading issues internal to Novodex that prevent you from changing the state of the physics simulation in any callback or between time-steps.

Someone on the Novodex forums promised a special way to create force fields in a future release which will hopefully resolve all these problems. Meanwhile you have to find a hacky stabilizing solution. The biggest fix which gets you 90% of the way there is fluid friction; there is lots of it in water. It's not even a hack really, I just forgot about it at first, and without it, things would indeed bounce in and out of water. I didn't even bother to figure out the frictional force based on water density and peep surface area, I just hacked it with the linear and angular damping constants in Novodex. It took so much tweaking to get things to stablize, I wasn't going to sweat physical accuracy. I based the amount of damping on the number of submerged points. I increased the damping logarithmically so the amount of damping climbed rapidly even with a few points in the water, this just seemed to give better results.

Even with fluid friction, there is a lot of room for the peep to jump up and down on the surface, especially with a low density at low framerates (~10 fps things got really bad). I tried several things: clamping the velocity of the peep when partially submerged works really well. But when the peep is released from deep under water, there is a noticeable sluggish jerk as it breaks the surface and it's movement gets clamped.

I ended up with the following hack: given the sample point position P, the point-velocity V at point P, length of the frame dT, some constant C>1, the buoyant force F, and some multiplier 0<m<1, if( P + V * dT * C is above water level ) F = F * M. This tries to predict if the current velocity will shoot the sample point out of the water within the next frame. If so, scale down the buoyant force applied at the point severely. I added the C and M constants in there so I could tweak the equation. I found C=2.5, M=0.1 to be good values.

And the results? Good enough for me. Things still wobble a bit more at lower framerates but it looks like the wind is picking up, no more trampolining peeps though! I'm still testing other ideas (I also want to take force into consideration in the hack equation) and I have to test more shapes than just the marshmallow peep, but this seems like a good enough solution for now.

Tuesday, November 01, 2005

HDR to LDR Reflection Hack


I was sitting and staring (in awe) at our flooded golf-course demo again and I noticed that even though we have fresnel falloff on the water surface, the relative brightness of the sun's reflection never changes no matter where I move to. So I thought about it some more and it turns out, assuming a flat reflection plane, an infinitely distant object will only ever be viewed under one incident eye angle. The fresnel term (the amount of light reflected off the water surface, determined by the angle of incidence) will never change for the sun's reflection, nor for any other texel in the environment map. The diagram illustrates that the eye can be placed anywhere. The reflection angle R is always equal to the angle of incidence, I, and R has to be the same for the reflected vector to sample the same texel in the environment.


This means the fresnel falloff effect can be preprocessed on the environment map and baked directly into the texture. The resulting environment would remain bright at the horizon and get darker and darker towards the top and bottom. Aside from cutting precious shader instructions, this technique also allows us to take our high dynamic range environment map, bake the fresnel into it, and save it as a low dynamic range image without loss of quality! Because the benefits of HDR images are very noticeable when the images are modulated, fresnel falloff on the sky reflection really stands out in HDR. At greater angles of incidence the sky gets relatively dim but the sun remains blown out. When fresnel faloff is baked into the environment texture, there is no longer a need for any modulation, just a straight-up texture lookup will do. This means LDR will be as good as HDR.

Now, there are some minor drawbacks. When you give up true HDR you give up hopes of doing post processing effects like exposure, etc. It's also going to be difficult integrating the fresnel-baked skybox with reflections of the rest of the world, which is why we are only going to implement this algorithm in the environment-reflection-only render path. Also, the algorithm makes the assumption that the reflecting surface is a flat plane. It does not hold true for normal-mapped water because different surface normals combined with different eye positions can lead to the same texel being looked up.


However in practice (I'm hoping as we haven't actually tried this) the difference should be negligible. The overall effect is still there. Objects close to the horizon reflect more than those directly above the viewer and localized bumps in the normal-map will also appear to have fresnel falloff, it just won't be as accurate as it could be.

In other HDR news, the Lost Coast Half Life 2 level finally came out. It's a small technology demo for HDR support in the Source engine, complete with commentary from artists and programmers at Valve. The commentary is pretty cool, though they make it seem as if they invented ... any of the HDR stuff in the demo. The visual quality is quite impressive though, the weapons and water look a lot better. Even their use of bloom, also known as the chlorine-in-the-eyes effect, is relatively tasteful. One artifact that became very apparent was the transition between reflection maps on the guns. It just snaps from one cubemap to the next and can be quite distracting at times. We had talked about this too, as we will have to transition between both diffuse and specular irradiance maps when walking into, say, a densely wooded area.

I also liked their use of dynamic tone mapping (retinal response, dynamic exposure, whatever you want to call it). It was more subtle than other demos I've seen and did not look like someone was dimming the lights randomly. It was still a little touchy. There were times where it looked like the sun was being periodically blocked by clouds rather than exposure adjustment but it was unobtrusive. I would have liked to see it change through a wider exposure range and take longer to adjust though. It was also interesting how they weighed the brightness of pixels towards the center more heavily than those around the outside of the screen. I wonder if the exposure fluctuated too randomly otherwise. Anyway, props to Valve for talking about technical and game design decisions openly, and in a neat format too.