回到目錄
??大家好,我是阿趙。
??之前介紹了使用動態(tài)法線貼圖混合的方式模擬軌跡的凹凸感,這次來講一下更真實的凹凸感制作。不過在說這個內(nèi)容之前,這一篇先要介紹一下曲面細分著色器(Tessellation Shader)的用法。
一、為什么要做曲面細分
??之前通過法線貼圖模擬了凹凸的感覺:
??法線貼圖不會真的產(chǎn)生凹凸,它只是改變了這個平面上面的法線方向。所以,只有通過光照模型,通過法線方向和燈光方向進行點乘,才會計算出不同的光照角度,讓我們產(chǎn)生一定的凹凸感覺。
??但如果想做到這樣的效果,法線貼圖是不行的:
??這種效果,球是真的陷進去地面了。很明顯,這些都是需要偏移頂點讓網(wǎng)格產(chǎn)生真實的變形,才能做到。
??不過這里有一個問題,如果地面的網(wǎng)格面數(shù)并不是很高,那么就算我們有能力去偏移頂點,也產(chǎn)生不了這樣好的效果。
??比如一般的地面網(wǎng)格的面數(shù)都很低,只有這樣的水平:
??這個時候,球所在的地方,根本就沒有頂點,所以也偏移不了。就算再稍微多一點面,這樣的地面網(wǎng)格面數(shù)算比較高了,仍然產(chǎn)生不了很好的凹凸效果:
??所以這里有一個很嚴重的問題,我們難道需要用幾十萬甚至幾百萬面,去做一個地面的模型,才能產(chǎn)生真實的凹凸感嗎?
??這是不可能的,實際的情況是:
??在需要到很精確的頂點控制的一個小局部,才需要把面數(shù)變高,其他的地方,面數(shù)很是很低的。具體可以看看這個視頻:
Unity引擎動態(tài)曲面細分
??而這里用到的局部增加面數(shù)的技術,就是曲面細分(Tessellation)了。
二、曲面細分的過程
??在Unity里面寫頂點片段著色器的Shader,我們一般只會注意到需要些Vertex頂點程序,和fragment片段程序,因為在大多數(shù)情況下,其他的渲染管線流程都不是我們可以控制的,而我們能控制頂點程序改變模型的形狀,控制片段程序來改變模型的顏色。
??但在頂點程序和片段程序中間,其實還有一個曲面細分(tessellate)的過程,這個過程有2個程序是我們可以控制的
1、hullProgram
??這個程序會接受每個多邊形各個頂點的信息,記錄下來,然后通過指定一個Patch Constant Function,去設置細分的數(shù)量,這個過程是針對多邊形的每一條邊,還有多邊形的內(nèi)部,分別設置拆分的數(shù)量的。
2、domainProgram
??在前面的hullProgram里面,其實只是設置了頂點信息和拆分數(shù)量,并沒有真正的生成新的網(wǎng)格。而在這個domainProgram里面,拆分后的頂點信息已經(jīng)產(chǎn)生了,所以可以對拆分后的頂線進行操作,可以計算他們的位置、法線、uv等。
??為了避免難以理解,也不說太多,只要知道,需要做曲面細分的時候,需要添加2個程序過程,一個過程設置了拆分的數(shù)量和其他參數(shù),另外一個過程就得到了頂點,可以進行實際操作,這樣就行了。
三、曲面細分在Unity引擎的實現(xiàn)
1、Surface類型著色器
??Surface類型的Shader提供了很多Unity封裝好的方法,也包括提供了對應曲面細分著色器的方法。
使用很簡單:
1.#include “Tessellation.cginc”
2.指定曲面細分的方法:tessellate:tessFunction
3.指定target 4.6
看到這里有target 4.6的聲明了,沒錯Unity官方的說明也是這樣的:
When you use tessellation, the shader is automatically compiled into
the Shader Model 4.6 target, which prevents support for running on
older graphics targets.
??這里著重說一下曲面細分方法。
??由于Surface的曲面細分方法是Unity封裝好的,所以我們不需要走正常的渲染流程,不需要指定hullProgram、Patch Constant Function和domainProgram,只需要指定一個tessellate處理方法。這個方法實際是返回一個曲面細分的值,來決定某個面具體要細分成多少個網(wǎng)格。
而在Unity提供的方法里面,對于怎樣細分曲面,提供了3種選擇:
1.Fixed固定數(shù)量細分
??這種方式細分,在tessFunction里面直接返回一個數(shù)值,然后全部面就按照統(tǒng)一的數(shù)值去細分。
unity官方文檔里面的例子是這樣
Shader "Tessellation Sample" {
Properties {
_Tess ("Tessellation", Range(1,32)) = 4
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessFixed nolightmap
#pragma target 4.6
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _Tess;
float4 tessFixed()
{
return _Tess;
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
其中曲面細分方法是直接返回了一個指定的值
float4 tessFixed()
{
return _Tess;
}
2.根據(jù)距離細分
??這里的距離,指的是和攝像機的距離。根據(jù)離攝像機不同的距離,設置一個范圍來細分
unity官方文檔里面的例子是這樣:
Shader "Tessellation Sample" {
Properties {
_Tess ("Tessellation", Range(1,32)) = 4
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessDistance nolightmap
#pragma target 4.6
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _Tess;
float4 tessDistance (appdata v0, appdata v1, appdata v2) {
float minDist = 10.0;
float maxDist = 25.0;
return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, minDist, maxDist, _Tess);
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
其中曲面細分方法是傳入了最小距離、最大距離和一個控制值
float4 tessDistance (appdata v0, appdata v1, appdata v2) {
float minDist = 10.0;
float maxDist = 25.0;
return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, minDist, maxDist, _Tess);
}
UnityDistanceBasedTess就是Unity提供的根據(jù)距離計算細分值的方法。
3.根據(jù)邊的長度細分
??這個根據(jù)邊的長度,指的是多邊形的邊,在屏幕里面渲染的大小。
??所以從左圖可以看出,越近屏幕的邊,渲染的長度越大,所以細分得越多,而離屏幕越遠的邊,渲染的長度越小,細分得也越少。
??從右圖可以看出,同一個模型,如果通過縮放把邊拉長,它的細分程度也會隨著模型拉長而變大,最后保持著一個比較固定的細分密度。
unity官方文檔里面的例子是這樣的:
Shader "Tessellation Sample" {
Properties {
_EdgeLength ("Edge length", Range(2,50)) = 15
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessEdge nolightmap
#pragma target 4.6
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _EdgeLength;
float4 tessEdge (appdata v0, appdata v1, appdata v2)
{
return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
其中曲面細分程序傳入一個指定的值,需要注意的是,這個值越小,細分得越多
float4 tessEdge (appdata v0, appdata v1, appdata v2)
{
return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}
UnityEdgeLengthBasedTess 是Unity提供的根據(jù)邊長細分的方法
2、頂點片段程序?qū)崿F(xiàn)曲面細分
??如果不使用Surface類型的Shader,而用傳統(tǒng)的頂點片段程序著色器,實現(xiàn)曲面細分就只有一種方式,就是正常的添加hullProgram、Patch Constant Function和domainProgram,然后逐條邊和多邊形內(nèi)部指定細分的數(shù)量。我這里提供一個最簡單的Shader來說明一下寫法:
Shader "azhao/TessVF"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
_EditFactor("edgeFactor", Float) = 15
_InsideFactor("insideFactor",FLoat) =15
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
//在正常的vertex和fragment之間還需要hull和domain,所以在這里加上聲明
#pragma hull hullProgram
#pragma domain domainProgram
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
uniform float _EditFactor;
uniform float _InsideFactor;
struct a2v
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
struct v2t
{
float4 worldPos : TEXCOORD0;
float2 uv : TEXCOORD1;
};
struct t2f
{
float4 clipPos:SV_POSITION;
float2 uv: TEXCOORD0;
float4 worldPos:TEXCOORD1;
};
struct TessOut
{
float2 uv : TEXCOORD0;
float4 worldPos : TEXCOORD1;
};
struct TessParam
{
float EdgeTess[3] : SV_TessFactor;//各邊細分數(shù)
float InsideTess : SV_InsideTessFactor;//內(nèi)部點細分數(shù)
};
v2t vert(a2v i)
{
v2t o;
o.worldPos = mul(unity_ObjectToWorld,i.pos);
o.uv = i.uv;
return o;
}
//在hullProgram之前必須設置這些參數(shù),不然會報錯
[domain("tri")]//圖元類型,可選類型有 "tri", "quad", "isoline"
[partitioning("integer")]//曲面細分的過渡方式是整數(shù)還是小數(shù)
[outputtopology("triangle_cw")]//三角面正方向是順時針還是逆時針
[outputcontrolpoints(3)]//輸出的控制點數(shù)
[patchconstantfunc("ConstantHS")]//對應之前的細分因子配置階段的方法名
[maxtessfactor(64.0)]//最大可能的細分段數(shù)
//vert頂點程序之后調(diào)用,計算細分前的三角形頂點信息
TessOut hullProgram(InputPatch<v2t, 3> i, uint idx : SV_OutputControlPointID)
{
TessOut o;
o.worldPos = i[idx].worldPos;
o.uv = i[idx].uv;
return o;
}
//指定每個邊的細分段數(shù)和內(nèi)部細分段數(shù)
TessParam ConstantHS(InputPatch<v2t, 3> i, uint id : SV_PrimitiveID)
{
TessParam o;
o.EdgeTess[0] = _EditFactor;
o.EdgeTess[1] = _EditFactor;
o.EdgeTess[2] = _EditFactor;
o.InsideTess = _InsideFactor;
return o;
}
//在domainProgram前必須設置domain參數(shù),不然會報錯
[domain("tri")]
//細分之后,把信息傳到frag片段程序
t2f domainProgram(TessParam tessParam, float3 bary : SV_DomainLocation, const OutputPatch<TessOut, 3> i)
{
t2f o;
//線性轉(zhuǎn)換
float2 uv = i[0].uv * bary.x + i[1].uv * bary.y + i[2].uv * bary.z;
o.uv = uv;
float4 worldPos = i[0].worldPos * bary.x + i[1].worldPos * bary.y + i[2].worldPos * bary.z;
o.worldPos = worldPos;
o.clipPos = UnityWorldToClipPos(worldPos);
return o;
}
fixed4 frag (t2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv)*_Color;
return col;
}
ENDCG
}
}
}
需要注意的地方是:
1.聲明處理程序:
#pragma hull hullProgram
#pragma domain domainProgram
2.在hullProgram之前必須設置這些參數(shù),不然會報錯
[domain("tri")]//圖元類型,可選類型有 "tri", "quad", "isoline"
[partitioning("integer")]//曲面細分的過渡方式是整數(shù)還是小數(shù)
[outputtopology("triangle_cw")]//三角面正方向是順時針還是逆時針
[outputcontrolpoints(3)]//輸出的控制點數(shù)
[patchconstantfunc("ConstantHS")]//對應之前的細分因子配置階段的方法名
[maxtessfactor(64.0)]//最大可能的細分段數(shù)
3.domainProgram前必須設置domain參數(shù),不然會報錯
[domain("tri")]
四、根據(jù)范圍做局部曲面細分
??已經(jīng)介紹完怎樣使用曲面細分了,接下來就是要實現(xiàn)文章一開始說的,根據(jù)指定的中心點和范圍,做局部的曲面細分。
1、在頂點片段著色器實現(xiàn)局部細分
??由于使用頂點片段著色器做曲面細分,是可以直接設置每個多邊形的邊和內(nèi)部的細分數(shù)量,所以要實現(xiàn)局部細分也就非常簡單了,思路是:
1.獲得中心點坐標和范圍半徑
2.在著色器取得當前頂點的世界坐標,然后判斷是否在中心點的半徑范圍內(nèi)
3.用一個smoothStep做一個邊緣范圍過渡,作為細分強度
4.根據(jù)計算出的細分強度,設置最終的細分值。
寫成代碼大概就是這樣:
Shader "azhao/GroundTessVF"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
_centerPos("CenterPos", Vector) = (0,0,0,0)
_minVal("minVal", Float) = 0
_maxVal("maxVal", Float) = 10
_factor("factor", Float) = 15
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
//在正常的vertex和fragment之間還需要hull和domain,所以在這里加上聲明
#pragma hull hullProgram
#pragma domain domainProgram
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
uniform float _minVal;
uniform float _maxVal;
uniform float3 _centerPos;
uniform float _factor;
struct a2v
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
struct v2t
{
float4 worldPos : TEXCOORD0;
float2 uv : TEXCOORD1;
};
struct t2f
{
float4 clipPos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 worldPos : TEXCOORD1;
};
struct TessOut
{
float2 uv : TEXCOORD0;
float4 worldPos : TEXCOORD1;
};
struct TessParam
{
float EdgeTess[3] : SV_TessFactor;//各邊細分數(shù)
float InsideTess : SV_InsideTessFactor;//內(nèi)部點細分數(shù)
};
v2t vert(a2v i)
{
v2t o;
o.worldPos = mul(unity_ObjectToWorld,i.pos);
o.uv = i.uv;
return o;
}
//在hullProgram之前必須設置這些參數(shù),不然會報錯
[domain("tri")]//圖元類型,可選類型有 "tri", "quad", "isoline"
[partitioning("integer")]//曲面細分的過渡方式是整數(shù)還是小數(shù)
[outputtopology("triangle_cw")]//三角面正方向是順時針還是逆時針
[outputcontrolpoints(3)]//輸出的控制點數(shù)
[patchconstantfunc("ConstantHS")]//對應之前的細分因子配置階段的方法名
[maxtessfactor(64.0)]//最大可能的細分段數(shù)
//vert頂點程序之后調(diào)用,計算細分前的三角形頂點信息
TessOut hullProgram(InputPatch<v2t, 3> i, uint idx : SV_OutputControlPointID)
{
TessOut o;
o.worldPos = i[idx].worldPos;
o.uv = i[idx].uv;
return o;
}
//指定每個邊的細分段數(shù)和內(nèi)部細分段數(shù)
TessParam ConstantHS(InputPatch<v2t, 3> i, uint id : SV_PrimitiveID)
{
TessParam o;
float4 worldPos = (i[0].worldPos + i[1].worldPos + i[2].worldPos) / 3;
float smoothstepResult = smoothstep(_minVal, _maxVal, distance(worldPos.xz, _centerPos.xz));
float fac = max((1.0 - smoothstepResult)*_factor, 1);
//由于我這里是根據(jù)指定的中心點和半徑范圍來動態(tài)算細分段數(shù),所以才有這個計算,不然可以直接指定變量來設置。
o.EdgeTess[0] = fac;
o.EdgeTess[1] = fac;
o.EdgeTess[2] = fac;
o.InsideTess = fac;
return o;
}
//在domainProgram前必須設置domain參數(shù),不然會報錯
[domain("tri")]
//細分之后,把信息傳到frag片段程序
t2f domainProgram(TessParam tessParam, float3 bary : SV_DomainLocation, const OutputPatch<TessOut, 3> i)
{
t2f o;
//線性轉(zhuǎn)換
o.worldPos = i[0].worldPos * bary.x + i[1].worldPos * bary.y + i[2].worldPos * bary.z;
o.clipPos = UnityWorldToClipPos(o.worldPos);
float2 uv = i[0].uv * bary.x + i[1].uv * bary.y + i[2].uv * bary.z;
o.uv = uv;
return o;
}
fixed4 frag (t2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv)*_Color;
return col;
}
ENDCG
}
}
}
??使用的時候,在C#端中心點改變的時候,傳入centerPos,通過調(diào)整_maxVal和_minVal,可以控制半徑和邊緣強度漸變的效果
2、在Surface著色器實現(xiàn)局部細分
??在Surface著色器里面實現(xiàn)曲面細分,需要寫的代碼很少,我們就使用上面介紹的Fixed類型然后同樣的通過傳入中心點,還有_maxVal和_minVal,來確定需要細分的范圍,實現(xiàn)思路和上面的頂點片段著色器是一樣的。
代碼會是這樣的:文章來源:http://www.zghlxwxcb.cn/news/detail-743994.html
Shader "azhao/FootStepMeshSurface"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
_centerPos("centerPos", Vector) = (0,0,0,0)
_minVal("minVal", Float) = 0
_maxVal("maxVal", Float) = 10
_factor("factor", Float) = 15
_footstepRect("footstepRect",Vector) = (0,0,0,0)
_footstepTex("footstepTex",2D) = "gray"{}
_height("height" ,Float) = 0.3
_Glossiness("Glossiness",Float) = 0
_Metallic("Metallic",Float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#include "Tessellation.cginc"
#pragma surface surf Standard fullforwardshadows vertex:vertexDataFunc tessellate:tessFunction
#pragma target 4.6
struct Input
{
float2 uv_texcoord;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
uniform sampler2D _mainTex;
SamplerState sampler_mainTex;
uniform float4 _mainTex_ST;
uniform float _minVal;
uniform float _maxVal;
uniform float3 _centerPos;
uniform float _factor;
float4 _footstepRect;
sampler2D _footstepTex;
float _height;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)
float RemapUV(float min, float max, float val)
{
return (val - min) / (max - min);
}
//這里處理細分相關邏輯
float4 tessFunction(appdata_full v0, appdata_full v1, appdata_full v2)
{
float3 worldPos = mul(unity_ObjectToWorld, (v0.vertex + v1.vertex + v2.vertex) / 3);
float smoothstepResult = smoothstep(_minVal, _maxVal, distance(worldPos.xz, _centerPos.xz));
float fac = max((1.0 - smoothstepResult)*_factor, 0.1);
return fac;
}
void vertexDataFunc(inout appdata_full v)
{
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = _Color;
float2 uv_mainTex = IN.uv_texcoord * _mainTex_ST.xy + _mainTex_ST.zw;
float4 mainTex = tex2D(_mainTex, uv_mainTex);
o.Albedo = mainTex.rgb*c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
3、選擇哪種方式的Shader實現(xiàn)會比較好?
??這個問題是沒有直接答案的,需要根據(jù)自己的實際情況來選擇。
??頂點片段著色器的優(yōu)點是可控性強,自己可以隨意的定義各種光照模型、修改細節(jié)的效果,缺點是寫法麻煩。
??Surface著色器的優(yōu)點是寫法簡單,缺點是可控性比較弱一點。
??我個人是習慣用頂點片段著色器的,因為我比較的喜歡自己控制各個環(huán)節(jié)的細節(jié)。所以在接下來的例子里面,我還是會用頂點片段著色器的寫法來繼續(xù)做這個地面交互效果的demo。不過其實如果頂點片段著色器上知道了怎樣實現(xiàn),在Surface著色器上面實現(xiàn)的過程就更簡單了。文章來源地址http://www.zghlxwxcb.cn/news/detail-743994.html
到了這里,關于Unity地面交互效果——3、曲面細分基礎知識的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!