参考
https://roystan.net/articles/toon-water/
源码
https://github.com/IronWarrior/ToonWaterShader
两张噪声图
挂在相机上,开启深度模式,使shader可以拿到深度图
using UnityEngine;
public class ChangeCameraDepth : MonoBehaviour
{
public DepthTextureMode textureMode;
private void OnValidate()
{
GetComponent<Camera>().depthTextureMode = textureMode;
}
private void Awake()
{
GetComponent<Camera>().depthTextureMode = textureMode;
}
}
水面shader
Shader "Custom/Water"
{
Properties
{
_ShallowColor ("_ShallowColor", Color) = (0.4,0.9,1,1)
_DeepColor ("_DeepColor", Color) = (0,0.2,0.7,1)
_FoamColor("_FoamColr",COLOR) = (1,1,1,1)
//这里用黑白噪声
_NoiseTex ("_NoiseTex", 2D) = "white" {}
//用彩色噪声表示扭曲
_DistortionTex("_DistortionTex",2D) = "white"{}
_FoamMinDistance("FoamMinDistance",float) = 0.02
_FoamMaxDistance("_FoamMaxDistance",float) = 0.05
_SurferNoiseCutoff("_SurferNoiseCutoff",Range(0,1)) = 0.7
_foamSpeed("_foamSpeed",vector) = (0.3,0.3,0,0)
}
SubShader
{
Tags { "Queue" = "Transparent" }
pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _ShallowColor;
float4 _DeepColor;
float4 _FoamColor;
float _FoamMinDistance;
float _FoamMaxDistance;
float _SurferNoiseCutoff;
//水波移动速度,只用xy两个参数
float2 _foamSpeed;
sampler2D _CameraDepthTexture;
sampler2D _CameraNormalsTexture;
sampler2D _NoiseTex;
sampler2D _DistortionTex;
float4 _NoiseTex_ST;
float4 _DistortionTex_ST;
struct appdata
{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
float3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
float4 screenPos:TEXCOORD0;
float2 noiseUV:TEXCOORD1;
float2 distortionUV:TEXCOORD2;
float3 viewNormal:NORMAL;
};
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//齐次坐标下的屏幕坐标值
o.screenPos = ComputeScreenPos(o.pos);
o.viewNormal = COMPUTE_VIEW_NORMAL;
o.noiseUV = TRANSFORM_TEX(v.uv,_NoiseTex);
o.distortionUV = TRANSFORM_TEX(v.uv,_DistortionTex);
return o;
}
float4 frag(v2f i):SV_TARGET0
{
float4 depth = tex2Dproj(_CameraDepthTexture,UNITY_PROJ_COORD( i.screenPos));
//等价于
//depth = tex2D(_CameraDepthTexture,i.screenPos.xy/i.screenPos.w);
float depthliner = LinearEyeDepth( depth.x);
//i.screenPos.zw= i.pos.zw,所以这里还可以替换成i.pos.w ,pos.w就是裁剪空间里与相机的距离(大概是,之后去确认下)
float depthDiff = depthliner-i.screenPos.w;
//这步非常重要,不然把视角拉平之后会出现颜色堆积渐变,也就是远处颜色深近处颜色浅,此外,在linner色彩空间下明显,在gamma空间下不明显,不太明白为啥
float waterDepthDifference = saturate(depthDiff/1);
float3 existingNormal = tex2Dproj(_CameraNormalsTexture,UNITY_PROJ_COORD(i.screenPos));
//相当于拿到屏幕坐标和view坐标下法线的夹角,以此判断物体边缘
float normalDot = saturate(dot(existingNormal,i.viewNormal));
float normalDistance = lerp(_FoamMinDistance,_FoamMaxDistance,normalDot);
float foamDepthDiff = saturate(depthDiff/normalDistance) *_SurferNoiseCutoff;
//水的小扭曲水花
float4 distortionSample = tex2D(_DistortionTex,i.distortionUV);
float2 noiseUV = i.noiseUV;
noiseUV =float2(noiseUV.x+ _Time.y*_foamSpeed.x+distortionSample.r,noiseUV.y+_Time.y*_foamSpeed.y+distortionSample.g);
float surferNoiseSample = tex2D(_NoiseTex,noiseUV).r;
float4 waterColor = lerp(_ShallowColor,_DeepColor,waterDepthDifference);
float4 foamColor = _FoamColor;
foamColor.a = smoothstep( foamDepthDiff-0.1,foamDepthDiff+0.1,surferNoiseSample);
float4 res = lerp(waterColor,foamColor,foamColor.a);
//调下透明度,让水底物体可见,而不是深度图的颜色
res.a = 0.7;
return res;
}
ENDCG
}
}
}
核心就两点,一是对_CameraDepthTexture进行采样,获得水深水浅效果,同时获得物体水下的效果
二是对_CameraNormalsTexture进行采样拿到屏幕深度图,再和水面的view空间下的法线进行点乘,可以获得物体与水面相交的泡沫
SmoothStep用来生成:指定范围内0到1的平滑过渡值smoothstep(a,b,x) x取a-b之间
简化其中的步骤,使
foamColor.a = saturate((0.1-foamDepthDiff)/0.2);
return(0,0,0,0.7-foamDepthDiff);
可以单输出泡沫
shader想弄明白,就一条一条去把每行代码修改的结果输出出来看看,这个折腾了很长时间就是因为数值太小,输出在r通道上看不出来,弄到a通道上就清楚了