Unity3d surface Shaderswith DX11 Tessellationhtml
Unity3d surface shader 在DX11上的曲面細分web
I write this article, according to the unity3d official document, and look up some data in the web, and add to some idea by myself.算法
根據官方文檔,並查閱了一些資料加上我我的的理解寫出此文。api
I write in two languages. One passage write in Chineseone passage translate into English, If I write some thing wrong, welcome to correct my article.app
我用了兩種語言,一段英文一段中文,英語語法錯誤,或者寫的不對的各位能夠告訴我及時改正dom
Thanks for my teacher correct my article.ide
感謝個人老師對本文寫的不對的地方進行了一些修改函數
Surface Shadershave some support for Direct 11 GPU Tessellation,post
1. Tessellation is indicated by tessellate:FunctionName
modifier. That function computes triangleedge and inside tessellation factors.性能
2. When tessellation is used, 「vertex modifier」 (vertex:FunctionName
) is invokedafter tessellation,for each generated vertex in the domain shader. Here you’d typically todisplacement mapping.
3. Surface shaders can optionally compute Phong Tessellation to smooth model surface even without anydisplacement mapping.
Unity中Surface shader 支持 DX11 GPU曲面細分的緣由在於:
1. 曲面細分函數用 tessellate:FunctionName 表示。這個函數能計算三角形邊緣和一些曲面細分的內部因素。
2. 當曲面細分被使用時,「頂點函數」(vertex:FunctionName)在曲面細分以後被調用,在shader包括的物體以內的每一個頂點都是如此。所以你能夠再次進行貼圖置換。
3. surface shader 能隨意地計算 phong 曲面細分 來光滑模型的表面,甚至不須要貼圖置換。
Current limitationsof tessellation support:
1. Only triangle domain - no quads, no isoline tessellation.
2. When tessellation is used,shader is automatically compiled intoShader Model 5.0 target, which means itwill only work on DX11.
如今unity3d對曲面細分支持的侷限性:
1. 只有三角形面片能夠而四邊形的不行,等值線曲面細分也不支持(isoline tessellation)。
2. 當使用曲面細分時,shader自動編譯成 shader model 5.0 版本,所以曲面細分只能用在DX11上(ShaderModel 5.0 → DirectX 11)。
The Displacement can be usedto instead the Bump Maping technique.The Bump Maping is illusion that can have concave-convex feeling, but the model is flat like before. The Displacement mapping can changemesh’s vertex transposition, chang the model that make it have trueconcave-convex.
貼圖置換(Displacement mapping),可被用作現有凹凸貼圖技術的臨時替代技術,bumpmap只是假象,模型並無真正凹凸,貼圖置換是移動模型頂點,使模型真正產生了凹凸。
Let’s start with a surface shader that does some displacement mapping without using tessellion.Base-on the amount come from Displacement mapping, move the vertex along their normals.
咱們先在不使用曲面細分的狀況下貼圖置換。基於貼圖置換的數值,沿着法線移動頂點。
let's see the shader:
讓咱們看看shader
Shader "Custom/testShader" { Properties { _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 nolightmap #pragma target 5.0 struct appdata { float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; 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" }
tex2Dlod - 2D texture lookup with specified level of detail and optional texel offset.
tex2Dlod 以指定的細節級別和可選的位移來解析貼圖
In this vertex function,we move each vertex along their normals(depend on the _Displacement value);
在這個頂點函數中,把每一個頂點都向着法線方向偏移一些(取決於_Displacement的值)
The above shader is fairly standard:
1. Vertex modifier disp samples the displacement map and moves vertices along their normals.
2. It uses custom 「vertex data input」 structure (appdata) instead of default appdata_full. This is not needed yet, but it’s more efficient for tessellation to use as small structure as possible.
3. Since our vertex data does not have 2nd UV coordinate, we add nolightmap directive to exclude lightmaps.
上面的shader很是標準:
1. 在定點函數disp內,每一個頂點都根據displacement的值的大小沿着該點的法線移動
2. 使用了自定義的頂點輸出的結構體appdata,而不是默認值appdata_full。這個如今還用不到,可是結構體儘量的小,會提升曲面細分的效率。
3. 頂點數據沒有uv座標,在#pragma處加上nolightmap指令,就不包含光照貼圖了
Let’s see how the vertex produce, according to the kinds of beginning cell, there are triangle and quads, as mentioned before, unity just support triangle, not quads. Its just refinement each layer with regulation. The base rule add new vertex to form new edges and planes in the low resolving.(Or though 「cut corner」 method),use recursion to smooth and refinement.DX11 used thePN-Triangles Algorithms to converts low resolution models into curved surfaces which are then redrawn as a mesh of finely tessellated triangles. Then eliminate the feint and the artificial things in games.
讓咱們看看這些點是怎麼生成的
按照初始單元網格分類,包括三角形網格細分和四邊形網格細分,前面已經提到了,unity只支持三角形網格細分,而不支持四邊形。
曲面細分就是用必定的規則對多邊形網格進行逐層細化。細分模式的基本規則是從粗糙的大網格,經過添加新的頂點來造成新的邊和麪(或經過削角的辦法造成新的邊和麪),這樣遞歸地平滑細分,直到最終得到光滑曲面,DX11採用了PN-Triangles算法能將低分辨率模型轉化爲彎曲表面,該表面以後能夠被從新繪製成「高精度曲面細分」的三角形網格。用來消除遊戲中本該是圓滑倒是多邊形的假象。
This approach is suitable if your model’s faces are roughly the same size on screen. Some script could then change the tessellation level from code, based on distance to the camera.
若是整個模型在屏幕上的細分程度同樣,使用這個方法很合適。有些腳本基於模型與相機的的距離來改變細分程度。
Shader "Custom/testShader" { 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 5.0 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" }
曲面細分函數tessFixed返回一個float4的值:xyz是三角形的三個頂點的細分程度,w是比例。在本shader裏只是一個float常量來做爲模型的細分程度。
We can also change tessellation level based on distance from the camera. For example, we could define two distance values; distance at which tessellation is at maximum, and distance towards which tessellation level gradually decreases.
咱們也能經過模型與相機的距離改變細分程度。舉個例子,咱們須要定義兩個距離值;一個是距離相機最近的距離值,一個是最遠的距離值。
Shader "Custom/testShader" { 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 5.0 #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" }
We found the UnityDistanceBasedTess function In "Tessellation.cginc"
「Tessellation.cginc」中咱們找到了UnityDistanceBasedTess函數
float4 UnityDistanceBasedTess (float4 v0, float4 v1, float4 v2, float minDist, float maxDist, float tess) { float3 f; f.x = UnityCalcDistanceTessFactor (v0,minDist,maxDist,tess); f.y = UnityCalcDistanceTessFactor (v1,minDist,maxDist,tess); f.z = UnityCalcDistanceTessFactor (v2,minDist,maxDist,tess); return UnityCalcTriEdgeTessFactors (f); }
We can see the UnityDistanceBasedTess function is call theUnityCalcDistanceTessFactor function, we also found it in the "Tessellation.cginc";
咱們能看到UnityDistanceBasedTess函數主要仍是調用UnityCalcDistanceTessFactor這個函數,因而咱們又在"Tessellation.cginc"中找到了它;
float UnityCalcDistanceTessFactor (float4 vertex, float minDist, float maxDist, float tess) { float3 wpos = mul(_Object2World,vertex).xyz; float dist = distance (wpos, _WorldSpaceCameraPos); float f = clamp(1.0 - (dist - minDist) / (maxDist - minDist), 0.01, 1.0) * tess; return f; }
In the UnityCalcDistanceTessFactor function ,take the vertex chang to world space, then compute the distance between the vertex and the camera,finally compute the tess.
在UnityCalcDistanceTessFactor函數中把點轉換爲世界座標wpos,在求出點與同是世界座標的相機的距離dist,再求出細分程度tess。
Here the tessellation function takes three parameters; the vertex data of three triangle corners before tessellation. This is needed to compute tessellation levels, which depend on vertex positions now. We include a built-in helper file 「Tessellation.cginc」 (in Editor\Data\CGIncludes) and call UnityDistanceBasedTess function from it to do all the work. That function computes distance of each vertex to the camera and derives final tessellation factors.
這裏的細分函數有三個參數;三角形的三個角在曲面細分以前頂點數據。這經過頂點位置來計算細分程度。咱們聲明包含幫助文件「Tessellation.cginc」(Editor\Data\CGIncludes中)而且調用其中的UnityDistanceBasedTess函數來作全部工做。那個函數計算每一個頂點與相機的距離,並返回細分函數的float4值。
遠(far):
近(close):
這個gif也能體現相機距離與細分程度的變化
Purely distance based tessellation is good only when triangle sizes are quite similar.
Tessellation levels could be computed based on triangle edge length on the screen - the longer the edge, the larger tessellation factor should be applied.
單純的基於距離的曲面細分只是在當在三角形大小差很少時效果很好。
將要基於屏幕上的三角形邊緣長度計算曲面細分,可使用更大的細分程度
Shader "Custom/testShader" { 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 5.0 #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 UnityEdgeLengthBasedTess (float4 v0, float4 v1, float4 v2, float edgeLength) { float3 pos0 = mul(_Object2World,v0).xyz; float3 pos1 = mul(_Object2World,v1).xyz; float3 pos2 = mul(_Object2World,v2).xyz; float4 tess; tess.x = UnityCalcEdgeTessFactor (pos1, pos2, edgeLength); tess.y = UnityCalcEdgeTessFactor (pos2, pos0, edgeLength); tess.z = UnityCalcEdgeTessFactor (pos0, pos1, edgeLength); tess.w = (tess.x + tess.y + tess.z) / 3.0f; return tess; }
UnityEdgeLengthBasedTess function inbound three parameters v0, v1, v2 is the three vertices of the triangle.
UnityEdgeLengthBasedTess傳入的v0,v1,v2是三角形的三個頂點
float UnityCalcEdgeTessFactor (float3 wpos0, float3 wpos1, float edgeLen) { // distance to edge center float dist = distance (0.5 * (wpos0+wpos1), _WorldSpaceCameraPos); // length of the edge float len = distance(wpos0, wpos1); // edgeLen is approximate desired size in pixels float f = max(len * _ScreenParams.y / (edgeLen * dist), 1.0); return f; }
In UnityEdgeLengthBasedTess function compute the distance of the middle of two vertices and the camera’s postation(both two are in world space) , then compute the distance of two vertices, finally compute the tess;
UnityCalcEdgeTessFactor中先求出兩個點的中點與相機的距離(全是世界座標),再求出兩個點的距離,再求出細分程度
For performance reasons, it’s advisable to call UnityEdgeLengthBasedTessCull function instead, which will do patch frustum culling. This makes the shader a bit more expensive, but saves a lot of GPU work for parts of meshes that are outside of camera’s view.
因爲性能的緣由,能夠適當的調用UnityEdgeLengthBasedTessCull函數代替(經過判斷相機平截頭體,對不在範圍內的點不進行細分),
調用這個代替函數雖然也浪費一些性能,但沒必要細分沒必要要的點
float4 UnityEdgeLengthBasedTessCull (float4 v0, float4 v1, float4 v2, float edgeLength, float maxDisplacement) { float3 pos0 = mul(_Object2World,v0).xyz; float3 pos1 = mul(_Object2World,v1).xyz; float3 pos2 = mul(_Object2World,v2).xyz; float4 tess; if (UnityWorldViewFrustumCull(pos0, pos1, pos2, maxDisplacement)) // UnityWorldViewFrustumCull平截頭體的剔除若是被剔除(返回0)則不進行細分(Tess = 0) { tess = 0.0f; } else { tess.x = UnityCalcEdgeTessFactor (pos1, pos2, edgeLength); tess.y = UnityCalcEdgeTessFactor (pos2, pos0, edgeLength); tess.z = UnityCalcEdgeTessFactor (pos0, pos1, edgeLength); tess.w = (tess.x + tess.y + tess.z) / 3.0f; } return tess; }
Before see the Phong tessellation shader, we need to know that theory.
再看Phong曲面細分的shader以前先
深刻了解一下Phong曲面細分
This is the Phong tessellation by Tamy Boubekeu and Marc Alexa
Tamy Boubekeur和Marc Alexa 作出的phong曲面細分,
They explain the theory in a video.
他們用一個視頻來簡要講解作法
輸入一個帶有法線的三角形,在重心插入點來作線性細分
正交投影在與此點的「點法式」(法線相垂直的)平面上
在投射的位置線性的插入
1. 計算出線性細分
2. 在三角形三個點上的正切平面上作正交投射
3. 計算重心插入這三個投影
這是他們作出的結果:
Phong Tessellation modifies positions of the subdivided faces so that the resulting surface follows the mesh normals a bit. It’s quite an effective way of making low-poly meshes become more smooth.
Unity’s surface shaders can compute Phong tessellation automatically using tessphong:VariableName compilation directive.
Phong細分修改細分面的位置,使細分面沿着法線突出一點。這是一個讓低模變光滑的很是有效的方式。
Unity得surface shaders中能使用tessphong:VariableName編譯指令自動計算Phong曲面細分。
Shader "Custom/testShader" { Properties { _EdgeLength ("Edge length", Range(2,50)) = 5 _Phong ("Phong Strengh", Range(0,1)) = 0.5 _MainTex ("Base (RGB)", 2D) = "white" {} _Color ("Color", color) = (1,1,1,0) } SubShader { Tags { "RenderType"="Opaque" } LOD 300 CGPROGRAM #pragma surface surf Lambert vertex:dispNone tessellate:tessEdge tessphong:_Phong nolightmap #include "Tessellation.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; void dispNone (inout appdata v) { } float _Phong; float _EdgeLength; float4 tessEdge (appdata v0, appdata v1, appdata v2) { return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength); } struct Input { float2 uv_MainTex; }; fixed4 _Color; sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
Let’s see a comparison between regular shader and one that uses Phong tessellation. Even without any displacement mapping, the surface becomes more round.
看看普通shader和Phong細分以後的比較。若是不用以前幾個shader的貼圖置換,你也能看到模型的表面比之前更圓更光滑了
Base on Phong tessellation we add Displacement mapping, the result is perfect
曲面細分再加上貼圖置換(Displacement mapping)就是完美
Shader "Custom/sufaceshaderTessellation" { Properties { _EdgeLength ("Edge length", Range(2,50)) = 5 _Phong ("Phong Strengh", Range(0,1)) = 0.5 _MainTex ("Base (RGB)", 2D) = "white" {} _Color ("Color", color) = (1,1,1,0) _DispTex ("Disp Texture", 2D) = "gray" {} _NormalMap ("Normalmap", 2D) = "bump" {} _Displacement ("Displacement", Range(0, 1.0)) = 0.3 _SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5) } SubShader { Tags { "RenderType"="Opaque" } LOD 300 CGPROGRAM #pragma surface surf Lambert vertex:disp tessellate:tessEdge tessphong:_Phong nolightmap #include "Tessellation.cginc" struct appdata { float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; 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; } float _Phong; float _EdgeLength; float4 tessEdge (appdata v0, appdata v1, appdata v2) { return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength); } struct Input { float2 uv_MainTex; }; fixed4 _Color; sampler2D _MainTex; sampler2D _NormalMap; 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)); o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
thanks for read ------------- by wolf96 http://blog.csdn.net/wolf96/
consult:
參考:
1. Unity Manual Document - 點擊打開連接
2. Tamy Boubekeur's Phong tessellation page - 點擊打開連接
3. NVIDIA's DirectX11 Tessellation - 點擊打開連接