图形引擎实战:近岸海浪初步尝试

背景

游戏中水面的渲染质量可以作为评估整个游戏图形质量的一个标准。这种看法源于水面渲染技术的复杂性,因为它涉及光线反射、折射、波纹动态等多个图形处理方面。一个游戏如果能够精确并美观地渲染水面效果,通常意味着其图形渲染较为先进,能够处理复杂的视觉效果。关于水体渲染的主题有很多,如水面网格生成,FFT,着色等。今天想分享的一个主题是海浪效果的初步尝试,主要做法是RT+Displacement,效果如下:

视频见链接:https://zhuanlan.zhihu.com/p/695392779

做法

通过加入Renderpass,近岸处摄像机自上而下地拍保留特有LightMode的物体,目的是为了读取手摆的海浪的高度

需要可能注意的点是:有可能会因为RT分辨率过低或者RT占比过低导致读取到的信息模糊,从而导致将信息应用到水面上产生一些不好的效果,需要根据项目需要而去调整拍RT的位置和RT的分辨率。

摄像机的相关设置代码如下

CameraPosition = camera.transform.position +  Offset;

float frustumHeight = orthoSize * 2f;

CameraPosition += (Vector3.up * frustumHeight * 0.5f);

projection = Matrix4x4.Ortho(-orthoSize, orthoSize, -orthoSize, orthoSize, 0.03f, frustumHeight);

view = Matrix4x4.TRS(CameraPosition, viewRotation, viewScale).inverse;

cmd.SetViewProjectionMatrices(view, projection);
 
viewportRect.width = width;

viewportRect.height = height;

cmd.SetViewport(viewportRect);

RT的相关设置代码如下:

RT的SRGB应设为false,因为高度信息属于线性数据,如果设为srgb,可能会导致我们所想要的结果和实际结果不相符

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
            //***Something Setttigs
            RenderTextureDescriptor des = cameraTextureDescriptor;
            des.width = width;
            des.height = height;
            des.depthBufferBits = 16;
            des.colorFormat = RenderTextureFormat.ARGB64;
            des.graphicsFormat = GraphicsFormat.R16G16B16A16_SFloat;
            des.sRGB = false;
            cmd.GetTemporaryRT(_WaterShoreWaveBufferID,des);
            renderTarget= _WaterShoreWaveBufferID; 
            ConfigureTarget(renderTarget,_WaterShoreWaveBufferID);
            ConfigureClear(ClearFlag.All, clearColor);
            //*** Another Settings
 
}

也可以根据需要,写入多张RT,如法线信息,浮沫信息。下面是一个MRT的示例

des.width = width;
des.height = height;
des.depthBufferBits = 16;
des.colorFormat = RenderTextureFormat.ARGB64;
des.graphicsFormat = GraphicsFormat.R16G16B16A16_SFloat;
des.sRGB = false;
cmd.GetTemporaryRT(_WaterShoreWaveBufferID,des);
des.depthBufferBits = 0;
cmd.GetTemporaryRT(_WaterShoreWaveBufferNormalID, des);
renderTarget[0]= _WaterShoreWaveBufferID;
renderTarget[1] = _WaterShoreWaveBufferNormalID;
ConfigureTarget(renderTarget,_WaterShoreWaveBufferID);

执行绘制RT的逻辑代码

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get();

            DrawingSettings drawingSettings = CreateDrawingSettings(ShaderTag, ref renderingData, SortingCriteria.CommonTransparent);
 

            using (new ProfilingScope(cmd, profilerSampler))
            {
                ref CameraData cameraData = ref renderingData.cameraData;
                context.ExecuteCommandBuffer(cmd);
                cmd.Clear();
                context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref FilteringSettings, ref RenderStateBlock);
                cmd.SetViewProjectionMatrices(cameraData.camera.worldToCameraMatrix, cameraData.camera.projectionMatrix);
 
            }

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
 
        }

带有特定LightMode的shader写入信息如下

float Heightdisplacement  SAMPLE_TEXTURE2D(_HeightdisplacementMap, sampler_HeightdisplacementMap, uv).r
Heightdisplacement = Heightdisplacement*_HeightScale;
float Foam = foamTex.r *saturate(HeightDisplacement-HMin/HMax-Hmin);

一个是高度信息,一个是浮沫信息,浮沫信息可以根据高度做一个Mask来进行控制,理论应该加入更多的控制和信息来做浮沫的动画来表示白沫的产生和消失的演变过程,这是一个可以优化的点。

最后,水面顶点读取高度信息和浮沫信息

