使用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神的项目)