Thursday, December 4, 2008

Lighting Revisited (AKA I'm Back!)

So, after almost 3 months of being sidetracked with various other projects I came up with, I finally decided to come back to C# and XNA with welcome arms.

And I have come back in a big way, in my opinion. After discussing it with one of my friends, I decided that it would be best to procedurally generate a moon instead of a planet. This greatly simplifies many things; no atmosphere, no fog, no water, no vegetation, etc. However, this meant that I needed to figure out how to properly do two things: generate craters and have realistic lighting.

You may recall that I had a previous blog entry about my attempts with realistic lighting. [] In the end, I determined that it would be too difficult to do now, and easy to do in the future once I had geometry shader support. So, I had settled with simple spherical lighting across the entire planet.

So, it took me quite a bit of effort to even start looking at alternative ways to render realistic lighting. In the past I was trying to calculate the other two vertices of the triangle in the vertex shader. It was with a great facepalm that I realized that what I was trying to find was the tangent of the surface and then I could use that to adjust the spherical normal. Well, as you may remember from your math classes, a tangent is calculated by taking the derivative of the original function. So, I looked into quite a few articles about how to find the derivative of a Perlin Noise function.

This article was very informative, but I wasn't quite sure on what speeds would be on the GPU:

Finally, I found an article by Ken Perlin himself that shows how to approximate the derivative: (Section 5.6)

With this little nugget of knowlege I went into my shader and wrote a function to do exactly that.

float3 NoiseDerivative(float3 position, float e)
//calculate the height values using Perlin Noise
float F0 = ridgedmf(position*noiseScale, 8);
float Fx = ridgedmf(float3(position.x+e, position.y, position.z)*noiseScale, 8);
float Fy = ridgedmf(float3(position.x, position.y+e, position.z)*noiseScale, 8);
float Fz = ridgedmf(float3(position.x, position.y, position.z+e)*noiseScale, 8);
float3 dF = float3(Fx-F0, Fy-F0, Fz-F0) / e;

//calculate the actual normal
return normalize(position - dF);

Then I simply added a call to this function in my pixel shader:
input.Normal = NoiseDerivative(input.Normal, 0.000001);

Lo and behold, it worked!

Check out the pics:

No comments: