Deferred shading是這樣一種技術:將光照/渲染計算推遲到第二步進行計算。我們這樣做的目的是為了避免多次(超過1次)渲染同一個像素。
其基本思想如下:
1、在第一步中,我們渲染場景,但是與通常情況下應用反射模型計算片斷顏色不同的是,我們只是簡單的將幾何信息(位置坐標,法線向量,紋理坐標,反射系數等等)存儲在中間緩沖區中,這樣的緩沖區我們稱之為g-buffer(g是幾何geometry的縮寫)。
2、在第二步,我們從g-buffer中讀取信息,應用反射模型,計算出每個像素的最終顏色。
Deferred shading技術的應用使得我們避免了應用反射模型于最終不可見的片斷上。例如,考慮這樣的像素,它位于兩個多邊形重疊的區域。通常的片斷著色器會讀對每個多邊形分別計算那個像素一次;然而,兩次執行的結果最終只有一個成為該像素的最終顏色(這里基于的一個假設是:混合已被禁用)。這樣,其中的一次計算就是無用的。有了Deferred shading技術,反射模型的計算會推遲到所有幾何體被處理之后,那時候每個像素位置幾何體的可見性也是已知的。這樣,對于屏幕上的每個像素,反射模型的計算只會發生一次。
Deferred shading容易懂而且便于使用。它能夠幫助實施很復雜的光照/反射模型。
二、結合例子來說明Deferred shading技術
下面的例子采用Deferred shading技術渲染了一個包含一個茶壺和一個圓環的場景。效果如下:
圖一 場景渲染效果圖
在這個例子中,我們將位置坐標、法線以及漫反射因子存儲在g-buffer里。在第二步的時候,我們使用g-buffer里面的數據來進行漫反射光照模型的計算。
g-buffer包含3個紋理:分別用來存儲位置坐標、法線以及漫反射因子。對應的采用了3個uniform變量:PositionTex、NormalTex、ColorTex。
他們均被關聯到一個FBO上。關于FBO使用見:FBO。
下面是創建包含g-buffer的FBO的代碼:
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); // The position buffer glActiveTexture(GL_TEXTURE0); // Use texture unit 0 glGenTextures(1, &posTex); glBindTexture(GL_TEXTURE_2D, posTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // The normal buffer glActiveTexture(GL_TEXTURE1); glGenTextures(1, &normTex); glBindTexture(GL_TEXTURE_2D, normTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // The color buffer glActiveTexture(GL_TEXTURE2); glGenTextures(1, &colorTex); glBindTexture(GL_TEXTURE_2D, colorTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Attach the images 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); 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); // The position buffer glActiveTexture(GL_TEXTURE0); // Use texture unit 0 glGenTextures(1, &posTex); glBindTexture(GL_TEXTURE_2D, posTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // The normal buffer glActiveTexture(GL_TEXTURE1); glGenTextures(1, &normTex); glBindTexture(GL_TEXTURE_2D, normTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // The color buffer glActiveTexture(GL_TEXTURE2); glGenTextures(1, &colorTex); glBindTexture(GL_TEXTURE_2D, colorTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Attach the images 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);
注意:三個紋理分別使用函數glFramebufferTexture2D()關聯到FBO的顏色關聯點0、1、2上面。接著調用函數glDrawBuffers把它們和片斷著色器的輸出變量聯系起來。
函數glDrawBuffer指示了FBO成員和片斷著色器輸出變量之間的聯系。FBO中的第i個成員對應片斷著色器中的索引為i的輸出變量。這樣,片斷著色器(下面列出了完整代碼)中相對應的輸出變量分別是PosiutionData,NormalData和ColorData。
頂點著色器實現了一個很簡單的功能:將位置坐標和法線轉化到eye sapce中,然后傳遞到片斷著色器中。而紋理坐標則沒有發生變化。
片斷著色器如下:
#version 400 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; uniform sampler2D PositionTex, NormalTex, 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(); } #version 400 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; uniform sampler2D PositionTex, NormalTex, 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(); }
片斷著色器則包含了關于光源、材料的一些信息,都是uniform變量,以用于光照計算。
片斷著色器里面使用了subroutine技術,實現了兩個函數pass1和pass2,分別包含了第一步和第二步的操作。我們在OpenGL應用程序中通過設置uniform變量的值可以選擇使用相應的功能。
在OpenGL應用程序里面,
實施第一步的步驟如下:
1、綁定FBO;
2、情況顏色以及深度緩沖區,選擇pass1 subroutine函數,啟用深度測試;
3、渲染場景。
實施第二步的步驟是:
1、去除FBO綁定(將其綁定到0),目的是能夠渲染場景到默認緩沖區,而不是FBO里面,它就能顯示到屏幕上;
2、清除顏色緩沖去對象。禁用深度測試;
3、選擇pass2 subroutine函數,渲染一個充滿屏幕的四邊形,帶有紋理坐標,每個方向的紋理坐標的范圍都是從0到1.計算光照模型,得出最后的片斷顏色。
三、如何選擇使用Deferred shading技術
在圖形學領域,關于Deferred shading技術的優點和缺陷備受爭議。這種技術并不適用所有的場合,它取決于你的應用程序的需求。因此在覺得是否采用這個技術之前一定要權衡它帶來的優點和缺陷。
Deferred shading技術帶來一個很重要的缺點就是不能使用基于硬件實現的多重采樣抗鋸齒功能。因為渲染過程發生在第二步,所以我們在第二步需要多個樣本。但是,在第二步我們只有每一個像素的一個樣本。
另外一個缺點就是不能使用混合技術。
新聞熱點
疑難解答