Tuesday, June 19, 2012

Let there be light

I remember many many years ago, when first learning graphics concepts, I was working though 'Programming Role Playing Games with DirectX'. There was a model included on the CD of a castle with a mote and I thought it looked terrible. The textures were gaudy, the resolution was low and it was quiet jarring and unpleasant to look at. In a later chapter, an introduction to lights and shading, the same model was reused. The difference in results was unbelievable. With the correct shading depth perception was easier and the scene, despite its initial poor quality, took on a much more natural and realistic feel.

Here is a comparison of the same mesh, with the same texture, rendered from the same point of view with and without shading.


The picture above, and the video below, both use a simple diffuse lighting calculation to shade the side of the earth which is facing away from the sun. This can be implemented as per vertex lighting, or per pixel lighting. Each method has pros and cons which I shall briefly discuss here. If I get anything wrong or leave something out please comment.

(Instead of using the diffuse value to shade the back side of the earth, I use it as a ratio in a multisample between a day and night time texture. Since the night time texture is already colored to show shade and darkness the result is the same but I also get lights from civilization on the dark side)

Per Vertex Lighting
  • Diffuse value is calculated in the vertex shader.
  • Faster since diffuse value is only calculated once per polygon and each pixel in the polygon has the same diffuse value (or is that 3 times per polygon, once per vertex, and the diffuse value is interpolated across the face of the poly?)
  • Since the diffuse value is per vertex and not per pixel the value is not always correct and some shade popping occurs. Check out the video to see this.
Vertex Shader

attribute vec4 position;
attribute vec4 normal;
attribute vec2 uv0;

varying vec2 _uv0;
varying float _diffuse;

uniform mat4 modelViewProjectionMatrix;
uniform vec4 normalizedSunPosition;

void main()
{
    //Texure coordinates are needed in fragment shader
    _uv0 = uv0;

    //Calculate diffuse value
    vec4 nor = normalize(modelViewProjectionMatrix * normal);
    _diffuse = max(dot(nor, normalizedSunPosition), 0.0);
   
    //Translate vertex
    gl_Position = modelViewProjectionMatrix * position;
} 

Fragment Shader

varying lowp vec2 _uv0;
varying lowp float _diffuse;

uniform sampler2D dayTexture;
uniform sampler2D nightTexture;

void main()
{
    gl_FragColor = (texture2D(nightTexture, _uv0) * (1.0 - _diffuse)) + (texture2D(dayTexture, _uv0) * _diffuse);
}


Per Pixel Shading
  • Diffuse value is calculated in the fragment shader.
  • Potentially slower as there are generally allot more pixels rendered than vertices and therefore allot more diffuse calculations.
  • More realistic, smooth results.
Vertex Shader

attribute vec4 position;
attribute vec4 normal;
attribute vec2 uv0;

varying vec2 _uv0;
varying vec4 _normal;

uniform mat4 modelViewProjectionMatrix;

void main()
{
    _uv0 = uv0;
    _normal = normalize(modelViewProjectionMatrix * normal);
    gl_Position = modelViewProjectionMatrix * position;
}

Fragment Shader

varying lowp vec2 _uv0;
varying lowp vec4 _normal;

uniform sampler2D dayTexture;
uniform sampler2D nightTexture;

uniform lowp vec4 normalizedSunPosition;

void main()
{
    lowp float _diffuse = max(dot(_normal, normalizedSunPosition), 0.0);
   
    gl_FragColor = (texture2D(nightTexture, _uv0) * (1.0 - _diffuse)) + (texture2D(dayTexture, _uv0) * _diffuse);
}




No comments:

Post a Comment