1. 程式人生 > >OpenGL順序無關的透明(OIT)加權平均法

OpenGL順序無關的透明(OIT)加權平均法

有了
OpenGL渲染到幀快取物件(FBO)

OpenGL紋理
的基礎,就可以用加權平均法(WA)做順序無關的透明度(OIT)了

加權平均法

Pass0 PixelShader
關閉深度測試,每一個畫素上可能是由 i 個顏色疊加起來的,我們把它們全被加起來,記錄在一張紋理(Color)上
vec3( r1, g1, b1 ) * Alpha1 + vec3( r2, g2 ,b2 ) * Alpha2 + … + vec3( ri, gi, bi ) * Alphai
同時我們把畫素累加的次數 i 也記錄在一張紋理(Number)上

Pass1 PixelShader
取樣紋理Color和Number
用color除以number則得到了某一個畫素上的樣色的平均值

初始化

GLenum g_drawBuffers[] = {GL_COLOR_ATTACHMENT0_EXT,
					   GL_COLOR_ATTACHMENT1_EXT,
					   GL_COLOR_ATTACHMENT2_EXT,
					   GL_COLOR_ATTACHMENT3_EXT,
					   GL_COLOR_ATTACHMENT4_EXT,
					   GL_COLOR_ATTACHMENT5_EXT,
					   GL_COLOR_ATTACHMENT6_EXT
};
void InitAccumulationRenderTargets()
{
//生成兩張紋理
	glGenTextures(2, g_accumulationTexId);
    
//第一張為記錄顏色的紋理Color
	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g_accumulationTexId[0]);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 
	             GL_RGBA16F_ARB, //注意這裡的內部格式是Float,這樣在Shader中我們得到的紋理是一個可以超越[-1.0,1.0]的全精度浮點數
				 g_imageWidth, g_imageHeight, 0, GL_RGBA, GL_FLOAT, NULL);
				 
//第二張為記錄每個畫素上顏色疊加次數的紋理Number
	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g_accumulationTexId[1]);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 
	             GL_FLOAT_R32_NV, //注意這裡的內部格式是Float,這樣在Shader中我們得到的紋理是一個可以超越[-1.0,1.0]的全精度浮點數
				 g_imageWidth, g_imageHeight, 0, GL_RGBA, GL_FLOAT, NULL);
				 
//建立一個FBO
	glGenFramebuffersEXT(1, &g_accumulationFboId);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_accumulationFboId);
	
//把這個FBO的COLOR_ATTACHMENT0與第一個紋理Color關聯
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
							  GL_TEXTURE_RECTANGLE_ARB, g_accumulationTexId[0], 0);
							  
//把這個FBO的COLOR_ATTACHMENT0與第二個紋理Number關聯
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
							  GL_TEXTURE_RECTANGLE_ARB, g_accumulationTexId[1], 0);

    CHECK_GL_ERRORS;
}

刪除,用於停止渲染時

void DeleteAccumulationRenderTargets()
{
	glDeleteFramebuffersEXT(1, &g_accumulationFboId);
	glDeleteTextures(2, g_accumulationTexId);
}

渲染迴圈

void RenderAverageColors()
{
	glDisable(GL_DEPTH_TEST);//我們不要深度測試,因為我們要把一條光線上的所用畫素疊加到一個畫素上

	// ---------------------------------------------------------------------
	// 1. Accumulate Colors and Depth Complexity
	// ---------------------------------------------------------------------
//繫結我們直接生成的FBO
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_accumulationFboId);
//選擇我們要把東西畫到COLOR_ATTACHMENT0和COLOR_ATTACHMENT1上
	glDrawBuffers(2, g_drawBuffers);

	glClearColor(0, 0, 0, 0);
	glClear(GL_COLOR_BUFFER_BIT);
	
//選擇直接疊加的融混方式
	glBlendEquationEXT(GL_FUNC_ADD);
	glBlendFunc(GL_ONE, GL_ONE);
	glEnable(GL_BLEND);
	
//繫結第一個pass的shader並開始畫
	g_shaderAverageInit.bind();
	g_shaderAverageInit.setUniform("Alpha", (float*)&g_opacity, 1);
	DrawModel();
	g_shaderAverageInit.unbind();

//在畫第二個pass前一定要記得關閉為第一個pass開啟的狀態
	glDisable(GL_BLEND);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	glDrawBuffer(GL_BACK);
	
	// ---------------------------------------------------------------------
	// 2. Approximate Blending
	// ---------------------------------------------------------------------
	
	g_shaderAverageFinal.bind();
	g_shaderAverageFinal.setUniform("BackgroundColor", g_backgroundColor, 3);
	g_shaderAverageFinal.bindTextureRECT("ColorTex0", g_accumulationTexId[0], 0);
	g_shaderAverageFinal.bindTextureRECT("ColorTex1", g_accumulationTexId[1], 1);
	glCallList(g_quadDisplayList);
	g_shaderAverageFinal.unbind();

	CHECK_GL_ERRORS;
}

Shader

Pass0的PixelShader
#extension ARB_draw_buffers : require

vec4 ShadeFragment();//不重要,可以自己構建,只要得到一個最後輸出的顏色即可

void main(void)
{
	vec4 color = ShadeFragment();
	//獲得要輸出的顏色,把rgb乘上透明度,加到Color這張紋理上
	gl_FragData[0] = vec4(color.rgb * color.a, color.a);
	//在記錄累加次數的紋理上加1
	gl_FragData[1] = vec4(1.0);
}

Pass1的PixelShader

uniform samplerRECT ColorTex0;
uniform samplerRECT ColorTex1;
uniform vec3 BackgroundColor;
void main(void)
{
	vec4 SumColor = textureRect(ColorTex0, gl_FragCoord.xy);
	float n = textureRect(ColorTex1, gl_FragCoord.xy).r;

	if (n == 0.0) {
		gl_FragColor.rgb = BackgroundColor;
		return;
	}

	vec3 AvgColor = SumColor.rgb / SumColor.a;//這裡沒有注意除0的情況!!若有透明的為0的情況需要修改
	float AvgAlpha = SumColor.a / n;

	float T = pow(1.0-AvgAlpha, n);
	gl_FragColor.rgb = AvgColor * (1 - T) + BackgroundColor * T;
}

這篇文章參考
http://developer.download.nvidia.com/SDK/10/opengl/src/dual_depth_peeling/doc/DualDepthPeeling.pdf
你可以在這裡獲得所有的程式碼
或者訪問下面的網站:
http://developer.download.nvidia.com/SDK/10/opengl/screenshots/samples/dual_depth_peeling.html