消融(dissolve)效果:
原理:噪声纹理+透明度测试
- 1.使用噪声纹理的采样结果和某个阈值比较,小于阈值,使用clip函数把它对应的像素裁剪掉,这对应了图中的烧毁区域。
- 2.烧焦效果则是将两种颜色混合,再用pow函数处理后,与原纹理颜色混合后的结果。
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Chan/Chapter15_Dissolve" {
Properties
{
//控制消融程度
_BurnAmount("Burn Amount",Range(0.0,1.0)) = 0.0
//控制模拟烧焦的线宽,值越大,火焰蔓延的范围越广
_LineWidth("Burn Line Width",Range(0.0,0.2)) = 0.1
_MainTex("Main Tex",2D) = "white"{}
_BumpMap("Normal Map",2D) = "bump"{}
//火线边缘的两种颜色
_BurnFirstColor("Burn First Color",Color) = (1,0,0,1)
_BurnSecondColor("Burn Second Color",Color) = (1,0,0,1)
//噪声纹理
_BurnMap("Burn Map",2D) = "white"{}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass
{
Tags{"LightMode" = "ForwardBase"}
//消融效果会导致裸露模型内部的构造,如果只渲染正面会出现错误 因此关闭面片剔除
Cull Off
CGPROGRAM
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
fixed _BurnAmount;
fixed _LineWidth;
sampler2D _MainTex;
sampler2D _BumpMap;
sampler2D _BurnMap;
float4 _MainTex_ST;
float4 _BumpMap_ST;
float4 _BurnMap_ST;
fixed4 _BurnFirstColor;
fixed4 _BurnSecondColor;
struct a2v
{
float4 vertex:POSITION;
float4 texcoord:TEXCOORD0;
//TANGENT_SPACE_ROTATION 计算需要
float3 normal:NORMAL;
float4 tangent:TANGENT;
};
struct v2f
{
float4 pos:SV_POSITION;
float2 uvMainTex:TEXCOORD0;
float2 uvBumpMap:TEXCOORD1;
float2 uvBurnMap:TEXCOORD2;
float3 lightDir:TEXCOORD3;
float3 worldPos:TEXCOORD4;
SHADOW_COORDS(5)
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//获取三个纹理的纹理(uv)坐标
o.uvMainTex = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uvBumpMap = TRANSFORM_TEX(v.texcoord,_BumpMap);
o.uvBurnMap = TRANSFORM_TEX(v.texcoord,_BurnMap);
// TANGENT_SPACE_ROTATION 宏 相当于嵌入如下两行代码:得到模型空间像切线空间的转置矩阵rotation
// float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
// float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal);
TANGENT_SPACE_ROTATION;
//光源方向从模型空间转化到切线空间
o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
//得到阴影纹理的采样坐标 参见chapter_9
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i):SV_Target
{
fixed3 burn = tex2D(_BurnMap,i.uvBurnMap).rgb;
//burn.r - _BurnAmount < 0 该像素被剔除,不会显示到屏幕上
clip(burn.r - _BurnAmount);
float3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap,i.uvBumpMap));
//采样纹理,得到漫反射纹理的材质的反射率 = albedo
fixed3 albedo = tex2D(_MainTex,i.uvMainTex).rgb;
//得到环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
//切线空间下,Lambert计算漫反射
fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(tangentNormal,tangentLightDir));
fixed t = 1 - smoothstep(0.0,_LineWidth,burn.r - _BurnAmount);
//插值两种烧焦颜色
fixed3 burnColor = lerp(_BurnFirstColor,_BurnSecondColor,t);
//^5 加深颜色?
burnColor = pow(burnColor,5);
UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
//混合正常的光照颜色(环境光+漫反射),和烧焦颜色
//spec = step(阈值,spec);// step函数 spec小于阈值返回0,大于等于返回1 保证阈值小于0.0001时,没有消融效果
fixed3 finalColor = lerp(ambient + diffuse * atten,burnColor,t * step(0.0001,_BurnAmount));
return fixed4(finalColor,1);
}
ENDCG
}
}
FallBack "Diffuse"
}
有透明度操作的物体,阴影需要特殊处理,不然被剔除掉的区域,仍然会向其他物体投射阴影,造成穿帮。因此为了让物体的阴影也配合透明度测试,产生正确的效果。需要自定义投射阴影Pass:
//专门处理阴影
Pass
{
Tags{"LightMode" = "ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
sampler2D _BurnMap;
float4 _BurnMap_ST;
fixed _BurnAmount;
struct v2f
{
V2F_SHADOW_CASTER;
float2 uvBurnMap:TEXCOORD1;
};
v2f vert(appdata_base v)
{
v2f o;
//填充V2F_SHADOW_CASTER里边的变量
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
//得到噪声纹理的uv坐标
o.uvBurnMap = TRANSFORM_TEX(v.texcoord,_BurnMap);
return o;
}
fixed4 frag(v2f i):SV_Target
{
fixed3 burn = tex2D(_BurnMap,i.uvBurnMap);
//根据阈值剔除模型的某些片元
clip(burn.r - _BurnAmount);
//把结果输出到深度图和阴影映射纹理中
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
注解:
采样噪声纹理,r分量跟阈值进行比较。剔除模型的某些片元,然后根据剔除后的模型,生成阴影。
https://blog.csdn.net/qq_19269527/article/details/88774065
水波效果
- 水波法线纹理由一张噪声纹理生成,并且随着时间变化不断平移,模拟波光粼粼的效果。
- 使用菲涅尔系数动态决定,反射和折射混合系数。
Shader "Chan/Chapter15_WaterWave" {
Properties {
//控制水面颜色
_Color("Main Color",Color) = (1,1,1,1)
//水面波纹纹理
_MainTex("Main Tex",2D) = "white"{}
//噪声纹理生成的法线纹理
_WaveMap("Wave Map",2D) = "bump"{}
//模拟反射的立方体纹理
_Cubemap("Environment Cubemap",Cube) = "_Skybox"{}
//控制法线纹理的x,y方向的平移速度
_WaveXSpeed("Wave Horizontal Speed",Range(-0.1,0.1)) = 0.01
_WaveYSpeed("Wave Vertical Speed",Range(-0.1,0.1)) = 0.01
_Distortion("Distortion",Range(0,100)) = 10
}
SubShader {
//"Queue" = "Transparent" 确保水波纹理渲染时候,所有不透明物体已经被渲染
Tags{"Queue" = "Transparent" "RenderType" = "Opaque"}
//抓取屏幕的Pass,纹理存到_RefractionTex
GrabPass{"_RefractionTex"}
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _WaveMap;
float4 _WaveMap_ST;
samplerCUBE _Cubemap;
fixed _WaveXSpeed;
fixed _WaveYSpeed;
float _Distortion;
sampler2D _RefractionTex;
//使用纹素大小,对纹理采样坐标进行偏移时用到
float4 _RefractionTex_TexelSize;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 tangent:TANGENT;
float4 texcoord:TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
float4 srcPos:TEXCOORD0;
float4 uv:TEXCOORD1;
float4 Ttow0:TEXCOORD2;
float4 Ttow1:TEXCOORD3;
float4 Ttow2:TEXCOORD4;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//得到对应被抓取屏幕图像_RefractionTex的采样坐标
o.srcPos = ComputeGrabScreenPos(o.pos);
//得到_MainTex,_WaveMap的纹理坐标
o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord,_WaveMap);
//需要在片元着色器中把法线方向(法线纹理采样得到)从切线空间变换到世界空间下,以便对Cubemap进行采样,因此
//需要计算顶点从切线空间变换到世界空间的变换矩阵,得到切线空间下的三个(切线,副切线,法线)
//然后按列组成一个变换矩阵即可
float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal,worldTangent) * v.tangent.z;
//传递给片元着色器
o.Ttow0 = float4(worldTangent.x,worldBinormal.x,worldNormal.x,worldPos.x);
o.Ttow1 = float4(worldTangent.y,worldBinormal.y,worldNormal.y,worldPos.y);
o.Ttow2 = float4(worldTangent.z,worldBinormal.z,worldNormal.z,worldPos.z);
return o;
}
float4 frag(v2f i):SV_Target
{
float3 worldPos = float3(i.Ttow0.w,i.Ttow1.w,i.Ttow2.w);
//片元对应的视角方向
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float2 speed = _Time.y * float2(_WaveXSpeed,_WaveYSpeed);
//对法线纹理进行偏移
//对法线纹理进行两次采样,模拟两层交叉的水面波动,归一化后得到切线空间下的法线方向
fixed3 bump1 = UnpackNormal(tex2D(_WaveMap,i.uv.zw + speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_WaveMap,i.uv.zw - speed)).rgb;
fixed3 bump = normalize(bump1 + bump2);
//利用切线空间下的法线对抓取的屏幕纹理的uv进行偏移,进行uv动画,得到折射颜色
//因为切线空间下的法线方向可以反映顶点局部空间下的法线方向
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
//模拟屏幕深度值z越大,折射程度越大
i.srcPos.xy = offset * i.srcPos.z + i.srcPos.xy;
//i.srcPos.xy/i.srcPos.w) = 透视除法
//以下一句得到模拟的折射颜色
fixed3 refrCol = tex2D(_RefractionTex,i.srcPos.xy/i.srcPos.w).rgb;
//将法线从切线空间变化到世界空间
bump = normalize(half3(dot(i.Ttow0.xyz,bump),dot(i.Ttow1.xyz,bump),dot(i.Ttow2.xyz,bump)));
//对主纹理也进行uv动画,模拟水波效果
fixed4 texColor = tex2D(_MainTex,i.uv.xy + speed);
fixed3 reflDir = reflect(-viewDir,bump);
//根据反射角度,采样Cube 得到反射颜色
fixed3 reflCol = texCUBE(_Cubemap,reflDir).rgb * texColor.rgb * _Color.rgb;
//菲涅尔系数
fixed fresnel = pow(1-saturate(dot(viewDir,bump)),4);
//根据菲涅尔系数,混合反射颜色和折射颜色 = 最终颜色
fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel);
return fixed4(finalColor,1);
}
ENDCG
}
}
FallBack "Diffuse"
}
注解:
1.把噪声纹理当做法线纹理,并对法线进行偏移,然后用偏移后的切线空间下的法线对抓取的屏幕纹理的uv进行偏移,然后用该坐标对抓取的屏幕纹理进行采样,得到折射后的颜色。
2.然后把法线从切线空间变化到世界空间,利用视角方向和法线方向得到反射方向,然后用反射方向对Cubemap进行采样,得到反射颜色。
3.对主纹理也进行uv动画,模拟水波效果
效果如下:
第一图,只有折射,没有反射。水面成"无色"。第二图,反射周围Cubemap颜色。
全局雾效
基于相同高度的雾,在相同高度雾效是一样的。要想模拟不均匀的雾效,使雾看起来更缥缈,可以通过噪声纹理实现。
实现思路:
随时间变化偏移当前噪声纹理的uv,拿着偏移后uv采样噪声纹理,得到的noise值混入雾效浓度计算中。
Shader "Chan/FogWithNoise" {
Properties
{
_MainTex("Main Tex",2D) = "white"{}
//雾密度
_FogDensity("Fog Density",float) = 1.0
//雾颜色
_FogColor("Fog Color",Color) = (1,1,1,1)
//雾开始高度
_FogStart("Fog Start",float) = 0.0
//雾结束高度
_FogEnd("Fog End",float) = 1.0
//噪声纹理
_NoiseTex("Noise Tex",2D) = "white"{}
//
_FogXSpeed("Fog Horizontal Speed",float) = 0.1
_FogYSpeed("Fog Vertical Speed",float) = 0.1
//控制噪声程度
_NoiseAmount("Noise Amount",float) = 1
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
float4x4 _FrustumCornersRay;
sampler2D _MainTex;
half4 _MainTex_TexelSize;
//深度纹理
sampler2D _CameraDepthTexture;
half _FogDensity;
fixed4 _FogColor;
float _FogStart;
float _FogEnd;
sampler2D _NoiseTex;
half _FogXSpeed;
half _FogYSpeed;
half _NoiseAmount;
struct v2f
{
float4 pos:SV_POSITION;
half2 uv:TEXCOORD0;
half2 uv_depth:TEXCOORD1;
//存储
float4 interpolateRay:TEXCOORD2;
};
v2f vert(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
o.uv_depth = v.texcoord;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
{
o.uv_depth.y = 1 - o.uv_depth.y;
}
#endif
//屏幕后全局所用的模型是一个四边形网格,只包含四个顶点
//判断当前顶点是哪个顶点,此处使用uv纹理坐标判断。左下->右下->右上->左上
int index = 0;
if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5)
{
index = 0;
}
else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5)
{
index = 1;
}
else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5)
{
index = 2;
}
else
{
index = 3;
}
//平台,设置差异 可能导致左下->右下->右上->左上错乱,跟c#脚本对上,差异化处理
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
{
index = 3 - index;
}
#endif
o.interpolateRay = _FrustumCornersRay[index];
return o;
}
fixed4 frag(v2f i) :SV_Target
{
//不同平台要通过uv采样深度纹理得到深度值,可能有差异。使用宏定义 内部处理了。SAMPLE_DEPTH_TEXTURE
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth));
//计算得到某个像素点的世界坐标
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolateRay.xyz;
//随时间变化偏移当前噪声纹理的uv,拿着偏移后uv采样噪声纹理,得到的noise值混入雾效浓度计算中
float2 speed = _Time.y * float2(_FogXSpeed,_FogYSpeed);
float noise = (tex2D(_NoiseTex,i.uv + speed).r - 0.5) * _NoiseAmount;
//根据公式,雾效密度
float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
//
fogDensity = saturate(fogDensity * _FogDensity * (1 + noise));
fixed4 finalColor = tex2D(_MainTex,i.uv);
//原始纹理颜色混合雾效颜色 = 最终颜色
finalColor.rgb = lerp(finalColor.rgb,_FogColor.rgb,fogDensity);
return finalColor;
}
ENDCG
Pass
{
ZTest Always Cull Off
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack Off
}