I want to render a planet like sphere. The general idea is as follows:
- Generate a bunch of unit length vertices which make up a sphere.
- While rendering the sphere the shader evaluates the 3D simplex noise at the point on the unit sphere.
- The result is used as the "height" to displace the current vertex along its direction.
Up to here everything is working like it should.
Now I want to add lighting and therefore need normals to the surface.
While implementing the lighting related parts I quickly added a method to estimate the normals of the terrain using partial derivatives in the fragment shader like this:
vec3 X = dFdx(ins.position);
vec3 Y = dFdy(ins.position);
vec3 normal = normalize(cross(X,Y));
where ins.position
is the interpolated world position.
While this works it does not look very good, because it essentially results in per-face normals.
Now to the actual questions:
- Calculating per-vertex normals would result in smooth normals, unlike in the picture, correct?
- One of the advantages of Simplex Noise over Perlin Noise is that is has a "well-defined and continuous gradient everywhere that can be computed quite cheaply" (to cite the excellent Simplex Noise demystified) and with the gradient one should be able to compute the normal, correct?
If the seconds question is a "yes" I have two problems:
- The simplex noise algorithm was taken from a popular source, which sadly does not include the gradient calculation. I will post my attempt of adding it below, but I have no idea if it is correct.
- Even if I had the gradient I am stuck on deriving the normal from there.
Any help is greatly appreciated!
My shot at the gradient implementation (replaced the last few lines of snoise):
float snoise(vec3 v, out vec3 grad)
{
......
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
vec4 m2 = m * m;
vec4 m4 = m2 * m2;
vec4 pdotx = vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3));
vec4 temp = m2 * m * pdotx;
grad = -8.0 * (temp.x * x0 + temp.y * x1 + temp.z * x2 + temp.w * x3);
grad += m4.x * p0 + m4.y * p1 + m4.z * p2 + m4.w * p3;
grad *= 42.0;
return 42.0 * dot(m4, pdotx);
}
UPDATE:
The part about calculating the surface normal from the gradient has been answered here: Surface normal to point on displaced sphere.
The remaining question now is how to implement the gradient calculation into the GLSL version of the 3D Simplex Noise, because my implementation seems to have issues.
UPDATE 2:
The gradient calculation seems to be almost right, just the scaling seems to be off.
Instead of multiplying by 42, dividing by 5 gives pretty good results, but that was found out by trial and error. A proper scaling factor would be nice.
Alright, turns out my problem was almost purely math related.
If anyone is interested:
The GLSL implementation of the gradient calculation posted in the question is totally correct.
Calculating the normals directly from the gradient is possible as described here.
And the result looks exactly like I wanted it to be, I am happy ;)