Lights present a problem for the rasterization-based graphics because they involve non-local, all-pairs interactions.
In this lecture, we'll talk about three strategies for dealing with lights: forward multi-pass rendering; forward light-loop rendering; and deferred rendering.
We tend to think of scenes like this:
But interesting scenes are more like this:
So how do we render multiple lights that influence multiple objects?
//Fragment Shader:
#version 330
uniform sampler2D TEX;
uniform vec3 LIGHT_LOCATION;
uniform vec3 LIGHT_DIRECTION;
uniform vec3 LIGHT_ENERGY;
uniform float LIGHT_CUTOFF;
in vec3 position;
in vec3 normal;
in vec4 color;
in vec2 texCoord;
out vec4 fragColor;
void main() {\n"
//compute light contribution:
vec3 n = normalize(normal);
vec3 l = normalize(LIGHT_LOCATION - position);
float nl = max(0.0, dot(n, l));
float c = dot(l,-LIGHT_DIRECTION);
nl *= smoothstep(LIGHT_CUTOFF,mix(LIGHT_CUTOFF,1.0,0.1), c);
vec3 e = nl * LIGHT_ENERGY;
//compute material albedo:
vec4 albedo = texture(TEX, texCoord) * color;
//compute final color:
fragColor = vec4(e*albedo.rgb, albedo.a);
}
Handle lights one by one, adding up each contribution. (Uses the linearity of light!)
Complexity: for N object pixels and L lights, must perform N*L material computations, read N*(L-1) fragments, and write N*L fragments.
//Fragment Shader:
#version 330
uniform sampler2D TEX;
uniform int LIGHTS;
uniform vec3 LIGHT_LOCATION[40];
uniform vec3 LIGHT_DIRECTION[40];
uniform vec3 LIGHT_ENERGY[40];
uniform float LIGHT_CUTOFF[40];
in vec3 position;
in vec3 normal;
in vec4 color;
in vec2 texCoord;
out vec4 fragColor;
void main() {\n"
vec3 e = vec3(0.0);
vec3 n = normalize(normal);
//compute light contributions:
for (int light = 0; light < LIGHTS; ++light) {
vec3 l = normalize(LIGHT_LOCATION[light] - position);
float nl = max(0.0, dot(n, l));
float c = dot(l,-LIGHT_DIRECTION[light]);
nl *= smoothstep(LIGHT_CUTOFF[light],mix(LIGHT_CUTOFF[light],1.0,0.1), c);
e += nl * LIGHT_ENERGY[light];
}
//compute material albedo:
vec4 albedo = texture(TEX, texCoord) * color;
//compute final color:
fragColor = vec4(e*albedo.rgb, albedo.a);
}
Improvement: Move the light loop from the CPU to the GPU.
Complexity: for N object pixels and L lights, must perform N*L material computations and write N fragments.
//Fragment Shader:
#version 330
uniform sampler2D POSITION_TEX;
uniform sampler2D NORMAL_TEX;
uniform sampler2D ALBEDO_TEX;
uniform vec3 LIGHT_LOCATION;
uniform vec3 LIGHT_DIRECTION;
uniform vec3 LIGHT_ENERGY;
uniform float LIGHT_CUTOFF;
in vec2 texCoord;
out vec4 fragColor;
void main() {\n"
vec3 position = texelFetch(POSITION_TEX, ivec2(gl_FragCoord.xy), 0);
vec3 normal = texelFetch(NORMAL_TEX, ivec2(gl_FragCoord.xy), 0);
vec3 albedo = texelFetch(ALBEDO_TEX, ivec2(gl_FragCoord.xy), 0);
//compute light contribution:
vec3 n = normalize(normal);
vec3 l = normalize(LIGHT_LOCATION - position);
float nl = max(0.0, dot(n, l));
float c = dot(l,-LIGHT_DIRECTION);
nl *= smoothstep(LIGHT_CUTOFF,mix(LIGHT_CUTOFF,1.0,0.1), c);
vec3 e = nl * LIGHT_ENERGY;
//compute final color:
fragColor = vec4(e*albedo.rgb, albedo.a);
}
Render geometry like objects, then render lights like objects.
Complexity: for N object pixels and P light pixels, must perform P material computations, read 3*P fragments, and write 3*N + P fragments.