UnityShader(五)

这次要用表面着色器实现一个水的特效。先翻到最下边看代码,看不懂再看下面的解释。

首先第一步要实现水的深浅判断,实现深水区和浅水区的区分。

这里需要用到深度图的概念。不去说太多概念,只去说怎么实现的,首先我们的水面是在地面上的,我们可以利用深度图,计算得到人眼到地面的距离depth,人眼到水面的距离记作IN.Proj.z

可以看的出来,当我们看深水区的时候,depth-IN.Proj.z的差值会比浅水区大,所以可以通过这个差值的大小去判断深水区和浅水区。这个IN就是我这个水面的其中某一块着色面片表面着色器的输入结构体值。depth可以通过调一系列api得到,里面涉及到很多空间变换,不仔细去讲了,只说大体思路。

然后,在判断完水的深浅以后,我们要通过设置一个深水色和一个浅水色,将我们的depth-IN.Proj.z映射到[0,1]中,这样通过对每个面片设置一个lerp差值,lerp(_WaterShallowColor,_WaterDeepColor, min(_DepthRange, deltaDepth)/_DepthRange),就可以得到整片水域的颜色深浅过度,而这个映射的过程,我们可以通过设置另外一个变量_DepthRange实现。

min(_DepthRange, deltaDepth)/_DepthRange,这个意思就是,当我水很深的时候,min(_DepthRange, deltaDepth)=_DepthRange,整体得1,所以在lerp函数中取的是深水色,当我水很浅的时候,min(_DepthRange, deltaDepth)=deltaDepth,lerp的第三个参数就成了deltaDepth/_DepthRange,因为水很浅,所以这个数接近0,颜色是接近浅水色,如果水的深浅介于深水和浅水之间,那么颜色也介于深水色和潜水色之间。但是这样深水和浅水虽然区分开了,但是都是纯色的,没有立体效果。

第二步通过对法线贴图采样,实现水的立体效果。

下面的代码是说,本来把法线贴图假设是第i,j个面片采样到我当前模型的面片,但是现在不是采样到我当前面片, 而是到了x方向加上_WaterSpeed * _Time.x这个值的面片,先不管_Time.x会动,因为uv的范围都是0-1,所以本质上是在0-1的范围内,采样的数量少了,因为是本来是法线0-1对应模型0-1,现在相当于同样一个点采样的距离大了,假设法线0-1对应模型0-1.4,模型1-1.4的不会显示,所以相当于是原来的0.6倍。

float4 bumpOffset1 = tex2D(_NormalTex, IN.uv_NormalTex + float2(_WaterSpeed * _Time.x, 0));

剩下三个计算也是同理,只不过需要注意,_Time.x是会变的,这样子,本来法线贴图假设是第i,j个面片采样到模型的m,n个面片,一段时间后,就是对应到了模型第m + k,n个面片,所有面片都这样映射,相当于整体是移动了,也就是有了大波浪效果,汹涌澎湃。

float4 bumpOffset2 = tex2D(_NormalTex, float2(1 - IN.uv_NormalTex.y, IN.uv_NormalTex.x) + float2(_WaterSpeed * _Time.x, 0));

float4 offsetColor = (bumpOffset1 + bumpOffset2)/2;

float2 offset = UnpackNormal(offsetColor).xy * _Refract;

float4 bumpColor1 = tex2D(_NormalTex, IN.uv_NormalTex + offset + float2(_WaterSpeed * _Time.x, 0));

float4 bumpColor2 = tex2D(_NormalTex, float2(1 - IN.uv_NormalTex.y, IN.uv_NormalTex.x) + offset + float2(_WaterSpeed * _Time.x, 0));

第三步是要实现波纹,主要对浅水区,波纹的实现是要对WaveTex贴图进行采样。

WaveTex贴图就是一个黑白交替的图,白色表示有波纹,黑色表示没有,但它是0-1,对应模型0-1,但其实我们是需要将一个更大范围的x映射到一个更小范围的WaveTex贴图上的,此时就可以用到GPUGem里讲的正弦波模拟水面波纹了

首先要对波形图和噪声图进行采样,对波形图进行采样两次,是为了产生两道波纹,采样的时候使用噪声图是为了产生随机的效果,对波形图采样要采用上面的正弦波的方式去采,这样就有一个波浪的效果了

