Unity Shader - 实现简单水体 - 浅水到深水颜色控制
文章目录
- 制作步骤
- 准备好水体网格
- 扰动水体网格
- 添加水体网格色调,纹理
- 放置海上放哨点(一些随便放的立方体)
- 添加水的深浅透视效果
- 添加水光效
- 重构水顶点法线
- 正交的相机的深度需要注意
- 改进
- Project
- References
简单的模拟水的效果(3A游戏效果请绕开哦)
效果:
制作步骤
- 准备好水体网格(网格脚本生成的,参考:Unity Shader - Noise 噪点图 - 实现简单山脉)
- 扰动水体网格顶点(参考:Unity Shader - 使用Noise噪点图生成简单山脉(使用tex2Dlod控制顶点高度))
- 添加水体网格色调,纹理
- 放置海上放哨点(一些随便放的立方体)
- 添加水的深浅透视效果
- 添加水光效
- 重构水顶点法线(参考:Unity Shader - 简单山脉 - 顶点着色器重构法线)
准备好水体网格
网格脚本生成的,参考:Unity Shader - Noise 噪点图 - 实现简单山脉
扰动水体网格
参考:Unity Shader - 使用Noise噪点图生成简单山脉(使用tex2Dlod控制顶点高度)
我使用了:大波浪与小波浪的叠加
float centerH = sin(_Time.y * _BigWaveLen + v.uv.x + v.uv.y) * _BigWaveAmplitude;centerH += tex2Dlod(_MainTex, float4(v.uv + float2(_Time.x * _SmallWaveSpeedX, _Time.x * _SmallWaveSpeedY), 0, 0)).r * _SmallWaveAmplitude;v.vertex.y = centerH;
添加水体网格色调,纹理
着了个色调,并对uv滚动动画
fixed4 col = tex2D(_MainTex, i.uv + float2(_Time.y * _BigWaveLen * _UVSpeed, 0));col.rgb *= _MainColor.rgb;
放置海上放哨点(一些随便放的立方体)
添加水的深浅透视效果
这个参考了unity内置的shader中,处理软粒子的思路:。
- 获取深度纹理的view space的
buffViewZ
。 - 获取当前片段的view space的
fragViewZ
。 - 将
delta = buffViewZ - fragViewZ
的深度插值,来控制水体的alpha透视,或lerp的颜色过渡。 - 还可以使用我们下面实现代码中的fade来控制浅水到深度的效果,我这里就控制海水原来的颜色(
combined.rgb + _ShallowColor.rgb * (_ShallowColor.a * 2)
)到浅水着色变量的颜色来过渡(combined.rgb
)。
combined.rgb = lerp(combined.rgb + _ShallowColor.rgb * (_ShallowColor.a * 2), combined.rgb, fade);
如果需要你可以创建一个用个纹理:_ShallowToDeepTex
的纹理然后:
var shallowToDeepCol = tex2D(_ShallowToDeepTex, i.uv);
combined.rgb = lerp(combined.rgb + shallowToDeepCol.rgb * (shallowToDeepCol.a * 2), combined.rgb, fade);
下面是参考的:unity内置的shader软粒子代码:
// Soft particles fragment function
#if defined(SOFTPARTICLES_ON) && defined(_FADING_ON)
#define fragSoftParticles(i) \if (SOFT_PARTICLE_NEAR_FADE > 0.0 || SOFT_PARTICLE_INV_FADE_DISTANCE > 0.0) \{ \float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projectedPosition))); \float fade = saturate (SOFT_PARTICLE_INV_FADE_DISTANCE * ((sceneZ - SOFT_PARTICLE_NEAR_FADE) - i.projectedPosition.z)); \ALBEDO_MUL *= fade; \}
#else
#define fragSoftParticles(i)
#endif
参考我手绘的一张图:
下面我们的实现代码
// vert#if DEEP_EFFo.projPos = ComputeScreenPos (o.pos);COMPUTE_EYEDEPTH(o.projPos.z);#endif// frag#if DEEP_EFF // 水深效果// 深度效果// 这里参考unity内置的shader中,实现软例子:Soft particle的写法// 与背景深度远时,alpha比较高,里的近则alha低// 先是获取深度图的view space下的z值: buffViewZfloat buffViewZ = LinearEyeD、epth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));// 再是获取片段的view space的z值:fragViewZfloat fragViewZ = i.projPos.z;// 然后去他们之间的距离值做alpha的控制,与颜色lerp的变化float fade = saturate ((buffViewZ-fragViewZ) * _DeepFactor);combined.rgb = lerp(combined.rgb + _ShallowColor.rgb * (_ShallowColor.a * 2), combined.rgb, fade);combined.a *= fade;#endif
材质中暴露了一个DeepFactor
参数,可以用于控制水深的系数。如下:
也暴露了一个浅水的颜色色调着色:
添加水光效
这个就是传统的经验光照模型:ambient + diffuse(HalfLambert) + specular(blinn phong)
// frag#if LIGHTING_ON // 光照效果// ambientfixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;// diffusehalf3 L = normalize(_WorldSpaceLightPos0.xyz);half3 N = normalize(i.normal);half LdotN = dot(L, N) * 0.5 + 0.5;fixed3 diffuse = col.rgb * LdotN;// specularhalf3 specular = 0;half3 V = normalize(_WorldSpaceCameraPos.xyz - i.wPos);half3 H = normalize(L + V);half HdotN = max(0, dot(H, N)); // blinn-phonespecular = _LightColor0.rgb * pow(HdotN, _SpecularGlossy * 100) * _SpecularIntensity;// combined colorfixed4 combined = fixed4(ambient + diffuse + specular, col.a);#else // LIGHTING_OFFfixed4 combined = col;#endif
重构水顶点法线
参考:Unity Shader - 简单山脉 - 顶点着色器重构法线,这里不说了,参考的文章说得很清楚的。
#if REBUILD_NORMAL // 重构法线,参考:https://blog.csdn.net/linjf520/article/details/104859710// reconstruct normals// 这个4x4数据也可以通过外部传入,可以节省顶点着色器ALU的计算量与L1 caches缓存量。const float4x4 offset_xz = {{+1,+0, /* gap **/ +1,-1}, // 右下{+0,-1, /* gap **/ -1,-1}, // 左下{-1,+0, /* gap **/ -1,+1}, // 左上{+0,+1, /* gap **/ +1,+1} // 右上};// 默认向量也可以外部传入,因为上面的默认法线是可以调整的// 下面我讲默认法线初始化为:upfloat3 sumNormal = float3(0, 1, 0);float4 uv4 = 0;for (int i = 0; i < 4; i++) {// 获取偏移数据float4 uvs_offset = offset_xz[i];// 获取偏移数据分别的高度half h1 = sin(_Time.y * _BigWaveLen + v.uv.x + uvs_offset.x * _MainTex_TexelSize.x + v.uv.y + uvs_offset.y * _MainTex_TexelSize.y) * _BigWaveAmplitude;uv4 = float4(v.uv + uvs_offset.xy * _MainTex_TexelSize.xy + float2(_Time.x * _SmallWaveSpeedX, _Time.x * _SmallWaveSpeedY), 0, 0);h1 += tex2Dlod(_MainTex, uv4).r * _SmallWaveAmplitude;half h2 = sin(_Time.y * _BigWaveLen + v.uv.x + uvs_offset.z * _MainTex_TexelSize.x + v.uv.y + uvs_offset.w * _MainTex_TexelSize.y) * _BigWaveAmplitude;uv4 = float4(v.uv + uvs_offset.zw * _MainTex_TexelSize.xy + float2(_Time.x * _SmallWaveSpeedX, _Time.x * _SmallWaveSpeedY), 0, 0);h2 += tex2Dlod(_MainTex, uv4).r * _SmallWaveAmplitude;// 根据偏移方向,高度重构从当前顶点,指向附近偏移点的高度两个向量float3 dir1 = float3(uvs_offset.x * _GridGap, h1 - centerH, uvs_offset.y * _GridGap);float3 dir2 = float3(uvs_offset.z * _GridGap, h2 - centerH, uvs_offset.w * _GridGap);// 根据平面两个向量(两个向量可以确定一个平面,如同:TBN当中的TB两个切向量)// 叉乘得到平面的法线向量float3 newNormal = (cross(dir1, dir2));// 累加到混合向量中sumNormal += newNormal;}// 均值混合sumNormal /= 5;o.normal = UnityObjectToWorldNormal(sumNormal);#else // REBUILD_NORMAL offo.normal = UnityObjectToWorldNormal(v.normal);#endif
正交的相机的深度需要注意
unity底层在处理:相机的正交与透视投影模式下,分别对_CameraDepthTexture有不一样的编码过程(如果你使用的是_CameraDepthNormalsTexture就没这个问题),具体,可以参考我之前写过的一篇文章:Unity Shader - 获取BuiltIn深度纹理和自定义深度纹理的数据-只看:注意Unity正交相机中的深度纹理的编码 部分就好了。
因为我们使用的是_CameraDepthTexture,所以我们需要处理一下读取深度值的逻辑:
// frag// jave.lin 2020.03.15#if DEEP_EFF || FOAM// unity_OrthoParams变量在:UnityShaderVariables.cginc有定义:// x = orthographic camera's width// y = orthographic camera's height// z = unused// w = 1.0 if camera is ortho, 0.0 if perspective// float4 unity_OrthoParams;float linearEyeDepth = 0;float linear01Depth = 0;// ortho - 处理正交的if (unity_OrthoParams.w == 1) {float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos));#if defined(UNITY_REVERSED_Z) // 正交需要处理这个宏定义,透视不用,估计后面unity版本升级后会处理正交的这个宏定义处理吧depth = 1 - depth;#endif/* Linear01Depth的逆运算// Z buffer to linear 0..1 depthinline float Linear01Depth( float z ){return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);}Linear01Depth = 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);(_ZBufferParams.x * z + _ZBufferParams.y) * Linear01Depth = 1(_ZBufferParams.x * z + _ZBufferParams.y) = 1/Linear01Depth(_ZBufferParams.x * z) = 1/Linear01Depth - _ZBufferParams.yz = (1/Linear01Depth - _ZBufferParams.y) / _ZBufferParams.x Linear01Depth == ndcZz = (1/ndcZ - _ZBufferParams.y) / _ZBufferParams.x */linearEyeDepth = LinearEyeDepth (/*to ndcZ**/(1.0/depth - _ZBufferParams.y) /_ZBufferParams.x);linear01Depth = (depth);} else { // perspective - 处理透视的float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos));linearEyeDepth = LinearEyeDepth (depth);linear01Depth = Linear01Depth(depth);}#endif#if DEEP_EFF // 水深效果// 深度效果// 这里参考unity内置的shader中,实现软例子:Soft particle的写法// 与背景深度远时,alpha比较高,里的近则alha低// 先是获取深度图的view space下的z值: buffViewZ// float buffViewZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));float buffViewZ = linearEyeDepth;// 再是获取片段的view space的z值:fragViewZfloat fragViewZ = i.projPos.z;// 然后去他们之间的距离值做alpha的控制,与颜色lerp的变化float fade = saturate ((buffViewZ-fragViewZ) * _DeepFactor);combined.rgb = lerp(combined.rgb + _ShallowColor.rgb * (_ShallowColor.a * 2), combined.rgb, fade);combined.a *= fade;#endif
注意上面代码中,i.projPos.z
,可以改写为使用i.vertex.w
即可,因为在project matrix中,m[4,3] == -1
,就将model view space下的pos.w=-pos.z
了。
正交下图效果:
改进
- 添加水边Fresnel 反射
- 添加透视
- 添加折射
- 添加水体背光透视
- 水体网格面细分(Tessellation不用CPU端生成的方式)
- 水体顶点分形、DFT快速傅里叶波动
后面有空再处理
Project
backup : UnityShader_SimpleWater_ShallowToDeepEffect_2018.3.0f2
References
- Simple Water Shader in Unity
- 网格脚本生成的,参考:Unity Shader - Noise 噪点图 - 实现简单山脉。
- 扰动水体网格顶点,参考:Unity Shader - 使用Noise噪点图生成简单山脉(使用tex2Dlod控制顶点高度)。
- 重构水顶点法线,参考:Unity Shader - 简单山脉 - 顶点着色器重构法线。
- 【Unity Shader学习笔记】实现反射与折射模拟水面、使用grabPass与环境贴图 - 写完文章后,法线有个博主写得挺好的,推荐阅读。
- Unity法线水,顺便利用CommandBuffer实现廉价的深度和截屏 - 这个后面可以尝试学习。
- Unity海洋shader笔记①
Unity Shader - 实现简单水体 - 浅水到深水颜色控制相关推荐
- 【Unity Shader实例】 水体WaterEffect(二) 用贴图和uv动画模拟水效
Unity Shader实现简单水体效果 效果展示 原理 用贴图和uv动画模拟水效实现"假"水. 设计 找一张水波的贴图,处理它的uv值,让贴图流动起来.这样就用静态纹理和uv动画 ...
- Unity Shader 之 简单 护盾Shield 效果的实现
Unity Shader 之 简单 护盾Shield 效果的实现 目录 Unity Shader 之 简单 护盾Shield 效果的实现 一.简单介绍 二.实现原理
- Unity Shader 之 简单实现物体被压扁(top顶点的逐渐与bottom顶点重合)的效果
Unity Shader 之 简单实现物体被压扁(top顶点的逐渐与bottom顶点重合)的效果 目录 Unity
- Unity Shader 之 简单实现物体被黑洞吸收吞噬(或者从黑洞中出来)的效果
Unity Shader 之 简单实现物体被黑洞吸收吞噬(或者从黑洞中出来)的效果 目录
- Unity Shader 之 简单实现折叠平面(翻书)的效果
Unity Shader 之 简单实现折叠平面(翻书)的效果 目录 Unity Shader 之 简单实现折叠平面(翻书)的效果 一.简单介绍 二.实现原理 三.注意事项 四.效果预览 五.实现步骤 ...
- 【Unity Shader实例】 水体WaterEffect(一) 设计
Unity Shader 水体效果实现的设计 在设计水体效果的实现方案之前,我们先参考一下大神们写好的精彩的例子,比如DCG Water Shader的效果,这也是我们努力的目标. 好!~ 现在开始实 ...
- Unity Shader 实现简单的压扁效果
有点累啊,一个CoverMap搞了一周多,还是太嫩了,还有好多东西等着我去学呢,今天就写个简单的东西吧--一个把模型压扁的效果,参考博客Unity Shader - 一些玩具Shader.话不多说,先 ...
- 【Unity 工具,简单学习】DoTween,脚本控制动画工具
DOTween:脚本控制动画工具 简单介绍 安装 简单使用 eg.1 简单移动,跳动 eg2. 缓动 Easing(动画插值) eg3. 循环 Looping eg4. 变换颜色,变换大小 eg5. ...
- Unity Shader 实现简单的宝石渲染
入职实习的第一周过去了,感觉还是相当不错的,找到工作了也就不用再顶着高压去学习了,快乐. 闲话少说,先上效果图啦. 效果还是很棒的,但这不是我写的,哈哈,这个代码可以完全说就是抄Unity的案例.As ...
最新文章
- 专访云知声黄伟:场景定义芯片,未来所有场景都需要AI | AI名人堂
- mysql数据导库常用操作
- 我的2008年(上)-《走出软件作坊》是怎样炼成的
- 让数据库变快的10个建议
- Java 技术篇-IntelliJ IDEA修改java、jdk版本实例演示
- Fliptile(状压+思维)
- 精通 RPM 之查询篇
- 加拿大计算机专业学什么,加拿大哥伦比亚大学计算机专业课程
- 文件I/O实践(2) --文件stat
- HTML5 Canvas 绘图
- 20145326蔡馨熤《计算机病毒》——代码的动静态分析结合
- 【亲测有效】Ubuntu18.04 sudo apt update无法解析域名的解决方案
- CephOpenstack结合说明操作
- 项目如何开始:怎样和客户谈需求(转)
- python读取文件夹下所有图像_Python 读取指定文件夹下的所有图像方法
- 微型计算机工作适宜的温度,应用电脑(1)第一章 第一节 计算机的基本组成...
- 服装计算机辅助设计论文,服装设计计算机辅助设计应用探索-服装设计论文-设计论文.docx...
- python右对齐输出乘法表_python输出九九乘法表
- 中国大陆手机号码如何注册谷歌账号?完美解决收不到验证码的问题
- 知识图谱与KBQA——槽填充