《三弦》技术分享—波纹效果及其实现
- 作者:admin
- /
- 时间:2018年05月31日
- /
- 浏览:4514 次
- /
- 分类:厚积薄发
这篇文章主要和大家聊一聊带波浪的地面的实现,以及场景特效的实现方法。如下图所示,地板随时间以波浪状变化,角色在地板上走的时候会随着它的波浪高低起伏。
视觉实现
关于波浪效果的实现,作者最开始的时候走了些弯路。一开始,我想的是试试用直接修改地板Mesh的方式,达到视觉和物理上的同步实现。事实上这被证明是非常不科学的,因为修改Mesh会严重影响性能。在涉及物理的情况下,我测试的结果是游戏帧率调到了个位数。所以最终选择了利用Shader实现视觉效果+利用脚本伪造行走物理效果的办法。
首先是地板的模型,这里使用了一个Plane:
波浪函数的实现,这里是函数实现,用的是两个方向的波进行叠加,波纹随时间变化。即构造一个全局函数,因为c#代码和Shader代码里都要使用,所以这是一个以World-Space的坐标x,z,以及游戏时间Time.time为变量的,以波浪高度y为结果的函数。使用的波纹函数为三角函数:
两路波形线性叠加,具体实现如下:
[SerializeField] float height1 = 1f;
[SerializeField] float frequency1 = 1f;
[SerializeField] float wavelength1 = 1f;
[SerializeField] Vector3 wave1Direction;
[SerializeField] float height2 = 1f;
[SerializeField] float frequency2 = 1f;
[SerializeField] float wavelength2 = 1f;
[SerializeField] Vector3 wave2Direction;
[SerializeField][Range(0,10f)] float overallHeight = 1f;
[SerializeField][Range(0,10f)] float overallFrequency = 1f;
[SerializeField][Range(0,10f)] float overallWaveLength = 1f;
[SerializeField] float offset;
public float GetHeight( float x , float z )
{
float x1 = x * wave1Direction.normalized.x + z * wave1Direction.normalized.z;
float y1 = Mathf.Sin (x1 / (wavelength1 * overallWaveLength) + Time.time * frequency1 * overallFrequency) * height1 * overallHeight;
float x2 = x * wave2Direction.normalized.x + z * wave2Direction.normalized.z;
float y2 = Mathf.Sin (x2 / (wavelength2 * overallWaveLength) + Time.time * frequency2 * overallFrequency) * height2 * overallHeight;
return y1 + y2 + offset;
}
当然这是在C#里的实现,其实和在Shader里的实现类似。不过注意,需要把函数的参数传给Material。同时,需要每帧对时间参数进行同步(C#代码):
public void SetMaterial( Material material ) // called at start
{
material.SetFloat ("_height1", height1 * overallHeight);
material.SetFloat ("_frequency1", frequency1 * overallFrequency);
material.SetFloat ("_waveLength1", wavelength1* overallWaveLength);
material.SetFloat ("_height2", height2 * overallHeight);
material.SetFloat ("_frequency2", frequency2 * overallFrequency);
material.SetFloat ("_waveLength2", wavelength2* overallWaveLength);
material.SetVector ("_wave1Direction", wave1Direction.normalized);
material.SetVector ("_wave2Direction", wave2Direction.normalized);
material.SetFloat ("_waveOffset", offset);
}
public void Update Material(Material material ) // called at update
{
material.SetFloat ("_Timer", Time.time);
}
在Shader里,需要在Vert函数里,对顶点的位置进行重新计算:
v2f vert(appdata v )
{
v2f o;
o.uv = v.uv;
o.worldPos = mul(unity_ObjectToWorld , v.vertex);
o.worldPos.y = GetWaveHeight( o.worldPos.x , o.worldPos.z , _Timer);
v.vertex.y = mul(unity_WorldToObject , o.worldPos).y;
o.vertex = UnityObjectToClipPos( v.vertex );
}
实现的效果如下:
至此,视觉部分已经完成。
这里把错误示范列出来,利用Mesh实现波浪效果的脚本:
public Mesh m_mesh;
void Update ()
{
{
List<Vector3> verticles = new List<Vector3> (m_mesh.vertices);
for (int i = 0; i < verticles.Count; ++i) {
var vect = verticles [i];
vect.y = GetWaveHeight( vecticles[i].x , verticles[i].z );
verticles [i] = vect;
}
m_mesh.SetVertices (verticles);
m_mesh.RecalculateBounds ();
}
}
逻辑实现
逻辑部分的实现也是绕了不少弯路。
一开始想的是直接更改Mesh来实现,但是消耗太大,故弃之。
接下来的思路还是利用物理系统,用一个Cube模拟玩家脚下的地面,每帧更新它的位置和角度信息。理论上来说,这种实现方法是可以模拟玩家和地表的物理碰撞。但只是理论,实际上这种实现方法会出现比较严重的抖动情况。(角色的控制使用的是自带的Character Controller)
最后的实现方法是,在角色控制的脚本里,直接对角色的位置进行修改。由于波纹函数是平滑的,所以实现的效果也可以做到比较平滑。同时,根据场地形的斜度对速度进行一定的调整,达到上坡下坡的效果。
float climbRate = Mathf.Clamp( WaveController.Instance.GetGradient ( Position.x , Position.z , m_MoveDir) * 10f + 1f , 0.6f , 1.5f ) ;
speed *= climbRate;
斜率的计算直接用数值法求导(用导数法太麻烦了):
public float GetGradient( float x , float z , Vector3 velocity )
{
// if the velocity is too small
if (velocity.magnitude < Mathf.Epsilon)
return 0;
float thisY = GetHeight (x, z);
Vector3 delta = velocity * Time.deltaTime;
float otherY = GetHeight (x + delta.x, z + delta.z);
// the gradient is delta Y / delta V
return - (otherY - thisY) / delta.magnitude;
}
弄好之后,就可以愉快地用角色爬坡了~
后期特效实现
后期的特效实现的是一种反向Bloom的效果,下图是该效果夸张处理后的实现:
物体的黑色部分被加强,相当于一个反向的Bloom。
选择Standard Asset里的Optimized Bloom作为基础,进行修改。源代码在Asset Store上可以免费下载:
https://assetstore.unity.com/packages/essentials/legacy-image-effects-83913
首先分析Bloom的代码,查看BloomOptimized和在MobileBloom.Shader里,可以看到Bloom的流程是:
Downsample(提取颜色高亮的部分)->Blur(进行横竖方向的模糊)->Bloom(把高光颜色和原画面进行叠加)
根据这个流程,如果我们需要把Bloom效果反向,在Bloom部分把颜色进行剔除,同时需要在DownSample部分进行修改,进行曲线上的调整。
具体的做法如下:
fixed4 fragDownsample ( v2f_tap i ) : SV_Target
{
fixed4 color = tex2D (_MainTex, i.uv20);
color += tex2D (_MainTex, i.uv21);
color += tex2D (_MainTex, i.uv22);
color += tex2D (_MainTex, i.uv23);
//return max(color/4 - THRESHHOLD, 0) * ONE_MINUS_THRESHHOLD_TIMES_INTENSITY;
// this curve is used for no reason, I just feel it looks good
return ( color / 4 ) * THRESHHOLD + 1 - THRESHHOLD;
}
fixed4 fragBloom ( v2f_simple i ) : SV_Target
{
#if UNITY_UV_STARTS_AT_TOP
fixed4 color = tex2D(_MainTex, i.uv2);
// return color + tex2D(_Bloom, i.uv);
// combine the color by mutiply
return color * max( 1 - ( 1 - tex2D(_Bloom, i.uv)) * INTENSITY , 0 );
#else
fixed4 color = tex2D(_MainTex, i.uv);
// return color + tex2D(_Bloom, i.uv);
return color * max( 1 - ( 1 - tex2D(_Bloom, i.uv)) * INTENSITY , 0 );
#endif
}
最后的成果展示:
这是侑虎科技第399篇文章,感谢作者邓佳迪供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。QQ群:793972859
作者个人主页:https://zhuanlan.zhihu.com/p/34806560,同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!