OpenGL超级宝典(第7版)笔记7 细分曲面初介绍 清单3.7-3.8

编程入门 行业动态 更新时间:2024-10-27 00:29:52

OpenGL超级宝典(第7版)笔记7 细分曲面初介绍 清单3.7-3.8

文章目录

  • OpenGL超级宝典(第7版)笔记7 细分曲面初介绍 清单3.7-3.8
  • 1 细分曲面
  • 2 细分曲面控制着色器TCS
          • 遗留的问题:gl_ClipDistance[]的值有什么用?
          • 遗留的问题:gl_TessLevelOuter中如果设置为非整数那会怎样?
  • 3 细分曲面引擎
  • 4 细分曲面评估着色器TES
          • 遗留的问题:TES中的gl_TessCoord究竟是什么,为什么说清单这里是没有改变顶点的位置?
          • 遗留的问题:TES在处理线,三角面等高阶基元时运行多少次,如何运行?
  • 5 编译绘制运行
  • 6 引用参考资料
  • 7 实际效果
  • 8 总结


上一次介绍了着色器之间的数据传输,其特点是单向性、对应性,当然还要注意顶点着色器到片段着色器的插值问题。
从这一篇开始我们将介绍其他种类的着色器,这些着色器并不是每个程序必须的,但它们可以提供更多更强大的功能。我们还是跟着书籍(OpenGL超级宝典第7版)来粗略的过一遍每种着色器(并不会非常细致),让大家熟悉一下管线的流程,也可以让大家记住每种着色器的用途,方便我们以后使用。

注意从本篇开始就可能会出现 遗留的问题:…> 像这样的小节(这是为了我以后补充留的标题),也就是下面关于遗留的问题有关的描述都不一定是准确的,还请大家见谅,毕竟我也是初学者,也希望大家指正,当然我以后要是弄清楚了会再进行修改,也有可能我弄懂了之后忘记修改了。

本篇是关于细分曲面控制着色器、细分曲面引擎、细分曲面评估着色器三个环节的简介。

1 细分曲面

由于我们都是使用三角面来表示曲面的,所以我们用这种方式表达的曲面其实并不精确,用到的三角面越多,我们显示的结果越接近真正的曲面(例如下图中的球形,当三角面变多时越来越像球形了,表面也变得更"光滑"了)

(截图来源于:哔哩哔哩闫令琪计算机图形学课程p12 9分50左右)
但是问题在于,有时会有大量的物体,每个物体有含有很多的曲面,每个曲面有包含很多的三角面,这导致进行一次加载要导入超级多的顶点,假如我们还想让曲面更精细,那需要的顶点数量将非常多,那将拖慢电脑的运行。所以我们采用曲面细分的方法,导入少量的三角形面然后通过算法让大三角形面分裂成很多的小三角面,并通过算法更改小三角面的顶点位置,让它们更贴合真正的曲面(见下图:曲面细分后曲面更接近真实的牛的形状了)

(截图来源于:哔哩哔哩闫令琪计算机图形学课程p12 7分20左右)

OpenGL中实现曲面的细分功能的就是,细分曲面控制着色器、细分曲面引擎、细分曲面评估着色器这三者。其中细分曲面控制着色器用于生成控制点并设置细分等级(比如把边缘分为几份),细分曲面引擎用于通过前面设定的控制点和细分等级,真正分割曲面并生成大量的顶点,细分曲面评估着色器用于更改分割后小三角面顶点的移动更改(这让曲面更贴合,更"光滑")。

2 细分曲面控制着色器TCS

前面已经讲过了细分曲面控制着色器用于计算控制点信息并设置细分等级,这样做了设置之后细分曲面引擎才能根据我们设定的控制点和细分等级来生成大量的顶点。

在计算控制点之前,首先是设置控制点的数量,用glPatchParameteri();来设置。

glPatchParameteri(GL_PATCH_VERTICES, 3);//只需要在render();中设定一次就行了

其中第一个参数指示我们要设置细分曲面控制着色器的哪些东西,这里我们填写的是GL_PATCH_VERTICES即我们要设置每个patch(贴片)的控制点数量(patch指的就是传入到细分曲面控制着色器的那些东西,比如传入的是三角形,是四边形等等)。我们这里设置控制点数量为3。

注意这里设置的控制点数量为输入细分曲面控制着色器的控制点数量,而由于细分曲面控制着色器是用来计算控制点相关信息的着色器,它其实可以产生更多的控制点或删去一些控制点(即控制点输入数量可以不等于输出的控制点数量)。

英文原文:The number of control points per patch can be changed such that the number of control points that is output by the tessellation control shader can differ from the number of control points that it consumes. The number of control points produced by the control shader is set using an output layout qualifier in the control shader’s source code.

