|
version 0.3.1 is required
Deferred shading
This Tutorial describes a basic setup for deferred shading.
Advantages and Disadvantages of deferred shading are:
geometry and lighting are independent
The lighting system is unified. Instead rendering the light into the buffer, a
description of the light interaction will be rendered.
No MSAA possible
MSAA is impossible as long there is no extension for accessing the samples separately
like in DX10
Low performance if only a few lights are used
Deferred renderings works well for night scenes with many lights. Bad for Daylight
scenes.
Basic setup
The fist step is to setup the G and Light Buffer and a script that controlls the
rendering. Enter into the Console:
Global = World.addNode("Global");
Global.addScript("Globalscript");
Global.addTexture("G_Diffuse");
Global.addTexture("G_Normal");
Global.addTexture("G_Depth");
Global.addTexture("Light");
Global.addVertexShader("Vertexpost");
Global.addFragmentShader("Fragmentpost");
This creates all required stuff for the basic setup. No fill Vertexpost with that code:
void main(void){
gl_Position = vec4( gl_Vertex.xy * 2.0 - 1.0,0.5,1.0);
}
Fragmentpost:
#extension GL_ARB_texture_rectangle : enable
uniform sampler2DRect G_Depth; // usefully for debugging
uniform sampler2DRect G_Normal;
uniform sampler2DRect Light;
void main(void){
gl_FragColor = texture2DRect(Light, gl_FragCoord.xy);
}
This shader didn't do anything except copying the cont from a texture into the
backbuffer.
Now the first part of "Globalscript"
TMU0 = 0; TMU1 = 1; TMU2 = 2;
post = gl.Shader(Vertexpost,Fragmentpost);
post.Uniformi("G_Depth",TMU0);
post.Uniformi("Light",TMU1);
post.Uniformi("G_Normal",TMU2);
Gbuffer = gl.Framebuffer();
Lbuffer = gl.Framebuffer();
resizeEvent(World.getCamWidth(),World.getCamHeight());
function resizeEvent(W,H){
G_Diffuse.ImageRect(W,H,gl.RGBA8);
G_Normal.ImageRect(W,H,gl.RGBA8);
G_Depth.ImageRect(W,H,gl.DEPTH_COMPONENT24);
Light.ImageRect(W,H,gl.RGBA16F);
Gbuffer.Append(G_Diffuse,0);
Gbuffer.Append(G_Normal,1);
Gbuffer.Append(G_Depth);
Lbuffer.Append(Light,0);
Lbuffer.Append(G_Depth);
}
The first line are for loading the postfilter shader. gl.Framebuffer() (may change
to "new Framebuffer();" in Future) creates a new Framebuffer object, which can have more
than one attachment (Multiple Render Target). Two Framebuffer object are created, The
first is the G-Buffer and the second the L-Buffer for the light pass.
Then resizeEvent function will be called to initialize the G Buffer textures with the
right size and format and attach them to the Framebuffer Objects. The gl.RGBA16F format
is usable for HDR lighting (replace it by RGBA8 for older cards like Radeon 9500)
The second part is the render function:
function render(){
mrt.Bind();
gl.Clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT);
World.Call("gpass");
mrt.Unbind();
That binds the Framebuffer object, clears it. And call the function "gpass" in all
scripts.
G_Depth.Bind(TMU0);
G_Diffuse.Bind(TMU1);
G_Normal.Bind(TMU2);
Lbuffer.Bind();
gl.DepthMask(false);
gl.DepthFunc(gl.ALWAYS);
gl.Clear(gl.COLOR_BUFFER_BIT);
gl.Enable(gl.BLEND);
gl.BlendFunc(gl.ONE,gl.ONE);
World.Call("light");
gl.Disable(gl.BLEND);
Lbuffer.Unbind();
This binds the light buffer texture as rendertarget and clears it. Then blending will be
activated to accumulate the lights. (Instead clearing it's also possible to render a
global light with a fullscreen quad)
Then the "light" function of any script will be called.
During the lighting process it's important that writing to the depth buffer is avoided.
The Depthtext isn't necessary, but usefull to reject Fragments outside a light volume
G_Depth.Bind(TMU0);
Light.Bind(TMU1);
post.Bind();
gl.Begin(gl.QUADS);
gl.Vertex(1,1);
gl.Vertex(1,0);
gl.Vertex(0,0);
gl.Vertex(0,1);
gl.End();
gl.DepthMask(true);
gl.DepthFunc(gl.LESS);
}
The last part of "Globalscript" is the post processing stage, it copies the content from
the HDR light buffer into the backbuffer. At the end the Depthbuffers defaults will be
restored.
Now save to file and run the script. If a FBO error popup appears the format texture
format isn't supported.
A simple head light
A simple headlight requires a new node with script and shader:
Headlight = World.addNode("Headlight");
Headlight.addScript("Script");
Headlight.addVertexShader("Vertexlight");
Headlight.addFragmentShader("Fragmentlight");
Script:
TMU0 = 0; TMU1 = 1; TMU2 = 2;
lighting = gl.Shader(Vertexlight,Fragmentlight);
lighting.Uniformi("G_Depth",TMU0);
lighting.Uniformi("G_Diffuse",TMU1);
lighting.Uniformi("G_Normal",TMU2);
function light(){
lighting.Bind();
gl.Begin(gl.QUADS);
gl.Vertex(1,1);
gl.Vertex(1,0);
gl.Vertex(0,0);
gl.Vertex(0,1);
gl.End();
}
Vertexlight is the same code like Vertexpost:
void main(void){
gl_Position = vec4( gl_Vertex.xy * 2.0 - 1.0,0.5,1.0);
}
Fragmentlight:
#extension GL_ARB_texture_rectangle : enable
uniform sampler2DRect G_Diffuse;
uniform sampler2DRect G_Normal;
uniform sampler2DRect G_Depth;
void main(void){
vec3 N = normalize(texture2DRect(G_Normal, gl_FragCoord.xy).xyz -0.5);
float light = max(0.0,dot(N,vec3(0,0,1)));
gl_FragColor = texture2DRect(G_Diffuse, gl_FragCoord.xy) * light;
}
This shader reads the normal from the G_Buffer calculates a simple dot product light,
and multiplies it with the difuse color.
Now save and run the script...
Gears
A deferred renderer doesn't make sense. If ther is nothing to render. The gears from the
second tutorial with small modification. First create gears with 8,12, and 16 teeth.
Then create a vertex, a fragment shader and a script:
Script:
shader = gl.Shader(Vertexshader,Fragmentshader);
function Draw(Gear){
Gear.Vertex.Bind();
Gear.Normal.Bind();
Gear.Index.Draw();
}
function gpass(){
alpha = World.getTime();
beta = 60 * Math.sin(World.getTime()/3*3.141)
gl.Rotate(60 * World.getTime(), 0,1,0.5);
gl.Scale(-0.1,-0.1,-0.1);
gl.Rotate(-10, 1,0,0);
shader.Uniform("col",1.0,0.9,0.4,0);
gl.PushMatrix();
gl.Translate(16,0,0);
gl.Rotate(90,0,1,0);
gl.Rotate(80 * alpha,0,0,1);
Draw(Gear12);
gl.PopMatrix();
gl.Rotate(-60 * alpha,0,0,1);
shader.Uniform("col",0.0,0.4,0.0,0);
gl.PushMatrix();
gl.Translate(0,0,12);
Draw(Gear16);
gl.PopMatrix();
shader.Uniform("col",0.8,0.8,1.0,0);
b = Array(beta,-beta,beta);
for (i = 0; i < 3; i++){ // the loop makes the code a little bit smaller
gl.PushMatrix();
gl.Rotate(90 * i,0,1,0);
gl.Translate(0,0,8);
gl.Rotate(b[i],0,0,1);
Draw(Gear8);
gl.PopMatrix();
}
}
Vertexshader, except for the removed Tangent, it's the same like in the second tutorial:
varying vec3 N;
void main(void){
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
N = normalize(gl_NormalMatrix * gl_Normal);
}
Fragment shader:
#extension GL_ARB_draw_buffers : enable
varying vec3 T,N;
uniform vec4 col;
void main(void){
gl_FragData[0] = col;
gl_FragData[1].xyz = N/2.0 + 0.5;
}
This code is very simple. The gear color is stored into gl_FragData[0] and the
Screenspace normal into gl_FragData[1].xyz. These values will be used by the light
shader.
The result should look like this.
|