解决OpenGL绘制半透明物体时的显示问题的创新方法

【1】问题:Opengl绘制半透明物体时,会出现显示问题

其他人的博客也有介绍:
1.opengl渲染透明的三角面片的问题
在这里插入图片描述
可以看出上图的斯坦福兔子渲染出来有很严重的阴影斑点问题。

【2】 传统解决办法

1. 油画家算法

将场景中的三角面片根据深度进行排序,然后按照顺序进行描绘,一般是先绘制远处的场景,再绘制近处的场景。
缺点:但如果两个多边形相交,就没法对他们进行排序了。甚至我们都不需要两个不同的物体来复现这个问题。组成玻璃杯的那些三角形会怎样?要让它们正确显示,需要在前面的绘制之前先绘制后面的三角面片,所以需要对每个三角形进行排序。

2. 母体切分

将母体切分成几大块,每块中的面片没有相互遮挡,相当于对所有面片粗排序了。
缺点:对于复杂模型,不好分块,操作困难。

3.加权平均值算法(Weighted Average)

使用简单的透明混合公式来实现无序透明渲染的算法,它通过扩展透明混合公式,来实现无序透明物件的渲染,从而得到一定程度上逼真的结果。【1】

NVIDIA公司的Louis Bavoil在此基础上提出了新的算法,使用物体的不透明度作为加权值的加权平均值算法。此算法的主要思想如下:
对于某个位置的像素点,如果所有的不透明物件是相同的颜色,那么渲染的结果与它们的渲染顺序无关。那么,对于不相同颜色值的情况,如果我们用某一个颜色来替换这些颜色,比如这些颜色的平均值。对于这种情况,我们使用各个颜色的不透明度作为权重来计算出它们的平均值。
此算法的优点很明显,效率高,速度快,只需要对物体进行一次的渲染,然后加上一次全屏的后处理。
缺点:透明结果只是一个近似值,而不是确切的正确结果。然而,它的效果仍然远远好过不做任何处理的简单透明混合,而且高效性也使得它有一定的应用空间,如游戏开发。

4.、深度剥离(Depth Peeling)

深度剥离是一种对深度值进行排序的技术。它的原理比较直观,标准的深度检测使场景中的Z值最小的点输出到屏幕上,就是离我们最近的顶点。但还有离我们第二近的顶点,第三近的顶点存在。要想显示它们,可以用多遍渲染的方法。第一遍渲染时,按照正常方式处理,这样就得到了离我们最近的表面中的每个顶点的z值。在第二遍渲染时,把现在每个顶点的深度值和刚才的那个深度值进行比较,凡是小于等于第一遍得到的Z值,把它们剥离,后面的过程依次类推即可。
引用映射的原理很简单,如果光源和目标点之间的连线没有任何物体阻挡的话,则目标点没有在阴影中;如果有物体遮挡,则目标点处于阴影中。而ShadowMap,就是一张记录了每个像素处用于比较遮挡关系信息的纹理。
深度剥离算法就是利用了它的这一特性模拟深度测试,从而实现对颜色层的剥离操作。具体算法如下:
1、首先用深度缓冲的深度测试功能渲染透明物件。保护得到的颜色值,这就是最前的一层,同时将深度值拷贝到有阴影映射功能的深度纹理中。
2、打开深度纹理的比较功能,使得深度值比较大的颜色通过测试,这时加上深度缓存本身的最小深度值功能,我们就能得到最前一层的下一层的颜色值和对应的深度值。
重复2的操作直到所有的颜色都成功的剥离出来,然后再将它们按照从后往前的顺序进行渲染,这样就可以得到正确的结果了。这种效果在OpenGL里实现起来比较简单,可以使用ARB扩展功能中的API函数。
缺点:该算法能保证得到正确的结果,但是其效率却是限制其使用的瓶颈。从上面的描述可知,它需要对透明物体进行N遍的渲染,这里的N是透明物件的深度复杂度。

【3】本文方法

在这里插入图片描述
原来的模型如上图所示。
设定三角面片,顶点逆时针方向对应得面为外表面。
首先开启背面剔除功能。
先渲染里面,将三角面片顶点排序由(0,1,2),改为顶点排序(0,2,1)。此时,因为开启了背面剔除功能,我们只能看到模型远离我们的那部分的内部。如下图所示的黄色部分。
在这里插入图片描述

再渲染外面,三角面片得顶点排序调整为原来的(0,1,2),因为剔除了背景,所以这个阶段我们只能看到紫色部分,如下图:
在这里插入图片描述

两个阶段叠加起来,设置为透明模型,效果如下图:
在这里插入图片描述

效果图
在这里插入图片描述
代码

