目录
仅裁剪版本
场景布局:地板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();
}
}
}
}