使用Unity實現Voxelizer網格變換

使用Unity實現Voxelizer Mesh變換

2019年初米哈遊官方在B站放出一部八重櫻的次世代卡通渲染視頻,效果可以說是非常驚豔,當我看到八重櫻從頭髮開始溶解,然後變成方塊並向上消失的時候不禁發出了:臥槽。
在這裏插入圖片描述
後來在github上找到了k神的各種Mesh變換的炫酷效果,想着自己也來實現一下。
在這裏插入圖片描述

實現原理

首先用一張圖來回顧一下渲染管線的各個階段,目前爲止我們接觸的着色器有頂點着色器和像素着色器,而接觸到的渲染管線階段有:輸入裝配階段、頂點着色階段、光柵化階段、像素着色階段、輸出合併階段.:
在這裏插入圖片描述
可以看到,幾何着色器是我們在將頂點送入光柵化階段之前,可以操作頂點的最後一個階段。它同樣也允許我們編寫自己的着色器代碼。幾何着色器可以做如下事情:

  1. 讓程序自動決定如何在渲染管線中插入/移除幾何體;
  2. 通過流輸出階段將頂點信息再次傳遞到頂點緩衝區;
  3. 改變圖元類型(如輸入點圖元,輸出三角形圖元);

也就是說我們可以在幾何着色器階段產生比頂點着色器輸入更多的基礎圖元,從而可以實現各種炫酷的效果。

實現步驟

首先需要用一個腳本來獲取模型的MeshRenderer,同時計算產生特效的向量並傳給shader。

        if (_sheet == null) _sheet = new MaterialPropertyBlock();

        var fwd = transform.forward / transform.localScale.z;
        var dist = Vector3.Dot(fwd, transform.position);
        var vector = new Vector4(fwd.x, fwd.y, fwd.z, dist);
        _sheet.SetVector("_EffectVector", vector);

        if (_linkedRenderers != null)
            foreach (var r in _linkedRenderers) r.SetPropertyBlock(_sheet);

第一步是對模型Mesh上隨機選取一些三角面進行重新繪製並進行拉伸操作,其餘三角面進行隨機的旋轉位移和縮小操作。
在這裏插入圖片描述
計算頂點座標、法線、uv

    float3 p0 = input[0].position.xyz;
    float3 p1 = input[1].position.xyz;
    float3 p2 = input[2].position.xyz;

    float3 n0 = input[0].normal;
    float3 n1 = input[1].normal;
    float3 n2 = input[2].normal;

    float2 uv0 = input[0].texcoord;
    float2 uv1 = input[1].texcoord;
    float2 uv2 = input[2].texcoord;

    float3 center = (p0 + p1 + p2) / 3;

計算畸變參數

float param = 1 - dot(_EffectVector.xyz, center) + _EffectVector.w;
    // Pass through the vertices if deformation hasn't been started yet.
    if (param < 0)
    {
        outStream.Append(VertexOutput(p0, n0, uv0));
        outStream.Append(VertexOutput(p1, n1, uv1));
        outStream.Append(VertexOutput(p2, n2, uv2));
        outStream.RestartStrip();
        return;
    }

    // Draw nothing at the end of deformation.
    if (param >= 1) return;

三角面拉伸部分

        float t_anim = 1 + param * 60;
        float3 t_p0 = lerp(center, p0, t_anim);
        float3 t_p1 = lerp(center, p1, t_anim);
        float3 t_p2 = lerp(center, p2, t_anim);

其餘三角面進行隨機的旋轉位移和縮小操作

        float ss_param = smoothstep(0, 1, param);

        // Random motion
        float3 move = RandomVector(seed + 1) * ss_param * 0.5;

        // Random rotation
        float3 rot_angles = (RandomVector01(seed + 1) - 0.5) * 100;
        float3x3 rot_m = Euler3x3(rot_angles * ss_param);

        // Simple shrink
        float scale = 1 - ss_param;

        // Apply the animation.
        float3 t_p0 = mul(rot_m, p0 - center) * scale + center + move;
        float3 t_p1 = mul(rot_m, p1 - center) * scale + center + move;
        float3 t_p2 = mul(rot_m, p2 - center) * scale + center + move;
        float3 normal = normalize(cross(t_p1 - t_p0, t_p2 - t_p0));

        // Edge color (emission power) animation
        float edge = smoothstep(0, 0.1, param); // ease-in
        edge *= 1 + 20 * smoothstep(0, 0.1, 0.1 - param); // peak -> release

        // Vertex outputs (front face)
        outStream.Append(TriangleVertex(t_p0, normal, uv0, float3(1, 0, 0), edge));
        outStream.Append(TriangleVertex(t_p1, normal, uv1, float3(0, 1, 0), edge));
        outStream.Append(TriangleVertex(t_p2, normal, uv2, float3(0, 0, 1), edge));
        outStream.RestartStrip();

        // Vertex outputs (back face)
        outStream.Append(TriangleVertex(t_p0, -normal, uv0, float3(1, 0, 0), edge));
        outStream.Append(TriangleVertex(t_p2, -normal, uv2, float3(0, 0, 1), edge));
        outStream.Append(TriangleVertex(t_p1, -normal, uv1, float3(0, 1, 0), edge));
        outStream.RestartStrip();