void CShowShader::ShaderTransparence(TriMesh*Tmesh)
{      
	if (Tmesh == NULL) return;
	Tmesh->need_normals();
	///
	/// 绘制背面
	/// 
	glEnableClientState(GL_VERTEX_ARRAY);//表示启用顶点数组。
	glEnableClientState(GL_NORMAL_ARRAY);//表示启用法矢数组。
	int fn = Tmesh->faces.size();
	glDisable(GL_COLOR_MATERIAL); //关闭服务器端GL功能,才能使用glMaterialfv
	glCullFace(GL_BACK); // 剔除背面
	glEnable(GL_CULL_FACE); // 启用剔除功能
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); //使物体的背面接受的光照准确,
	glDepthMask(GL_FALSE); //下面将绘制半透明物体了,因此将深度缓冲设置为只读
	glVertexPointer(3, GL_FLOAT, 0, &Tmesh->vertices[0][0]); //指定顶点数组的位置,3表示每个顶点由三个量构成(x, y, z),GL_FLOAT表示每个量都是一个GLfloat类型的值。第三个参数0,参见后面介绍“stride参数”。最后的vertex_list指明了数组实际的位置。
	glNormalPointer(GL_FLOAT, 0, &Tmesh->normals[0][0]);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 设置正面和反面为填充模式,GL_LINE替换GL_FILL就是线型模式
	vector<int> F_Index(fn * 3);
	for (int i = 0; i < fn; i++)
	{
		F_Index[3 * i + 0] = Tmesh->faces[i][0];
		F_Index[3 * i + 2] = Tmesh->faces[i][1];
		F_Index[3 * i + 1] = Tmesh->faces[i][2];
		
	}
	if (fn)
	{
		COpenglToolFunction::SetMaterialColor(RGB(0, 255, 0), 0.3);//chengse
		glDrawElements(GL_TRIANGLES, fn * 3, GL_UNSIGNED_INT, &F_Index[0]); //1 绘制的三角形 2 总共多少定点 3 表示序号数组内每个序号都是一个int类型的值 4 索引数组实际的位置
	}
	// 完成半透明物体的绘制,将深度缓冲区恢复为可读可写的形式
	glDisable(GL_BLEND);
	glDepthMask(GL_TRUE);
	glDisableClientState(GL_NORMAL_ARRAY);//表示关闭法矢数组。
	glDisableClientState(GL_VERTEX_ARRAY);//表示关闭顶点数组。
	glDisable(GL_CULL_FACE); // 禁用剔除功能

	///
	/// 绘制正面
	///
	glEnableClientState(GL_VERTEX_ARRAY);//表示启用顶点数组。
	glEnableClientState(GL_NORMAL_ARRAY);//表示启用法矢数组。
	glDisable(GL_COLOR_MATERIAL); //关闭服务器端GL功能,才能使用glMaterialfv
	glCullFace(GL_BACK); // 剔除背面
	glEnable(GL_CULL_FACE); // 启用剔除功能
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); //使物体的背面接受的光照准确,
	glDepthMask(GL_FALSE); //下面将绘制半透明物体了,因此将深度缓冲设置为只读
	glVertexPointer(3, GL_FLOAT, 0, &Tmesh->vertices[0][0]); //指定顶点数组的位置,3表示每个顶点由三个量构成(x, y, z),GL_FLOAT表示每个量都是一个GLfloat类型的值。第三个参数0,参见后面介绍“stride参数”。最后的vertex_list指明了数组实际的位置。
	glNormalPointer(GL_FLOAT, 0, &Tmesh->normals[0][0]);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 设置正面和反面为填充模式,GL_LINE替换GL_FILL就是线型模式

	for (int i = 0; i < fn; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			F_Index[3 * i + j] = Tmesh->faces[i][j];
		}
	}
	if (fn)
	{
		COpenglToolFunction::SetMaterialColor(RGB(0, 255, 0), 0.5);//绿色
		glDrawElements(GL_TRIANGLES, fn * 3, GL_UNSIGNED_INT, &F_Index[0]); //1 绘制的三角形 2 总共多少定点 3 表示序号数组内每个序号都是一个int类型的值 4 索引数组实际的位置
	}
	// 完成半透明物体的绘制,将深度缓冲区恢复为可读可写的形式
	glDisable(GL_BLEND);
	glDepthMask(GL_TRUE);
	glDisableClientState(GL_NORMAL_ARRAY);//表示关闭法矢数组。
	glDisableClientState(GL_VERTEX_ARRAY);//表示关闭顶点数组。
	glDisable(GL_CULL_FACE); // 禁用剔除功能
}

参考博客:
【1】OpenGL深度剥离算法(Depth Peeling)半透明实现

对比图

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
转载请注明来源,谢谢。

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值