使用Unity實現Voxelizer Mesh變換
2019年初米哈遊官方在B站放出一部八重櫻的次世代卡通渲染視頻,效果可以說是非常驚豔,當我看到八重櫻從頭髮開始溶解,然後變成方塊並向上消失的時候不禁發出了:臥槽。
後來在github上找到了k神的各種Mesh變換的炫酷效果,想着自己也來實現一下。
實現原理
首先用一張圖來回顧一下渲染管線的各個階段,目前爲止我們接觸的着色器有頂點着色器和像素着色器,而接觸到的渲染管線階段有:輸入裝配階段、頂點着色階段、光柵化階段、像素着色階段、輸出合併階段.:
可以看到,幾何着色器是我們在將頂點送入光柵化階段之前,可以操作頂點的最後一個階段。它同樣也允許我們編寫自己的着色器代碼。幾何着色器可以做如下事情:
- 讓程序自動決定如何在渲染管線中插入/移除幾何體;
- 通過流輸出階段將頂點信息再次傳遞到頂點緩衝區;
- 改變圖元類型(如輸入點圖元,輸出三角形圖元);
也就是說我們可以在幾何着色器階段產生比頂點着色器輸入更多的基礎圖元,從而可以實現各種炫酷的效果。
實現步驟
首先需要用一個腳本來獲取模型的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神的項目)