Wiki

Clone wiki

PolyVox / Computing normals in a fragment shader


Last tested version of PolyVox: Shouldn't matter


PolyVox has two different 'cubic' surface extractors, one which includes normals for the vertices and one which doesn't. The advantage of not including vertex normals is that many more vertices can be shared (for example, on the corner of a cube) which results in fewer vertices overall. However, it then raises the question of where you get the normals for lighting calculations. One option is to computer these normal in the pixel shader.

This can be done using derivitive instructions. Derivitive instructions in a pixel shader calculate the rate at which a particular variable is changing betwen fragments. By using these instructions on our world space position we can compute two vectors which lie on the triangle being drawn. The cross product of these gives us the surface normal.

The following code shows how this is done in Cg/HLSL:

float3 worldNormal = cross(ddy(inWorldPosition.xyz), ddx(inWorldPosition.xyz));
worldNormal = normalize(worldNormal);

And in GLSL:

vec3 worldNormal = cross(dFdy(inWorldPosition.xyz), dFdx(inWorldPosition.xyz));
worldNormal = normalize(worldNormal);

You may find you need to flip the resulting normal - I'm not certain about this and it may differ between OpenGL/Direct3D.

I don't have any performance data about the cost of doing this vs interpolating normals from the vertices. And obviously this approach is no good if you need normals in your vertex shader.

A more complete GLSL Example

Vertex Shader

varying vec3 v_V;
varying vec3 v_P;
 
void main()
{
	gl_Position = ftransform();
	v_P = gl_Position.xyz; // v_P is the world position
	v_V = (gl_ModelViewMatrix * gl_Vertex).xyz;
}

Fragment Shader

#version 120

varying vec3 v_V;
varying vec3 v_P;

void main() {
	vec3 N = normalize(cross(dFdy(v_P), dFdx(v_P))); // N is the world normal
	vec3 V = normalize(v_V);
	vec3 R = reflect(V, N);
	vec3 L = normalize(vec3(gl_LightSource[0].position));

	vec4 ambient = gl_FrontMaterial.ambient;
	vec4 diffuse = gl_FrontMaterial.diffuse * max(dot(L, N), 0.0);
	vec4 specular = gl_FrontMaterial.specular * pow(max(dot(R, L), 0.0), gl_FrontMaterial.shininess);

	gl_FragColor = ambient + diffuse + specular;
}

Updated