概述
之前是使用OpenGL练习图形学项目的,现在要转向在Unity 3d中练习图像学项目。由此,才有了这一系列的项目。本篇将从比较简单的顶点动画说起,开启图形学的篇章。
原理
主要的原理,就是在顶点着色器中,对顶点进行各种偏移。这种偏移,可以根据自己的需要,在模型空间、世界空间、裁剪空间等空间中进行。
1、压扁效果
开放两个属性,TopY和BottomY,代表物体的上部和底部(世界空间中的数值)。再开放一个滑动属性用来控制顶点运动的幅度。
以TopY为基准,先计算出每个顶点的世界坐标,然后对顶点的Y坐标进行归一化处理。
float GetNormalizeDist(float worldY)
{
float range = _TopY - _BottomY;
float distance = _TopY - worldY;
return saturate(distance / range);
}
然后使用Control滑动属性控制相应的顶点进行位移。(在模型空间移动,之后再转到裁剪空间。)
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、被门吸收
与上面的压扁效果的原理类似,只是移动的方向变了,同时还要将顶点的世界坐标传递给片元着色器。
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);
还要做一些特殊处理,对超过“门”的部分进行 C l i p Clip Clip操作,这是通过比较顶点的世界坐标的 Y Y Y值和 T o p Y TopY TopY来判断的。
clip(_TopY - input.positionWS.y);
3、黑洞吸收
与上面的原理类似,只是顶点的移动方向不再是上面的 Y Y Y轴,而是朝向某个“黑洞”点。这里需要开放“黑洞”位置的属性接口。同时,也需要传递顶点的世界坐标给片元着色器。
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 X坐标和“黑洞”的 X X X坐标,进行 C l i p Clip Clip操作。
clip(_BlackHolePos.x - input.positionWS.x);
4、残影
这里的实现原理是,一个 P a s s Pass Pass用来渲染本体,另一个 P a s s Pass Pass用来渲染残影。这里有一点需要注意,渲染本体的 P a s s Pass Pass的 L i g h t M o d e LightMode LightMode是 U n i v e r s a l F o r w a r d UniversalForward UniversalForward,要想让渲染残影的 P a s s Pass Pass也能正常渲染,需要设置该 P a s s Pass Pass的 L i g h t M o d e LightMode LightMode为 S R P D e f a u l t U n l i t SRPDefaultUnlit SRPDefaultUnlit。
残影主要有两个实现点,一个是偏离本位,一个是残影自身的抖动。
首先是残影偏离本位,向特定方向整体偏移残影即可。
input.positionOS += _Offset * cos(_Time.y * _ShakeSpeed) * _ShakeDir * _Control;
接着是残影的抖动。这里的做法是先把顶点坐标的 X X X放大 10 10 10倍,再向下取整,然后求取对于 2 2 2的余数,对奇数部分的顶点进行偏移。
float yOffset = 0.5 * (floor(input.positionOS.x * 10) % 2);
input.positionOS += _ShakeLevel * yOffset * sin(_Time.y * _ShakeSpeed) * _ShakeDir * _Control;
5、折纸
这里的原理是,对顶点以折叠处为中心进行旋转。同时要注意的是,为了同时渲染纸张的正面和背面,需要两个 P a s s Pass Pass,每个 P a s s Pass Pass都要设置正确的 C u l l Cull Cull模式。
这里是主要的重新计算折叠后的顶点位置的过程。
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);
注意事项
上面的这几个小项目,只是练习用的,在真正用到项目中的时候,可能会有一些问题。在《 U n i t y S h a d e r 入 门 精 要 Unity Shader入门精要 UnityShader入门精要》一书的 11.3 顶 点 动 画 11.3 顶点动画 11.3顶点动画一节中,提到了一些注意事项。一个问题是批处理对顶点动画的影响;一个是对有顶点动画的物体添加阴影的问题。这里不再展开,有需要的可以在书中找到答案。
参考
- [1] Unity Shader - 一些玩具Shader
- [2] 《 U n i t y S h a d e r 入 门 精 要 Unity Shader入门精要 UnityShader入门精要》