第二步,三角面進行頂點拓展成三棱柱。
在這裏插入圖片描述
在這裏插入圖片描述
第三步,計算立方體cube座標生成新片元,三棱柱過渡向立方體。
在這裏插入圖片描述
最後一步,對cube進行拉伸和位移操作。
在這裏插入圖片描述
變換部分的相關源碼

        // Cube animation
        float rnd = Random(seed + 1); // random number, gradient noise
        float4 snoise = snoise_grad(float3(rnd * 2378.34, param * 0.8, 0));

        float move = saturate(param * 4 - 3); // stretch/move param
        move = move * move;

        float3 pos = center + snoise.xyz * 0.02; // cube position
        pos.y += move * rnd;

        float3 scale = float2(1 - move, 1 + move * 5).xyx; // cube scale anim
        scale *= 0.05 * saturate(1 + snoise.w * 2);

        float edge = saturate(param * 5); // Edge color (emission power)
        // Cube points calculation
        float morph = smoothstep(0.25, 0.5, param);
        float3 c_p0 = lerp(t_p2, pos + float3(-1, -1, -1) * scale, morph);
        float3 c_p1 = lerp(t_p2, pos + float3(+1, -1, -1) * scale, morph);
        float3 c_p2 = lerp(t_p0, pos + float3(-1, +1, -1) * scale, morph);
        float3 c_p3 = lerp(t_p1, pos + float3(+1, +1, -1) * scale, morph);
        float3 c_p4 = lerp(t_p2, pos + float3(-1, -1, +1) * scale, morph);
        float3 c_p5 = lerp(t_p2, pos + float3(+1, -1, +1) * scale, morph);
        float3 c_p6 = lerp(t_p0, pos + float3(-1, +1, +1) * scale, morph);
        float3 c_p7 = lerp(t_p1, pos + float3(+1, +1, +1) * scale, morph);

        // Vertex outputs
        float3 c_n = float3(-1, 0, 0);
        outStream.Append(CubeVertex(c_p2, n0, c_n, uv0, float3(0, 0, 1), float2(0, 0), morph, edge));
        outStream.Append(CubeVertex(c_p0, n2, c_n, uv2, float3(1, 0, 0), float2(1, 0), morph, edge));
        outStream.Append(CubeVertex(c_p6, n0, c_n, uv0, float3(0, 0, 1), float2(0, 1), morph, edge));
        outStream.Append(CubeVertex(c_p4, n2, c_n, uv2, float3(1, 0, 0), float2(1, 1), morph, edge));
        outStream.RestartStrip();

        c_n = float3(1, 0, 0);
        outStream.Append(CubeVertex(c_p1, n2, c_n, uv2, float3(0, 0, 1), float2(0, 0), morph, edge));
        outStream.Append(CubeVertex(c_p3, n1, c_n, uv1, float3(1, 0, 0), float2(1, 0), morph, edge));
        outStream.Append(CubeVertex(c_p5, n2, c_n, uv2, float3(0, 0, 1), float2(0, 1), morph, edge));
        outStream.Append(CubeVertex(c_p7, n1, c_n, uv1, float3(1, 0, 0), float2(1, 1), morph, edge));
        outStream.RestartStrip();

        c_n = float3(0, -1, 0);
        outStream.Append(CubeVertex(c_p0, n2, c_n, uv2, float3(1, 0, 0), float2(0, 0), morph, edge));
        outStream.Append(CubeVertex(c_p1, n2, c_n, uv2, float3(1, 0, 0), float2(1, 0), morph, edge));
        outStream.Append(CubeVertex(c_p4, n2, c_n, uv2, float3(1, 0, 0), float2(0, 1), morph, edge));
        outStream.Append(CubeVertex(c_p5, n2, c_n, uv2, float3(1, 0, 0), float2(1, 1), morph, edge));
        outStream.RestartStrip();

        c_n = float3(0, 1, 0);
        outStream.Append(CubeVertex(c_p3, n1, c_n, uv1, float3(0, 0, 1), float2(0, 0), morph, edge));
        outStream.Append(CubeVertex(c_p2, n0, c_n, uv0, float3(1, 0, 0), float2(1, 0), morph, edge));
        outStream.Append(CubeVertex(c_p7, n1, c_n, uv1, float3(0, 0, 1), float2(0, 1), morph, edge));
        outStream.Append(CubeVertex(c_p6, n0, c_n, uv0, float3(1, 0, 0), float2(1, 1), morph, edge));
        outStream.RestartStrip();

        c_n = float3(0, 0, -1);
        outStream.Append(CubeVertex(c_p1, n2, c_n, uv2, float3(0, 0, 1), float2(0, 0), morph, edge));
        outStream.Append(CubeVertex(c_p0, n2, c_n, uv2, float3(0, 0, 1), float2(1, 0), morph, edge));
        outStream.Append(CubeVertex(c_p3, n1, c_n, uv1, float3(0, 1, 0), float2(0, 1), morph, edge));
        outStream.Append(CubeVertex(c_p2, n0, c_n, uv0, float3(1, 0, 0), float2(1, 1), morph, edge));
        outStream.RestartStrip();

        c_n = float3(0, 0, 1);
        outStream.Append(CubeVertex(c_p4, -n2, c_n, uv2, float3(0, 0, 1), float2(0, 0), morph, edge));
        outStream.Append(CubeVertex(c_p5, -n2, c_n, uv2, float3(0, 0, 1), float2(1, 0), morph, edge));
        outStream.Append(CubeVertex(c_p6, -n0, c_n, uv0, float3(0, 1, 0), float2(0, 1), morph, edge));
        outStream.Append(CubeVertex(c_p7, -n1, c_n, uv1, float3(1, 0, 0), float2(1, 1), morph, edge));
        outStream.RestartStrip();

參考

https://github.com/keijiro/GVoxelizer(K神的項目)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章