赞
踩
通过本节的学习可以得到如下的效果
将一个低模的模型
通过渲染管线的曲面细分功能
得到一个高模的结果
当我们开启渲染管线的tessellation开关时,我们传统意义上的顶点着色器功能就发生了改变。因为此时我们提交给IA阶段的内容不再被看作是有三个顶点(vertex)的三角面(triangle)(因为经过曲面细分之后才是三角面),而是看作一个有三个控制点(control point)的片(patch)。
这里的控制点,可以理解成贝塞尔曲线的控制点或者PhotoShop钢笔工具的控制点,是控制点组成了这个图元的原始信息。
Hull Shader实际上由两部分组成
对于每一个patch(原始三角片) 都会执行一次这个constant hull shader,其功能是用来输出所谓的细分因子(tessellation factor).细分因子用于在tessellation 阶段告诉硬件如何对patch进行细分。
struct PatchTess
{
float EdgeTess[3]:SV_TessFactor;
float InsideTess:SV_InsideTessFactor;
};
PatchTess constantHS(InputPatch<VertexOut,3> patch,uint patchID:SV_PrimitiveID)
{
PatchTess pt;
pt.EdgeTess[0]=3;
pt.EdgeTess[1]=3;
pt.EdgeTess[2]=3;
pt.InsideTess=3;
}
constant hull shader 通过InputPatch<VertexOut,3> 以patch内的所有控制点作为输入。看渲染管线图可以知道,hull shader的输入来自于顶点着色器,因此VertexOut就是顶点着色器的输出。系统同时通过SV_PrimitiveID提供了一个称之为patch ID的变量,这个ID是patch在该次绘制中的唯一标识。constant hull shader必须以 细分因子作为输出。对于拓扑结构为三角面的模型,其细分因子的结构如上面代码所示。不同细分因子的结果如下所示
D3D11 支持的最大tessellation factor 为 64. 如果所有的tessellation factor都为0,则该patch会被从后续的渲染管线中剔除。这个功能可以让我们实现基于patch的视锥体剔除或者背面剔除等优化操作。
使用曲面细分是为了增加模型的细节,但是我们通常没必要在用户关注不到地方增加不必要的细节,因此我们会采取一些策略来得到动态的曲面细分因子:
对于URP管线,在"Packages/com.unity.render-pipelines.core/ShaderLibrary/Tessellation.hlsl" 里有不同的动态计算细分因子的策略的示例。
关于曲面细分还有如下guide line需要参考:
control point hull shader使用多个控制点作为输入(原始模型顶点),并且输出多个控制点。每输出一个控制点都会调用一次 control point hull shader。通常在hull shader阶段输出的控制点数目和输入的控制点数目一致,除非我们要改变模型的几何结构,例如把一个三角面输出为一个三阶贝塞尔曲面。真真正的曲面细分实在下一个tessellation stage完成的。
struct HullOut { float3 PosL:TEXCOORD0; }; [domain("tri")] [partitioning("integer")] [outputtopology("triangle_cw")] [outputcontrolpoints(3)] [patchconstantfunc("ConstantHS")] [maxtessfactor(64.0f)] HullOut HS(InputPatch<VertexOut,3> p,uint i:SV_OutputControlPointID) { HullOut hout; hout.PosL=p[i].PosL; return hout; }
前面提到 control point hull shader是每一个输出的control point 都要执行一次,因此这里引入了SV_OutputControlPointID语义修饰的参数i,表示当前hull shader正在处理的那个 control point 的索引。例子中中输入的controlpoint和输出的contorl point数目一致,但是实践中输出的control point数目可以多于输入的control point的数目,多出来的control point的信息,可以根据算法以及输入的control point进行计算。 control point hull shader引入了一系列属性:
作为程序员我们没法控制 tessellation stage的执行,该阶段的任务都是由硬件完成的,硬件根据constant hull shader输出的细分因子和control point,来决定如何对patch进行细分。
tessellation stage输出我们新创建的所有顶点。 对于每一个tessellation stage输出的顶点都会调用一次domain shader.
当开启曲面细分时,vertex shader的功能是处理每一个control point,而domain shader才是实际上的处理细分的patch的顶点着色器。使用中,我们通常在这里把细分过的顶点坐标,投影到齐次裁减空间,包括顶点法线,切线,UV的处理都在这里执行。在domain shader中,以 constant hull shader输出的细分因子和control point hull shader 输出的control point,以及和细分过的顶点位置相关的参数化的(u,v,w)坐标作为输入,使用这个和实际顶点位置一一对应的参数化的(u,v,w)坐标以及其他输入参数,我们可以计算得到实际的顶点坐标。
对于拓扑结构为三角面的图元,这里的(uvw)三维坐标是重心点坐标。对于其他拓扑结构例如四边形quad,只需要二维(uv)即可描述细分坐标(类似纹理uv)。
struct DomainOut
{
float4 PosH:SV_POSITION;
};
[domain("tri")]
DomainOut DS(PatchTess patchTess,float3 baryCoords:SV_DomainLocation,const OutputPatch<HullOut,3> triangles)
{
DomainOut dout;
float3 p=triangles[0].PosL*baryCoords.x+triangles[1].PosL*baryCoords.y+triangles[2].PosL*baryCoords.z;
dout.PosH=TransformObjectToHClip(p.xyz);
return dout;
}
可以看到这里的输出DomainOut里的SV_Position就是没有开启曲面细分时顶点着色器的输出,同时也是光栅化阶段的输入。
完整代码如下
Shader "tutorial/chapter_2/water" { Properties { } SubShader { Pass { HLSLPROGRAM #pragma target 4.6 #pragma vertex vert #pragma hull HS #pragma domain DS #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct app_data { float4 positionOS:POSITION; }; struct VertexOut { float3 PosL:TEXCOORD0; }; VertexOut vert(app_data IN) { VertexOut o; o.PosL=IN.positionOS.xyz; return o; } struct PatchTess { float EdgeTess[3]:SV_TessFactor; float InsideTess:SV_InsideTessFactor; }; PatchTess ConstantHS(InputPatch<VertexOut,3> patch,uint patchID:SV_PrimitiveID) { PatchTess pt; pt.EdgeTess[0]=15; pt.EdgeTess[1]=15; pt.EdgeTess[2]=15; pt.InsideTess=15; return pt; } struct HullOut { float3 PosL:TEXCOORD0; }; [domain("tri")] [partitioning("integer")] [outputtopology("triangle_cw")] [outputcontrolpoints(3)] [patchconstantfunc("ConstantHS")] [maxtessfactor(64.0f)] HullOut HS(InputPatch<VertexOut,3> p,uint i:SV_OutputControlPointID) { HullOut hout; hout.PosL=p[i].PosL; return hout; } struct DomainOut { float4 PosH:SV_POSITION; }; [domain("tri")] DomainOut DS(PatchTess patchTess,float3 baryCoords:SV_DomainLocation,const OutputPatch<HullOut,3> triangles) { DomainOut dout; float3 p=triangles[0].PosL*baryCoords.x+triangles[1].PosL*baryCoords.y+triangles[2].PosL*baryCoords.z; dout.PosH=TransformObjectToHClip(p.xyz); return dout; } half4 frag(DomainOut IN):SV_Target { return half4(1,1,1,1); } ENDHLSL } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。