OpenGL顺序无关的透明(OIT)加权平均法

本文介绍如何使用OpenGL的帧缓存对象(FBO)和纹理,通过加权平均法实现顺序无关的透明度(OIT),包括初始化、渲染循环及Shader代码。此方法适用于需要精确叠加像素颜色的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有了
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_ATTACHMENT1与第二个纹理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

<think>好的,用户想了解OpenGL中的次序无关透明度OIT)。首先,我需要回忆一下OIT的基本概念和它在图形渲染中的作用。透明度处理在实时渲染中一直是个挑战,因为传统的透明度处理(如alpha混合)需要按从后到前的顺序绘制物体,否则会出现渲染错误。但在复杂场景中,尤其是透明物体相互交错时,确保正确的绘制顺序非常困难,这时候就需要次序无关的解决方案。 接下来,我应该解释为什么传统方有局限。比如,当透明物体的顺序预知或动态变化时,排序会消耗大量CPU资源,而且有时候根本无正确排序,导致视觉上的错误,比如前后关系错乱或者颜色混合不正确。 然后,需要介绍OIT的几种常见技术。比如深度剥离(Depth Peeling),这是一种多层渲染技术,逐层提取深度信息,但性能开销较大。还有基于加权平均的方(Weighted Blended OIT),它通过加权公式在片段着色器中累积颜色和透明度,适合移动端等性能有限的设备。另外,基于链表的方(Per-Pixel Linked Lists)需要GPU支持原子操作和较大的显存,虽然灵活但实现复杂。 还要提到这些方的优缺点,比如深度剥离的准确性和性能问题,加权平均的效率但可能存在精度损失,链表方的高质量但硬件要求高。此外,需要说明在OpenGL中实现这些技术时需要注意的要点,比如帧缓冲对象(FBO)的使用、着色器程序的设计,以及不同硬件平台的兼容性问题。 最后,总结OIT的应用场景和选择依据,帮助用户根据项目需求选择合适的方。可能还需要提醒用户,虽然OIT解决了顺序问题,但每种技术都有其适用场景和限制,需要权衡效果和性能。 在组织内容时,要确保结构清晰,分步骤讲解,使用用户容易理解的语言,避免过于专业的术语,或者在必要时进行解释。同时,按照系统级指令,正确使用LaTeX格式,比如用$...$包裹行内数学表达式,独立公式用$$...$$。例如,加权平均的公式应该用LaTeX正确呈现,确保语正确。 现在检查是否有遗漏点,比如是否提到了最新的技术发展,或者不同OpenGL版本对OIT的支持情况。可能还需要提到计算着色器在OIT中的应用,或者现代GPU的特性如何影响OIT的实现选择。另外,确保回答符合用户的需求,用户可能是开发者,需要技术细节和实现考虑,所以需要提供足够的信息帮助他们决策。 总之,回答需要覆盖OIT的定义、传统方的问题、常见技术及其优缺点、实现注意事项,以及应用建议。结构上分点说明,逐步深入,同时保持语言简洁明了,符合中文表达习惯。</think>好的,我将分步骤介绍OpenGL中的次序无关透明度(Order-Independent Transparency, OIT),并解释其原理和实现方。 --- ### 一、传统透明渲染的问题 在实时渲染中,透明物体通常使用**Alpha混合**($C_{result} = C_{src} \cdot \alpha + C_{dst} \cdot (1-\alpha)$)实现。但传统方要求: 1. 物体必须按**从后到前**顺序渲染 2. 动态或复杂场景中排序困难 3. 交错透明物体无正确处理(如交叉的玻璃) 这会导致视觉错误(如颜色错乱或深度冲突)。 --- ### 二、次序无关透明度OIT)的核心目标 **无需依赖物体渲染顺序**,正确混合所有透明片段,即使物体顺序随机或动态变化。 --- ### 三、常见OIT实现技术 #### 1. 深度剥离(Depth Peeling) - **原理**:逐层提取并混合透明片段 - 第一遍:渲染最靠近相机的透明层 - 第二遍:渲染次近的层,依此类推 - 混合公式:$$C_{final} = \sum_{i=1}^{n} C_i \cdot \alpha_i \cdot \prod_{j=1}^{i-1}(1-\alpha_j)$$ - **优点**:精确 - **缺点**:多次渲染通道,性能开销大 #### 2. 加权混合(Weighted Blended OIT) - **原理**:在片段着色器中累积颜色和权重 ```glsl // 片段着色器示例 vec4 color = calculateColor(); float weight = color.a * clamp(1e2 / (1e-2 + pow(gl_FragCoord.z, 3.0)), 1e-2, 1e2); accumulation += color.rgb * color.a * weight; revealage *= (1 - color.a); ``` - **混合公式**:$$C_{final} = \frac{\sum C_i \cdot w_i}{\sum w_i}$$ - **优点**:单次渲染,适合移动端 - **缺点**:近似结果,可能出现过度变亮 #### 3. 每像素链表(Per-Pixel Linked Lists) - **原理**: 1. 使用GPU原子操作和链表存储所有透明片段 2. 在后期处理阶段按深度排序并混合 - **数据结构**: ```c struct Node { vec4 color; float depth; int next; }; ``` - **优点**:支持任意复杂度 - **缺点**:需GPU支持原子操作,显存消耗大 --- ### 四、OpenGL实现要点 1. **帧缓冲对象(FBO)**:需多个附件存储深度、颜色和权重 2. **着色器程序**:需自定义混合操作(禁用默认`glBlendFunc`) 3. **计算着色器**:现代OpenGL(4.3+)可用计算着色器加速排序 4. **性能优化**:根据硬件选择方案(如移动端优先用加权混合) --- ### 五、选择建议 | 技术 | 适用场景 | 性能消耗 | 精度 | |------|----------|----------|------| | 深度剥离 | 高精度需求(CAD/医学) | 高 | 精确 | | 加权混合 | 移动端/快速实现 | 低 | 近似 | | 每像素链表 | PC端复杂场景 | 中高 | 精确 | --- ### 六、挑战与未来发展 - **硬件限制**:链表需显存带宽支持 - **混合**:如何平衡权重计算与物理准确度 - **Vulkan/DXR**:新API提供了更高效的原子操作和光线追踪OIT方案 如果需要具体代码示例或某技术的深入分析,请进一步说明!
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值