中文原文:每个贴片的控制点数量可以更改,因此细分曲面控制着色器的控制点数量与其使用的控制点数量不同。控制着色器生成的控制点数量使用控制着色器源代码中的输出配置限定符设置。

而我们可以通过:

layout (vertices=3) out;

来设置当前细分曲面控制着色器的输出控制点数量是多少(虽然我们这里设置的输入控制点数量等于输出控制点数量),这里设置输出的控制室数量为3。

注意
细分曲面控制着色器TCS是每有一个控制点输出,就会运行一次,也就是说如果为layout (vertices=4) out;那么TCS会在该patch上运行4次。

之后我们要在着色器中设置具体的细节了。这里给出清单3.7
清单3.7 我们的第一个细分曲面控制着色器

#version 450 core						
layout (vertices=3) out;				
void main(void)						
{										
	if(gl_InvocationID==0){				
		gl_TessLevelInner[0]=5.0;		
		gl_TessLevelOuter[0]=2.0;		
		gl_TessLevelOuter[1]=5.0;		
		gl_TessLevelOuter[2]=5.0;		
	}									
	gl_out[gl_InvocationID].gl_Position=gl_in[gl_InvocationID].gl_Position;
}	

在着色器中gl_TessLevelOuter和gl_TessLevelInner分别设置的是每条边分成几份,中心区域分成几份。

其中你可以看到两者都是数组,虽然gl_TessLevelOuter和gl_TessLevelInner中都有很多的元素(比如gl…Outer[0]、gl…Outer[1]、gl…Outer[2]、gl…Outer[3]…等等gl_TessLevelInner同理),但是OpenGL会根据输出的控制点数量来取数组的前一部分数值作为细分等级。(比如layout (vertices=3) out; 这种情况就会取Inner的第一个元素,Outer的前三个元素,数组后方的元素你赋值并不会起到作用)

详见第八章8.1.1曲面细分基元模式

具体说说gl_TessLevelOuter和gl_TessLevelInner设置的是什么
gl_TessLevelOuter设置的是每条边上分成几份,比如gl_TessLevelOuter[0] = 2.0;就是把第一条边分成两段(2.0),gl_TessLevelOuter[1] = 5.0;就是把第二条边分成五段(5.0),同理gl_TessLevelOuter[2] = 5.0;就是把第三条边分成五段(5.0)。这个就不给图了,很好理解。

gl_TessLevelInner设置的是将内边缘分成几段(书中也没解释什么是内边缘),对于layout (vertices=3) out;来说只取gl_TessLevelInner[0]作为内部区域细分的参数。对于对于layout (vertices=4) out;来说只取gl_TessLevelInner[0]和gl_TessLevelInner[1]作为内部区域细分的参数,其中gl_TessLevelInner[0]控制水平方向的细分,gl_TessLevelInner[1]控制上下方向的细分。

我虽然没有找到确定的说法,但是我们可以通过unity的细分法则,猜测这个内边缘细分的具体分法是什么样的。

原文为Unity Shader:细分着色器(Tessellation Shader)在Unity顶点着色器中的写法以及各参数变量解释

首先对内部的分级分为奇数和偶数,当为奇数时候(如为2n+1时,n为整数)就会形成一环一环的小三角形,有n个环(见下图蓝色线)。而当为偶数是(如为2n时,n为整数)会在中间形成一个点,外面为一环一环的小三角形,有n-1个环(见下图黄色)。

而具体的细分规则是,从三角形的一角出发,每次连接紧挨着的内部三角形的一个角或是一边或是中心细分点,算作将内部分为一段。
以下面的为例子:比如gl_TessLevelInner[0]=3.0; 从左下角开始到中间小三角形的左下角(线1),再到右手边的边(线2),再到大三角形的右手边(线3)。其实我觉得走2’和3’这样数也可以。可以看到内部分成了3段。所以正对应了Inner设置的是3.0。
当然如果是四边形的话gl_TessLevelInner[0]设置的是水平的细分,gl_TessLevelInner[1]设置的是垂直的细分,比如:gl_TessLevelInner[0]=2.0; gl_TessLevelInner[1]=3.0;就是水平分为两段,垂直分为3段,(注意设置为偶数会在中间产生一个点,因为只是水平上为偶数,所以你看到的是一条线)
蓝色为水平上的分段,绿色为垂直上的分段

图片来源于:GLSL Tessellation Shader的编程入门介绍
还记得前面蓝色和黄色的三角环吗,其中蓝色的为

gl_TessLevelInner[0]=5.0;
gl_TessLevelOuter[0]=3.0;
gl_TessLevelOuter[1]=4.0;
gl_TessLevelOuter[2]=5.0;

黄色为:

gl_TessLevelInner[0]=4.0;
gl_TessLevelOuter[0]=3.0;
gl_TessLevelOuter[1]=4.0;
gl_TessLevelOuter[2]=5.0;

