什麼是延遲着色(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) 在複雜光照條件下有着性能優點,但它也有缺點:大內存開銷。還有在光源不是不少的場景中,延遲渲染並不必定會更快,甚至有時候因爲開銷過大還會變得更慢。固然在更復雜的場景中,延遲渲染會變成一個重要的性能優化手段。