Shader學習筆記 06 - 貼花

Mesh Decal

1. 獲取與投影框相交的mesh

多種方法如:手動指定、遍歷靜態物體、碰撞檢測、搜索等等。php

2. 將Mesh的頂點、法線轉換到投影框的模型座標系內

主要是爲裁剪作準備。html

var mesh = meshFilter.sharedMesh; // 相交物體的 Mesh
List<Vector3> oriVertices = new List<Vector3>();
mesh.GetVertices(oriVertices);
List<Vector3> oriNormals = new List<Vector3>();
mesh.GetNormals(oriNormals);

List<Vector3> vertices = new List<Vector3>();
List<Vector3> normals = new List<Vector3>();

int vertexCount = mesh.vertexCount;
for (int i = 0; i < vertexCount; i++)
{
    // meshFilter.transform.TransformPoint 從模型座標轉換到世界座標
    // 投影框的transform,transform.InverseTransformPoint,從世界座標轉換到投影框的模型座標
    positions.Add(transform.InverseTransformPoint(meshFilter.transform.TransformPoint(oriVertices[i])));
    normals.Add(transform.InverseTransformDirection(meshFilter.transform.TransformDirection(oriNormals[i])));
}
3. 獲取投影框內的三角網格

須要剔除投影框外的三角網格,保留投影框內的三角網格,裁剪部分投影框內三角網格,背部剔除(可選)。less

for(遍歷三角網格)
{
    1. 背部剔除(可選),判斷三角網格的法線朝向,背部則continue
    2. 投影框內的頂點
    3. 若是投影框內的頂點=3,則continue
    4. 獲取全部在三角網格內與投影框的邊(8個角所組成的包圍邊)的交點
    5. 獲取三角網格的邊與投影框面的交點(即裁剪)
    
    // 我一開始的作法是將全部遍歷所得的頂點保存,並使用三角剖分組成新的Mesh,大部分狀況下沒問題,可是對於複雜的模型(好比中空平面)會出錯,中空的地方會被補上。
    // 因此應該在原有三角網格的基礎下衍生新的多個三角網格 //TODO 驗證
    6. 對當前循環內的所得頂點三角剖分,保存全部新的三角網格
}
  • 保留投影框內的三角網格

三角網格的三個頂點xyz座標是否都在投影框內,通常使用單位立方體做爲投影框,即xyz範圍[-0.5,0.5]ide

image

  • 獲取全部在三角網格內與投影框的邊的交點

在投影框的模型座標系下,投影框其實是一個軸對稱包圍盒。spa

將三維的三角網格分別投影到xz,xy,yz軸平面上(eg:投影到xz軸平面上,直接將三角網格三個頂點的y值置爲零便可)。獲得的二維平面可能結果:.net

image

ABCD是投影框的投影,三角形是三角網格的投影,也多是一條直線,並不影響計算結果。code

判斷ABCD是否在三角形內(重心法),若是是的話,則將對應點返回到三維空間內。orm

// eg: xz投影,求y
// (corner - triangle.A)*Normal = 0
float y = (
    Vector3.Dot(triangle.Normal, triangle.A) -
    triangle.Normal.x * corner.x - triangle.Normal.z * corner.y
    ) / triangle.Normal.y;

而後判斷點是否在投影框內,是則表示這個點是三角網格內與投影框的邊的交點。htm

image

  • 獲取三角網格的邊與投影框面的交點

逐邊裁剪(Sutherland Hodgman)。blog

for(遍歷投影框6個面)
{
    if(可遍歷三角網格的邊=0) break;
    for(遍歷三角網格的邊)
    {
        if(邊位於外側即不可見一側) 直接剔除這條邊;
        // 交點的某個座標固定,經過這個值對開始、結束座標插值,便可獲取交點座標
        if(開始位於內側,結束位於外側 或 結束位於內側,開始位於外側) 獲取交點,判斷交點是否在投影框內,是則保存。
        // 都位於內側的狀況,須要保存的結束點在上面的流程2已經保存。
    }
}