你能数对吗~

gl_out,gl_in的作用
最后是gl_out,gl_in,这两个是两个结构数组,他们所包含的数据如下

in gl_PerVertex
{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
}gl_in[gl_MaxPatchVertices];
out gl_PerVertex
{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
} gl_out[];

两个结构数组主要包含的是输入(gl_in)、输出(gl_out)细分曲面控制着色器的顶点位置、大小、裁剪距离,其中gl_in数组的元素数量是跟输入控制点数对应的(通过glPatchParameteri设置的),而gl_out数组的元素数量是跟输出控制点数对应的(通过layout (vertices=?) out;来设置的)。上面的清单3.7只是做了一个简单的传递,并没有对数据进行修改计算。也就是输出着色器的控制点位置等于输入着色器的控制点位置。

gl_InvocationID
gl_InvocationID是OpenGL自带的变量,是控制点的索引值,有点类似于gl_VertexID。当细分曲面控制着色器运行在第一个控制点时,gl_InvocationID值为0,当运行在第二个控制点时,gl_InvocationID值为1…以此类推。因为细分曲面控制着色器TCS会根据输出的控制点数量来运行相同的次数,所以gl_InvocationID也会与之对应(例如:layout (vertices=5) out;那么gl_InvocationID最大为4,因为gl_InvocationID从0开始,TCS在该patch上只会运行5次)。

由于这里的输入的控制点数量(通过glPatchParameteri设置的)等于输出的控制点数量(通过layout (vertices=?) out;来设置的),所以用gl_InvocationID作为结构数组gl_in和gl_out的索引值,就能将数据从输入传递到输出了。

对于if(gl_InvocationID==0)作用的猜想:防止同一个patch(贴片)在设置细分等级的时候重复运行下面的代码,因为一个patch可能会有很多的控制点。

遗留的问题:gl_ClipDistance[]的值有什么用?
遗留的问题:gl_TessLevelOuter中如果设置为非整数那会怎样?

3 细分曲面引擎

这一部分是OpenGL的固定功能,我们并不能对齐进行修改(所以弄懂前面的那些参数才能产生想要的细分结果)。细分曲面引擎通过接受上面的gl_out、gl_TessLevelOuter、gl_TessLevelInner的信息来将patch(贴片)这样的高阶面分解成更基础的单元,比如点、线、小三角面,然后这些基础的单元将传递给细分曲面评估着色器,细分曲面评估着色器获取到引擎所给的顶点后将他们组装成三角面,然后再往下进行光栅化。

4 细分曲面评估着色器TES

接受到引擎所传出的大量顶点后,细分曲面评估着色器(TES)会在这些顶点上进行调用(每个顶点运行一次TES),如果引擎传出的不是顶点而是更高阶的基元(比如线,三角面)那么TES还会运行很多次。

#version 450 core						
layout (triangles,equal_spacing,cw)	in;	
void main(void)						
{						
	gl_Position=(gl_TessCoord.x*gl_in[0].gl_Position+		
				 gl_TessCoord.y*gl_in[1].gl_Position+		
				 gl_TessCoord.z*gl_in[2].gl_Position);		
}						

首先第一句layout (triangles,equal_spacing,cw) in;这里是告诉着色器我们输入数据的相关信息。
triangles指示我们着色器的输入图元是三角形。
equal_spacing细分层级将会截断在[1,max]之间(就是在Inner或是Outer中的细分等级会被强制放在[1,max]之间),并且取到下一个整数(向后取整,比如:2.3取整为3)。
cw顺时针顶点环绕顺序生成三角形

由于TES有些类似于细分小三角形的顶点着色器,TES输出的是小三角形的顶点,但是注意这里的gl_in结构数组和TCS中的gl_in不是一个结构数组,虽然它们的构成是一样的,但是这里的gl_in是接收的TCS的输出gl_out。具体可以看下图:

遗留的问题:TES中的gl_TessCoord究竟是什么,为什么说清单这里是没有改变顶点的位置?
遗留的问题:TES在处理线,三角面等高阶基元时运行多少次,如何运行?

5 编译绘制运行

跟一般的着色器一样,也要生成着色器对象,导入代码,编译,连接到着色器程序。

