Unity3D 简单水面shader的实现

实现思路

今天来实现一个简单的水面效果。水面的话主要是以下几个效果叠加
1.水面的动态波浪效果,我们这里使用最简单的正弦波加上法线贴图来模拟。如果想要更精确的波动效果可以考虑使用Gerstner波
2.不同水深颜色不同的效果,使用该顶点深度和顶点坐标的差值来计算深度,然后映射到颜色
3.浪涌效果,还是根据顶点深度和顶点坐标的差值来计算深度,然后叠加上一层浪涌
4.折射效果,用grabpass,采样之后进行偏移,最后叠加上去。

代码实现

首先是顶点位移函数,使用正弦函数代入顶点的x坐标和z坐标来算,因为这里写的水面效果是一个平面效果而不是体积水的效果。所以不用计算y轴。_WaveSpeed和_WaveGap 用来控制波浪速度和间隔。最后对每个顶点朝顶点的法线方向进行偏移。

        void vertexDataFunc(inout appdata_full v, out Input o)
        {
            UNITY_INITIALIZE_OUTPUT(Input, o);
            fixed height = sin(_Time.y * _WaveSpeed + v.vertex.z * _WaveGap + v.vertex.x) * _WaveHeight;
            v.vertex.xyz += v.normal * height;
        }

水面深度效果,首先在输入中声明screenPos变量获取范围在[0,w]之间的坐标值,xyz除以w获得[0,1]的uv坐标值。
使用该uv坐标值采样_CameraDepthTexture深度纹理获得该点的深度值,使用LinearEyeDepth转换成线性深度值。接着再使用LinearEyeDepth(IN.screenPos.z)获得顶点的实际深度。LinearEyeDepth其实就是把[0,1]之间的非线性深度值转换为线性深度值。
有一个疑惑点是为什么这两个深度值不同,这是因为透明物体是不会写入到深度纹理里面去的,所以读取某个屏幕坐标的深度值,其实是找到第一个不透明的点获取他的深度值,而如果这个点是透明的,那么深度纹理中读取的就不是他的深度值。但是无论如何直接使用LinearEyeDepth(IN.screenPos.z)获取的就是该顶点的实际线性深度值。
所以这两个深度的差值其实就是透过一个透明点后的第一个非透明点和这个透明点之间距离的差值。

			IN.screenPos.xyz /= IN.screenPos.w;
            //深度纹理中的深度
            float depth1 = LinearEyeDepth(tex2D(_CameraDepthTexture, IN.screenPos.xy).r);
            //当前顶点的深度
            float depth2 = LinearEyeDepth(IN.screenPos.z);
            float distance = abs(depth1 - depth2);
            //深度插值颜色
            fixed4 waterColor = lerp(_EdgeColor, _Color, saturate(distance * _EdgeThreshold));

设置tag ,“RenderType” = "Transparent"是为了不写入深度值,“Queue” = "Transparent"是为了使该shader在所有不透明物体渲染结束之后再渲染,是为了grabpass获取到正确图像,两个Transparent设置的目的是不同的

 Tags
        {
            "RenderType" = "Transparent" "Queue" = "Transparent" 
        }

浪涌效果的话还是根据这个深度的差值来算,判断深度差值小于_FoamThreshold这个阈值的话就采样浪涌贴图叠加上一层效果。

			//form
            float4 foamColor = tex2D(_FoamTex, (IN.uv_FoamTex + fixed2(1, 1) * _Time.x * _FoamSpeed) * 20);
            fixed formDegree = clamp(distance, 0, _FoamThreshold);
            //把0~_FoamThreshold 缩放到0~1
            formDegree = formDegree / _FoamThreshold;
            fixed4 formColor = lerp(foamColor,fixed4(0, 0, 0, 0), formDegree) * _FoamColorScale;

要实现水面波光粼粼的效果还需要法线贴图的帮助。这边采样两次法线贴图,采样的时候分别按时间往不同的方向对uv做偏移,最后叠加两个法线,就会有一种波光粼粼的效果。嗯。。但是我也不知道原理是什么,反正写shader看上去是对的就可以了,看了几个水的shader也都有这样的处理。

 			//法线
            float2 speed = _Time.x * float2(_WaveSpeed, _WaveSpeed) * _NormalSpeed;
            fixed3 bump1 = UnpackNormal(tex2D(_NormalTex, IN.uv_FoamTex.xy + speed)).rgb;
            fixed3 bump2 = UnpackNormal(tex2D(_NormalTex, IN.uv_FoamTex.xy - speed)).rgb;
            fixed3 bump = normalize(bump1 + bump2);
            bump.xy *= _NormalScale;
            bump = normalize(bump);
            o.Normal = bump;

折射的话就是采样之前grabpass声明的_RefractionTex,然后再根据法线来做偏移就可以得到折射效果

 			//折射
            float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
            fixed3 refrCol = tex2D(_RefractionTex, IN.screenPos.xy + offset).rgb;

最后对算出来的各种颜色进行叠加

            o.Albedo = waterColor.rgb * refrCol + formColor.rgb;
            o.Alpha = waterColor.a;

最终效果。
请添加图片描述
gif有点糊
请添加图片描述

参数面板
请添加图片描述

完整代码

