Unity Shader:细分着色器在顶点与片段着色器中的写法及参数变量详解

发表于2018-11-05
评论0 4.9k浏览
这篇文章属于知识点的讲解篇,主要给大家介绍下unty shader中细分着色器在顶点与片段着色器中的写法及各参数变量的解释,如果有还未掌握的可以看看。


(上图:将sphere与quad进行细分后)

Unity官网关于细分着色器的资料比较少,只有在Surface Shader中使用的例子。我看了下Surface Shader的generated code,总结出如何在顶点与片段着色器中写hull shader和domain shader并实现基本的Tessellation细分功能。

首先看一下Unity Shader的4个可编程着色器的执行顺序:

再进一步分解Tessellation shader的结构:

再看一下Tessellation中两个可编程的Shader:

1、细分控制着色器 hull shader

细分控制着色器起到一个数据传递的作用,最主要的作用是要计算Tessellation factor,这个值决定几何图元如何进行细分。 hull shader要通过特性[UNITY_patchconstantfunc(“hsconst”)]指明计算factor的方法,然后在方法hsconst中计算周长Tessellation factor和Inside Tessellation factor,Unity在Lighting.cginc内对UnityTessellationFactors包了一层(针对三角形图元),此float4向量的前三个标量为周长Tessellation factor,最后一个为Inside Tessellation factor,在细分三角形时,内部会根据Inside factor产生N个同心三角形,三角形的边上会根据周长产生新的细分点。

上图:周长Tessellation factor分别为3,5,2。inside factor为3。相对的,左边分为了三段,底边分为了5段,右边分为了2段,内部分为了三段(每个顶点至它的对边。inside factor如为4,既是偶数时,三角形中心点也会被视为一个细分点,所以视觉上依然还是两个三角形。具体细分的规则细节请查看MSDN或OpenGL资料)。

细分着色器除了可用于三角形,也可用于四边形和线段(正常情况下Unity网格上的默认图元都是三角形的,除非手动更改Mesh.Topology),这种情况下需改写Lighting.cginc内UnityTessellationFactors的向量长度以适应相对的几何图元,MSDN的语意解释:

SV_TessFactor //周长
Type Input Topology
float[4] quad patch //四边形
float[3] tri patch //三角形
float[2] isoline //线段

SV_InsideTessFactor //内部
Type Input Topology
float[2] quad patch
float tri patch
unused isoline

Tessellation factor确定后,由于是float,下一步需要决定如何进行舍入。[UNITY_partitioning(“fractional_odd”)]意为factor截断在[1,max]范围内,然后取整到下一个奇数整数值。周长会被划分为n-2的等长部分,以及两个位于两端的部分(可能比中间部分更短)。另外两种舍人方式equal_spacing与even_spacing的具体解释可看OpenGL相关文档。注意,就算是factor赋值没有小数部分,在这里仍然会按相应的方式被舍入(注意本文最开始处quad进行(4,4,4,4)细分后的图片,由于舍入方式是fractional_odd,实际周长为5段)。

特性[UNITY_domain(“tri”)]定义输入进hull shader的图元是三角形。

特性[UNITY_outputtopology(“triangle_cw”)]决定图元的朝向,由组成三角形的三个顶点的顺序所产生的方向决定,cw为clockwise顺时针,ccw为counter clockwise逆时针。

特性[UNITY_outputcontrolpoints(3)]应为hull shader输出的outputpatch中的顶点数量。

2、细分计算着色器 Domain Shader

细分计算着色器负责计算由hull shader传入的顶点的细分坐标的位置,包括空间转换(如没有几何着色器,否则也可选择在几何shader中转换)。

特性[UNITY_domain(“tri”)]决定了下面SV_DomainLocation的类型,不同类型图元使用不同长度的向量:
SV_DomainLocation
Type Input Topology
float2 quad patch //xy坐标
float3 tri patch //重心坐标
float2 isoline //xy坐标

由于三角形的特殊性无法用xy坐标分析,三角形中SV_DomainLocation为三角形的重心坐标(具体请查重心坐标系),结合上面hull shader计算出的UnityTessellationFactors,每个细分顶点都会有一个自己的SV_DomainLocation,根据SV_DomainLocation可算出细分顶点的位置,三角形细分顶点的位置的算法如下:
v.vertex=vi[0].vertex*bary.x+vi[1].vertex*bary.y+vi[2].vertex*bary.z;  
再进行空间转换到Clip空间,就可以输出到fragment shader片元着色器处进行最终渲染了。

全部代码如下:
Shader "Unlit/Tessellation"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass
        {
            CGPROGRAM
            #pragma vertex tessvert
            #pragma fragment frag
            #pragma hull hs
            #pragma domain ds
            #pragma target 4.6
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                float4 tangent : TANGENT;
                float3 normal : NORMAL;
                float2 texcoord : TEXCOORD0;
            };
            struct v2f
            {
                float2 texcoord:TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 tangent : TANGENT;
                float3 normal : NORMAL;
            };
            struct InternalTessInterp_appdata {
              float4 vertex : INTERNALTESSPOS;
              float4 tangent : TANGENT;
              float3 normal : NORMAL;
              float2 texcoord : TEXCOORD0;
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            InternalTessInterp_appdata tessvert (appdata v) {
              InternalTessInterp_appdata o;
              o.vertex = v.vertex;
              o.tangent = v.tangent;
              o.normal = v.normal;
              o.texcoord = v.texcoord;
              return o;
            }
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }
            UnityTessellationFactors hsconst (InputPatch<InternalTessInterp_appdata,3> v) {
              UnityTessellationFactors o;
              float4 tf;
              tf = float4(4.0f,4.0f,4.0f,4.0f);
              o.edge[0] = tf.x; 
              o.edge[1] = tf.y; 
              o.edge[2] = tf.z; 
              o.inside = tf.w;
              return o;
            }
            [UNITY_domain("tri")]
            [UNITY_partitioning("fractional_odd")]
            [UNITY_outputtopology("triangle_cw")]
            [UNITY_patchconstantfunc("hsconst")]
            [UNITY_outputcontrolpoints(3)]
            InternalTessInterp_appdata hs (InputPatch<InternalTessInterp_appdata,3> v, uint id : SV_OutputControlPointID) {
              return v[id];
            }
            [UNITY_domain("tri")]
            v2f ds (UnityTessellationFactors tessFactors, const OutputPatch<InternalTessInterp_appdata,3> vi, float3 bary : SV_DomainLocation) {
              appdata v;
              v.vertex = vi[0].vertex*bary.x + vi[1].vertex*bary.y + vi[2].vertex*bary.z;
              v.tangent = vi[0].tangent*bary.x + vi[1].tangent*bary.y + vi[2].tangent*bary.z;
              v.normal = vi[0].normal*bary.x + vi[1].normal*bary.y + vi[2].normal*bary.z;
              v.texcoord = vi[0].texcoord*bary.x + vi[1].texcoord*bary.y + vi[2].texcoord*bary.z;
              v2f o = vert (v);
              return o;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(1.0f,1.0f,1.0f,1.0f);
            }
            ENDCG
        }
    }
}
附:Tessellation对GPU有一定要求,有些显卡不支持这一功能(模型会显示粉色,但不会报错),具体请看Unity官方。
来自:https://blog.csdn.net/liu_if_else/article/details/75039895

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引