从opengles3.2开始,支持了一项新的特性,Tessellation Shader,这是一个新的shader,发生在顶点处理阶段,曲面细分可以在显卡上动态生成新的顶点和面,这在支持gles3.2的设备上,可以用来表现模型的lod,我们不用传输大量的顶点数据给显卡,而使显卡能够根据一定规则自动生成细节层次更多的顶点。也有应用结合高度图生成细节更加复杂的模型,因为不用再cpu这边准备高面数的模型,减轻了内存和cpu的负担,使用建模表现高模的一种硬件加速方法。
因为支持gles3.2的设备还不多,所以还找不到太多应用这个新特性的例子,不过可以先看下3.2新的管线
可以看到早vertex shader后面新加了三道流程,Tessell Control Shader,Tessellation Primitive Generator,Tesselation Evaluation Shader。这三者共同完成曲面细分的硬件处理。
1. 首先是TCS,这是一个可编程管线,但是是可选的,如果没有会使用一个默认的参数。它的输入是patch原语。我们知道原语比较典型的有点 线 三角形 四边形,在tessellation这里多了一种原语patch,一个patch不是一个点也不是一个三角形,而是一个顶点序列,这个顶点序列就是产生细分曲面的基础顶点。在这个过程中一般要定义生成多少个细分曲面,这个要定义两个量,innersize,outsize。这里还可以对patch做一些调整操作,设置每个patch的序列长度,这些量将在下一个过程使用。
2.然后是TPG,这是一个固定管线,它负责真正的进行曲面细分,它利用上一步进来的patch,以及innersize ,outsize,以及space策略细分曲面,如下图
对外围的三角形进行了曲面细分后,生成了中间更多的顶点。这里面的输出是细分后的每个顶点及其顶点属性,在这里的顶点位置属性是相对的在0-1之间。
3.最后一步是TES,这是一个编程管线,且必须有,如果没有这个shader,则不发生曲面细分。这个shader将根据上一步进来的每个细分产生的顶点的绝对坐标,生成顶点真实的坐标。
本文不作Tessellation Shader的基本介绍。直接给出细分曲线的”Hellow World“代码。
下面代码将使用Tessellation Shader,传入2个控制点的情况下绘制一条正弦曲线连接这2个控制点。
效果如下图:(细分数目分别为1,8,32)
Vertex Shader:
-
-
layout (location = 0) in vec3 in_Vertex;
-
uniform mat4 ModelViewProjectionMatrix;
-
-
void main()
-
{
-
gl_Position = vec4(in_Vertex, 1);
-
}
Tessellation Control Shader(TCS):
-
-
layout( vertices= 4 ) out;
-
void main()
-
{
-
// Pass along the vertex position unmodified
-
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
-
-
gl_TessLevelOuter[ 0] = float( 1);
-
gl_TessLevelOuter[ 1] = float( 32);
-
}
第一:把输入的控制点的坐标信息(gl_in[gl_InvocationID].gl_Position)原封不动的输出。
第二:设置了细分控制参数。对于不同的细分类型(这里是曲线细分,会在下一个shader中看到细分类型的选择),这2个参数意义不一样。
gl_TessLevelOuter[0]表示要生成几条曲线。这里我们选择1条。
gl_TessLevelOuter[1] 表示将曲线细分成几段。这个很是决定细分程度的关键参数。我们分别使用1、8、32测试。
【注意】gl_TessLevelOuter[0]与gl_TessLevelOuter[1]的意义,在ATI显卡和NV显卡中是相反的!我这里以NV的卡为例。
Tessellation Evaluation Shader(TES):
-
-
layout( isolines ) in;
-
uniform mat4 ModelViewProjectionMatrix;
-
void main()
-
{
-
float u = gl_TessCoord.x;
-
vec3 p0 = gl_in[ 0].gl_Position.xyz;
-
vec3 p1 = gl_in[ 1].gl_Position.xyz;
-
float leng = length(p1 - p0)/ 2.0;
-
// Linear interpolation
-
vec3 p;
-
p.x = p0.x*u + p1.x*( 1-u);
-
p.y = p0.y + leng* sin(u* 2* 3.1415);
-
// Transform to clip coordinates
-
gl_Position = ModelViewProjectionMatrix * vec4(p, 1);
-
}
这里就是最关键的细分曲线算法了。
首先看到layout( isolines ) 的申明。这就告诉了TES我们使用的细分类型为曲线。这样,这里的gl_TessCoord.x,对于曲线来说我们只用到x分量就够了,他的取值范围在[0, 1]之间自动插值(根据Tessellation Control Shader中设置的分段数)
对于传入的2个控制点p0和p1,我们计算两点之间的长度,然后使用[0, 2*pi]区间,绘制一条正弦曲线。最后进行MVP坐标转换输出。
Fragment Shader
-
-
-
void main()
-
{
-
gl_FragColor = vec4( 1, 0, 0, 1.0);
-
}
OpenGL代码:
-
-
pShader->sendUniform( string( "ModelViewProjectionMatrix"), value_ptr(matMVP2));
-
gl::BindVertexArray(_vertexArrayBlock);
-
gl::EnableVertexAttribArray( 0);
-
-
gl::PatchParameteri(GL_PATCH_VERTICES, 2);
-
gl::DrawArrays(GL_PATCHES, 0, 2);
-
-
gl::BindVertexArray( 0);
-
gl::DisableVertexAttribArray( 0);
这里看到绘制类型一定只能为GL_PATCHES,另外注意设置Patch参数为GL_PATCH_VERTICES。由于只有2个控制点,DrawArrays传递参数2.
【注】2个控制点的VBO或者VAO的设置,以及4个Shader的编译、链接等步骤,这里就省略不写了。