fixed4 waveColor = tex2D(_WaveTex,float2(waveA + _WaveRange * sin(_Time.x * _WaveSpeed + noiseColor.r) , 1) + offset);

fixed4 waveColor2 = tex2D(_WaveTex,float2(waveA + _WaveRange * sin(_Time.x * _WaveSpeed + _WaveDelta + noiseColor.r) , 1) + offset);

复制代码
Shader "Custom/MyWater2"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _WaterShallowColor("WaterShallowColor", Color) = (1,1,1,1)
        _WaterDeepColor("WaterDeepColor", Color) = (1,1,1,1)
        _TranAmount("TranAmount", Range(0,100)) = 0.5
        _DepthRange("DepthRange",float) = 1
        _NormalTex("Normal",2D) = "bump"{}
        _WaterSpeed("WaterSpeed",float) = 5
        //控制法线的密集程度
        _Refract("Refract",float) = 0.5
        _Specular("Specular",float) = 1
        _Gloss("Gloss",float) = 0.5
        _SpecularColor("_SpecularColor", Color) = (1,1,1,1)
        //波浪效果需要先对波浪图采样,噪声图是为了产生随机效果
        _WaveTex("WaveTex",2D) = "white"{}
        _NoiseTex("NoiseTex",2D) = "white"{}
        _WaveSpeed("WaveSpeed",float) = 1
        _WaveRange("WaveRange",float) = 0.5
        _WaveRangeA("WaveRangeA",float) = 1
        _WaveDelta("WaveDelta", float) = 0.5
    } 
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent"  }
        LOD 200
        //水是透明的,所以关闭ZWrite
        ZWrite off
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf WaterLight vertex:vert alpha noshadow

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
        //加float是为了增加精度
        sampler2D_float _CameraDepthTexture;
        sampler2D _MainTex;
        fixed4 _WaterShallowColor;
        fixed4 _WaterDeepColor;
        half _TranAmount;
        half _DepthRange;
        sampler2D _NormalTex;
        half _WaterSpeed;
        sampler2D _WaveTex;
        sampler2D _NoiseTex;
        float _WaveSpeed;
        float _WaveRange;
        //这两个控制波浪的范围
        float _WaveRangeA;
        float _WaveDelta;

        struct Input
        {
            float2 uv_MainTex;
            float4 proj;
            float2 uv_NormalTex;
            float2 uv_WaveTex;
            float2 uv_NoiseTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        half _Refract;
        half _Specular;
        half _Gloss;
        fixed4 _SpecularColor;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        fixed4 LightingWaterLight(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
        {
            float diffuseFactor = max(0,dot(normalize(lightDir),s.Normal));
            half3 halfDir = normalize(lightDir + viewDir);
            float nh = max(0,dot(halfDir,s.Normal));
            float spec = pow(nh, s.Specular * 128) * s.Gloss;
            fixed4 c;
            c.rgb = (s.Albedo * _LightColor0.rgb * diffuseFactor + _SpecularColor.rgb * spec * _LightColor0.rgb) * atten;
            c.a = s.Alpha + spec * _SpecularColor.a;
            return c;
        }

        void vert(inout appdata_full v,out Input i)
        {
            UNITY_INITIALIZE_OUTPUT(Input, i);
            i.proj = ComputeScreenPos(UnityObjectToClipPos(v.vertex));
            COMPUTE_EYEDEPTH(i.proj.z);
        }

        void surf (Input IN, inout SurfaceOutput o)
        {
        //关键点是深度图判断深水和浅水,法线移动实现波浪效果
            //tex2Dproj(_CameraDepthTexture, IN.proj)=tex2D(_CameraDepthTexture, IN.proj.xy/IN.proj.w)
            //对深度图采样,采样屏幕空间
            //它的uv表示当前模型在顶点着色器画到屏幕后的坐标
            //采样后的每个变量都是一样的,取r即可
            //越近深度图上对应的区域越红(深度值接近1),越远则越黑(深度值接近0)。也就是说深度值从0到1代表的是从远到近。
            half depth = LinearEyeDepth(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(IN.proj)).r);
            //depth 就相当于是这样计算出来的
            //SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(IN.proj));
            //UNITY_PROJ_COORD(IN.proj)是返回的采样后的纹理坐标,大多数平台就是返回的IN.proj,所以写不写区别不大
            //摄像机深度贴图的深度值减去地面模型面片的深度值
            half deltaDepth = depth - IN.proj.z;
            
            fixed4 c = lerp(_WaterShallowColor,_WaterDeepColor, min(_DepthRange, deltaDepth)/_DepthRange);



            //对法线贴图采样
            float4 bumpOffset1 = tex2D(_NormalTex, IN.uv_NormalTex + float2(_WaterSpeed * _Time.x, 0));
            float4 bumpOffset2 = tex2D(_NormalTex, float2(1 - IN.uv_NormalTex.y, IN.uv_NormalTex.x) + float2(_WaterSpeed * _Time.x, 0));
            float4 offsetColor = (bumpOffset1 + bumpOffset2)/2;
            float2 offset = UnpackNormal(offsetColor).xy * _Refract;
            float4 bumpColor1 = tex2D(_NormalTex, IN.uv_NormalTex + offset + float2(_WaterSpeed * _Time.x, 0));
            float4 bumpColor2 = tex2D(_NormalTex, float2(1 - IN.uv_NormalTex.y, IN.uv_NormalTex.x) + offset + float2(_WaterSpeed * _Time.x, 0));
            
            //波浪 跟水的深浅相反即可 min(_DepthRange, deltaDepth)/_DepthRange接近1的地方,应该无波浪
            //因为波浪要出现在水的边缘,min(_DepthRange, deltaDepth)/_DepthRange接近1的地方 表示deltadepth很大,水很深
            half waveA = 1 - min(_WaveRangeA,deltaDepth)/_WaveRangeA;
            fixed4 noiseColor = tex2D(_NoiseTex,IN.uv_NoiseTex);
            //sin函数来对波纹图采样,产生流动效果
            fixed4 waveColor = tex2D(_WaveTex,float2(waveA + _WaveRange * sin(_Time.x * _WaveSpeed + noiseColor.r) , 1) + offset);
            fixed4 waveColor2 = tex2D(_WaveTex,float2(waveA + _WaveRange * sin(_Time.x * _WaveSpeed + _WaveDelta + noiseColor.r) , 1) + offset);
            o.Albedo = c.rgb + (waveColor.rgb + waveColor2.rgb) * waveA;
            o.Normal = UnpackNormal((bumpColor1 + bumpColor2)/2).xyz;
            o.Gloss = _Gloss;
            o.Specular = _Specular;


            o.Alpha = min(_TranAmount,deltaDepth) / _TranAmount;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

法线贴图,噪声图,波纹图网盘链接

链接:https://pan.baidu.com/s/1Qdm3ly2YW9vq9lNqYvZnbA?pwd=jk3l

提取码:jk3l

相关推荐
mxwin6 天前
Unity Shader 逐像素光照 vs 逐顶点光照性能与画质的权衡策略
unity·游戏引擎·shader·着色器
mxwin7 天前
Unity URP 全局光照 (GI) 完全指南 Lightmap 采样与实时 GI(光照探针、反射探针)的 Shader 集成
unity·游戏引擎·shader·着色器
mxwin7 天前
Unity URP 溶解效果基于噪声纹理与 clip 函数实现物体渐隐渐显
unity·游戏引擎·shader
mxwin7 天前
Unity Shader 顶点色:利用模型顶点颜色传递渲染数据
unity·游戏引擎·shader
mxwin7 天前
Unity URP 下的 GPU Instancing减少 DrawCall 的关键技术
unity·游戏引擎·shader
mxwin7 天前
Unity URP SRP Batcher 完全指南 URP/HDRP 下的核心批处理机制,大幅降低 CPU 开销
unity·游戏引擎·shader·单一职责原则
mxwin8 天前
Unity Shader UV 坐标与纹理平铺Tiling & Offset 深度解析
unity·游戏引擎·shader·uv
mxwin9 天前
Unity Shader Blinn-Phong vs PBR传统经验模型与现代物理基础渲染
unity·游戏引擎·shader
mxwin10 天前
Unity URP 阴影映射 深度纹理、阴影采样与分辨率控制的深度解析
unity·游戏引擎·shader·着色器
mxwin11 天前
Unity Shader 顶点动画:在顶点着色器中实现风吹草动、河流波动、布料模拟
unity·游戏引擎·shader·着色器