GPU Pro2 - 1.Terrain and Ocean Rendering with Hardware Tessellation

最近时间多了起来,准备捡起扔下了的渲染部分的知识。想拜读下GPU Pro系列并且做个笔记,不知道自己能否坚持下来,但愿可以吧。自己能力也有限,写的东西也只是自己的理解,肯定有很多理解不到位甚至错误的地方,也希望如果哪位大神看到了可以指正。关于tessellation,这篇博客介绍的很好http://www.javashuo.com/article/p-kfrxyhqs-py.html

如果想让场景更真实,你可能需要细节更高,面数更多的模型,但是这样会使得cpu计算增加,运行变慢。

The best solution for tessellation is the recently developed tessellator stage in DirectX 11. This stage, together with the hull and the domain shader, allows the programmer to tessellate very quickly into the GPU. With this method you can send low-level detail meshes to the GPU and generate the missing geometry to the GPU depending on the camera distance, angle, or whatever you want. 

Tessellation(细分曲面)技术是将低模细节度低的模型输入至gpu中,在DirectX11之后,在hull 和 domain shader中可以通过tessellation快速的增加细节来增强表现力,却又不需要损耗cpu的性能。

 

 

通过DirectX 11中的渲染流水线可以看出,在Vertex Shader和 Geometry Shader之间插入了 Hull Shader Stage ,Tessellator Stage 和 Domain Shader Stage三个阶段,用于处理Tessellation。

Hull Shader Stage

Hull shader 的 输入比较特殊,叫做 contral point patch list。常规情况下,vertex shader的输出是顶点, 但是如果要用硬件曲面细分,那么顶点着色器输出就不再是顶点,而是control point的patch,一个patch包含多个control point,最多可以包含32个。vertex shader输出patch给HS用。

Hull shader 的输出包含两部分,一部分是control points(可以在Hull shader阶段进行修改),另一部分是一些常量,用于后续的tessellator stage 和 domain shader stage。为了计算这两部分输出,在该阶段需要有两个函数。

第一个函数叫做Constant Hull Shader,这个函数executed for every patch,也就是以patch为单位执行,在这个函数里,设置了tessellation的一些参数。

由示例代码可以看出,该函数的输入InputPatch是一个模板类,对应的是顶点着色器的输出,如果这里要用曲面细分的话,就不能在VS中乘以世界和摄像机投影矩阵(这是因为在tessellation中新加入了细节,新生成了顶点,如果在vertex shader中就做完了这些操作,新生成的顶点咋办呐),而是要留到细分结束后再变换(如果有几何着色器的话就留到几何着色器阶段再变换)。然后这里可以用SV_PrimitiveID获取Primitive的id。这里可以把细分等级和离摄像机的远近、屏幕覆盖范围、朝向、粗糙度等挂钩,而不是写一个定的值,可以起到优化的效果。

该函数的输出有两个值,分别为SV_TessFactor和SV_InsideTessFactor,前者是边的细分等级,后者是面片中心的细分等级。前者有几条边就要输出几个,分别对应每条边的参数,后者则要输出两个,分别对应的是u和v方向。

struct PatchTess
{
float EdgeTess[4] : SV_TessFactor;
float InsideTess[2] : SV_InsideTessFactor;
// Additional info you want associated per patch.
};
PatchTess ConstantHS(InputPatch<VertexOut, 4> patch,
uint patchID : SV_PrimitiveID)
{
PatchTess pt;
// Uniformly tessellate the patch 3 times.
pt.EdgeTess[0] = 3; // Left edge
pt.EdgeTess[1] = 3; // Top edge
pt.EdgeTess[2] = 3; // Right edge
pt.EdgeTess[3] = 3; // Bottom edge
pt.InsideTess[0] = 3; // u-axis (columns)
pt.InsideTess[1] = 3; // v-axis (rows)
return pt;
}

第二个函数叫做Control Point Hull Shader,这个部分是每个control point都调用一次,因此和顶点着色器类似,只不过对象是control point,在这个阶段我们可以改变曲面的表达形式,比如把输入的三角面(三个control point)变成由包含十个control point的patch控制的贝塞尔曲线输出,等等。
Control Point HS要定义不少属性,其中domain是patch type,可选的有tri,quad或者isoline。
partitioining是细分模式,integer的细分等级会突变,而fractional_odd或者fractional_even的细分等级会渐变,顶点会逐渐移动直到消失,而不会突然pop出来或者消失。
outputtopology输出的三角面的winding order,有triangle_cw,triangle_ccw,line这三个选项
outputcontrolpoints是输出顶点的数量,也就是hs的执行次数,可以用SV_OutputControlPointID获取当前Control Point的ID。
patchconstantfunc则是constantHS的名字
maxtessfactor是最大细分数量,dx11最高支持到64,这里可以手动设置得更低。
 

struct HullOut
{
	float3 PosL : POSITION;
};
[domain(“quad”)]
[partitioning(“integer”)]
[outputtopology(“triangle_cw”)]
[outputcontrolpoints(4)]
[patchconstantfunc(“ConstantHS”)]
[maxtessfactor(64.0f)]
HullOut HS(InputPatch<VertexOut, 4> p,
uint i : SV_OutputControlPointID,
uint patchId : SV_PrimitiveID)
{
	HullOut hout;
	hout.PosL = p[i].PosL;
	return hout;
}

The Tessellation Stage

Hull shader结束之后就是细分阶段,该阶段由硬件完成,不可编程,通过Hull shader中设置的参数,该阶段会新生成顶点,但是得出的只是顶点的uv,实际的位置等信息是在之后的domain shadr中进行计算。

在这里,可以理解下之前设置的参数,SV_TessFactor和SV_InsideTessFactor。

对于四边形图元,边的顺序是左上右下顺时针。如下图中的第一个图:每个边的参数都为4,也就是说每个边等分成了四份。中心的参数为(4,4),也就是中心也等分成了4份。

再如下图中的第三个图:左边和上边参数为2,则二等分,右边和下边为4则是4等分。中心参数为2和4,分别对应u方向和v方向,所以中心部分横向(u方向)二等分,纵向(v方向)四等分。

对于三角形图元,细分操作同,只不过中心参数只有一个,中心的操作是在每个三角形定点到中心的延长线方向进行。

Domain Shader Stage

In the domain shader we have to reconstruct every final vertex and we have to calculate the position, normal, and texture coordinates. This is the part where the difference between terrain and ocean rendering is more important.

该阶段的输入是细分好了的曲面,在该阶段我们要做的是根据uv来算出顶点的位置,法线和真正的纹理坐标。然后如果没有几何着色器的话,我们需要在这个阶段把顶点变换到屏幕坐标里。
这里说的给定uv是针对四边面,如果用的是三个control point的patch,那么这里给定的是质心坐标系下的uvw。

Terrain&Ocean

说实话,后边这些没有太看懂。。还望各位大佬指导,大体总结几个点吧:

1.对于tessellation新生成的顶点,x和z方向的坐标可以通过patch中已有顶点的位置用插值方式得到。对于Terrain,y方向的坐标可以通过高度图和法线图得到已有顶点的高度参数后进行插值,对于Ocean,y坐标可以通过波浪函数计算得到。

2.很重要的一点是使用一些技术来计算tessellation factor,也就是tessellation的参数。比如对于某条边,它必然属于两个patch,要确保在这两个patch中该边的参数一致。再比如通过mipmap参数计算tessellation参数,距离摄像机越近的,tessellation参数越大,距离摄像机越远的地方,tessellation参数越小。