tess_control_shader = glCreateShader(GL_TESS_CONTROL_SHADER);
	glShaderSource(tess_control_shader, 1, tess_control_shader_source, NULL);
	glCompileShader(tess_control_shader);
	glGetShaderiv(tess_control_shader, GL_COMPILE_STATUS, &shader_success);//检查编译是否成功
	if (!shader_success)
	{
		glGetShaderInfoLog(tess_control_shader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::TESS_CONTROL::COMPILATION_FAILED\n" << infoLog << std::endl;
	}

	tess_evaluation_shader = glCreateShader(GL_TESS_EVALUATION_SHADER);
	glShaderSource(tess_evaluation_shader, 1, tess_evaluation_shader_source, NULL);
	glCompileShader(tess_evaluation_shader);
	glGetShaderiv(tess_evaluation_shader, GL_COMPILE_STATUS, &shader_success);//检查编译是否成功
	if (!shader_success)
	{
		glGetShaderInfoLog(tess_evaluation_shader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::TESS_EVALUATION::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	glAttachShader(program, tess_evaluation_shader);
	glAttachShader(program, tess_control_shader);

指的注意的是,在绘制三角形的时候不再选择GL_TRIANGLES了,而是GL_PATCHES。

	glDrawArrays(GL_PATCHES, 0, 3);
	//注意这里不是glDrawArrays(GL_TRIANGLES, 0, 3);,其中应改成GL_PATCHES

快来试试曲面细分吧,看看会遇到什么问题,在试着解决吧(文章有错误一定要指出来啊!)

6 引用参考资料

1,GLSL Tessellation Shader的编程入门介绍
2,Unity Shader:细分着色器(Tessellation Shader)在Unity顶点着色器中的写法以及各参数变量解释
3,Vulkan_曲面细分(Tessellation Shader) 这个应该比较难(我还没看太懂)
4,OpenGL 编程指南(第八版)学习笔记——9 细分着色器
5,OpenGL超级宝典(第7版)相关章节
6,OpenGL编程指南(第8版)相关章节

7 实际效果


细分后(这里只是细分,没有设置函数修改细分点的位置,所以你能看到杯子把手上还是很粗糙):

8 总结

这一次介绍了细分曲面相关内容(清单3.7-3.8),稍微了解了一下,细分曲面的种种内容,但是我们还并没有深入的理解这些参数、变量究竟是什么意思,究竟如何实现的,以后专门出一篇来说吧(现在还不太会),下一篇我们说说几何着色器,这是很有意思的一篇!

我们下篇见~~

关于书籍的问题
如果你手中没有该书,我还是建议你购买一本,毕竟书本毕竟更加严谨专业,我这里难免遗漏一些细节,主要是提供实例,并做一个消化,将很混乱的流程为大家理清,但这笔记一定是通俗的,是对新手友好的(当然有时候你需要在某些方面自己努努力,比如后面出现的基本线性代数的内容,还有C语言或是c++的基础知识,虽然我可能也不太懂O(∩_∩)O,慢慢来吧)。

别被吓住
刚开始的时候很容易被OpenGL的巨长的函数和超级复杂的流程吓到,其实并没有那么可怕,只要对这样或那样的流程熟悉之后,一切都变得相当简单(当然如果你能提出一个更好的流程那就更好了,当我们把很多基础的工作做完,我们会不断的提出新问题新点子,用新的技术来实现它,最终完成OpenGL的学习)

虽然我也不知道后面将是怎样的道路,但至少努力学习是没错的。

我看过的相关内容
以下并不是全看完了,大部分看了15%就看不下去了,实在是没看懂。(本人没什么计算机编程基础,算是野生程序员吧,很多内容都不能标准表述,望见谅)
如果你对opengl的工作有了一定的了解,我一开始也是从这里开始的,但是仍然有很多的不懂的,最后至今为止,我杂糅了很多的网站内容包括LearnOpenGL、极客学院、哔哩哔哩的闫令琪计算机图形学、哔哩哔哩的傅老师的OpenGL课程、OpenGL编程指南"也称为红宝书"、OpenGL超级宝典"也称为蓝宝书"、当然还有很多的csdn文章O(∩_∩)O这就不介绍了,等用到是时候我在放链接吧O(∩_∩)O

这里面图形学比较易懂也很基础推荐可以作为开始(如果你是学OpenGL需要马上用,应该可以跳过,但是其中的内容很是很重要,这会让后面涉及变换透视的章节更加易懂,推荐大家看看),之后是蓝宝书或是极客学院翻译的教程比较推荐,这两个还是比较适合你我这样的新手的。
这里不推荐看的是红宝书,这本书我看了有点类似于字典那样的工具书,不太适合新手上手学,而且讲的也并不是很通俗易懂(可能是我的书版本比较老吧…)

加油
当然如果你对我有信心,我也会持续更新(虽然前路漫漫),跟大家一同进步(虽然很可能没人看(╥╯^╰╥),无所谓了,当然如有错误还请大家指正∠(°ゝ°),哪里不懂我会尽力解决,哪里说的不好也可以指出我会及时修改~)

我们下篇见~~

更多推荐

OpenGL超级宝典(第7版)笔记7 细分曲面初介绍 清单3.7-3.8

本文发布于:2023-06-11 02:14:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1372255.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:曲面   清单   宝典   笔记   OpenGL

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!