【Shader案例】大地图迷雾实现思路

目录

仅裁剪版本

最终版扩展闪烁、消融效果


仅裁剪版本

参考:[SLG] 大地图迷雾实现思路 - 哔哩哔哩 

场景布局:地板Scale(40,1,40)  云层Cloud平面Scale(40,1,40) 保持一样大小,将云层平面X和Z值保持与地板一样,Y值拉大,让云层平面正位于地板上空。

新建1个层级:Cloud,云层物体Layer层级设置为Cloud,射线检测只检测这一层的。

其他代码部分相当简单了,就是射线检测拿到uv转局部坐标(x,y)去设置动态生成的遮罩纹理Texture2D(x,y)的像素R通道为0,就达到了裁剪这一个格子效果。

Shader分别用了2个Pass去做,第一个Pass是ShadowCaster(投射阴影)而是这个阴影是被经过Clip裁剪的,一定要开启深度写入(否则不会有阴影)。

第二个Pass则是透明度混合形式裁剪,关闭深度写入的,反正就是直接拿到裁剪图R值作为透明度,其他计算都是为了表现好看点而已。

未实现部分:云层效果、渐变裁剪效果、裁剪提示UI;
疑问:参考文章中的阴影好像并不是我这个样子的,看着不像阴影,只是多了一个深色的云层?

建议不要加阴影,有点怪

Shader "Unlit/FogShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        
        _MaskTex ("裁剪图", 2D) = "white" {}
        
        _MinMaxPowValue("边缘渐变范围(x,y),精度系数(z)", vector) = (1,1,1,1)
        
        _NoiseTex ("扰动边缘图", 2D) = "white" {}
        
        _NoiseOffsetMultValue ("扰动边缘偏移(x,y),扰动幅度(z)", vector) = (0,0,1,1)

        _Cutoff ("Alpha Cutoff", Range(0,1)) = 0.5        
    }
    SubShader
    {        
        Tags{ "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" "DisableBatching" = "True" }  

         Pass{//第一个Pass,让物体可以投射阴影,即把该物体加入到相机的深度纹理的计算中, 并进行裁剪
		    Tags{"LightMode"="ShadowCaster"}
 
            ZWrite On
		    CGPROGRAM
		 
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadercaster
            #include "UnityCG.cginc"
 
            sampler2D _MainTex;
            float4 _MainTex_ST;
		    sampler2D _MaskTex;
	        float4 _MaskTex_ST;
		    fixed _Cutoff;                      
            sampler2D _NoiseTex;
            float4 _MinMaxPowValue;
            float4 _NoiseOffsetMultValue;            
 
	        struct v2f {
	            V2F_SHADOW_CASTER;
	            float2 uv:TEXCOORD0;
	        };
 
            v2f vert(appdata_base v)
            {
	            v2f o;
	            TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
	            o.uv= TRANSFORM_TEX(v.texcoord, _MaskTex);
	            return o;
            }

            //常见算法
            float ClampAndPowValue(float val, float3 minMaxPow){
                float mValue;
                mValue = saturate((val - minMaxPow.x)/(minMaxPow.y - minMaxPow.x)); //边缘数值约束在(Min,Max)范围归一化[0,1] (加快或减缓边缘渐变)
                mValue = saturate(pow(mValue, minMaxPow.z));//提高边缘精度
                return mValue;
            }
 
            float4 frag(v2f i) :SV_Target{
	            //对不透明物体不需要下面两行代码,对使用透明度测试的物体,其完全透明部分不会有阴影,要将其剔除	            
                fixed4 col = tex2D(_MainTex, i.uv);
                float4 noise = tex2D(_NoiseTex, i.uv);
                float gray = noise.r;
                float2 disOffset = float2(gray - _NoiseOffsetMultValue.x, gray - _NoiseOffsetMultValue.y) * _NoiseOffsetMultValue.z * 0.01;
                float4 mask = tex2D(_MaskTex, i.uv + disOffset);
                float r = mask.r;
                //裁剪边缘精度、边缘渐变幅度控制
                r = ClampAndPowValue(r, _MinMaxPowValue.xyz);
                r = col.a * r;
	            clip(r - _Cutoff); 
	            SHADOW_CASTER_FRAGMENT(i)
            }
 
	        ENDCG
        }


        Pass
        {
            Tags
			{
				"LightMode" = "ForwardBase"
			}
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha            
            Cull Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                float3 worldPos : TEXCOORD1;
                SHADOW_COORDS(2)
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;            
            sampler2D _MaskTex;
            sampler2D _NoiseTex;
            float4 _MinMaxPowValue;
            float4 _NoiseOffsetMultValue;            

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                TRANSFER_SHADOW(o)
                return o;
            }
            
            //常见算法
            float ClampAndPowValue(float val, float3 minMaxPow){
                float mValue;
                mValue = saturate((val - minMaxPow.x)/(minMaxPow.y - minMaxPow.x)); //边缘数值约束在(Min,Max)范围归一化[0,1] (加快或减缓边缘渐变)
                mValue = saturate(pow(mValue, minMaxPow.z));//提高边缘精度
                return mValue;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                float4 noise = tex2D(_NoiseTex, i.uv);
                float gray = noise.r;
                float2 disOffset = float2(gray - _NoiseOffsetMultValue.x, gray - _NoiseOffsetMultValue.y) * _NoiseOffsetMultValue.z * 0.01;
                float4 mask = tex2D(_MaskTex, i.uv + disOffset);
                float r = mask.r;
                //裁剪边缘精度、边缘渐变幅度控制
                r = ClampAndPowValue(r, _MinMaxPowValue.xyz);

                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                float a = col.a * r;                
                col = fixed4(col.rgb * atten, a);
                return col;
            }
            ENDCG
        }
    }

    FallBack "Transparent/Cutout/VertexLit" //自带裁剪投影ShadowCaster(保底手段)但我们自己实现了一个ShadowCaster
}

