第九章 细分着色器
细分面片
细分着色器只能处理面片(patch)类型的图元,如果启用细分着色器,将其他类型图元传递给它会产生GL_INVALID_OPERATION错误,如果没有启用细分着色器,那么渲染面片数据也会得到GL_INVALID_OPERATION错误。
面片就是传递给OpenGL的顶点列表,处理过程要保证顺序正确。
指定面片顶点数量
void glPatchParameteri(GLenum pname, GLint value);
pname必须设置为GL_PATCH_VERTICES,value为面片顶点个数,范围需要限制在[0, GL_MAX_PATCH_VERTICES]中。面片顶点个数默认为3个,如果顶点个数小于3那么将忽略该面片,并且不会产生几何体。
细分控制着色器
生成输出面片的顶点
细分控制着色器的输入保存在gl_in当中,它的大小与glPatchParameteri()中设置的面片大小是相通的,可以通过gl_PatchVerticesIn来获取它的大小,相当于gl_in.length()。
生成的顶点列表保存在gl_out当中,其大小可以通过布局限定符(layout)来设置:
layout (vertices = 16) out;
该声明实现两个目的:设置gl_out的数量,以及细分控制着色器执行的次数:每个输出面片顶点执行一次。
可以使用gl_InvocationID来判断当前处理的是哪个输出顶点,也就是gl_out中的索引位置,同一个patch中的顶点可以互相访问数据。
细分控制着色器的变量
gl_in的每个元素的结构为:
in gl_PerVertex{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
} gl_in[gl_PatchVerticesIn];
细分控制着色器的其他输入变量
变量声明 | 描述 |
gl_InvocationID | 当前TCS请求的输出顶点索引 |
gl_PrimitiveID | 当前输入patch的图元索引 |
gl_PatchVerticesIn | 输入patch顶点数量 |
gl_PatchVerticesOut | 输出patch顶点数量 |
细分控制
外侧细分层级:gl_TessLevelOuter[4]
内侧细分层级:gl_TessLevelInner[2]
如果没有绑定TCS指定细分层级:
void glPatchParameterfv(GLenum pname, const GLfloat* values);
pname使用GL_PATCH_DEFAULT_OUTER_LEVEL或GL_PATCH_DEFAULT_INNER_LEVEL。前者需要values中包含4个浮点值,后者values需要包含2个浮点值。
细分计算着色器
每一个通过图元生成得到的细分坐标都需要执行一次细分计算着色器,用以判断从细分坐标而来的顶点位置。
配置图元生成器
通过layout布局限定符来控制
设置图元生成域
图元类型 | 描述 | 域坐标 |
quads | 单位块上的一个四边形域 | (u, v)对的形式,范围从0~1 |
triangles | 使用重心坐标的三角形 | (a, b, c)坐标形式,取值范围是0~1且a+b+c=1 |
isolines | 一系列穿过单位块的线段集合 | (u, v)对形式, u的范围从0~1, v的范围从0到接近于1的值 |
设置生成图元的面朝向
cw表示顶点按照顺时针排列
ccw表示顶点按照逆时针排列
设置细分坐标的间隔
选项 | 描述 |
equal_space | 细分层级被截断在[1, max]内,然后取整到下一个整数值 |
fractional_even_spacing | 数值将被阶段在[2, max]内,然后取整到下一个偶整数值n。然后将边界划分为n-2个等长的部分,以及2个位与两端的部分(可能比其他部分的长度更短) |
fractional_odd_spacing | 数值将被阶段在[1, max]内,然后取整到下一个奇整数值n。然后将边界划分为n-2个等长的部分,以及2个位与两端的部分(可能比其他部分的长度更短) |
设置输出点集
使用point_mode选项来指定输出点集,而不是等值线或者填充区域。
layout(triangles, equal_spacing, ccw, points) out;
设置顶点位置
着色器中的细分坐标是通过gl_TessCoord变量给出的。
细分着色器的输入变量
gl_in的结构
in gl_PerVertex{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
} gl_in[gl_PatchVerticesIn];
其他输入变量
变量声明 | 描述 |
gl_PrimitiveID | 当前输入面片的图元索引 |
gl_PatchVerticesIn | 输入面片的顶点数,也就是gl_in的大小 |
gl_TessLevelOuter[4] | 外侧细分层级的值 |
gl_TessLevelInner[2] | 内侧细分层级的值 |
gl_TessCoord | 还未进入细分计算着色器中面片域空间的顶点坐标值 |
输出顶点数据结构
out gl_PerVertex{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
};
细分的共享边域裂缝
OpenGL中的细分可以确保面片中生成的几何体不会有任何的裂缝存在,但是它无法保证共享一条边的面片也不存在裂缝。
一种常见的解决方法是,找到所有对周长边的顶点有贡献的输出面片顶点,按照预设的方式进行排序,也就是沿着边长向量增加大小的方式。
另一种避免裂缝的方法就是在着色器计算的时候,如果存在两个着色器请求的顶点顺序不一致的情况,就是用precise限定符。