![b40659a8e65c62d72d3e7d737bdfff2a.png](https://i-blog.csdnimg.cn/blog_migrate/e2c47ac47e935b6bdb8fc8be98651ae8.jpeg)
最近其实做了好多东西,但是实在是忙啊
没有时间归纳和总结,先把最近做的这个东西拿出来和大家分享
后续逐步把所会的东西一点点分享出来
先放一个效果出来:
![85af44302def9017e9b40aaff8131f0f.png](https://i-blog.csdnimg.cn/blog_migrate/128310ed6e4679c3f20ebebd7644bf86.jpeg)
法线水其实和顶点偏移+曲面细分的波浪水本质是一样的
只是波浪的呈现方式不同,我们可以通过学习法线水的制作方式掌握大致的架构,
然后慢慢升级更多不同的效果
最后,请大家不要只收藏不点赞...
完整项目的Git链接:
lingzerg/UnityDemogithub.com![01514376e38474a06b92c2d93d781084.png](https://i-blog.csdnimg.cn/blog_migrate/c5422850f2bbe870c5ff3ff9bb8e4381.jpeg)
我先把各个要做的功能列出来:
反光波浪 - 法线水顾名思义是用法线做的反光
岸边混合 - 就是根据深度利用地面颜色和水体产生混合,这样会有柔和的边缘混合,所以这里需要用到深度和截屏,
我们这里用Command buffer实现从相机中直接抽取颜色和深度缓冲
岸边波浪 - 这里也是根据深度,叠加一个或者多个波形,产生不断有海水拍打岸边的效果
泡沫 - 但是在某些情况下,是没有波浪的,比如内陆河,那么可能岸边带一点泡沫就够了,所以我们也要支持泡沫
水下折射 - 这个就要利用UV扭曲截屏的图像
基于Command buffer,从相机内抽取深度和颜色 - 大思路是绑定两个Command Buffer到相机上,
设置为全局纹理,是改自一个大哥写的文章,最后我会放出全部代码, 这个方法有一点小问题,
就是在物体比较少的时候,他本身的消耗就比直接取深度大,
因为unity经典管线深度图的获取成本实际上和场景的复杂度有直接关系, 所以也要酌情使用
一些小Trick - 中间会讲一些小Trick,
例如3张跨度比较大的纹理,利用UV缩放让不同距离的过渡更平滑,
一张纹理旋转UV多次采样当多张纹理使用(泡沫),利用插值实现布尔类型的开关
还有一些关于shader格式化的例子
还有哪些可以做的? -
我们这里可以重点说下波浪
法线水其实已经具备了所有的功能
如果我们想要效果更好的水,只需要在这个基础上修改即可,实际上我后面也试了下顶点偏移+曲面细分水,
之后有空在写一篇专门讲高级波浪的文章
顶点偏移+曲面细分水可以尝试用 Gwave波 或者FFT 实现
你只需要去掉法线的部分,改成顶点偏移就好了
而手机上,你根据深度加入一点点顶点偏移,不要曲面细分也是完全的可以的
这样可以做类似"权利游戏"海边波浪起伏的效果
碧蓝航线里那种海面的起伏也差不多可以这样做
而更高级的做法,我觉得最好的是Wave particles, 可以看这篇PPT:
http://advances.realtimerendering.com/s2016/Rendering%20rapids%20in%20Uncharted%204.pptxadvances.realtimerendering.com波浪
法线水嘛,波浪肯定是法线做的,
之后着色因为仅仅是个平面, 所以可以直接用高光+水体颜色
高光可以用Phong实现, 你当然可以用你喜欢的方式实现
但是这里有2个小Trick
首先是缩放,我们用水体的时候,经常会缩放,但是我们并不喜欢水体上的纹理因为缩放拉伸
这个同样作用于制作各种全屏效果,例如迷雾
那我们就可以用这个代码乘以UV来保持缩放比例:
//通过世界到模型空间转换矩阵,拿到基向量的变化,进而得知缩放信息
//假如平面被缩放,我们要保证波浪不被跟着拉变形,所以需要这个信息
float3 recipObjScale = float3(length(unity_WorldToObject[0].xyz), length(unity_WorldToObject[1].xyz), length(unity_WorldToObject[2].xyz));
float3 objScale = 1.0 / recipObjScale;
下面我们先看一张法线图的效果:
![b107d239de392144fa39b4ae96e9f4d2.png](https://i-blog.csdnimg.cn/blog_migrate/f805177c6205520e75f3e910d324febc.jpeg)
左边是着色的, 右边是直接输出法线图的效果,我们想要的表面凹凸已经有了,
但是这样也太直白单调了
所以我们增加2张法线图
得到的结果是这样的:
![d27b5c05b069a4f07d1ed1906f4e5462.png](https://i-blog.csdnimg.cn/blog_migrate/1bbd6b82753b90f09f3bec95c3bf0e2b.jpeg)
肯定比上面要好看啦
但是单纯的法线叠加,有个问题,就是在缩放之后,会出现大量的重复感,
例如:
![5fc33c1b1eb291ac52b8853afce7f772.png](https://i-blog.csdnimg.cn/blog_migrate/aa2b7e2decc3904c31a84ac408e77297.jpeg)
为什么会这样呢?因为我们吧相机拉远之后,tiling的大小并没有变换,
所以我们可以使用插值,根据摄像机距离水面的距离,来缩放uv
结果就可以在远景保持这样的效果:
![b197c6064920b12aefe444cb77f8aa1f.png](https://i-blog.csdnimg.cn/blog_migrate/e981f68938069359572dccc24c67a265.jpeg)
贴一个完整的代码让大家可以在引擎内看效果,
我加上了详细的注释
之后就不在贴出完整代码了, 完整的代码放到文末
话说知乎的代码编辑器真不好用啊....
法线波浪完整的代码如下:
Shader
计算深度 - 实现岸边颜色混合 和 折射
先看下目前的效果:
![539d1d1a5cb4c8d5b5360a6a9dd15bb5.png](https://i-blog.csdnimg.cn/blog_migrate/77e3af014b5f516402d10e47c6f6cf4c.jpeg)
没有混合,非常生硬
从这里开始我们就开始逐段写出代码
法线完成后,我们就可以考虑根据深度和截屏的颜色来混合岸边
大家大致看下想法,参数我就不贴出来了
/**根据深度和坐标拿到当前的深度差**/
//屏幕深度
float sceneZ = max(0,
LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)))) - _ProjectionParams.g);
float partZ = max(0, i.projPos.z - _ProjectionParams.g);
//算出深度差, 这个就可以做很多事,比如岸边,根据深度颜色变化
float depthGap = sceneZ - partZ;
//深度乘数,用深度差和预设参数运算出一个乘数,方便下面的运算
float deepMultiplier = pow(saturate(depthGap / _DepthTransparency), _ShoreFade)*saturate(depthGap / _ShoreTransparency);
然后是折射:
//利用偏移抓取屏幕颜色的截图UV, 实现类似折射的效果
float2 sceneUVs = float2(1, grabSign) * i.screenPos.xy * 0.5 + 0.5
+ lerp(
((normalWaveLocal.rg*(_MediumWaveRefraction*0.02))*deepMultiplier),
float2(0, 0), saturate(pow((distance(i.posWorld.rgb, _WorldSpaceCameraPos) / _RefractionDistance),
_RefractionFalloff)));
//截屏用在这里
float4 sceneColor = tex2D(_GrabTexture, sceneUVs);
然后根据上面深度的内容,我们混合一个岸边的颜色出来:
//根据深度混合Deep color 和 water color
float3 _blendWaterColor = saturate(
_DeepWaterColor.rgb + sceneColor.rgb * saturate(_Fade - depthGap) * _WaterColor.rgb
);
你也可以分辨输出一下看看_blendWaterColor 具体的造型:
![786978610059fab001b9fa885f173276.png](https://i-blog.csdnimg.cn/blog_migrate/259bd6186f8511f4e31cdfa7e3367da5.jpeg)
最后我们在颜色混合后根据颜色成熟插值一下岸边颜色:
float3 finalColor = directSpecular + _blendWaterColor;
fixed4 finalRGBA = fixed4(lerp(sceneColor.rgb, finalColor, deepMultiplier), 1);
此时如果你自己凑了几个参数,你应该实现了这样的效果:
![44072718c86ae83fcdcf4f47c92e7453.png](https://i-blog.csdnimg.cn/blog_migrate/8badc4eea71100b01b80ce382a118822.jpeg)
泡沫
泡沫这里有一个小track,就是用一个旋转矩阵,旋转UV可以做出多层叠加一起的效果
和法线贴图一样,我们这里重复采样增加丰富度
然后泡沫的方法如下:
float3 getFoam(Interpolators i, float3 _blendWaterColor,float3 objScale, float depthGap, float3 normalWaveLocal) {
//根据波浪法线和屏幕坐标采样, *0.5+0.5是为了归一化
float2 _remap = (i.screenPos.rg + normalWaveLocal.rg)*0.5 + 0.5;
float4 _ReflectionTex_var = tex2D(_ReflectionTex, TRANSFORM_TEX(_remap, _ReflectionTex));
float _rotator_ang = 1.5708;
float _rotator_spd = 1.0;
float _rotator_cos = cos(_rotator_spd*_rotator_ang);
float _rotator_sin = sin(_rotator_spd*_rotator_ang);
float2 _rotator_piv = float2(0.5, 0.5);
//旋转UV, uv * 2D旋转矩阵
float2 _rotator =
(mul(i.uv - _rotator_piv, float2x2(_rotator_cos, -_rotator_sin, _rotator_sin, _rotator_cos)) + _rotator_piv);
//泡沫贴图的Tiling和物体缩放相乘拿到UV缩放比例
float2 _FoamDivision = objScale.rb*_FoamTiling;
//UV根据时间偏移具体量
float3 _foamUVSpeed = (float3(_FoamSpeed / _FoamDivision, 0.0)*(_Time.r / 100.0));
旋转后的UV + uv偏移量,拿到最终UV
float2 _FoamAdd = (_rotator + _foamUVSpeed);
UV * 缩放乘数
float2 _foamUV = (_FoamAdd*_FoamDivision);
float4 _foamTex1 = tex2D(_FoamTexture, _foamUV);
float2 _FoamAdd2 = (i.uv + _foamUVSpeed);
float2 _foamUV2 = (_FoamAdd2*_FoamDivision);
float4 _foamTex2 = tex2D(_FoamTexture, _foamUV2);
float2 _foamUV3 = (_FoamAdd*objScale.rb*_FoamTiling / 3.0);
float4 _foamTex3 = tex2D(_FoamTexture, _foamUV3);
float2 maxUV = (_FoamAdd2*_foamUV3);
float4 _foamTex4 = tex2D(_FoamTexture, maxUV);
//根据距离混合泡沫纹理的几种不同的UV
float3 blendFoamRGB = lerp((_foamTex1.rgb - _foamTex2.rgb), (_foamTex3.rgb - _foamTex4.rgb),
saturate(pow((distance(i.posWorld.rgb, _WorldSpaceCameraPos) / 20), 3)));
//去色
float3 foamRGBGray = (dot(blendFoamRGB, float3(0.3, 0.59, 0.11)) - _FoamContrast) / (1.0 - 2 * _FoamContrast);
//根据深度混合颜色
float3 foamRGB = foamRGBGray * _FoamColor.rgb *_FoamIntensity;
float3 sqrtFoamRGB = (foamRGB*foamRGB);
return lerp(_blendWaterColor, sqrtFoamRGB, _FoamVisibility);
}
大家可以在最后输出一下泡沫的方法,看下效果
泡沫覆盖了所有的海域,很好,下面我们在海浪上根据深度处理下泡沫的范围就可以做出贴边的海浪了
![1454c80f25df9cdbd2b5365e09e3b521.png](https://i-blog.csdnimg.cn/blog_migrate/dd317aa6172ef5bf64367bddda607c06.jpeg)
海浪
海浪我是在网上找一个大佬写的代码,遗憾的时候不太记得是哪里看的了
不过原理也非常简单
用一个纹理作为海浪的范围,然后用一个sin函数不断循环,依旧是两次采样做出叠加的海浪
然后根据深度控制海浪范围海浪
代码如下:
//岸边拍打的海浪相关业务
float3 getSurge(Interpolators i,float3 objScale, float depthGap)
{
//缩放UV
float2 surgeUVScale = objScale.rb / 200;
//噪波图
fixed4 noiseColor = tex2D(_NoiseTex, i.uv*objScale.rb / 5);
//第一个海浪
fixed4 surgeColor = tex2D(_SurgeTex, float2(1 - min(_Range, depthGap) / _Range + _SurgeRange * sin(_Time.x*_SurgeSpeed + noiseColor.r*_NoiseRange), 1)*surgeUVScale);
surgeColor.rgb *= (1 - (sin(_Time.x*_SurgeSpeed + noiseColor.r*_NoiseRange) + 1) / 2)*noiseColor.r;
//第二个海浪
fixed4 surgeColor2 = tex2D(_SurgeTex, float2(1 - min(_Range, depthGap) / _Range + _SurgeRange * sin(_Time.x*_SurgeSpeed + _SurgeDelta + noiseColor.r*_NoiseRange) + 0.5, 1)*surgeUVScale);
surgeColor2.rgb *= (1 - (sin(_Time.x*_SurgeSpeed + _SurgeDelta + noiseColor.r*_NoiseRange) + 1) / 2)*noiseColor.r;
//根据深度控制海浪范围
half surgeWave= 1 - min(_Range, depthGap) / _Range;
return (surgeColor.rgb + surgeColor2.rgb * _SurgeColor) * surgeWave;
}
但是这里还没完, 海浪有两种情况
一种是海边,那就是正常的海浪
但是还有一种是在河流中,那这时候我们只要根据深度输出泡沫而不要海浪
于是我们做一个单选框:
[Toggle]
_SurgeType("SurgeType",Float) = 1
在最后颜色混合的时候,根据插值混合下深度或者直接乘波浪
float3 finalColor = directSpecular + _blendWaterColor+
lerp(1-(saturate(depthGap / _FoamBlend)), surgeFinalColor, _SurgeType)* foamColor; //重点在这里
左边是勾上,右边是不勾,不过我适当调整了泡沫的参数,来调整亮度
![3ada4346318b69d6a99d8762630b6773.png](https://i-blog.csdnimg.cn/blog_migrate/cf9906caae2128eaa2a2b06f84a9837d.jpeg)
这样整个海浪我们就都完成了
CommandBuffer的例子
我之前看一个大佬用CommandBuffer在经典管线下获取深度,来降低DC
BQ哥:unity自定义深度图(drawcall不翻倍使用深度图)zhuanlan.zhihu.com![47475ccb005248575a1a187f4f41912e.png](https://i-blog.csdnimg.cn/blog_migrate/4edf904734f4b5cd633f2ca6c3f0938f.jpeg)
Unity本身的深度获取方式太2了,当然你要用SRP就省事了,利用MRT就可以
我转念一想...干嘛不把颜色一起拿出来,于是我稍微改了一下,
也象征性的可以支持Post也求教诸位大佬有没有更好的方案
请大家无视Bloom的逗比写法, 比如最后那个:Graphics.Blit(dest,src);
我只是想试试post能不能顺利工作
如果你要应用这个,请把这个脚本挂到相机上
并且注释掉 GrabPass{ "_GrabTexture" }
同时替换水体shader里最上面两个纹理
打开 注释并且替换正文里的纹理引用
//自定义全局纹理, 深度和屏幕颜色
uniform sampler2D_float _LastDepthTexture;
uniform sampler2D_float _SceneColorTexture;
//uniform sampler2D _GrabTexture;
//uniform sampler2D_float _CameraDepthTexture;
......
float sceneZ = max(0, LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_LastDepthTexture, UNITY_PROJ_COORD(i.projPos)))) - _ProjectionParams.g);
......
//截屏用在这里
float4 sceneColor = tex2D(_SceneColorTexture, sceneUVs);
C#代码也可以去Git链接里看
完整代码和资源
如果你想支持延迟渲染,只要最后改一下各个参数的混合模式就行了
还有比如把float 改成fixed这种事,请大家根据喜好酌情修改吧
lingzerg/UnityDemogithub.com![01514376e38474a06b92c2d93d781084.png](https://i-blog.csdnimg.cn/blog_migrate/c5422850f2bbe870c5ff3ff9bb8e4381.jpeg)