生成遮罩图的脚本,点击Cloud物体(Plane物体)会进行裁剪点击区域四方形格子,透明化。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[ExecuteInEditMode]
public class GenerateMask : MonoBehaviour
{
    public int maskWidth;
    public int maskHeight;
    private Texture2D maskTexture2D;

    private Material material;

    // Start is called before the first frame update
    void Start()
    {
        material = GetComponent<MeshRenderer>().sharedMaterial;

        Texture2D tempMaskTexture2D = new Texture2D(maskWidth, maskHeight, TextureFormat.ARGB32, false);
        for (int y = 0; y < maskHeight; y++)
        {
            for (int x = 0; x < maskWidth; x++)
            {
                tempMaskTexture2D.SetPixel(x, y, new Color(1, 1, 1, 1)); //默认全遮罩R为1                
            }
        }
        tempMaskTexture2D.Apply();
        maskTexture2D = tempMaskTexture2D;
        material.SetTexture("_MaskTex", maskTexture2D);
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 10000, LayerMask.GetMask("Cloud")))
            {
                Vector2 uv = hit.textureCoord;
                Vector2 localPos = new Vector2(uv.x * maskWidth, uv.y * maskHeight);
                //取整拿到x,y
                int x = Mathf.FloorToInt(localPos.x);
                int y = Mathf.FloorToInt(localPos.y);
                Debug.Log("点击到物体:" + hit.collider.gameObject + ",uv:" + uv + ", 位置:" + localPos + $",(x,y):({x},{y})");
                maskTexture2D.SetPixel(x, y, new Color(0, 1, 1, 1));
                maskTexture2D.Apply();
            }
        }
    }
}

投射的阴影距离受这个参数影响

最终版扩展闪烁、消融效果

