Deferred shading 是把应用光照或shading推迟到第二帧来做。这样可以避免了不必要的像 素计算。
如下图只用了漫反射光照
基本算法思想如下:
1. 在第一帧渲染时,我们只渲染场景,而不计算反射模型来确定片元的最终颜色,我们只简单的存贮几何信息(位置position、法线normal,纹理t坐标xture coordinate 等等)在一个临时缓冲区中,这些统称为g-buffer (g 代表 Geometry)
2. 在第二帧时,我们只读区g-buffer,计算反射模型,位每个像素生成最终的颜色值。
利用deferred shading ,我们可以避免计算对最终渲染效果无用的片元的光照模型。例如,两个多边形重叠的部分将会各执行一次shading 计算,但最终的渲染效果只有一个多边形对最终渲染效果起作用(前提 没有透明)。而应用deferred shading ,所有几何模型的最终像素都确定了才计算光照模型,因而,光照模型只为屏幕上的每个像素执行了一次。
void SceneDeferred::createGBufTex( GLenum texUnit, GLenum format, GLuint &texid ) { glActiveTexture(texUnit); glGenTextures(1, &texid); glBindTexture(GL_TEXTURE_2D, texid); glTexStorage2D(GL_TEXTURE_2D, 1, format, width, height); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } void SceneDeferred::setupFBO() { GLuint depthBuf, posTex, normTex, colorTex; // Create and bind the FBO glGenFramebuffers(1, &deferredFBO); glBindFramebuffer(GL_FRAMEBUFFER, deferredFBO); // The depth buffer glGenRenderbuffers(1, &depthBuf); glBindRenderbuffer(GL_RENDERBUFFER, depthBuf); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height); // Create the textures for position, normal and color createGBufTex(GL_TEXTURE0, GL_RGB32F, posTex); // Position createGBufTex(GL_TEXTURE1, GL_RGB32F, normTex); // Normal createGBufTex(GL_TEXTURE2, GL_RGB8, colorTex); // Color // Attach the textures to the framebuffer glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuf); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, posTex, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, normTex, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, colorTex, 0); GLenum drawBuffers[] = {GL_NONE, GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2}; glDrawBuffers(4, drawBuffers); glBindFramebuffer(GL_FRAMEBUFFER, 0); }
顶点shader
#version 440 layout( location = 0 ) in vec3 VertexPosition; layout( location = 1 ) in vec3 VertexNormal; layout( location = 2 ) in vec2 VertexTexCoord; out vec3 Position; out vec3 Normal; out vec2 TexCoord; uniform mat4 ModelViewMatrix; uniform mat3 NormalMatrix; uniform mat4 ProjectionMatrix; uniform mat4 MVP; void main() { Normal = normalize( NormalMatrix * VertexNormal); Position = vec3( ModelViewMatrix * vec4(VertexPosition,1.0) ); TexCoord = VertexTexCoord; gl_Position = MVP * vec4(VertexPosition,1.0); }
#version 440 struct LightInfo { vec4 Position; // Light position in eye coords. vec3 Intensity; // A,D,S intensity }; uniform LightInfo Light; struct MaterialInfo { vec3 Kd; // Diffuse reflectivity }; uniform MaterialInfo Material; subroutine void RenderPassType(); subroutine uniform RenderPassType RenderPass; layout(binding=0) uniform sampler2D PositionTex; layout(binding=1) uniform sampler2D NormalTex; layout(binding=2) uniform sampler2D ColorTex; in vec3 Position; in vec3 Normal; in vec2 TexCoord; layout (location = 0) out vec4 FragColor; layout (location = 1) out vec3 PositionData; layout (location = 2) out vec3 NormalData; layout (location = 3) out vec3 ColorData; vec3 diffuseModel( vec3 pos, vec3 norm, vec3 diff ) { vec3 s = normalize(vec3(Light.Position) - pos); float sDotN = max( dot(s,norm), 0.0 ); vec3 diffuse = Light.Intensity * diff * sDotN; return diffuse; } subroutine (RenderPassType) void pass1() { // Store position, normal, and diffuse color in textures PositionData = Position; NormalData = Normal; ColorData = Material.Kd; } subroutine(RenderPassType) void pass2() { // Retrieve position and normal information from textures vec3 pos = vec3( texture( PositionTex, TexCoord ) ); vec3 norm = vec3( texture( NormalTex, TexCoord ) ); vec3 diffColor = vec3( texture(ColorTex, TexCoord) ); FragColor = vec4( diffuseModel(pos,norm,diffColor), 1.0 ); } void main() { // This will call either pass1 or pass2 RenderPass(); }
在为g-buffer设置FBO时,我们为位置position和法线normal设置纹理的内部格式GL_RGB32F。
然后用glFrameBufferTexture2D 把三个纹理附加到FBO上,分别为color Attachment 0,1,and 2。它们分别对应片元着色器的输出变量(用glDrawBuffers函数设置)。
如:GLenum drawBuffers[] = {GL_NONE, GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1,GL_COLOR_ATTACHMENT2}; glDrawBuffers(4, drawBuffers);
说明片元shader中的out layout 0对应GL_NONE,layout 1 对应GL_COLOR_ATTACHMENT0 ,等等。
注意:在 渲染第二帧的时候(pass 2),实际上没有必要向fragment传递和转换normal和position。可以用在vertex shader中使用 subroutine来关闭转换。当然在片元着色器中gl_Position 是必须要赋值的!
Deferred shading 并不是对所有解决方案都是有效的,它很多程度上取决于软件的需求,在使用它前,我们需要权衡deferred shading 的缺点和优点。
缺点(Drawback):一个很大的缺点就是是用deferred shading 将不能用硬件实现多重采样抗锯齿(multisample anti-aliasing )。在deferred shading 要实现多重采样本来应该在第二帧(pass 2),但是,在第二帧每个像素只有单一采样的几何信息。为了实现这个,我们需要多重纹理和为每次采样选择正确的纹理的能力,但是目前OpenGL做不到这一点。(不过,我们可以用边缘探测edge detection 和 模糊过滤 blur filter 来提供一个粗糙的 抗锯齿anti-aliasing 功能)
另一个缺点是:deferred shading 对融合/透明处理不是太好。事实上融合blending 根本不可能。
Deferred shading优点:在deferred shading 中可以保存整个深度缓存,这个深度缓存作为一个纹理可以实现好多基于深度的算法,例如 深度模糊depth blur,屏幕空间环境光遮蔽 screen space ambient occlusionscreen space ambient occlusion、volumetric particles等其他类似技术。