Shader "LX/simpleWater"
{
    Properties
    {
        _Color ("Color", Color) = (0,0,1,1)
        _EdgeColor ("EdgeColor", Color) = (0,1,1,1)
        _EdgeThreshold ("EdgeThreshold", float) =0.1
        _WaveHeight ("WaveHeight", float) = 1
        _WaveSpeed ("WaveSpeed", float) = 1
        _WaveGap ("WaveGap", float) = 1
        _FoamTex ("FoamTex", 2D) = "white"
        _FoamThreshold ("FoamThreshold", float) =0.1
        _FoamColorScale ("FoamColorScale", float) =0.2
        _FoamSpeed ("FoamSpeed", float) =0.1
        _NormalTex ("NormalTex", 2D) = "white"
        _NormalScale ("NormalScale", float) = 1
        _NormalSpeed ("NormalSpeed", float) = 1
        _Distortion("Distortion",float) = 1


    }
    SubShader
    {
        Tags
        {
            "RenderType" = "Transparent" "Queue" = "Transparent" "IgnoreProjector" = "True"
        }
        LOD 200

        GrabPass
        {
            "_RefractionTex"
        }
        CGPROGRAM
        #pragma surface surf Standard alpha:fade keepalpha fullforwardshadows vertex:vertexDataFunc
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_FoamTex;
            float4 screenPos;
        };

        sampler2D _CameraDepthTexture;
        fixed4 _Color;
        fixed4 _EdgeColor;
        float _WaveHeight;
        float _WaveSpeed;
        float _WaveGap;
        float _EdgeThreshold;
        float _FoamThreshold;
        sampler2D _FoamTex;
        float _FoamColorScale;
        sampler2D _NormalTex;
        float _NormalScale;
        float _NormalSpeed;
        float _FoamSpeed;
        float _Distortion;

        sampler2D _RefractionTex;
        float4 _RefractionTex_TexelSize;

        void vertexDataFunc(inout appdata_full v, out Input o)
        {
            UNITY_INITIALIZE_OUTPUT(Input, o);
            fixed height = sin(_Time.y * _WaveSpeed + v.vertex.z * _WaveGap + v.vertex.x) * _WaveHeight;
            v.vertex.xyz += v.normal * height;
        }

        void surf(Input IN, inout SurfaceOutputStandard o)
        {
            IN.screenPos.xyz /= IN.screenPos.w;
            //深度纹理中的深度
            float depth1 = LinearEyeDepth(tex2D(_CameraDepthTexture, IN.screenPos.xy).r);
            //当前顶点的深度
            float depth2 = LinearEyeDepth(IN.screenPos.z);
            float distance = abs(depth1 - depth2);
            //深度插值颜色
            fixed4 waterColor = lerp(_EdgeColor, _Color, saturate(distance * _EdgeThreshold));

            //form
            float4 foamColor = tex2D(_FoamTex, (IN.uv_FoamTex + fixed2(1, 1) * _Time.x * _FoamSpeed) * 20);
            fixed formDegree = clamp(distance, 0, _FoamThreshold);
            //把0~_FoamThreshold 缩放到0~1
            formDegree = formDegree / _FoamThreshold;
            fixed4 formColor = lerp(foamColor,fixed4(0, 0, 0, 0), formDegree) * _FoamColorScale;

            //法线
            float2 speed = _Time.x * float2(_WaveSpeed, _WaveSpeed) * _NormalSpeed;
            fixed3 bump1 = UnpackNormal(tex2D(_NormalTex, IN.uv_FoamTex.xy + speed)).rgb;
            fixed3 bump2 = UnpackNormal(tex2D(_NormalTex, IN.uv_FoamTex.xy - speed)).rgb;
            fixed3 bump = normalize(bump1 + bump2);
            bump.xy *= _NormalScale;
            bump = normalize(bump);
            o.Normal = bump;

            //折射
            float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
            fixed3 refrCol = tex2D(_RefractionTex, IN.screenPos.xy + offset).rgb;

            o.Albedo = waterColor.rgb * refrCol + formColor.rgb;
            o.Alpha = waterColor.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

另外代码也传到github仓库里了,大家也可以关注一下哦~
我的github

  • 19
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
实现Unity3D视差Shader的一般步骤如下: 1. 在ShaderLab中创建一个新的shader,并指定渲染队列。 2. 在CG程序中编写实现视差效果的代码。通常,这涉及到计算纹理坐标的偏移量,根据深度信息计算出位移量。最终,将位移量添加到纹理坐标中,使纹理随着深度变化而产生位移。 下面是一个简单的视差Shader代码示例: Shader "Custom/Parallax" { Properties { _MainTex ("Texture", 2D) = "white" {} _BumpMap ("BumpMap", 2D) = "bump" {} _Parallax ("Parallax", Range(0, 1)) = 0.02 } SubShader { Tags {"Queue"="Transparent" "RenderType"="Opaque"} Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; struct v2f { float2 texcoord : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; sampler2D _BumpMap; float _Parallax; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.texcoord = v.texcoord; return o; } fixed4 frag (v2f i) : SV_Target { float2 uv = i.texcoord; float depth = tex2D(_BumpMap, uv).r; float2 offset = normalize(i.vertex.xy / i.vertex.w - i.texcoord) * depth * _Parallax; uv += offset; return tex2D(_MainTex, uv); } ENDCG } } FallBack "Diffuse" } 这个Shader使用了一张主纹理和一张法线贴图,同时增加了一个Parallax参数。在片段着色器中,使用法线贴图中的深度信息计算位移量,并将其添加到纹理坐标中,产生视差效果。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值