Shader "Unlit/FogShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        
        _MaskTex ("裁剪图", 2D) = "white" {}
        
        _MinMaxPowValue("边缘渐变范围(x,y),精度系数(z)", vector) = (1,1,1,1)
        
        _NoiseTex ("扰动边缘图", 2D) = "white" {}
        
        _NoiseOffsetMultValue ("扰动边缘偏移(x,y),扰动幅度(z)", vector) = (0,0,1,1)

        _Cutoff ("Alpha Cutoff", Range(0,1)) = 0.5        

        _FlickSpeed ("闪烁速度", float) = 1        
        _FlickIntensity("闪烁幅度", float) = 1
        _FlickLevel("闪烁强度", float) = 1
        _FlickSinMult("闪烁Sin值倍数", float) = 1
        _FlickSinOffset("闪烁Sin值偏移", float) = 0        

        _DissolveTex("消融贴图", 2D) = "white" {}
        _DissolveIntensity("消融幅度", float) = 5
    }
    SubShader
    {        
        Tags{ "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" "DisableBatching" = "True" }  

        Pass{//第一个Pass,让物体可以投射阴影,即把该物体加入到相机的深度纹理的计算中, 并进行裁剪
		    Tags{"LightMode"="ShadowCaster"}
 
            ZWrite On
		    CGPROGRAM
		 
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadercaster
            #include "UnityCG.cginc"
 
            sampler2D _MainTex;
            float4 _MainTex_ST;
		    sampler2D _MaskTex;
	        float4 _MaskTex_ST;
		    fixed _Cutoff;                      
            sampler2D _NoiseTex;
            float4 _MinMaxPowValue;
            float4 _NoiseOffsetMultValue;            
 
	        struct v2f {
	            V2F_SHADOW_CASTER;
	            float2 uv:TEXCOORD0;
	        };
 
            v2f vert(appdata_base v)
            {
	            v2f o;
	            TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
	            o.uv= TRANSFORM_TEX(v.texcoord, _MaskTex);
	            return o;
            }

            //常见算法
            float ClampAndPowValue(float val, float3 minMaxPow){
                float mValue;
                mValue = saturate((val - minMaxPow.x)/(minMaxPow.y - minMaxPow.x)); //边缘数值约束在(Min,Max)范围归一化[0,1] (加快或减缓边缘渐变)
                mValue = saturate(pow(mValue, minMaxPow.z));//提高边缘精度
                return mValue;
            }
 
            float4 frag(v2f i) :SV_Target{
	            //对不透明物体不需要下面两行代码,对使用透明度测试的物体,其完全透明部分不会有阴影,要将其剔除	            
                fixed4 col = tex2D(_MainTex, i.uv);
                float4 noise = tex2D(_NoiseTex, i.uv);
                float gray = noise.r;
                float2 disOffset = float2(gray - _NoiseOffsetMultValue.x, gray - _NoiseOffsetMultValue.y) * _NoiseOffsetMultValue.z * 0.01;
                float4 mask = tex2D(_MaskTex, i.uv + disOffset);
                float r = mask.r;
                //裁剪边缘精度、边缘渐变幅度控制
                r = ClampAndPowValue(r, _MinMaxPowValue.xyz);
                r = col.a * r;
	            clip(r - _Cutoff); 
	            SHADOW_CASTER_FRAGMENT(i)
            }
 
	        ENDCG
        }

        Pass
        {
            Tags
			{
				"LightMode" = "ForwardBase"
			}
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha            
            Cull Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                float3 worldPos : TEXCOORD1;
                SHADOW_COORDS(2)
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;            
            sampler2D _MaskTex;
            sampler2D _NoiseTex;
            float4 _MinMaxPowValue;
            float4 _NoiseOffsetMultValue;       
            float _FlickSpeed;
            float _FlickIntensity;
            float _FlickLevel;
            sampler2D _DissolveTex;
            float _FlickSinMult;
            float _FlickSinOffset;
            float _DissolveIntensity;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                TRANSFER_SHADOW(o)
                return o;
            }
            
            //常见算法
            float ClampAndPowValue(float val, float3 minMaxPow){
                float mValue;
                mValue = saturate((val - minMaxPow.x)/(minMaxPow.y - minMaxPow.x)); //边缘数值约束在(Min,Max)范围归一化[0,1] (加快或减缓边缘渐变)
                mValue = saturate(pow(mValue, minMaxPow.z));//提高边缘精度
                return mValue;
            }

            float DissolveMaskR(float r, float b, float2 dissolveUV){
                float dissolve = tex2D(_DissolveTex, dissolveUV).r;
                float maskR = lerp(r * smoothstep(0, saturate(dissolve * b) + _DissolveIntensity, b), r, b);
                return maskR;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                float4 noise = tex2D(_NoiseTex, i.uv);
                float gray = noise.r;
                float2 disOffset = float2(gray - _NoiseOffsetMultValue.x, gray - _NoiseOffsetMultValue.y) * _NoiseOffsetMultValue.z * 0.01;
                float4 mask = tex2D(_MaskTex, i.uv + disOffset);
                float r = mask.r;
                //裁剪边缘精度、边缘渐变幅度控制
                r = ClampAndPowValue(r, _MinMaxPowValue.xyz);
                //使用B通道进行消融R值
                r = DissolveMaskR(r, mask.b, i.uv);

                //使用G通道进行闪烁效果
                //float flicker = lerp(1, sin(_Time.z * _FlickSpeed) * _FlickSinMult + _FlickSinOffset, _FlickIntensity);//第一种方式
                float flicker = sin(_Time.z * _FlickSpeed) * _FlickSinMult + _FlickSinOffset; //第二种方式比较好控制
                col.rgb += col.rgb * flicker * mask.g * _FlickLevel;

                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                float a = col.a * r;                
                col = fixed4(col.rgb, a);
                return col;
            }
            ENDCG
        }
    }

    FallBack "Transparent/Cutout/VertexLit" //自带裁剪投影ShadowCaster(保底手段)但我们自己实现了一个ShadowCaster
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[ExecuteInEditMode]
public class GenerateMask : MonoBehaviour
{
    public enum MaskState
    {
        None,
        Sharking,
        Dissolving,
    }

