unity 2d摄像机类型_Unity法线水,顺便利用CommandBuffer实现廉价的深度和截屏
最近其实做了好多东西,但是实在是忙啊
没有时间归纳和总结,先把最近做的这个东西拿出来和大家分享
后续逐步把所会的东西一点点分享出来
先放一个效果出来:
法线水最终效果https://www.zhihu.com/video/1154034073386377216
法线水其实和顶点偏移+曲面细分的波浪水本质是一样的
只是波浪的呈现方式不同,我们可以通过学习法线水的制作方式掌握大致的架构,
然后慢慢升级更多不同的效果
最后,请大家不要只收藏不点赞...
完整项目的Git链接:
lingzerg/UnityDemogithub.com
我先把各个要做的功能列出来:
反光波浪 - 法线水顾名思义是用法线做的反光
岸边混合 - 就是根据深度利用地面颜色和水体产生混合,这样会有柔和的边缘混合,所以这里需要用到深度和截屏,
我们这里用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;
下面我们先看一张法线图的效果:
左边是着色的, 右边是直接输出法线图的效果,我们想要的表面凹凸已经有了,
但是这样也太直白单调了
所以我们增加2张法线图
得到的结果是这样的:
肯定比上面要好看啦
但是单纯的法线叠加,有个问题,就是在缩放之后,会出现大量的重复感,
例如:
为什么会这样呢?因为我们吧相机拉远之后,tiling的大小并没有变换,
所以我们可以使用插值,根据摄像机距离水面的距离,来缩放uv
结果就可以在远景保持这样的效果:
贴一个完整的代码让大家可以在引擎内看效果,
我加上了详细的注释
之后就不在贴出完整代码了, 完整的代码放到文末
话说知乎的代码编辑器真不好用啊....
法线波浪完整的代码如下:
Shader
计算深度 - 实现岸边颜色混合 和 折射
先看下目前的效果:
没有混合,非常生硬
从这里开始我们就开始逐段写出代码
法线完成后,我们就可以考虑根据深度和截屏的颜色来混合岸边
大家大致看下想法,参数我就不贴出来了
/**根据深度和坐标拿到当前的深度差**/
//屏幕深度
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 具体的造型:
最后我们在颜色混合后根据颜色成熟插值一下岸边颜色:
float3 finalColor = directSpecular + _blendWaterColor;
fixed4 finalRGBA = fixed4(lerp(sceneColor.rgb, finalColor, deepMultiplier), 1);
此时如果你自己凑了几个参数,你应该实现了这样的效果:
泡沫
泡沫这里有一个小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偏移量,拿到最终UVfloat2 _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);//根据距离混合泡沫纹理的几种不同的UVfloat3 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);}
大家可以在最后输出一下泡沫的方法,看下效果
泡沫覆盖了所有的海域,很好,下面我们在海浪上根据深度处理下泡沫的范围就可以做出贴边的海浪了
海浪
海浪我是在网上找一个大佬写的代码,遗憾的时候不太记得是哪里看的了
不过原理也非常简单
用一个纹理作为海浪的范围,然后用一个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; //重点在这里
左边是勾上,右边是不勾,不过我适当调整了泡沫的参数,来调整亮度
这样整个海浪我们就都完成了
CommandBuffer的例子
我之前看一个大佬用CommandBuffer在经典管线下获取深度,来降低DC
BQ哥:unity自定义深度图(drawcall不翻倍使用深度图)zhuanlan.zhihu.com
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
unity 2d摄像机类型_Unity法线水,顺便利用CommandBuffer实现廉价的深度和截屏相关推荐
- unity 2d摄像机类型_Unity透明效果-开启深度写入的半透明效果实现
上一部分中提到了由于关闭深度写入而产生的遮挡问题,本小节将进行一种解决上述问题的实践--开启深度写入的两个Pass的透明效果实现. 基本思想 使用两个Pass进行渲染: 第一个Pass开启深度写入,但 ...
- unity 2d摄像机类型_Unity使用笔记2——功能介绍
记录一下unity的使用技巧. 1.Tag & Layers 选中GameObject后状态栏的第二排有这两种属性可以设置,有一些预设的 tag ,也可以自己加,注意此处的 Layer 并不决 ...
- Unity 2D 摄像机平滑跟随
在相机跟随脚本中加入如下代码: public Transform Player; private Vector3 Pos;void LateUpdate() {Pos = Player.transfo ...
- Unity之2D摄像机跟随
Unity之2D摄像机跟随 在2D游戏制作过程中,摄像机的跟随主角移动是必不可少的.有代码实现,也有插件实现.下面分别讲述两种方法. Unity中提供了专属的摄像机插件进行使用: 官方Cinemach ...
- Unity 3D 摄像机的类型
摄像机的类型 摄像机有两种类型:"Perspective"(放射观察角度)和"Orthographic"(垂直观察角度). 视图修改摄像机类型 在摄像机的Ins ...
- 在Unity中实现基于粒子的水模拟(二:开始着色)
在Unity中实现基于粒子的水模拟(二:开始着色) 文章目录 在Unity中实现基于粒子的水模拟(二:开始着色) 前言 一.生成顶点 二.偏移模拟 1.接收细分着色器输出的顶点 2.根据数据调用对应的 ...
- Unity 之 纹理类型导入设置和压缩格式介绍
Unity 之 纹理类型导入设置和压缩格式介绍 一,纹理相关 1.1 导入设置 1.2 支持格式 二,纹理类型 2.1 纹理类型说明 2.2 纹理尺寸大小 三,所有支持的纹理压缩格式 一,纹理相关 1 ...
- Unity 2D游戏开发教程之游戏中精灵的跳跃状态
Unity 2D游戏开发教程之游戏中精灵的跳跃状态 精灵的跳跃状态 为了让游戏中的精灵有更大的活动范围,上一节为游戏场景添加了多个地面,于是精灵可以从高的地面移动到低的地面处,如图2-14所示.但是却 ...
- ugui unity 图片缩放循环_Unity基础系列(四)——构造分形(递归的实现细节)...
点击蓝字关注我们吧! 目录 1 如何构建分形 2 展示内容 3 构造子节点 4 塑造子节点 5 创建多个子节点 6 更多的子节点,更好的代码 7 爆炸性生长 8 添加颜色 9.随机化Mesh 10 使 ...
最新文章
- 【ACM】杭电OJ 2023
- 微软官方教程教你如何在Windows上做Python开发?
- 100多个Android Demo的整合
- USB hub(221)
- c语言stdio中null的值,C/C++编程笔记:C语言NULL值和数字 0 值区别及NULL详解
- Nginx+Tomcat+Memcached实现tomcat集群和session共享
- Java 中Timer和TimerTask 定时器和定时任务使用的例子
- 轻量级代码生成器-OnlyCoder 第一篇
- Docker 安装 ES 7.7.0 及 Head、Kibana、IK分词器、Logstash、Filebeat 插件
- 物联网-移远M26模块OpenCPU开发第1讲
- python 将列表值赋予函数_python把空列表作为函数默认参数,可是有坑的
- 判断某点在多边形内——方法一
- 自加++(Java版本)
- Nginx编译./configure翻译
- MVC如何分离Controller与View在不同的项目
- 注册OCX控件并在VS2013的mfc程序中使用及常见问题总结
- java 判断文件编码格式(支持zip)
- Freemarker数字格式化总结
- 一篇文让你了解JAVA IO(超详细 基础篇)
- Activity简单几步支持向右滑动返回
热门文章
- 简单的java单位换算_Java实现蓝桥杯单位转换
- VB.NET中IIF和IF使用效率分析
- C++实现简单的五子棋程序
- 定时循环发送TCP消息(例如:控制设备的开关机等场景)—— 定时执行专家
- linux write文件,关于linux:write文件一个字节后何时发起写磁盘IO
- 图像的配准——MATLAB实现
- 古剑奇谭二服务器维护,《古剑奇谭网络版》2月28日更新维护公告
- ElementUI-textarea文本域高度自适应设置的方法
- python快速_Python3快速入门
- 王兴:8年时间,我对商业的思考