实现原理:噪声纹理+透明度测试
在我的理解下就是,存储着随机值的一张纹理图片,因为大量生成随机数非常的耗时而且麻烦,如果用一张图来存储随机值,这样获取起来会非常的简单高效,利用噪声纹理我们可以实现很多特殊的效果,比如消融效果。
消融效果
1、shader文件:首先需要利用噪声纹理编写一个实现随机剔除渲染内容的shader。
主要实现功能:获取到噪声纹理中的随机数,和预设的消融阈值做比较,如果随机值小于阈值表示当前片元将不会渲染,反之该片元就正常渲染。
代码:
(1)各个属性的声明
Properties
{
_MainTex ("Base (RGB)", 2D) = "while" {}
_BurnAmount("Burn Amount",Range(0.0,1.0))=0.0//消融程度,当为0是消融为0,意思是没有消融,如果消融为1,则表示完全消融
_LineWidth("Burn Line Width",Range(0.0,0.2))=0.1//烧焦效果的线宽,也就是烧焦边缘的效果宽度
_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)="while"{}
}
(2)设置总体的Tags,因为渲染的物体不存在透明物体或者是非透明纹理,所以设置如下
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
(3)设置实现消融效果的pass的 Tags以及其他内容
Tags{"LightMode"="ForwardBase"}
//剔除关闭因为消融会看到物体内部,如果开启剔除会导致内部没有渲染到
Cull Off
CGPROGRAM
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
fixed4 _Color;
fixed _BurnAmount;
fixed _LineWidth;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
sampler2D _BurnMap;
float4 _BurnMap_ST;
fixed4 _BurnFirstColor;
fixed4 _BurnSecondColor;
(4)设置两个结构体
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord : TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float2 uvMainTex:TEXCOORD0;
float2 uvBumpTex:TEXCOORD1;
float2 uvBurnTex:TEXCOORD2;
float3 lightDir:TEXCOORD3;
float3 worldPos:texcoord4;
SHADOW_COORDS(5)
};
(5)顶点着色器
v2f vert(a2v v){
v2f o;
o.pos =UnityObjectToClipPos(v.vertex);
o.uvMainTex =TRANSFORM_TEX(v.texcoord,_MainTex);
o.uvBumpTex =TRANSFORM_TEX(v.texcoord,_BumpMap);
o.uvBurnTex =TRANSFORM_TEX(v.texcoord,_BurnMap);
//变换得到切线空间下的光线向量,objspacelightdir是根据坐标得到物体空间下的光线坐标,再对光线坐标进行变换得到切线空间下的光线向量
//获取到变换矩阵rotation,从模型空间转换到切线空间
TANGENT_SPACE_ROTATION;
o.lightDir =mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
//从物体坐标到世界坐标
o.worldPos =mul(unity_ObjectToWorld,v.vertex).xyz;
//计算阴影纹理的采样坐标
TRANSFER_SHADOW(o);
return o;
}
(6)片元着色器
fixed4 frag(v2f i):SV_Target{
fixed3 burn =tex2D(_BurnMap,i.uvBurnTex).rgb;
//根据从噪声纹理的采样结果来和消融程度做比较来剔除一些片元不渲染,cilp函数是对于小于0的片元进行剔除,所以如果采样得到的随机值小于burnAmount则会被剔除,burnAmount设置的越大被剔除的部分就越多,设置的越小被剔除的也就越少
clip(burn.r-_BurnAmount);
//获取到切线空间下的光线向量和法线,用于计算漫反射
float3 tangentLightDir =normalize(i.lightDir);
float3 tangentNormal =UnpackNormal(tex2D(_BumpMap,i.uvBumpTex));
//反射率,从漫反射纹理上采样得到反射率
fixed3 albedo =tex2D(_MainTex,i.uvMainTex).rgb;
//环境光照乘以反射率得到反射光照
fixed3 ambient =UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
//漫反射
fixed3 diffuse =_LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentLightDir));
//根据消融程度得到0到线最宽距离间的差值,再根据该差值计算得到烧焦的颜色
//利用smoothstep根据差值获取到[0,1]的值,差值越小说明越靠近消融边缘,而因为最后混合颜色的需求,需要t值越小,越靠近非消融区域,所以需要用1-。
fixed t =1-smoothstep(0.0,_LineWidth,burn.r-_BurnAmount);
//t值越大表示越靠近消融区域。
fixed3 burnColor =lerp(_BurnFirstColor,_BurnSecondColor,t);
//pow一下更加突出
burnColor =pow(burnColor,5);
UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
//根据差值混合光照颜色和烧焦颜色
//利用step函数,保证当_BurnAmount为0的时候完全渲染图形,当_BurnAmount大于0.0001的时候,混合光照颜色和烧焦的颜色,当t越大越靠近消融区域。
fixed3 finalColor =lerp(ambient+diffuse*atten,burnColor,t*step(0.0001,_BurnAmount));
return fixed4(finalColor,1.0);
}
smoothstep 用来生成0到1的平滑过渡值,它也叫平滑阶梯函数。
float smoothstep(float a, float b, float x)
{x = clamp((x - a) / (b- a), 0.0, 1.0);
return x * x * (3 - 2 * x);
}`
效果是smoothstep(a, b, x)
step 通常用来取代 if-else 的代码
step (a, x)
{
if (x < a)
{
return 0;
}
else
{
return 1;
}
}
也就是step (a, x),如果a>x,则结果为0,反之结果为1
(7)控制阴影效果的Pass
由于被剔除的部分应该是没有阴影的所以,我们需要自定义一个阴影的pass,在阴影投射之前将不需要投射的部分剔除,利用和消融效果相同的思路,利用噪声纹理获取随机值与阙值进行比较。
Pass{
Tags{"LightMode"="ShadowCaster"}
CGPROGRAM
#include "unityCG.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
struct a2v{
float4 vertex:POSITION;
float2 texcoord:TEXCOORD0;
};
struct v2f{
//利用宏定义阴影投射时需要的变量
V2F_SHADOW_CASTER;
float2 uvBurnMap:TEXCOORD1;
};
fixed _BurnAmount;
sampler2D _BurnMap;
float4 _BurnMap_ST;
v2f vert(appdata_base v){
v2f o;
//计算我们投射阴影时候需要的变量
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
o.uvBurnMap =TRANSFORM_TEX(v.vertex,_BurnMap);
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 burn =tex2D(_BurnMap,i.uvBurnMap).rgb;
//将其剔除
clip(burn.r-_BurnAmount);
//完成未被剔除部分的阴影投射
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
2、c#脚本:编写脚本控制消融阙值在[0,1]之间变化,从而实现物体从完整消融到无。
实现思路:将脚本挂载在物体上,获取到物体的材质,随时间更新消融阈值,并且使得消融阈值一直在[0,1]之间。
Mathf.Repeat:
Repeat(float t,float length);
循环值t,使输出不会大于等于length,也不会小于0。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BurnAmountChangeWithTime : MonoBehaviour
{
public Material material;
[Range(0.01f, 1.0f)]
public float burnSpeed = 0.3f;
private float burnamount = 0.0f;
// Start is called before the first frame update
void Start()
{
if (material == null)
{
Renderer renderer = gameObject.GetComponentInChildren<Renderer>();
if (renderer != null)
{
material = renderer.material;
}
}
if (material == null)
{
this.enabled = false;
}
else
{
material.SetFloat("_BurnAmount", 0.0f);
}
}
// Update is called once per frame
void Update()
{
burnamount = Mathf.Repeat(Time.time * burnSpeed, 1.0f);
material.SetFloat("_BurnAmount", burnamount);
}
}
Shader "Custom/Chapter15-Dissolve"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "while" {}
_BurnAmount("Burn Amount",Range(0.0,1.0))=0.0//消融程度,当为0是消融为0,意思是没有消融,如果消融为1,则表示完全消融
_LineWidth("Burn Line Width",Range(0.0,0.2))=0.1//烧焦效果的线宽,也就是烧焦边缘的效果宽度
_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)="while"{}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
//消融pass
Pass{
Tags{"LightMode"="ForwardBase"}
//剔除关闭因为消融会看到物体内部,如果开启剔除会导致内部没有渲染到
Cull Off
CGPROGRAM
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
fixed4 _Color;
fixed _BurnAmount;
fixed _LineWidth;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
sampler2D _BurnMap;
float4 _BurnMap_ST;
fixed4 _BurnFirstColor;
fixed4 _BurnSecondColor;
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord : TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float2 uvMainTex:TEXCOORD0;
float2 uvBumpTex:TEXCOORD1;
float2 uvBurnTex:TEXCOORD2;
float3 lightDir:TEXCOORD3;
float3 worldPos:texcoord4;
SHADOW_COORDS(5)
};
v2f vert(a2v v){
v2f o;
o.pos =UnityObjectToClipPos(v.vertex);
o.uvMainTex =TRANSFORM_TEX(v.texcoord,_MainTex);
o.uvBumpTex =TRANSFORM_TEX(v.texcoord,_BumpMap);
o.uvBurnTex =TRANSFORM_TEX(v.texcoord,_BurnMap);
//变换得到切线空间下的光线向量,objspacelightdir是根据坐标得到物体空间下的光线坐标,再对光线坐标进行变换得到切线空间下的光线向量
TANGENT_SPACE_ROTATION;
o.lightDir =mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
//从物体坐标到世界坐标
o.worldPos =mul(unity_ObjectToWorld,v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 burn =tex2D(_BurnMap,i.uvBurnTex).rgb;
//根据从噪声纹理的采样结果来和消融程度做比较来剔除一些片元不渲染,cilp函数是对于小于0的片元进行剔除,所以如果采样得到的值小于burnAmount则会被剔除,burnAmount设置的越大被剔除的部分就越多,设置的越小被剔除的也就越少
clip(burn.r-_BurnAmount);
float3 tangentLightDir =normalize(i.lightDir);
float3 tangentNormal =UnpackNormal(tex2D(_BumpMap,i.uvBumpTex));
//反射率,从漫反射纹理上采样得到反射率
fixed3 albedo =tex2D(_MainTex,i.uvMainTex).rgb;
//环境光照乘以反射率得到反射光照
fixed3 ambient =UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
//漫反射
fixed3 diffuse =_LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentLightDir));
//根据消融程度得到0到线最宽距离间的差值,再根据该差值计算得到烧焦的颜色
fixed t =smoothstep(0.0,_LineWidth,burn.r-_BurnAmount);
fixed3 burnColor =lerp(_BurnSecondColor,_BurnFirstColor,t);
burnColor =pow(burnColor,5);
UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
//根据差值混合光照颜色和烧焦颜色
fixed3 finalColor =lerp(ambient+diffuse*atten,burnColor,1-t*step(0.0001,_BurnAmount));
return fixed4(finalColor,1.0);
}
ENDCG
}
//被剔除的物体在渲染阴影的pass里面没有被剔除而只是在渲染火焰颜色的时候被剔除了之后没有进行渲染,所以在渲染阴影的时候会给被剔除的片元也渲染阴影,这样就不正确了所以要重新编写阴影的pass
Pass{
Tags{"LightMode"="ShadowCaster"}
CGPROGRAM
#include "unityCG.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
struct a2v{
float4 vertex:POSITION;
float2 texcoord:TEXCOORD0;
};
struct v2f{
V2F_SHADOW_CASTER;
float2 uvBurnMap:TEXCOORD1;
};
fixed _BurnAmount;
sampler2D _BurnMap;
float4 _BurnMap_ST;
v2f vert(appdata_base v){
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
o.uvBurnMap =TRANSFORM_TEX(v.vertex,_BurnMap);
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 burn =tex2D(_BurnMap,i.uvBurnMap).rgb;
clip(burn.r-_BurnAmount);
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
}
FallBack "Diffuse"
}