image

4. 組成新的Mesh

新Mesh的uv取自頂點的xz座標或者xy座標(貼花的投射方向),或者根據Normal來動態改變,切線也須要從新計算。

image

Deferred Decal、Volume decals、Screen Space Decal

Deferred Decal
Screen Space Decal
Volume decals

三者原理基本相同。Deferred Decal在延遲着色中使用。Screen Space Decal能夠在向前渲染路徑中使用。Volume decals一樣在延遲着色中使用,做爲無縫(seamless )貼花,投影到複雜幾何上沒有拉伸現象,在其餘貼花方案經過法線動態修改貼圖採樣座標也能夠實現相似功能。

1. Shader配置
// 渲染順序在貼花處物體渲染後面
// 關閉陰影投射
Tags { "Queue" = "Transparent+1" "ForceNoShadowCasting"="True" }
// 貼花半透明時須要指定混合方式
Blend SrcAlpha OneMinusSrcAlpha
2. 獲取投影處

image橙色爲投影處

  • 經過深度重構世界座標
// 經過深度重構世界座標(有兩種方法,後處理、延遲光照中常常使用)
vert {
    o.viewRay = UnityObjectToViewPos(v.vertex)*float3(-1,-1,1);
    o.screenUV = ComputeScreenPos(o.vertex);
}
frag {
    float3 ray = i.viewRay*(_ProjectionParams.z/i.viewRay.z);
    float2 suv = i.screenUV.xy / i.screenUV.w;

    // 經過screen uv做爲採樣座標獲取深度(即上圖中投影框後物體座標的深度)
    float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, suv));
    float4 vpos=fixed4(ray*depth,1);
    float4 wpos=mul(unity_CameraToWorld,vpos);
}
  • 將世界座標轉換到投影框的模型座標系下,並判斷是否在投影框內,裁剪掉在投影框外
// 投影框通常爲單位正方體,只要判斷轉換後的座標xyz是否小於邊長的一半。
float3 opos=mul(unity_WorldToObject,wpos).xyz;
clip(0.5-abs(opos)); // 單位正方體邊長爲1

image

3. 獲取法線

法線主要用於光照計算、解決非平面貼花的拉伸現象。向前渲染從_CameraDepthNormalsTexture取的深度、法線精度不夠基本上無法使用,解決方案能夠是單獨渲一張精度足夠的深度法線貼圖,或者使用座標偏導從新構建法線(面法線 face normal)。

// 使用座標偏導構建法線
// ddx(wpos) 至關於三角網格的切線
float3 normal = normalize(cross(ddy(wpos), ddx(wpos)));

image使用偏導數計算的切線結果,三角網格比較明顯。向前渲染路徑在沒有頂點法線的狀況下目前不知道如何平滑面法線。

紋理法線須要切線空間轉換矩陣。

struct v2f
{
    half3 oriSpace[3]:TEXCOORD5;
};
vert {
    // 在頂點做色階段根據投射方向,指定xyz軸
    o.oriSpace[0] = mul((half3x3)unity_ObjectToWorld, half3(1, 0, 0));
    o.oriSpace[1] = mul((half3x3)unity_ObjectToWorld, half3(0, 1, 0));
    o.oriSpace[2] = mul((half3x3)unity_ObjectToWorld, half3(0, 0, 1));
}
frag {
    float2 decalUV = opos.xz + 0.5;
    fixed3 normal = UnpackNormal(tex2D(_NormalMap, decalUV));
    half3x3 norMat = half3x3(i.oriSpace[0], i.oriSpace[2], i.oriSpace[1]);
    normal = mul(normal, norMat);
}

image

4. 紋理採樣

根據投射方向,使用模型座標採樣紋理(通常是按Y軸從上往下投射,全部使用模型座標的xz份量)。角度變化明顯(法線變化大),根據實際狀況捨棄或者使用模型座標其餘份量採樣紋理。

image

相關文章
相關標籤/搜索