    public int maskWidth;
    public int maskHeight;
    private Texture2D maskTexture2D;

    private Material material;

    public float sharkTime = 2;
    public MaskState state = MaskState.None;
    private float timer;
    private int cacheX;
    private int cacheY;

    public float dissolveSpeed = 1;
    public float dissolve;

    // Start is called before the first frame update
    void Start()
    {
        material = GetComponent<MeshRenderer>().sharedMaterial;

        Texture2D tempMaskTexture2D = new Texture2D(maskWidth, maskHeight, TextureFormat.ARGB32, false);
        for (int y = 0; y < maskHeight; y++)
        {
            for (int x = 0; x < maskWidth; x++)
            {
                tempMaskTexture2D.SetPixel(x, y, new Color(1, 0, 1, 1)); //默认全遮罩R为1  G为0(不闪烁) B为1(不消融)
            }
        }
        tempMaskTexture2D.Apply();
        maskTexture2D = tempMaskTexture2D;
        material.SetTexture("_MaskTex", maskTexture2D);
    }

    // Update is called once per frame
    void Update()
    {
        if (state == MaskState.None && Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 10000, LayerMask.GetMask("Cloud")))
            {
                Vector2 uv = hit.textureCoord;
                Vector2 localPos = new Vector2(uv.x * maskWidth, uv.y * maskHeight);
                //取整拿到x,y
                int x = Mathf.FloorToInt(localPos.x);
                int y = Mathf.FloorToInt(localPos.y);

                //开始闪烁
                state = MaskState.Sharking;
                timer = 0;
                cacheX = x;
                cacheY = y;

                Debug.Log("点击到物体:" + hit.collider.gameObject + ",uv:" + uv + ", 位置:" + localPos + $",(x,y):({x},{y})");
                maskTexture2D.SetPixel(x, y, new Color(1, 1, 1, 1));
                maskTexture2D.Apply();

            }
        }
        else if (state == MaskState.Sharking)
        {
            timer += Time.deltaTime;
            if (timer >= sharkTime)
            {
                timer = 0;
                dissolve = 1;

                state = MaskState.Dissolving;
                maskTexture2D.SetPixel(cacheX, cacheY, new Color(1, 0, 1, 1));
                maskTexture2D.Apply();
            }
        }
        else if (state == MaskState.Dissolving)
        {
            if (dissolve == 0)
            {
                state = MaskState.None;
                timer = 0;
                dissolve = 1;
                maskTexture2D.SetPixel(cacheX, cacheY, new Color(0, 0, 1, 1));
                maskTexture2D.Apply();
            }
            else
            {
                dissolve -= dissolveSpeed * Time.deltaTime;
                dissolve = Mathf.Max(0, dissolve);
                maskTexture2D.SetPixel(cacheX, cacheY, new Color(1, 0, dissolve, 1));
                maskTexture2D.Apply();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值