float4 ShoreWaveData= SampleShoreWaveData(o.worldPos);
vertex.y += ShoreWaveData.r;
float positionCS = TransformObjectToHClip(vertex.xyz);

有一点注意的是:得根据需求来进行是

positionWS += (positionWS+offset),positionCS = TransformWorldToHClip(positionWS)

还是

 positionOS +=offset,positionCS =TransformObjectToHClip(positionOS.xyz)

前者会因为offset缺乏乘M矩阵,导致同一个Offset值对于不同scale的物体有不同的偏移效果(例如:风力大小一致,对于scale为1的小草来说是正常的,对于scale为0.1的小草可能就“吹倒”了,海浪的offset也是如此)。

泡沫信息也是简单地采样然后应用到最后的着色上

float4 ShoreWaveData = SampleDShoreWaveData(i.worldPos);
float3 foam =  ShoreWaveData .g *step(0,-worldNormal.x)*_foamColor.rgb;
finalColor += foam;

step(0,-worldNormal.x)是加入多一个判定泡沫产生的位置的信息,一般来说泡沫产生的位置都在海浪的背部,但这样的判定导致产生的泡沫的mask太“硬”不够自然,还有一些其他更好的计算方法,这是一个可以优化的点

最后是法线的计算和混合

根据水面y方向上产生的高度RT,可以用ddx,ddy的类似算法求出变化率算出X和Z方向上的法线分量(自上而下拍的RT对应的是X和Z轴),最后通过勾股定理求出y方向的分量

算法线的基本代码代码如下

float xLeft = (SAMPLE_TEXTURE2D(_WaterShoreWaveBuffer, sampler_LinearClamp,float2(uv.xy) - float2(1/_RTTexelSize, 0.0))));
float xLeft = (SAMPLE_TEXTURE2D(_WaterShoreWaveBuffer, sampler_LinearClamp,float2(uv.xy) + float2(1/_RTTexelSize, 0.0)));
float yUp = (SAMPLE_TEXTURE2D(_WaterShoreWaveBuffer, sampler_LinearClamp,float2(uv.xy) - float2( 0.0,1/_RTTexelSize,))));
float yDown = (SAMPLE_TEXTURE2D(_WaterShoreWaveBuffer, sampler_LinearClamp,float2(uv.xy) - float2( 0.0,1/_RTTexelSize,))));
float xDelta = ((xLeft - xRight) + 1.0) * 0.5f;
float yDelta = ((yUp - yDown) + 1.0) * 0.5f;
float4 normals = float4(xDelta, yDelta, 0.0, 0);

海浪和海面法线融合用了RNM Blending进行融合

相关的算法以下文章链接有更好的解释,读者有兴趣可以进行查阅观看https://zhuanlan.zhihu.com/p/364821684

未来可能改进的点:

1.可以考虑用Houdini烘焙序列帧来给海浪做更丰富的造型动画,在Height Displacement的基础上做Vector Displacement,相关实现可以参考如下大佬的实现。

https://zhuanlan.zhihu.com/p/495906664

用RT+Vector Displacement可以有更好的水面网格的通用性。对于某些水体来说,水面网格是程序化实时生成的,如果是用mesh插入到水面的基础网格上,可能适用性并不是太强,但是RT+Vector Displacement对于内存的负担更大。

2.泡沫的细节和产生消失动画需要优化。

3.海浪不仅需要以上表现还需要相对应的粒子特效,如下图

本人尝试过一些水花粒子特效,效果不尽人意,这也是未来优化的一个方向。

欢迎加入我们!

感兴趣的同学可以投递简历至:CYouEngine@cyou-inc.com

#引擎开发工程师##技术美术##搜狐畅游##我的成功项目解析#
全部评论
不是哥们开始讲技术了?
点赞 回复 分享
发布于 07-04 17:16 广东

相关推荐

不愿透露姓名的神秘牛友
11-21 17:16
科大讯飞 算法工程师 28.0k*14.0, 百分之三十是绩效,惯例只发0.9
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
10-12 10:48
已编辑
秋招之苟:邻居家老哥19届双2硕大厂开发offer拿遍了,前几天向他请教秋招,他给我看他当年的简历,0实习实验室项目技术栈跟开发基本不沾边😂,我跟他说这个放在现在中厂简历都过不了
点赞 评论 收藏
分享
头像
11-09 17:30
门头沟学院 Java
TYUT太摆金星:我也是,好几个华为的社招找我了
点赞 评论 收藏
分享
3 2 评论
分享
牛客网
牛客企业服务