什么是延迟着色(Deferred Shading)?它是相对于正常使用的正向着色(Forward Shading)而言的,正向着色的工做模式:遍历光源,获取光照条件,接着遍历物体,获取物体的几何数据,最后根据光照和物体几何数据进行计算。javascript
可是正向着色(Forward Shading)在光源很是多的状况下,对性能的消耗很是大。由于程序要对每个光源,每个须要渲染的物体,每个须要渲染的片断进行迭代!还有片断着色器的输出会被以后的输出覆盖,正向渲染会在场景中因多个物体重合在一个像素上浪费大量的片断着色器运行时间。html
延迟着色(Deferred Shading),就是为了解决上述问题而生,尤为是须要渲染几百上千个光源的场景。java
本节实现的效果请看:延迟着色 deferred sharding
web
正向着色伪代码:缓存
foreach light { foreach visible mesh { if (light volume intersects mesh) { render using this material/light shader; accumulate in framebuffer using additive blending; } } }
延迟着色(Deferred Shading)工做模式就是将计算量大的渲染光照部分 延迟(Defer) 到后期进行处理,所以它包含两个处理阶段(Pass):性能优化
延迟着色伪代码:函数
// g-buffer pass foreach visible mesh { write material properties to g-buffer; } // light accumulation pass foreach light { compute light by reading g-buffer; accumulate in framebuffer; }
延迟着色(Deferred Shading) 的 G缓冲(G-buffer) 是基于 帧缓冲(frameBuffer) 实现的,涉及到高级应用,帧缓冲 真的是无处不在啊!该demo的几何处理阶段分别对位置(position),法向量(normal),颜色(color) 进行缓存,那么对应就要创建3个颜色附件,别忘了同时创建用于深度测试用的 深度缓冲(Z-Buffer)。性能
const fb = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fb); const fbo = { framebuffer: fb, textures: [] }; // 建立颜色纹理 for(let i = 0; i < 3; i++){ const tex = initTexture(gl, { informat: gl.RGBA16F, type: gl.FLOAT }, width, height); framebufferInfo.textures.push(tex); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, 0); } // 建立深度渲染缓冲区 const depthBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
WebGL 实现多渲染目标须要打开 WEBGL_draw_buffers 这个扩展,可是 WebGL 2.0 直接就能使用的。我这里为了方便就基于 WebGL 2.0 来实现,多渲染目标调用方式以下:测试
gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2]);
由于延迟着色器分两个阶段,那么对应就须要两对着色器,首先来看几何处理阶段的着色器。优化
几何处理阶段 顶点着色器(vertex)
#version 300 es in vec4 aPosition; in vec4 aNormal; uniform mat4 modelMatrix; uniform mat4 vpMatrix; out vec3 vPosition; out vec3 vNormal; void main() { gl_Position = vpMatrix * modelMatrix * aPosition; vNormal = vec3(transpose(inverse(modelMatrix)) * aNormal); vPosition = vec3(modelMatrix * aPosition); }
几何处理阶段 片断着色器(fragment),这里的三个输出变量对应就是帧缓冲中的三个颜色纹理。
#version 300 es precision highp float; layout (location = 0) out vec3 gPosition;// 位置 layout (location = 1) out vec3 gNormal; // 法向量 layout (location = 2) out vec4 gColor; // 颜色 uniform vec4 color; in vec3 vPosition; in vec3 vNormal; void main() { gPosition = vPosition; gNormal = normalize(vNormal); gColor = color; }
接着就是光照处理阶段的着色器组了。
光照处理阶段 顶点着色器(vertex),这个很是简单,映射到帧缓冲,也就是个平面贴图而已。
#version 300 es in vec3 aPosition; in vec2 aTexcoord; out vec2 texcoord; void main() { texcoord = aTexcoord; gl_Position = vec4(aPosition, 1.0); }
光照处理阶段 片断着色器(fragment),须要从对应的纹理贴图取出对应的几何数据。也就是使用 texture 函数结合贴图和 贴图坐标(texcoord) 就能够计算出对应的几何数据,再结合光照数据渲染出最终结果。
#version 300 es precision highp float; uniform vec3 viewPosition; uniform vec3 lightDirection; uniform vec3 lightColor; uniform vec3 ambientColor; uniform float shininess; // 各类自定义变量 ... uniform sampler2D gPosition;// 位置 uniform sampler2D gNormal; // 法向量 uniform sampler2D gColor; // 颜色 in vec2 texcoord; // 坐标 out vec4 FragColor; void main() { vec3 fragPos = texture(gPosition, texcoord).rgb; vec3 normal = texture(gNormal, texcoord).rgb; vec3 color = texture(gColor, texcoord).rgb; // todo: 各类计算过程... // 环境光 vec3 ambient = ambientColor * color; // 漫反射 // ... vec3 diffuse = lightColor * color * cosTheta; // 高光 // ... vec3 specular = lightColor * specularIntensity; FragColor = vec4(ambient + diffuse + specular, 1.0); }
最后就是使用 JavaScript 将整个流程串起来,WebGL 的其余技术细节再也不详细介绍了,具体能够看我以前的 WebGL 教程。这里介绍一下大致的流程:
/** * 场景绘制到帧缓冲区 */ gl.bindFramebuffer(target, fbo.framebuffer); // 绑定帧缓冲 gl.viewport(0, 0, width, height); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 清屏 gl.useProgram(program); //采样到3个颜色附件(对应的几何纹理) gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2]); setUniforms(program, uniforms);// 设置uniform变量 setBuffersAndAttributes(gl, vao);// 设置缓存和attribute变量 drawBufferInfo(gl, vao); // 写入缓冲区 /** * 从帧缓存渲染到正常缓冲区 */ gl.bindFramebuffer(target, null); // 切换回正常缓冲 gl.viewport(0, 0, width, height); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.useProgram(fProgram); const uniforms = { // 其余变量 ... gPosition: fbo.textures[0],// 位置纹理 gNormal: fbo.textures[1],// 法向量纹理 gColor: fbo.textures[2], // 颜色纹理 }; setUniforms(fProgram, uniforms); setBuffersAndAttributes(gl, fVao); drawBufferInfo(gl, fVao); // 输出画面
本节实现的效果请看:延迟着色 deferred sharding
demo 使用了1个平行光源,10个点光源,3个聚光灯实现了相似舞厅五彩斑斓的渲染效果。
延迟着色(Deferred Shading) 在复杂光照条件下有着性能优点,但它也有缺点:大内存开销。还有在光源不是不少的场景中,延迟渲染并不必定会更快,甚至有时候因为开销过大还会变得更慢。固然在更复杂的场景中,延迟渲染会变成一个重要的性能优化手段。