白纸一张

三十而立,四十而不惑

0%

练习项目(一):顶点动画

概述

之前是使用OpenGL练习图形学项目的,现在要转向在Unity 3d中练习图像学项目。由此,才有了这一系列的项目。本篇将从比较简单的顶点动画说起,开启图形学的篇章。

原理

主要的原理,就是在顶点着色器中,对顶点进行各种偏移。这种偏移,可以根据自己的需要,在模型空间、世界空间、裁剪空间等空间中进行。

1、压扁效果

开放两个属性,TopY和BottomY,代表物体的上部和底部(世界空间中的数值)。再开放一个滑动属性用来控制顶点运动的幅度。

以TopY为基准,先计算出每个顶点的世界坐标,然后对顶点的Y坐标进行归一化处理。

1
2
3
4
5
6
7
float GetNormalizeDist(float worldY)
{
float range = _TopY - _BottomY;
float distance = _TopY - worldY;

return saturate(distance / range);
}

然后使用Control滑动属性控制相应的顶点进行位移。(在模型空间移动,之后再转到裁剪空间。)

1
2
3
4
5
6
7
8
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float normalizeDist = GetNormalizeDist(positionWS.y);

float3 localNegativeY = TransformWorldToObjectDir(float3(0, -1, 0));
float value = saturate(_Control - normalizeDist);
input.positionOS.xyz += localNegativeY * value;

output.vertex = TransformObjectToHClip(input.positionOS.xyz);

代码

2、被门吸收

与上面的压扁效果的原理类似,只是移动的方向变了,同时还要将顶点的世界坐标传递给片元着色器。

1
2
3
4
5
6
7
8
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float normalizeDist = GetNormalizeDist(positionWS.y);

float3 localNegativeY = TransformWorldToObjectDir(float3(0, 1, 0));
float value = saturate(_Control - normalizeDist);
input.positionOS.xyz += localNegativeY * value;

output.positionWS = TransformObjectToWorld(input.positionOS.xyz);

还要做一些特殊处理,对超过“门”的部分进行\(Clip\)操作,这是通过比较顶点的世界坐标的\(Y\)值和\(TopY\)来判断的。

1
clip(_TopY - input.positionWS.y);

代码

3、黑洞吸收

与上面的原理类似,只是顶点的移动方向不再是上面的\(Y\)轴,而是朝向某个“黑洞”点。这里需要开放“黑洞”位置的属性接口。同时,也需要传递顶点的世界坐标给片元着色器。

1
2
3
4
5
6
7
8
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float normalizeDist = GetNormalizeDist(positionWS.x);

float3 toBlackHole = TransformWorldToObjectDir(_BlackHolePos.xyz - positionWS.xyz);
float value = saturate(_Control - normalizeDist);
input.positionOS.xyz += toBlackHole * value;

output.positionWS = TransformObjectToWorld(input.positionOS.xyz);

在片元着色器中,比较顶点的\(X\)坐标和“黑洞”的\(X\)坐标,进行\(Clip\)操作。

1
clip(_BlackHolePos.x - input.positionWS.x);

代码

4、残影

这里的实现原理是,一个\(Pass\)用来渲染本体,另一个\(Pass\)用来渲染残影。这里有一点需要注意,渲染本体的\(Pass\)\(LightMode\)\(UniversalForward\),要想让渲染残影的\(Pass\)也能正常渲染,需要设置该\(Pass\)\(LightMode\)\(SRPDefaultUnlit\)

残影主要有两个实现点,一个是偏离本位,一个是残影自身的抖动。

首先是残影偏离本位,向特定方向整体偏移残影即可。

1
input.positionOS += _Offset * cos(_Time.y * _ShakeSpeed) * _ShakeDir * _Control;

接着是残影的抖动。这里的做法是先把顶点坐标的\(X\)放大\(10\)倍,再向下取整,然后求取对于\(2\)的余数,对奇数部分的顶点进行偏移。

1
2
3
float yOffset = 0.5 * (floor(input.positionOS.x * 10) % 2);

input.positionOS += _ShakeLevel * yOffset * sin(_Time.y * _ShakeSpeed) * _ShakeDir * _Control;

代码

5、折纸

这里的原理是,对顶点以折叠处为中心进行旋转。同时要注意的是,为了同时渲染纸张的正面和背面,需要两个\(Pass\),每个\(Pass\)都要设置正确的\(Cull\)模式。

这里是主要的重新计算折叠后的顶点位置的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
float angle = _FoldAngle;
float r = _FoldPos - input.positionOS.x;

#if ENABLE_DOUBLE
if (r < 0) {
angle = 360 - _FoldAngle;
}
#else
if (r < 0) {
angle = 180;
}
#endif

input.positionOS.x = _FoldPos + r * cos(angle * PI / 180);
input.positionOS.y = r * sin(angle * PI / 180);

output.vertex = TransformObjectToHClip(input.positionOS.xyz);

代码

注意事项

上面的这几个小项目,只是练习用的,在真正用到项目中的时候,可能会有一些问题。在《\(Unity Shader入门精要\)》一书的\(11.3 顶点动画\)一节中,提到了一些注意事项。一个问题是批处理对顶点动画的影响;一个是对有顶点动画的物体添加阴影的问题。这里不再展开,有需要的可以在书中找到答案。

参考

-------------本文结束感谢您的阅读-------------