Unity Shader:细分着色器(Tessellation Shader)在Unity顶点着色器中的写法以及各参数变量解释

这里写图片描述
图1:在Unity内将sphere细分后
这里写图片描述
图2:在Unity内将sphere细分后

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

首先看一下Unity Shader的4个可编程着色器的执行顺序:
这里写图片描述
再进一步分解Tessellation shader的结构:
这里写图片描述

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

1,细分控制着色器 hull shader

            #pragma hull hs

            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];
            }

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

#ifdef UNITY_CAN_COMPILE_TESSELLATION
struct UnityTessellationFactors {
    float edge[3] : SV_TessFactor;
    float inside : SV_InsideTessFactor;
};
#endif // UNITY_CAN_COMPILE_TESSELLATION

说一下factor与细分规则。边的细分比较简单,三角形的边上会根据Tessellation factor产生细分点,例如Tessellation factor=3,那么某个边上会出现两个细分点将边分为三段。例如下图周长Tessellation factor分别为3,5,2。相对的,左边分为了三段,底边分为了5段,右边分为了2段。
内部细分分为两种情况,如果Inside factor等于偶数,那么在三角形正中心会出现一个细分点,如果等于奇数中心没有细分点。内部细分的规则是,从三角形的一角出发,每次连接紧挨着的内部三角形的一个角或是一边或是中心细分点,算作将内部分为一段。
这里写图片描述
例如上图,Inside factor=3,没有中心细分点,从左下角出发连接中心三角形一角,再出发连接中心三角形右手边,再出发连接外部三角形右手边,这样完成了对内部的三段细分。
如果inside factor为4,既是偶数时,三角形中心点会出现一个细分点,从左下角出发连接中心三角形一角,再出发连接中心细分点,再出发连接中心三角形右手边,再出发连接外部三角形右手边,这样完成了对内部的四段细分。 所以视觉上与上图一样,依然还是两个三角形(但是连接所有细分点后产生的细分线不同)。具体细分的规则细节请查看MSDN或OpenGL资料)。

细分着色器除了可用于三角形,也可用于四边形和线段(正常情况下Unity网格上的默认图元都是三角形,C#中可以手动更改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

如果需要对非三角形图元细分,根据以上改写UnityTessellationFactors即可。

UNITY_patchconstantfunc定义声明好了,Tessellation factor确定后,由于是float,而根据细分规则可以看出细分只与整数有关,所以下一步需要决定如何对factor进行舍入。特性[UNITY_partitioning(“fractional_odd”)]决定舍入规则,fractional_odd意为factor截断在[1,max]范围内,然后取整到小于此数的最大奇数整数值。周长会被划分为n-2的等长部分,以及两个位于两端的部分(可能比中间部分更短)。另外两种舍人方式equal_spacing与even_spacing的具体解释可看OpenGL相关文档。注意,就算是factor赋值没有小数部分,在这里仍然会按相应的方式被舍入。例如本文最开始处的图2,实际上quad进行的是factor为(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

            #pragma domain ds
            [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;
            }

细分计算着色器负责计算由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为三角形的重心坐标既是上面的float3 tri patch(重心坐标原理可看前文)。之前的hull shader根据UnityTessellationFactors进行细分后,每个细分顶点都会有一个自己的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官方文档。

——————————————————————————————
参考:
OpenGL编程指南—Khronos Group
MSDN Reference for HLSL

日志:
2017-7-13:更改错字
2020-2-27:review

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值