图形 3.3 曲面细分与几何着色器 大规模草渲染
曲面细分与几何着色器 大规模草渲染
曲面细分与几何着色器的应用
曲面细分着色器的应用
曲面细分就是把一条直线进行不断的细分,然后把它和曲线进行逼近,逐渐变成曲线的形状。
可以使用在海浪、雪地的部分,比如雪地的脚印,也可以通过曲面细分着色器来处理和优化。
或者和置换贴图进行结合使用。置换贴图能改变物体的形状,使得在边缘也有很强的凹凸感,但是如果模型的面数不够多的情况下,边缘就会十分尖锐突兀。此时便可以使用曲面细分着色器把模型面数变多变细来解决这个问题。
几何着色器的应用
应用于常见的几何动画,比如爆破破碎效果。或者应用于草的生成,并可以和曲面细分着色器结合,来获得一个动态调整密度的草地。
着色器执行顺序
Tessellation Shader:曲面细分着色器
Geometry Shader:几何着色器
然后曲面细分着色器又分成了Hull Shader、Tessellation Primitive Generator、Doamin Shader。其中两个部分是可以控制的。
Hull Shader定义一些细分的参数,Tessellation Primitive Generator是API去处理的,Domain Shader则是曲面细分着色器细分之后的点
TESS(曲面细分着色器)的输入与输出
- 输入
Patch, 可以看成是多个顶点的集合,包含每个顶点的属性,可以 指定一个Patch包含的顶点数以及自己的属性。
- 功能
将图元细分(可以是三角形,矩形等)
- 输出
细分后的顶点
TESS流程
- HULL Shader
- 决定细分的数量(设定Tessellation factor以及Inside Tessellation factor)
- 对输入的Patch参数进行改变(如果需要)
- Tessellation Primitive Generation
进行细分操作
- Domain Shader
对细分后的点进行处理,从重心空间(Barycentric coordinate system )转换到屏幕空间
HULL Shader各参数解析
- Tessellation Factor
决定将一条边分成几部分:
equal_Spacing 等分
fractional_even_spacing 最小为2,向上取最近的偶数
fractional_odd_spacing 最小为1,向上取最近的奇数
fractional_even_spacing和fractional_odd_spacing会把周长分为n减2的等长的部分,以及两端的不等长部分
- Inner Tessellation Factor
说明内部的细分如何被绘制出来。
GS(几何着色器)的输入与输出
- 输入为图元(三角形,矩形,线等)
根据图元的不同,shader中会出现对应不同数量的顶点
- 输出同样为图元
一个或多个,需要自己从顶点构建,顺序很重要,同时需要定义最大输出的顶点数。
曲面细分Demo演示
- 将一个Quad细分
- 与置换贴图的结合
示例Shader
1 Shader "Unlit/TESS2" 2 { 3 Properties 4 { 5 _MainTex("MainTex",2D) = "white"{} 6 _DisplacementMap("_DisplacementMap",2D)="gray"{} //置换贴图 7 [HideInInspector]_DisplacementStrength("DisplacementStrength",Range(0,1)) = 0 8 _Smoothness("Smoothness",Range(0,5))=0.5 9 _TessellationUniform("TessellationUniform",Range(1,1024)) = 1 10 } 11 SubShader 12 { 13 Tags { "RenderType"="Opaque" 14 "LightMode"="ForwardBase"} 15 LOD 100 16 Pass 17 { 18 CGPROGRAM 19 //定义2个函数 hull domain 20 #pragma hull hullProgram 21 #pragma domain ds 22 23 #pragma vertex tessvert 24 #pragma fragment frag 25 26 #include "UnityCG.cginc" 27 #include "Lighting.cginc" 28 //引入曲面细分的头文件 29 #include "Tessellation.cginc" 30 31 #pragma target 5.0 32 float _TessellationUniform; 33 sampler2D _MainTex; 34 float4 _MainTex_ST; 35 36 sampler2D _DisplacementMap; 37 float4 _DisplacementMap_ST; 38 float _DisplacementStrength; 39 float _Smoothness; 40 41 struct VertexInput 42 { 43 float4 vertex : POSITION; 44 float2 uv : TEXCOORD0; 45 float3 normal : NORMAL; 46 float4 tangent : TANGENT; 47 }; 48 49 struct VertexOutput 50 { 51 float2 uv : TEXCOORD0; 52 float4 pos : SV_POSITION; 53 float4 worldPos:TEXCOORD1; 54 half3 tspace0 :TEXCOORD2; 55 half3 tspace1 :TEXCOORD3; 56 half3 tspace2 :TEXCOORD4; 57 }; 58 59 VertexOutput vert (VertexInput v) 60 //这个函数应用在domain函数中,用来空间转换的函数 61 { 62 VertexOutput o; 63 o.uv = TRANSFORM_TEX(v.uv,_MainTex); 64 //Displacement 处理置换贴图 65 //由于并不是在Fragnent shader中读取图片,GPU无法获取mipmap信息,因此需要使用tex2Dlod来读取图片,使用第四坐标作为mipmap的level,这里取了0 66 float Displacement = tex2Dlod(_DisplacementMap,float4(o.uv.xy,0.0,0.0)).g; 67 //_DisplacementStrength = frac((_Time) * 5) * 0.2; 68 _DisplacementStrength = abs(frac((_Time) * 3) - 0.5) * 0.4; 69 Displacement = (Displacement-0.5)*_DisplacementStrength; 70 v.normal = normalize(v.normal); 71 v.vertex.xyz += v.normal * Displacement; 72 73 o.pos = UnityObjectToClipPos(v.vertex); 74 o.worldPos = mul(unity_ObjectToWorld, v.vertex); 75 76 //计算切线空间转换矩阵 77 half3 vNormal = UnityObjectToWorldNormal(v.normal); 78 half3 vTangent = UnityObjectToWorldDir(v.tangent.xyz); 79 //compute bitangent from cross product of normal and tangent 80 half tangentSign = v.tangent.w * unity_WorldTransformParams.w; 81 half3 vBitangent = cross(vNormal,vTangent)*tangentSign; 82 //output the tangent space matrix 83 o.tspace0 = half3(vTangent.x,vBitangent.x,vNormal.x); 84 o.tspace1 = half3(vTangent.y,vBitangent.y,vNormal.y); 85 o.tspace2 = half3(vTangent.z,vBitangent.z,vNormal.z); 86 return o; 87 } 88 89 //和上面的shader一样 需要额外定义的宏 90 #ifdef UNITY_CAN_COMPILE_TESSELLATION 91 //顶点着色器结构的定义 92 struct TessVertex{ 93 float4 vertex : INTERNALTESSPOS; 94 float3 normal : NORMAL; 95 float4 tangent : TANGENT; 96 float2 uv : TEXCOORD0; 97 }; 98 99 struct OutputPatchConstant { 100 //不同的图元,该结构会有所不同 101 //该部分用于Hull Shader里面 102 //定义了patch的属性 103 //Tessellation Factor和Inner Tessellation Factor 104 float edge[3] : SV_TESSFACTOR; 105 float inside : SV_INSIDETESSFACTOR; 106 }; 107 108 TessVertex tessvert (VertexInput v){ 109 //顶点着色器函数 110 TessVertex o; 111 o.vertex = v.vertex; 112 o.normal = v.normal; 113 o.tangent = v.tangent; 114 o.uv = v.uv; 115 return o; 116 } 117 118 //float _TessellationUniform; 119 OutputPatchConstant hsconst (InputPatch<TessVertex,3> patch){ 120 //定义曲面细分的参数 121 OutputPatchConstant o; 122 o.edge[0] = _TessellationUniform; 123 o.edge[1] = _TessellationUniform; 124 o.edge[2] = _TessellationUniform; 125 o.inside = _TessellationUniform; 126 return o; 127 } 128 129 [UNITY_domain("tri")]//确定图元,quad,triangle等 130 [UNITY_partitioning("fractional_odd")]//拆分edge的规则,equal_spacing,fractional_odd,fractional_even 131 [UNITY_outputtopology("triangle_cw")] 132 [UNITY_patchconstantfunc("hsconst")]//一个patch一共有三个点,但是这三个点都共用这个函数 133 [UNITY_outputcontrolpoints(3)] //不同的图元会对应不同的控制点 134 135 TessVertex hullProgram (InputPatch<TessVertex,3> patch,uint id : SV_OutputControlPointID){ 136 //定义hullshaderV函数 137 return patch[id]; 138 } 139 140 [UNITY_domain("tri")]//同样需要定义图元 141 VertexOutput ds (OutputPatchConstant tessFactors, const OutputPatch<TessVertex,3>patch,float3 bary :SV_DOMAINLOCATION) 142 //bary:重心坐标 143 { 144 VertexInput v; 145 v.vertex = patch[0].vertex*bary.x + patch[1].vertex*bary.y + patch[2].vertex*bary.z; 146 v.tangent = patch[0].tangent*bary.x + patch[1].tangent*bary.y + patch[2].tangent*bary.z; 147 v.normal = patch[0].normal*bary.x + patch[1].normal*bary.y + patch[2].normal*bary.z; 148 v.uv = patch[0].uv*bary.x + patch[1].uv*bary.y + patch[2].uv*bary.z; 149 150 VertexOutput o = vert (v); 151 return o; 152 } 153 #endif 154 155 float4 frag (VertexOutput i) : SV_Target 156 { 157 float3 lightDir =_WorldSpaceLightPos0.xyz; 158 float3 tnormal = UnpackNormal (tex2D (_DisplacementMap, i.uv)); 159 160 half3 worldNormal; 161 worldNormal.x=dot(i.tspace0,tnormal); 162 worldNormal.y= dot (i.tspace1, tnormal); 163 worldNormal.z=dot (i.tspace2, tnormal); 164 165 float3 albedo=tex2D (_MainTex, i.uv). rgb; 166 float3 lightColor = _LightColor0.rgb; 167 float3 diffuse = albedo * lightColor * DotClamped(lightDir,worldNormal); 168 float3 viewDir = normalize (_WorldSpaceCameraPos. xyz-i. worldPos. xyz); 169 float3 halfVector = normalize(lightDir + viewDir); 170 float3 specular = albedo * pow (DotClamped (halfVector, worldNormal), _Smoothness * 100); 171 float3 result = specular + diffuse; 172 return float4(result, 1.0); 173 174 return float4(result,1.0); 175 } 176 ENDCG 177 } 178 } 179 Fallback "Diffuse" 180 }
几何着色器Demo演示
- 根据顶点的法线方向画线
- 根据顶点画三角形
1 Shader "Unlit/GEO1" 2 { 3 Properties 4 { 5 } 6 SubShader 7 { 8 Tags { "RenderType"="Opaque" } 9 LOD 100 10 11 Pass 12 { 13 CGPROGRAM 14 #pragma vertex vert 15 #pragma geometry geom 16 #pragma fragment frag 17 // make fog work 18 #pragma multi_compile_fog 19 20 #include "UnityCG.cginc" 21 22 struct appdata 23 { 24 float4 vertex : POSITION; 25 float3 normal : NORMAL; 26 float3 tangent : TANGENT; 27 }; 28 29 //注意这里并不是从顶点直接到片元 而是要先经过几何着色器 30 struct v2g //vert-geo 31 { 32 float4 vertex : SV_POSITION; 33 float3 normal : NORMAL; 34 float3 tangent : TANGENT; 35 }; 36 37 struct g2f //geo-frag 38 { 39 float4 vertex : SV_POSITION; 40 fixed4 col : COLOR; 41 }; 42 43 v2g vert (appdata v) 44 { 45 v2g o; 46 //不作操作 47 o.vertex = v.vertex; 48 o.normal = v.normal; 49 o.tangent = v.tangent; 50 return o; 51 } 52 53 [maxvertexcount(3)] 54 void geom(point v2g input[1], inout TriangleStream<g2f> outStream) 55 { 56 g2f o; 57 58 float3 normalP = input[0].vertex + input[0].normal; 59 //画三角形 60 o.vertex = UnityObjectToClipPos(input[0].vertex - input[0].tangent * 0.125); 61 o.col = fixed4(0.0, 1.0, 0.0, 1.0); 62 outStream.Append(o); 63 64 o.vertex = UnityObjectToClipPos(input[0].vertex + input[0].tangent * 0.125); 65 o.col = fixed4(0.0, 1.0, 0.0, 1.0); 66 outStream.Append(o); 67 68 o.vertex = UnityObjectToClipPos(normalP); 69 o.col = fixed4(0.0, 0.0, 1.0, 1.0); 70 outStream.Append(o); 71 } 72 73 fixed4 frag (g2f i) : SV_Target 74 { 75 fixed4 col = i.col; 76 return col; 77 } 78 ENDCG 79 } 80 } 81 }
- 制作一片草地
给三角形上色,使其接近草。
随机长度宽度。
加入随机转向
加入曲面细分
加入风场
示例shader
1 Shader "Unlit/GEO1" 2 { 3 Properties 4 { 5 _ColorRoot("ColorRoot", color) = (0,1,0,1) //根部颜色 6 _ColorTop("ColorTop", color) = (0,1,0,1) //尖部颜色 7 8 _WidthScale("WidthScale", Range(0.25,4)) = 1.0 //根部宽度倍率 9 _WidthRandPow("WidthRandPow", Range(1.0,2.0)) = 1.0//宽度随机强度 10 11 _HeightScale("HeightScale", Range(0.25,4)) = 1.0 //高度倍率 12 _HeightRandPow("HeightRandPow", Range(1.0,2.0)) = 1.0//高度随机强度 13 14 _RotAngle("RotAngle", Range(0.0,180.0)) = 0.0//旋转值 15 _RotRandPow("RotRandPow", Range(1.0,2.0)) = 1.0//旋转随机强度 16 17 _TessellationUniform("TessellationUniform",Range(1,16)) = 1//曲面细分强度 18 19 _WindMap("WindMap", 2D) = "white"{}//风场图 20 _WindSpeed("WindSpeed", Vector) = (0.1, 0.1, 0, 0)//吹风速度 21 _WindPow("WindPow", Float) = 1//风力强度 22 } 23 SubShader 24 { 25 Tags { "RenderType"="Opaque" } 26 LOD 100 27 28 Pass 29 { 30 CGPROGRAM 31 #pragma vertex vert 32 #pragma geometry geom 33 #pragma fragment frag 34 #pragma hull hs 35 #pragma domain ds 36 #pragma target 4.6 37 38 #include "UnityCG.cginc" 39 #include "Lighting.cginc" 40 41 struct appdata 42 { 43 float4 vertex : POSITION; 44 float3 normal : NORMAL; 45 float3 tangent : TANGENT; 46 }; 47 48 struct v2g //vert-tess 49 { 50 float4 vertex : SV_POSITION; 51 float3 normal : NORMAL; 52 float3 tangent : TANGENT; 53 }; 54 55 struct g2f //geo-frag 56 { 57 float4 vertex : SV_POSITION; 58 fixed4 col : COLOR; 59 }; 60 61 struct InternalTessInterp_appdata { 62 float4 vertex : INTERNALTESSPOS; 63 float3 tangent : TANGENT; 64 float3 normal : NORMAL; 65 float2 texcoord : TEXCOORD0; 66 }; 67 68 float4 _ColorRoot; 69 float4 _ColorTop; 70 71 float _WidthScale; 72 float _WidthRandPow; 73 float _HeightScale; 74 float _HeightRandPow; 75 float _RotAngle; 76 float _RotRandPow; 77 78 float _TessellationUniform; 79 80 sampler2D _WindMap; 81 float4 _WindMap_ST; 82 vector _WindSpeed; 83 float _WindPow; 84 v2g vert (appdata v) 85 { 86 v2g o; 87 //不作操作 88 o.vertex = v.vertex; 89 o.normal = v.normal; 90 o.tangent = v.tangent; 91 return o; 92 } 93 94 UnityTessellationFactors hsconst (InputPatch<v2g,3> v) { 95 UnityTessellationFactors o; 96 //_TessellationUniform = frac(_Time * 5) * 32; 97 o.edge[0] = _TessellationUniform; 98 o.edge[1] = _TessellationUniform; 99 o.edge[2] = _TessellationUniform; 100 o.inside = _TessellationUniform; 101 return o; 102 } 103 104 [UNITY_domain("tri")] 105 [UNITY_partitioning("fractional_odd")] 106 [UNITY_outputtopology("triangle_cw")] 107 [UNITY_patchconstantfunc("hsconst")] 108 [UNITY_outputcontrolpoints(3)] 109 v2g hs (InputPatch<v2g,3> v, uint id : SV_OutputControlPointID) { 110 return v[id]; 111 } 112 113 [UNITY_domain("tri")] 114 v2g ds (UnityTessellationFactors tessFactors, const OutputPatch<v2g,3> vi, float3 bary : SV_DomainLocation) { 115 appdata v; 116 117 v.vertex = vi[0].vertex*bary.x + vi[1].vertex*bary.y + vi[2].vertex*bary.z; 118 v.tangent = vi[0].tangent*bary.x + vi[1].tangent*bary.y + vi[2].tangent*bary.z; 119 v.normal = vi[0].normal*bary.x + vi[1].normal*bary.y + vi[2].normal*bary.z; 120 121 v2g o = vert (v); 122 return o; 123 } 124 125 [maxvertexcount(3)] 126 void geom(point v2g input[1], inout TriangleStream<g2f> outStream) 127 { 128 g2f o; 129 130 _WidthScale = _WidthScale * lerp(_WidthScale / _WidthRandPow, _WidthScale * _WidthRandPow, abs(sin(input[0].vertex.x * _WidthRandPow + cos(input[0].vertex.z)))); 131 _HeightScale = _HeightScale * lerp(_HeightScale / _HeightRandPow, _HeightScale * _HeightRandPow, abs(frac(fmod(input[0].vertex.x,_HeightRandPow) + (input[0].vertex.z - _HeightRandPow) * _HeightRandPow))); 132 133 float3 normalP = input[0].vertex + input[0].normal * _HeightScale; 134 135 float3 BinT = cross(input[0].normal, input[0].tangent); 136 _RotAngle = _RotAngle + 180 * (_RotRandPow - 1) * lerp(0, 180, abs(cos(input[0].vertex.x + cos(input[0].vertex.z)))); 137 float3 RotTangent = input[0].tangent * cos(_RotAngle / 3.141592); 138 float3 RotBinTangent = BinT * sin(_RotAngle / 3.141592); 139 float3 RotValue = (RotTangent + RotBinTangent) * 0.125 * _WidthScale; 140 141 //风场 142 float2 uv = input[0].vertex.xz * _WindMap_ST.xy + _WindMap_ST.zw + _WindSpeed * _Time.x; 143 float2 windTex = (tex2Dlod(_WindMap, float4(uv, 0, 0)).xy * 2 - 1) * _WindPow; 144 float3 wind = normalize(float3(windTex.x, windTex.y, 0)); 145 146 //画三角形 147 o.vertex = UnityObjectToClipPos(input[0].vertex - RotValue); 148 o.col = _ColorRoot; 149 outStream.Append(o); 150 151 o.vertex = UnityObjectToClipPos(input[0].vertex + RotValue); 152 o.col = _ColorRoot; 153 outStream.Append(o); 154 155 o.vertex = UnityObjectToClipPos(normalP + wind); 156 o.col = _ColorTop; 157 outStream.Append(o); 158 } 159 160 fixed4 frag (g2f i) : SV_Target 161 { 162 fixed4 col = i.col; 163 return col; 164 } 165 ENDCG 166 } 167 } 168 }
参考链接
【技术美术百人计划】图形 3.3 曲面细分与几何着色器 大规模草渲染
【Unity Shader】使用Geometry Shader进行大片草地的实时渲染