http://blog.csdn.net/a3070173/archive/2008/11/04/3220940.aspx
Glow即輝光效果現在已成為3D圖形中一個引人注目的特效.本文主要介紹如何使用GLSL實現一個典型的GLow效果.
實現步驟:1.渲染整個場景到一個禎緩沖區中
2.將場景中需要進行GLow處理的物體繪制第二個FBO紋理A中
3.在FBO紋理A和B之間進行橫和縱"高斯"過濾
4.將進行過GLow處理后的FBO紋理A與禎緩沖區中的場景圖像以glBlendFunc(GL_ONE, GL_ONE)方式進行混合處理
GLSL文件功能簡介:
FullScreen.vert - 用於繪制覆蓋整個視口的四邊形以進行Glow效果的高斯過濾
Filter.frag - 用於橫和縱的高斯過濾
Blend.frag - 用於處理過的GLowFBO紋理與原始場景圖像進行混合
為了直接進行生成Glow效果的介紹,這里假設程序已正確處理了OpenGL和GLSL的初始化.
void RenderOrigionalScene()
{
if (g_bUseFillRender)
{
glPolygonMode(GL_FRONT, GL_FILL);
}
else
{
glPolygonMode(GL_FRONT, GL_LINE);
}
RenderObject();
}
首先讓我們繪制原始場景,由於本Demo未繪制除輝光物體外的其它事物,所以此處就直接繪制為進行具有輝光效果的物體.
void RenderGlowObject()
{
// 設置視口
glViewport(0, 0, g_uiTextureWidth, g_uiTextureHeight);
// 將原始場景繪制到第二個FBO
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_uiFboColorOne);
// 清除第一個FBO顏色和深度緩存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 繪制輝光物體
RenderObject();
}
然后設置繪制目標到FBO紋理A並將欲進行Glow處理的物體繪制到上面來,這里需要注意的是必須根據FBO紋理的尺寸設置一個
視口使其跟FBO紋理一樣大,以使物體能夠准確地映射到整個FBO紋理上.清除FBO顏色緩沖區和繪制深度緩沖區是必要的,因為
每次繪制到FBO紋理中的圖像都不一樣.
void FilterGlowObject()
{
glPolygonMode(GL_FRONT, GL_FILL);
// 將水平過濾后的圖像繪制第二個FBO
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_uiFboColorTwo);
// 清除第二個FBO顏色
glClear(GL_COLOR_BUFFER_BIT);
// 重新設置片元着色器
glUseProgram(g_ProgramObjectOne);
// 設置水平過濾標志
GLint iUniformIndex = glGetUniformLocation(g_ProgramObjectOne, "g_bFiterMode");
glUniform1i(iUniformIndex, 1);
// 設置紋理
glBindTexture(GL_TEXTURE_2D, g_uiIDOne);
// 繪制
RenderFullScreen();
// 將豎直過濾后的圖像繪制第一個FBO
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_uiFboColorOne);
// 清除第一個FBO顏色和深度緩存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 設置豎直過濾標志
iUniformIndex = glGetUniformLocation(g_ProgramObjectOne, "g_bFiterMode");
glUniform1i(iUniformIndex, 0);
// 設置紋理
glBindTexture(GL_TEXTURE_2D, g_uiIDTwo);
// 繪制
RenderFullScreen();
}
下面到Glow效果處理的重頭戲,是否能生成完美的輝光效果關鍵就在於此步的處理.但其實也很簡單,主要就是為Filter着色器設
置進行合適橫,縱兩次過濾的標志和繪制目標,然后繪制全視口四邊形,剩下的過濾工作則由GLSL的高斯過濾着色器全權負責.
void RenderToScreen()
{
// 恢復視口
glViewport(0, 0, g_uiCurrentWindowWidth, g_uiCurrentWindowHeight);
// 恢復繪制目標為禎緩沖區
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, NULL);
// 啟動混合
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
// 綁定紋理
glBindTexture(GL_TEXTURE_2D, g_uiIDOne);
// 重新設置片元着色器
glUseProgram(g_ProgramObjectTwo);
// 繪制
RenderFullScreen();
// 恢復固定功能管線
glUseProgram(0);
// 關閉混合
glDisable(GL_BLEND);
}
最后一步無非就是將過濾好的Glow紋理與原始場景圖像進行混合,當然使用OpenGL固定功能管線或GLSL都可以輕易實現,但首先必
須把視口設置回原來的狀態.
以下是高斯過濾的GLSL着色器代碼,粘貼於此以方便讀者查閱.
頂點着色器:
void main()
{
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_Vertex;
}
高斯過濾着色器:
const int g_iFilterTime = 9; // 過濾次數
const float g_fGene = (1.0/(1.0 + 2.0*(0.93 + 0.8 + 0.7 + 0.6 + 0.5 + 0.4 + 0.3 + 0.2 + 0.1))); // 衰減因子
uniform sampler2D g_Decal;
uniform bool g_bFiterMode;
uniform float g_fGlowGene;
uniform vec2 g_vec2HorizontalDir; // 水平過濾方向
uniform vec2 g_vec2VerticalDir; // 豎直過濾方向
uniform float g_fFilterOffset; // 過濾偏移
void main()
{
float aryAttenuation[g_iFilterTime];
aryAttenuation[0] = 0.93;
aryAttenuation[1] = 0.8;
aryAttenuation[2] = 0.7;
aryAttenuation[3] = 0.6;
aryAttenuation[4] = 0.5;
aryAttenuation[5] = 0.4;
aryAttenuation[6] = 0.3;
aryAttenuation[7] = 0.2;
aryAttenuation[8] = 0.1;
// 采樣原始顏色
vec2 vec2Tex0 = gl_TexCoord[0].st;
vec4 vec4Color = texture2D(g_Decal, vec2Tex0)*g_fGene;
// 計算過濾方向
vec2 vec2FilterDir = g_vec2HorizontalDir + vec2(g_fFilterOffset, 0.0); // 水平過濾
if (!g_bFiterMode)
{
vec2FilterDir = g_vec2VerticalDir + vec2(0.0, g_fFilterOffset); // 豎直過濾
}
// 進行過濾
vec2 vec2Step = vec2FilterDir;
for(int i = 0; i< g_iFilterTime; ++i)
{
vec4Color += texture2D(g_Decal, vec2Tex0 + vec2Step)*aryAttenuation[i]*g_fGene;
vec4Color += texture2D(g_Decal, vec2Tex0 - vec2Step)*aryAttenuation[i]*g_fGene;
vec2Step += vec2FilterDir;
}
if (g_bFiterMode)
{
gl_FragColor = vec4Color*g_fGlowGene;
}
else
{
gl_FragColor = vec4Color;
}
}
混合着色器:
uniform sampler2D g_Decal;
void main()
{
gl_FragColor = texture2D(g_Decal, gl_TexCoord[0].st);
}
Demo效果圖:
參考資料:Nvidia OpenGL SDK 10.5 Simple Glow
exe文件:http://www.fileupyours.com/view/219112/GLSL/Glow%20Demo.rar