hull and domain渲染阶段
三角细分
控制细化数组
Unity2017.1.0dom
曲面细分是将表面切成更小更精细的技术。在咱们的例子中,咱们将三角形细分,最后获得覆盖相同空间的更多更小的三角形。
GPU可以拆分渲染提供给它的三角形。 这样作有多种缘由,当三角形的一部分最终被剪切时。 咱们没法控制,可是还有一个细分阶段能够配置。 此阶段位于顶点vertex和片断fragment着色器阶段之间, 添加一个Hulls and Domains程序。函数
建立Tessellation.shader 和 MyTessellation.cginc性能
Shader "Custom/Tessellation" { … }
#if !defined(TESSELLATION_INCLUDED)
#define TESSELLATION_INCLUDED
#endif
当使用Tessellation,最小的着色器目标等级是4.6。若是咱们不手动设置,Unity将会发出警告并自动使用该级别。咱们要把镶嵌阶段添加到base_pass和additive_pass,deferred_pass,阴影通道不必。优化
#pragma target 4.6 … #include "MyFlatWireframe.cginc" #include "MyTessellation.cginc"
建立一个quad、材质.ui
它由两个等腰直角三角形组成。短边长度为1,长对角线长度为√2.编码
相似geometry着色阶段,Hull程序能够处理三角形,四边形,或等值线,必须告诉它应该在什么表面上工做,并提供必要的数据。这是赫尔项目的工做。void函数开始spa
void MyHullProgram () {}
hull program在一个面片上运行,它做为参数传递给hull,添加一个InputPatch参数
void MyHullProgram (InputPatch patch) {}
面片上可能有许多网格顶点,必须指定顶点的数据格式。如今咱们将使用VertexData结构体
void MyHullProgram (InputPatch<VertexData> patch) {}
当咱们处理三角形时,每一个面片将包含三个顶点。这个也必须指定为InputPatch的第二个模板参数
void MyHullProgram (InputPatch<VertexData, 3> patch) {}
hull程序的工做是传递所需的顶点数据到Tessellation阶段。虽然给它提供了一整个面片,但该函数每次只能处理面片中一个顶点,附加一个参数指定hull应该与哪一个(顶点)一块儿工做。该参数是一个无符号整数,具备SV_OutputControlPointID语义
void MyHullProgram (
InputPatch<VertexData, 3> patch,
uint id : SV_OutputControlPointID
) {}
先只需将面片做为数组,根据索引id而后返回所需的顶点数据
VertexData MyHullProgram (
InputPatch<VertexData, 3> patch,
uint id : SV_OutputControlPointID
) {
return patch[id];
}
这看起来像一个函数程序
#pragma vertex MyVertexProgram #pragma fragment MyFragmentProgram #pragma hull MyHullProgram #pragma geometry MyGeometryProgram
这将产生一些编译错误,提示没有正确配置hull着色器。与几何函数同样,它须要属性来配置。首先,咱们必须明确地告诉它它适用于三角形。这是经过使用tri做为参数的UNITY_domain属性完成的
[UNITY_domain("tri")]
VertexData MyHullProgram …
还必须明确地指定每一个三角形一个patch输出三个控制点,
[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
VertexData MyHullProgram …
当GPU建立新的三角形时,它须要知道咱们想要将它们定义为顺时针仍是逆时针。像全部其余三角形同样,它们应该是顺时针的。这是经过UNITY_outputtopology属性来控制的。它的参数应该是triangle_cw
[UNITY_domain("tri")] [UNITY_outputcontrolpoints(3)] [UNITY_outputtopology("triangle_cw")] VertexData MyHullProgram …
GPU还须要经过UNITY_partitioning属性被告知如何分割-整数模式
[UNITY_domain("tri")] [UNITY_outputcontrolpoints(3)] [UNITY_outputtopology("triangle_cw")] [UNITY_partitioning("integer")] VertexData MyHullProgram …
除了划分方法,GPU还须要知道patch应该被分割成多少部分。这不是一个常数值,它能够在每一个patch中变化。咱们必须提供一个函数来计算它,叫作patch常数函数。
[UNITY_domain("tri")] [UNITY_outputcontrolpoints(3)] [UNITY_outputtopology("triangle_cw")] [UNITY_partitioning("integer")] [UNITY_patchconstantfunc("MyPatchConstantFunction")] VertexData MyHullProgram …
每一个patch仅被调用一次patch常数函数,而不是每一个顶点都调用一次。实际上,此功能是与MyHullProgram并行运行。
为了肯定如何细分三角形,GPU使用了四个细分因子。三角形patch的三个边都有一个因子,必须做为具备SV_TessFactor语义的float数组传递。 三角形的内部也有一个因子,使用SV_InsideTessFactor语义。
struct TessellationFactors
{ float edge[3] : SV_TessFactor; float inside : SV_InsideTessFactor; };
patch常量函数以patch做为输入参数并输出因子。全部因子设置为1,这将致使tessellation阶段不细分patch。
TessellationFactors MyPatchConstantFunction (InputPatch<VertexData, 3> patch) {
TessellationFactors f;
f.edge[0] = 1;
f.edge[1] = 1;
f.edge[2] = 1;
f.inside = 1;
return f;
}
一个完整的曲面细分阶段须要Hull肯定面片,也要Domain生成顶点。
void MyDomainProgram () {}
两者都做用在同一个阶段,再次使用UNITY_domain属性
[UNITY_domain("tri")] void MyDomainProgram () {}
Domain参数须要细分因子以及patch面片.
[UNITY_domain("tri")] void MyDomainProgram ( TessellationFactors factors, OutputPatch<VertexData, 3> patch ) {}
曲面细分阶段肯定细分时,它不会产生任何新的顶点。 相反,它会为这些顶点提供重心坐标。 使用这些坐标来导出最终顶点取决于Domain着色器。 为其提供重心坐标它们具备SV_DomainLocation语义。
[UNITY_domain("tri")] void MyDomainProgram ( TessellationFactors factors, OutputPatch<VertexData, 3> patch, float3 barycentricCoordinates : SV_DomainLocation ) {}
在函数内部,咱们必须生成最终的顶点数据。
[UNITY_domain("tri")] void MyDomainProgram ( TessellationFactors factors, OutputPatch<VertexData, 3> patch, float3 barycentricCoordinates : SV_DomainLocation ) { VertexData data; }
为了找到这个顶点的位置,使用重心坐标。X、Y和Z坐标决定了第一个、第二个和第三个控制点的权重。
VertexData data; data.vertex = patch[0].vertex * barycentricCoordinates.x + patch[1].vertex * barycentricCoordinates.y + patch[2].vertex * barycentricCoordinates.z;
可是这就必须用一样的方法插值全部数据。让咱们为它定义一个方便的宏,它能够用于全部的向量大小
// data.vertex = // patch[0].vertex * barycentricCoordinates.x + // patch[1].vertex * barycentricCoordinates.y + // patch[2].vertex * barycentricCoordinates.z; #define MY_DOMAIN_PROGRAM_INTERPOLATE(fieldName) data.fieldName = \ patch[0].fieldName * barycentricCoordinates.x + \ patch[1].fieldName * barycentricCoordinates.y + \ patch[2].fieldName * barycentricCoordinates.z; MY_DOMAIN_PROGRAM_INTERPOLATE(vertex)
除了顶点位置,还有法线,切线,和全部UV坐标.
MY_DOMAIN_PROGRAM_INTERPOLATE(vertex) MY_DOMAIN_PROGRAM_INTERPOLATE(normal) MY_DOMAIN_PROGRAM_INTERPOLATE(tangent) MY_DOMAIN_PROGRAM_INTERPOLATE(uv) MY_DOMAIN_PROGRAM_INTERPOLATE(uv1) MY_DOMAIN_PROGRAM_INTERPOLATE(uv2)
咱们惟一没有插入的是实例id。由于Unity不一样时支持GPU实例化和镶嵌,因此复制这个ID是没有意义的。为了防止编译错误,从三次着色器传递中删除多编译指令。这也将从着色器的GUI中移除实例化选项。
//#pragma multi_compile_instancing //#pragma instancing_options lodfade force_same_maxcount_for_gl
如今咱们有了一个新的顶点,它将在这一阶段以后被发送到几何程序或插值器。可是这些程序指望插值顶点数据,而不是顶点数据。为了解决这个问题,让Domain着色器接管了原始顶点程序的职责。这是经过在其中调用MyVertexProgram来完成的——就像其余任何函数同样——并返回结果。
[UNITY_domain("tri")] InterpolatorsVertex MyDomainProgram ( TessellationFactors factors, OutputPatch<VertexData, 3> patch, float3 barycentricCoordinates : SV_DomainLocation ) { … return MyVertexProgram(data); }
Now we can add the domain shader to our three shader passes, but we'll still get errors.
#pragma hull MyHullProgram #pragma domain MyDomainProgram
MyVertexProgram只须要调用一次,只是咱们改变了它调用的位置。可是咱们仍然须要指定一个顶点程序在顶点着色器阶段被调用,它位于Hull着色器以前。在这一点上,咱们不须要作任何事情,因此咱们可使用一个函数,它能够直接经过顶点数据,而不须要修改。
VertexData MyTessellationVertexProgram (VertexData v) {
return v;
}
使用这个函数给三个着色器的顶点程序传递数据。
#pragma vertex MyTessellationVertexProgram
这将产生另外一个编译器错误,提示重用了位置语义。咱们必须为咱们的顶点程序使用另外一个输出结构,使用INTERNALTESSPOS语义来表示顶点位置。结构体的其他部分与VertexData相同,只是它历来没有实例ID。将其命名为TessellationControlPoint。
struct TessellationControlPoint { float4 vertex : INTERNALTESSPOS; float3 normal : NORMAL; float4 tangent : TANGENT; float2 uv : TEXCOORD0; float2 uv1 : TEXCOORD1; float2 uv2 : TEXCOORD2; };
改变MyTessellationVertexProgram它会把顶点数据放入一个控制点结构中并返回那个值
TessellationControlPoint MyTessellationVertexProgram (VertexData v) {
TessellationControlPoint p;
p.vertex = v.vertex;
p.normal = v.normal;
p.tangent = v.tangent;
p.uv = v.uv;
p.uv1 = v.uv1;
p.uv2 = v.uv2;
return p;
}
接下来,MyHullProgram也必须进行更改,以便使用TessellationControlPoint而不是VertexData。只有它的参数类型须要更改.
TessellationControlPoint MyHullProgram (
InputPatch<TessellationControlPoint, 3> patch,
uint id : SV_OutputControlPointID
) {
return patch[id];
}
patch常数函数也是如此。
TessellationFactors MyPatchConstantFunction ( InputPatch<TessellationControlPoint, 3> patch ) { … }
Domain程序的参数类型也必须改变。
InterpolatorsVertex MyDomainProgram (
TessellationFactors factors,
OutputPatch<TessellationControlPoint, 3> patch,
float3 barycentricCoordinates : SV_DomainLocation
) {
…
}
在这一点上,咱们终于有了一个正确的Tessellation着色器。
整个Tessellation设置的要点是,咱们能够细分patch。这容许咱们用一组较小的三角形替换单个三角形。咱们如今就来作。
三角形的细分是由它的Tessellation因子控制的。咱们在mypatchconstant函数中肯定这些因素。目前,咱们将它们都设置为1,这不会产生视觉变化。Hull、Domain经过原始的顶点数据并无产生任何新的东西,将全部因子设置为2,效果立显。
TessellationFactors MyPatchConstantFunction (
InputPatch<TessellationControlPoint, 3> patch
) {
TessellationFactors f;
f.edge[0] = 2;
f.edge[1] = 2;
f.edge[2] = 2;
f.inside = 2;
return f;
}
Tessellation因子为2 vs 3.
镶嵌因子是偶数时,中心有一个顶点。当它们是奇数时,中心三角形会有代替。若是咱们使用较大的Tessellation因子,咱们会获得多个三角形。每向中心移动一步,三角形被细分的数量就会减小2,直到最后获得1或0个子边。
三角形如何细分是由因子控制。边缘因子可用于覆盖它们各自的边缘被细分的数量。这只会影响到原始的patch边缘,而不会影响到生成的内部三角形。为了清楚地看到这一点,将内因子设置为7,同时保持边因子为1。
f.edge[0] = 1; f.edge[1] = 1; f.edge[2] = 1; f.inside = 7;
内7,边缘1.
边缘因子也有可能大于内部因子。例如,将边缘因子设置为7,而将内部因子设置为1。
f.edge[0] = 7; f.edge[1] = 7; f.edge[2] = 7; f.inside = 1;
内1 边缘7.
在本例中,内部因子被强制执行为2,由于不然不会生成新的三角形。
硬编码很差,增长可配置属性.
float _TessellationUniform; … TessellationFactors MyPatchConstantFunction ( InputPatch<TessellationControlPoint, 3> patch ) { TessellationFactors f; f.edge[0] = _TessellationUniform; f.edge[1] = _TessellationUniform; f.edge[2] = _TessellationUniform; f.inside = _TessellationUniform; return f; }
增长1–64范围限制.
_TessellationUniform ("Tessellation Uniform", Range(1, 64)) = 1
GUI扩展.
void DoTessellation () { GUILayout.Label("Tessellation", EditorStyles.boldLabel); EditorGUI.indentLevel += 2; editor.ShaderProperty ( FindProperty("_TessellationUniform"), MakeLabel("Uniform") ); EditorGUI.indentLevel -= 2; }
在渲染模式和线框部分之间调用OnGUI中的这个方法。仅当所需属性存在时才执行此操做.
public override void OnGUI ( MaterialEditor editor, MaterialProperty[] properties ) { … DoRenderingMode(); if (target.HasProperty("_TessellationUniform")) { DoTessellation(); } if (target.HasProperty("_WireframeColor")) { DoWireframe(); } … }
可配置.
尽管咱们使用了一个浮点数来设置因子,但咱们老是会以每条边的等效细分结束。这是由于咱们使用的是整数模式。虽然这是一个很好的模式,看看镶嵌如何工做,它阻止咱们平滑过渡之间的细分级别。幸运的是,还有分式划分模式。咱们把模式改成fractional_odd。
[UNITY_domain("tri")] [UNITY_outputcontrolpoints(3)] [UNITY_outputtopology("triangle_cw")] [UNITY_partitioning("fractional_odd")] [UNITY_patchconstantfunc("MyPatchConstantFunction")] TessellationControlPoint MyHullProgram …
分数奇数分配
当使用整个奇数因子时,fractional_odd分区模式产生的结果与整数模式相同。但在奇因子之间转换时,额外的边细分会被分离和增加,或者收缩和合并。这意味着边再也不老是被分割成等长的段。这种方法的优势是细分层之间的过渡如今是平滑的。
分数偶数分配
什么因子是最完美的?这是个问题,但这个问题没有一个单一的客观答案。
细分因子必须提供,能够肯定每一个顶点的因子,而后对每条边进行平均。这些因子被储存在一个纹理中。在任何状况下,给定一条边的两个顶点,用一个单独的函数来肯定系数是很方便的。
float TessellationEdgeFactor ( TessellationControlPoint cp0, TessellationControlPoint cp1 ) { return _TessellationUniform; }
使用这个函数来处理mypatchconstant函数内的边缘因子。
TessellationFactors MyPatchConstantFunction (
InputPatch<TessellationControlPoint, 3> patch
) {
TessellationFactors f;
f.edge[0] = TessellationEdgeFactor(patch[1], patch[2]);
f.edge[1] = TessellationEdgeFactor(patch[2], patch[0]);
f.edge[2] = TessellationEdgeFactor(patch[0], patch[1]);
f.inside = _TessellationUniform;
return f;
}
对于内因子,咱们只须要用边因子的平均值.
f.inside = (f.edge[0] + f.edge[1] + f.edge[2]) * (1 / 3.0);
因为边因子控制细分多少原始三角形的边,以这些边的长度为基础的因子,这是有意义的。例如,咱们能够指定所需的三角形边长。若是咱们获得的三角形边比这个长,咱们应该把它们再除以所需的长度。为此添加一个变量。
float _TessellationUniform; float _TessellationEdgeLength;
还能够添加属性。咱们使用范围从0.1到1,默认值为0.5。这是世界空间单位。
_TessellationUniform ("Tessellation Uniform", Range(1, 64)) = 1 _TessellationEdgeLength ("Tessellation Edge Length", Range(0.1, 1)) = 0.5
咱们须要一个着色器功能,使它有可能在基于所有或基于边缘之间切换。使用_TESSELLATION_EDGE关键字,在咱们的三次传递中添加必需的指令.
#pragma shader_feature _TESSELLATION_EDGE
接下来,在MyLightingShaderGUI中添加一个enum类型来表示镶嵌模式。
enum TessellationMode {
Uniform, Edge
}
GUI扩展
void DoTessellation () { GUILayout.Label("Tessellation", EditorStyles.boldLabel); EditorGUI.indentLevel += 2; TessellationMode mode = TessellationMode.Uniform; if (IsKeywordEnabled("_TESSELLATION_EDGE")) { mode = TessellationMode.Edge; } EditorGUI.BeginChangeCheck(); mode = (TessellationMode)EditorGUILayout.EnumPopup( MakeLabel("Mode"), mode ); if (EditorGUI.EndChangeCheck()) { RecordAction("Tessellation Mode"); SetKeyword("_TESSELLATION_EDGE", mode == TessellationMode.Edge); } if (mode == TessellationMode.Uniform) { editor.ShaderProperty( FindProperty("_TessellationUniform"), MakeLabel("Uniform") ); } else { editor.ShaderProperty( FindProperty("_TessellationEdgeLength"), MakeLabel("Edge Length") ); } EditorGUI.indentLevel -= 2; }
边缘模式
如今咱们必须调整TessellationEdgeFactor。当定义_TESSELLATION_UNIFORM时,肯定两个点的世界位置,而后计算它们之间的距离。这是世界空间中的边长。边因子等于这个长度除以指望的长度.
float TessellationEdgeFactor ( TessellationControlPoint cp0, TessellationControlPoint cp1 ) { #if defined(_TESSELLATION_EDGE) float3 p0 = mul(unity_ObjectToWorld, float4(cp0.vertex.xyz, 1)).xyz; float3 p1 = mul(unity_ObjectToWorld, float4(cp1.vertex.xyz, 1)).xyz; float edgeLength = distance(p0, p1); return edgeLength / _TessellationEdgeLength; #else return _TessellationUniform; #endif }
不一样的四边形尺度,相同的指望边长.
由于咱们如今用边的长度来肯定边的镶嵌因子,咱们能够获得每条边的不一样因子。你能够看到这种状况发生在四边形上,由于对角线比其余边长。当对四边形使用非均匀尺度,在一维中拉伸它时,这一点也变得很明显。
拉伸quad.
要作到这一点,很重要的一点是,共享一条边的patch最终都要为那条边使用相同的镶嵌因子。不然,生成的顶点将不能沿着那条边匹配,这会在网格中产生可见的缺口。在咱们的例子中,咱们对全部的边使用相同的逻辑。惟一的区别是控制点参数的顺序。因为浮点数的限制,这在技术上可能会产生不一样的因素,但差别将是很是小的,不会被注意到.
虽然咱们如今能够控制世界空间中的三角形边长,但这与它们在屏幕空间中的显示方式并不一致。tessellation的要点是增长更多的三角形时。因此咱们不想再细分那些看起来很小的三角形。因此让咱们用屏幕空间的边长来代替。
首先,改变边长属性的范围。咱们将使用像素代替世界单位,因此5-100这样的范围更有意义。
_TessellationEdgeLength ("Tessellation Edge Length", Range(5, 100)) = 50
将世界空间计算替换为屏幕空间计算。要作到这一点,这些点必须转换到剪辑空间而不是世界空间。而后它们的距离在2D中肯定,使用它们的X和Y坐标,除以它们的W坐标,将它们投影到屏幕上。
//float3 p0 = mul(unity_ObjectToWorld, float4(cp0.vertex.xyz, 1)).xyz; //float3 p1 = mul(unity_ObjectToWorld, float4(cp1.vertex.xyz, 1)).xyz; //float edgeLength = distance(p0, p1); float4 p0 = UnityObjectToClipPos(cp0.vertex); float4 p1 = UnityObjectToClipPos(cp1.vertex); float edgeLength = distance(p0.xy / p0.w, p1.xy / p1.w); return edgeLength / _TessellationEdgeLength;
如今咱们在裁剪空间中获得告终果,它是一个大小为2的统一立方体,适合显示。为了转换为像素,咱们必须按像素的显示大小进行缩放。实际上,因为显示器不多是方形的,为了获得最精确的结果,在肯定距离以前,咱们应该分别缩放X和Y坐标。可是让咱们经过简单的缩放屏幕高度来看看它是什么样子.
return edgeLength * _ScreenParams.y / _TessellationEdgeLength;
相同的世界尺寸,不一样的屏幕显示尺寸.
们的三角形边缘如今被细分根据他们是多大渲染。位置、旋转和缩放都会影响到这一点。结果,Tessellation的数量改变时,事情在运动。.
纯粹依赖于边的视觉长度的一个缺点是,在世界空间中很长的边在屏幕空间中可能会很是小。这可能致使这些边根本没有被细分,而其余边被细分不少,当曲面细分被用来增长近距离的细节或生成复杂的轮廓。
另外一种方法是使用世界空间的边缘长度,可是根据视图距离调整因素。越远的东西,它应该在视觉上显得越小,所以它须要的镶嵌就越少。用边长除以边长到摄像机的距离。咱们能够用边的中点来肯定这个距离。
//float4 p0 = UnityObjectToClipPos(cp0.vertex); //float4 p1 = UnityObjectToClipPos(cp1.vertex); //float edgeLength = distance(p0.xy / p0.w, p1.xy / p1.w); //return edgeLength * _ScreenParams.y / _TessellationEdgeLength; float3 p0 = mul(unity_ObjectToWorld, float4(cp0.vertex.xyz, 1)).xyz; float3 p1 = mul(unity_ObjectToWorld, float4(cp1.vertex.xyz, 1)).xyz; float edgeLength = distance(p0, p1); float3 edgeCenter = (p0 + p1) * 0.5; float viewDistance = distance(edgeCenter, _WorldSpaceCameraPos); return edgeLength / (_TessellationEdgeLength * viewDistance);
咱们仍然能够保持Tessellation依赖于显示大小,经过简单地分解屏幕高度,并保持咱们的5-100滑块范围。注意,这些值再也不直接对应于显示像素。当你改变相机的视场时,这一点是很是明显的,这根本不会影响Tessellation。因此这种简单的方法并不适用于使用可变视场的游戏,例如放大和缩小。
return edgeLength * _ScreenParams.y / (_TessellationEdgeLength * viewDistance);
基于边缘长度和视图距离.
虽然Tessellation可能看起来在这一点上工做得很好。当使用一个统一的四边形时,它不是很明显,可是当使用一个变形的立方体时,它就变得很明显。 内部因素不正确的立方体.
在立方体的状况下,组成一个面的两个三角形各有一个很是不一样的内部因子。四边形和立方体面之间的惟一区别是定义三角形顶点的顺序。Unity的默认立方体不使用对称的三角形布局,而quad使用对称的三角形布局。这代表,边缘的顺序明显影响内部因子。咱们只是取边缘因子的平均值,因此它们的顺序不重要。必定是别的什么地方出了问题。
在计算内部因子时再次显式调用TessellationEdgeFactors函数。从逻辑上讲,这不该该有什么区别,由于咱们只是执行了两次彻底相同的计算。着色器编译器确定会优化它.
//f.inside = (f.edge[0] + f.edge[1] + f.edge[2]) * (1 / 3.0);
f.inside =
(TessellationEdgeFactor(patch[1], patch[2]) +
TessellationEdgeFactor(patch[2], patch[0]) +
TessellationEdgeFactor(patch[0], patch[1])) * (1 / 3.0);
内部因子正确的立方体.
显然,这确实有效果,由于两面三角形如今最终都使用几乎相同的内部因子。这里发生了什么?
patch常量函数与Hull着色器的其他部分并行调用。但实际上它能够变得更复杂。着色器编译器也可以并行化边缘因子的计算。MyPatchConstantFunction内部的代码被分解和部分复制,并被一个并行计算三个边缘因子的分支进程替换。一旦这三个过程都完成了,他们的结果被结合起来并用来计算内部因素。
它不影响咱们的着色器的结果,只会影响它的性能。不幸的是,在OpenGL Core生成的代码中有一个bug。在计算内部因子时,不使用三个边因子,而只使用第三个边因子。它只是访问了索引2三次,而不是索引0,1和2。因此咱们老是获得一个内因子等于第三条边因子。
对于patch常数函数,着色器编译器将并行化设置为优先级。它会尽快拆分,而后没法再优化TessellationEdgeFactor的重复调用。咱们以三个程序结束,每一个程序计算两个点的世界位置、距离、最终因子。而后还有一个计算内部因子的程序,如今它还计算三个点的世界位置,以及所涉及的全部距离和因素。因为咱们如今正在对内部因子进行全部工做,所以对边缘因子也单独完成部分工做是没有意义的。
事实证实,若是咱们首先计算这些点的世界位置,而后分别对边缘和内部因子计算TessellationEdgeFactor,则着色器编译器将决定不为每一个边缘因子分开单独的程序。咱们最终获得了一个能够所有计算的流程。在这种状况下,着色器编译器确实优化了TessellationEdgeFactor的重复调用。
float TessellationEdgeFactor (float3 p0, float3 p1) { #if defined(_TESSELLATION_EDGE) // float3 p0 = mul(unity_ObjectToWorld, cp0.vertex).xyz; // float3 p1 = mul(unity_ObjectToWorld, cp1.vertex).xyz; … #else return _TessellationUniform; #endif } TessellationFactors MyPatchConstantFunction ( InputPatch<TessellationControlPoint, 3> patch ) { float3 p0 = mul(unity_ObjectToWorld, patch[0].vertex).xyz; float3 p1 = mul(unity_ObjectToWorld, patch[1].vertex).xyz; float3 p2 = mul(unity_ObjectToWorld, patch[2].vertex).xyz; TessellationFactors f; f.edge[0] = TessellationEdgeFactor(p1, p2); f.edge[1] = TessellationEdgeFactor(p2, p0); f.edge[2] = TessellationEdgeFactor(p0, p1); f.inside = (TessellationEdgeFactor(p1, p2) + TessellationEdgeFactor(p2, p0) + TessellationEdgeFactor(p0, p1)) * (1 / 3.0); return f; }