第三人称游戏,我们经常会遇到相机被场景中的建筑物遮挡的情况。今天,本人就来研究一下相机被遮挡之后的处理。最简单的就是传说中的“鸵鸟法”,假装看不见,在一些游戏里面也有一些玩法设定,或者是本身遮挡较少,影响不大的情况,也可以直接不进行处理。
当然,更好一些的遮挡处理,就是X光的效果。在人物被遮挡的部分会透过遮挡物,用一个其他的颜色渲染出来。《火炬之光》中就使用过这个效果:
类似的,《耻辱2》中的透视效果也是游戏中经常使用的,这种暂且叫其遮挡高亮或者遮挡描边吧,对于刺杀类型的游戏,这种透视技能简直是神技,比如在柱子后面就能瞄见这货:
还有一种对于遮挡的处理,就是遮挡半透,这个在很多游戏里面都有出现,比如《黑魂2》,《奥瑞与黑暗森林》,下面是Ori中的一个遮挡半透的效果动图:
还有一个效果,暂且叫其遮挡溶解吧。这个效果我是在《神界3:原罪》中看到的,说实话,第一次看到这个效果的时候,着实被惊艳到了。
翻箱倒柜找出来这几个游戏,截了一发图,顺道怀念一下。哎呀,一不小心就给自己挖了个超级大的坑。四个效果,下面开始慢慢填坑吧。
//X光效果
//by:puppet_master
//2017.6.20
Shader "ApcShader/XRayEffect"
{
Properties
{
_MainTex("Base 2D", 2D) = "white"{}
}
SubShader
{
Tags{ "Queue" = "Geometry" "RenderType" = "Opaque" }
//渲染X光效果的Pass
Pass
{
Blend SrcAlpha One
ZWrite Off
ZTest Greater
CGPROGRAM
#include "Lighting.cginc"
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return fixed4(1,1,1,0.5);
}
#pragma vertex vert
#pragma fragment frag
ENDCG
}
//正常渲染的Pass
Pass
{
ZWrite On
CGPROGRAM
#include "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD1;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack "Diffuse"
}
效果如下:
上图基本实现了X光的效果,不过效果不是很美观,我们将X光的Pass更换一下,使用半透+边缘光的效果进行渲染,代码如下:
//X光效果
//by:puppet_master
//2017.6.20
Shader "ApcShader/XRayEffect"
{
Properties
{
_MainTex("Base 2D", 2D) = "white"{}
_XRayColor("XRay Color", Color) = (1,1,1,1)
}
SubShader
{
Tags{ "Queue" = "Geometry+100" "RenderType" = "Opaque" }
//渲染X光效果的Pass
Pass
{
Blend SrcAlpha One
ZWrite Off
ZTest Greater
CGPROGRAM
#include "Lighting.cginc"
fixed4 _XRayColor;
struct v2f
{
float4 pos : SV_POSITION;
float3 normal : normal;
float3 viewDir : TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.viewDir = ObjSpaceViewDir(v.vertex);
o.normal = v.normal;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 normal = normalize(i.normal);
float3 viewDir = normalize(i.viewDir);
float rim = 1 - dot(normal, viewDir);
return _XRayColor * rim;
}
#pragma vertex vert
#pragma fragment frag
ENDCG
}
//正常渲染的Pass
Pass
{
ZWrite On
CGPROGRAM
#include "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack "Diffuse"
}
来一张动图看一下X光的效果:
/********************************************************************
FileName: OccOutLineEffect.cs
Description: 遮挡描边后处理效果
Created: 2017/07/20
by puppet_master
*********************************************************************/
using UnityEngine;
using System.Collections;
using UnityEngine.Rendering;
public class OccOutLineEffect : PostEffectBase
{
private RenderTexture renderTexture = null;
private CommandBuffer commandBuffer = null;
//采样率
public float samplerScale = 1;
//降采样
public int downSample = 1;
//迭代次数
public int iteration = 2;
//描边强度
[Range(0.0f, 10.0f)]
public float outLineStrength = 3.0f;
//目标对象
public GameObject targetObject = null;
void OnEnable()
{
Renderer[] renderers = targetObject.GetComponentsInChildren();
//RT可以设置AA等级,降低锯齿效果
if (renderTexture == null)
renderTexture = RenderTexture.GetTemporary(Screen.width >> downSample, Screen.height >> downSample);
//创建描边prepass的command buffer
commandBuffer = new CommandBuffer();
commandBuffer.SetRenderTarget(renderTexture);
commandBuffer.ClearRenderTarget(true, true, Color.black);
foreach (Renderer r in renderers)
commandBuffer.DrawRenderer(r, r.sharedMaterial);
}
void OnDisable()
{
if (renderTexture)
{
RenderTexture.ReleaseTemporary(renderTexture);
renderTexture = null;
}
if (commandBuffer != null)
{
commandBuffer.Release();
commandBuffer = null;
}
}
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (_Material && renderTexture && commandBuffer != null)
{
//直接通过Graphic执行Command Buffer
Graphics.ExecuteCommandBuffer(commandBuffer);
//对RT进行Blur处理
RenderTexture temp1 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0);
RenderTexture temp2 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0);
//高斯模糊,两次模糊,横向纵向,使用pass0进行高斯模糊
_Material.SetVector("_offsets", new Vector4(0, samplerScale, 0, 0));
Graphics.Blit(renderTexture, temp1, _Material, 0);
_Material.SetVector("_offsets", new Vector4(samplerScale, 0, 0, 0));
Graphics.Blit(temp1, temp2, _Material, 0);
//如果有叠加再进行迭代模糊处理
for (int i = 0; i < iteration; i++)
{
_Material.SetVector("_offsets", new Vector4(0, samplerScale, 0, 0));
Graphics.Blit(temp2, temp1, _Material, 0);
_Material.SetVector("_offsets", new Vector4(samplerScale, 0, 0, 0));
Graphics.Blit(temp1, temp2, _Material, 0);
}
//叠加效果
_Material.SetTexture("_BlurTex", temp1);
_Material.SetFloat("_OutlineStrength", outLineStrength);
Graphics.Blit(source, destination, _Material, 2);
RenderTexture.ReleaseTemporary(temp1);
RenderTexture.ReleaseTemporary(temp2);
}
else
{
Graphics.Blit(source, destination);
}
}
}
shader代码如下,其实就是之前的描边shader,偷了个懒,继续用一下(Pass1没有用):
//后处理描边Shader
//by:puppet_master
//2017.1.12
Shader "Custom/OutLinePostEffect" {
Properties{
_MainTex("Base (RGB)", 2D) = "white" {}
_BlurTex("Blur", 2D) = "white"{}
}
CGINCLUDE
#include "UnityCG.cginc"
//用于剔除中心留下轮廓
struct v2f_cull
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
//用于模糊
struct v2f_blur
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 uv01 : TEXCOORD1;
float4 uv23 : TEXCOORD2;
float4 uv45 : TEXCOORD3;
};
//用于最后叠加
struct v2f_add
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float2 uv1 : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
sampler2D _BlurTex;
float4 _BlurTex_TexelSize;
float4 _offsets;
float _OutlineStrength;
//Blur图和原图进行相减获得轮廓
v2f_cull vert_cull(appdata_img v)
{
v2f_cull o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord.xy;
//dx中纹理从左上角为初始坐标,需要反向
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv.y = 1 - o.uv.y;
#endif
return o;
}
fixed4 frag_cull(v2f_cull i) : SV_Target
{
fixed4 colorMain = tex2D(_MainTex, i.uv);
fixed4 colorBlur = tex2D(_BlurTex, i.uv);
//最后的颜色是_BlurTex - _MainTex,周围0-0=0,黑色;边框部分为描边颜色-0=描边颜色;中间部分为描边颜色-描边颜色=0。最终输出只有边框
//return fixed4((colorBlur - colorMain).rgb, 1);
return colorBlur - colorMain;
}
//高斯模糊 vert shader(之前的文章有详细注释,此处也可以用BoxBlur,更省一点)
v2f_blur vert_blur(appdata_img v)
{
v2f_blur o;
_offsets *= _MainTex_TexelSize.xyxy;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord.xy;
o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);
o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;
o.uv45 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 3.0;
return o;
}
//高斯模糊 pixel shader
fixed4 frag_blur(v2f_blur i) : SV_Target
{
fixed4 color = fixed4(0,0,0,0);
color += 0.40 * tex2D(_MainTex, i.uv);
color += 0.15 * tex2D(_MainTex, i.uv01.xy);
color += 0.15 * tex2D(_MainTex, i.uv01.zw);
color += 0.10 * tex2D(_MainTex, i.uv23.xy);
color += 0.10 * tex2D(_MainTex, i.uv23.zw);
color += 0.05 * tex2D(_MainTex, i.uv45.xy);
color += 0.05 * tex2D(_MainTex, i.uv45.zw);
return color;
}
//最终叠加 vertex shader
v2f_add vert_add(appdata_img v)
{
v2f_add o;
//mvp矩阵变换
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//uv坐标传递
o.uv.xy = v.texcoord.xy;
o.uv1.xy = o.uv.xy;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv.y = 1 - o.uv.y;
#endif
return o;
}
fixed4 frag_add(v2f_add i) : SV_Target
{
//取原始场景图片进行采样
fixed4 ori = tex2D(_MainTex, i.uv1);
//取得到的轮廓图片进行采样
fixed4 blur = tex2D(_BlurTex, i.uv);
//return blur;
fixed4 final = ori + blur * _OutlineStrength;
return final;
}
ENDCG
SubShader
{
//pass 0: 高斯模糊
Pass
{
ZTest Off
Cull Off
ZWrite Off
Fog{ Mode Off }
CGPROGRAM
#pragma vertex vert_blur
#pragma fragment frag_blur
ENDCG
}
//pass 1: 剔除中心部分
Pass
{
ZTest Off
Cull Off
ZWrite Off
Fog{ Mode Off }
CGPROGRAM
#pragma vertex vert_cull
#pragma fragment frag_cull
ENDCG
}
//pass 2: 最终叠加
Pass
{
ZTest Off
Cull Off
ZWrite Off
Fog{ Mode Off }
CGPROGRAM
#pragma vertex vert_add
#pragma fragment frag_add
ENDCG
}
}
}
然后人物本身的shader我们换成边缘光的shader(可以参考之前的文章),效果如下:
/********************************************************************
FileName: TransparentControl.cs
Description: 遮挡半透查询控制器,挂于摄像机
Created: 2017/07/23
history: 23:7:2017 12:45 by puppet_master
*********************************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class TransparentControl : MonoBehaviour {
public class TransparentParam
{
public Material[] materials = null;
public Material[] sharedMats = null;
public float currentFadeTime = 0;
public bool isTransparent = true;
}
public Transform targetObject = null; //目标对象
public float height = 3.0f; //目标对象Y方向偏移
public float destTransparent = 0.2f; //遮挡半透的最终半透强度,
public float fadeInTime = 1.0f; //开始遮挡半透时渐变时间
private int transparentLayer; //需要遮挡半透的层级
private Dictionary transparentDic = new Dictionary();
private List clearList = new List();
void Start ()
{
transparentLayer = 1 << LayerMask.NameToLayer("OcclusionTran");
}
void Update ()
{
if (targetObject == null)
return;
UpdateTransparentObject();
UpdateRayCastHit();
RemoveUnuseTransparent();
}
public void UpdateTransparentObject()
{
var var = transparentDic.GetEnumerator();
while (var.MoveNext())
{
TransparentParam param = var.Current.Value;
param.isTransparent = false;
foreach (var mat in param.materials)
{
Color col = mat.GetColor("_Color");
param.currentFadeTime += Time.deltaTime;
float t = param.currentFadeTime / fadeInTime;
col.a = Mathf.Lerp(1, destTransparent, t);
mat.SetColor("_Color", col);
}
}
}
public void UpdateRayCastHit()
{
RaycastHit[] rayHits = null;
//视线方向为从自身(相机)指向目标位置
Vector3 targetPos = targetObject.position + new Vector3(0, height, 0);
Vector3 viewDir = (targetPos - transform.position).normalized;
Vector3 oriPos = transform.position;
float distance = Vector3.Distance(oriPos, targetPos);
Ray ray = new Ray(oriPos, viewDir);
rayHits = Physics.RaycastAll(ray, distance, transparentLayer);
//直接在Scene画一条线,方便观察射线
Debug.DrawLine(oriPos, targetPos, Color.red);
foreach (var hit in rayHits)
{
Renderer[] renderers = hit.collider.GetComponentsInChildren();
foreach (Renderer r in renderers)
{
AddTransparent(r);
}
}
}
public void RemoveUnuseTransparent()
{
clearList.Clear();
var var = transparentDic.GetEnumerator();
while(var.MoveNext())
{
if (var.Current.Value.isTransparent == false)
{
//用完后材质实例不会销毁,可以被unloadunuseasset销毁或切场景销毁。
var.Current.Key.materials = var.Current.Value.sharedMats;
clearList.Add(var.Current.Key);
}
}
foreach(var v in clearList)
transparentDic.Remove(v);
}
void AddTransparent(Renderer renderer)
{
TransparentParam param = null;
transparentDic.TryGetValue(renderer, out param);
if (param == null)
{
param = new TransparentParam();
transparentDic.Add(renderer, param);
//此处顺序不能反,调用material会产生材质实例。
param.sharedMats = renderer.sharedMaterials;
param.materials = renderer.materials;
foreach(var v in param.materials)
{
v.shader = Shader.Find("ApcShader/OcclusionTransparent");
}
}
param.isTransparent = true;
}
}
我们先用Unity自带的半透的shader试一下,这次我们直接用一个surface shader:
Shader "ApcShader/OcclusionTransparent" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
ZWrite Off
CGPROGRAM
#pragma surface surf Lambert alpha
sampler2D _MainTex;
fixed4 _Color;
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "VertexLit"
}
然后,我们在场景中尝试一下这个效果。首先,需要把需要遮挡半透的物体添加碰撞体组件,这里我们直接用BoxCollider,毕竟碰撞体越简单,性能越好。然后在相机上挂上TransparentController脚本,当相机和人物中间出现了遮挡物,遮挡物就会自动替换成半透shader。效果如下:
//增加prepass的半透渲染
//by:puppet_master
//2017.7.23
Shader "ApcShader/OcclusionTransparent" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Transparent" "Queue" = "Transparent-1"}
Pass
{
ZWrite On //开启深度写入
ColorMask 0 //不写颜色
}
ZWrite Off
CGPROGRAM
#pragma surface surf Lambert alpha
sampler2D _MainTex;
fixed4 _Color;
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "VertexLit"
}
下面来一张图来对比一下使用了 Prepass和正常渲染的半透效果:
//vertex阶段
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.screenPos = ComputeGrabScreenPos(o.pos);
//fragment阶段
float screenSpacePos = i.screenPos.y / i.screenPos.w;
return fixed4(screenSpacePos, screenSpacePos, screenSpacePos, 1);
比如我们把屏幕空间Y方向的位置输出出来,效果如下:
float2 screenPos = i.screenPos.xy / i.screenPos.w;
float2 dir = float2(0.5, 0.5) - screenPos;
float distance = sqrt(dir.x * dir.x + dir.y * dir.y);
return fixed4(distance, distance, distance, 1);
//遮挡溶解效果
//by:puppet_master
//2017.7.23
Shader "ApcShader/OcclusionDissolve"
{
Properties{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_DissolveColorA("Dissolve Color A", Color) = (0,0,0,0)
_DissolveColorB("Dissolve Color B", Color) = (1,1,1,1)
_MainTex("Base 2D", 2D) = "white"{}
_DissolveMap("DissolveMap", 2D) = "white"{}
_DissolveThreshold("DissolveThreshold", Range(0,2)) = 0
_ColorFactorA("ColorFactorA", Range(0,1)) = 0.7
_ColorFactorB("ColorFactorB", Range(0,1)) = 0.8
}
CGINCLUDE
#include "Lighting.cginc"
uniform fixed4 _Diffuse;
uniform fixed4 _DissolveColorA;
uniform fixed4 _DissolveColorB;
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform sampler2D _DissolveMap;
uniform float _DissolveThreshold;
uniform float _ColorFactorA;
uniform float _ColorFactorB;
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float2 uv : TEXCOORD1;
float4 screenPos : TEXCOORD2;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//计算屏幕坐标
o.screenPos = ComputeGrabScreenPos(o.pos);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float2 screenPos = i.screenPos.xy / i.screenPos.w;
//计算距离中心点距离
float2 dir = float2(0.5, 0.5) - screenPos;
float distance = sqrt(dir.x * dir.x + dir.y * dir.y);
//距离中心点近的才进行溶解处理
float disolveFactor = (0.5 - distance) * _DissolveThreshold;
//采样Dissolve Map
fixed4 dissolveValue = tex2D(_DissolveMap, i.uv);
//小于阈值的部分直接discard
if (dissolveValue.r < disolveFactor)
{
discard;
}
//Diffuse + Ambient光照计算
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 lambert = saturate(dot(worldNormal, worldLightDir));
fixed3 albedo = lambert * _Diffuse.xyz * _LightColor0.xyz + UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 color = tex2D(_MainTex, i.uv).rgb * albedo;
//这里为了比较方便,直接用color和最终的边缘lerp了
float lerpValue = disolveFactor / dissolveValue.r;
if (lerpValue > _ColorFactorA)
{
if (lerpValue > _ColorFactorB)
return _DissolveColorB;
return _DissolveColorA;
}
return fixed4(color, 1);
}
ENDCG
SubShader
{
Tags{ "RenderType" = "Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack "Diffuse"
}
给一张噪声图,调整一个比较酷炫的颜色,然后我们就可以看到之前普通的溶解效果会随着我们视线的位置溶解掉我们看到的东西:
//遮挡溶解效果
//by:puppet_master
//2017.7.26
Shader "ApcShader/OcclusionDissolve"
{
Properties{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_DissolveColorA("Dissolve Color A", Color) = (0,1,1,0)
_DissolveColorB("Dissolve Color B", Color) = (0.3,0.3,0.3,1)
_MainTex("Base 2D", 2D) = "white"{}
_DissolveMap("DissolveMap", 2D) = "white"{}
_DissolveThreshold("DissolveThreshold", Range(0,2)) = 2
_ColorFactorA("ColorFactorA", Range(0,1)) = 0.7
_ColorFactorB("ColorFactorB", Range(0,1)) = 0.8
_DissolveDistance("DissolveDistance", Range(0, 20)) = 14
_DissolveDistanceFactor("DissolveDistanceFactor", Range(0,3)) = 3
}
CGINCLUDE
#include "Lighting.cginc"
uniform fixed4 _Diffuse;
uniform fixed4 _DissolveColorA;
uniform fixed4 _DissolveColorB;
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform sampler2D _DissolveMap;
uniform float _DissolveThreshold;
uniform float _ColorFactorA;
uniform float _ColorFactorB;
uniform float _DissolveDistance;
uniform float _DissolveDistanceFactor;
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float2 uv : TEXCOORD1;
float4 screenPos : TEXCOORD2;
float3 viewDir : TEXCOORD3;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.viewDir = ObjSpaceViewDir(v.vertex);
//计算屏幕坐标
o.screenPos = ComputeGrabScreenPos(o.pos);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float2 screenPos = i.screenPos.xy / i.screenPos.w;
//计算距离中心点距离作为一个控制系数
float2 dir = float2(0.5, 0.5) - screenPos;
float screenSpaceDistance = 0.5 - sqrt(dir.x * dir.x + dir.y * dir.y);
//计算一下像素点到相机距离作为另一个控制系数
float viewDistance = max(0,(_DissolveDistance - length(i.viewDir)) / _DissolveDistance) * _DissolveDistanceFactor;
//用两个控制系数作为最终溶解的系数
float disolveFactor = viewDistance * screenSpaceDistance * _DissolveThreshold;
//采样Dissolve Map
fixed4 dissolveValue = tex2D(_DissolveMap, i.uv);
//小于阈值的部分直接discard
if (dissolveValue.r < disolveFactor)
{
discard;
}
//Diffuse + Ambient光照计算
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 lambert = saturate(dot(worldNormal, worldLightDir));
fixed3 albedo = lambert * _Diffuse.xyz * _LightColor0.xyz + UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 color = tex2D(_MainTex, i.uv).rgb * albedo;
//这里为了比较方便,直接用color和最终的边缘lerp了
float lerpValue = disolveFactor / dissolveValue.r;
if (lerpValue > _ColorFactorA)
{
if (lerpValue > _ColorFactorB)
return _DissolveColorB;
return _DissolveColorA;
}
return fixed4(color, 1);
}
ENDCG
SubShader
{
Tags{ "RenderType" = "Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack "Diffuse"
}
效果如下面动态图所示,只有在距离相机距离比较近的内容才会被溶解掉: