文章目录[x]
- 0.1:一、使用深度图的方式
- 0.2:二、使用Projector组件
游戏中许多地方会需要用到扫描效果,比如无人深空中的扫描,手雷扔出去前的预判范围,死亡搁浅等等。

一、使用深度图的方式
相关链接:https://zhuanlan.zhihu.com/p/143788955
https://github.com/Broxxar/NoMansScanner
Shader "Hidden/ScannerEffect"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_DetailTex("Texture", 2D) = "white" {}
_ScanDistance("Scan Distance", float) = 0
_ScanWidth("Scan Width", float) = 10
_LeadSharp("Leading Edge Sharpness", float) = 10
_LeadColor("Leading Edge Color", Color) = (1, 1, 1, 0)
_MidColor("Mid Color", Color) = (1, 1, 1, 0)
_TrailColor("Trail Color", Color) = (1, 1, 1, 0)
_HBarColor("Horizontal Bar Color", Color) = (0.5, 0.5, 0.5, 0)
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct VertIn
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 ray : TEXCOORD1;
};
struct VertOut
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float2 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2;
};
float4 _MainTex_TexelSize;
float4 _CameraWS;
VertOut vert(VertIn v)
{
VertOut o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv.xy;
o.uv_depth = v.uv.xy;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv.y = 1 - o.uv.y;
#endif
o.interpolatedRay = v.ray;
return o;
}
sampler2D _MainTex;
sampler2D _DetailTex;
sampler2D_float _CameraDepthTexture;
float4 _WorldSpaceScannerPos;
float _ScanDistance;
float _ScanWidth;
float _LeadSharp;
float4 _LeadColor;
float4 _MidColor;
float4 _TrailColor;
float4 _HBarColor;
float4 horizBars(float2 p)
{
return 1 - saturate(round(abs(frac(p.y * 100) * 2)));
}
float4 horizTex(float2 p)
{
return tex2D(_DetailTex, float2(p.x * 30, p.y * 40));
}
half4 frag (VertOut i) : SV_Target
{
half4 col = tex2D(_MainTex, i.uv);
float rawDepth = DecodeFloatRG(tex2D(_CameraDepthTexture, i.uv_depth));
float linearDepth = Linear01Depth(rawDepth);
float4 wsDir = linearDepth * i.interpolatedRay;
float3 wsPos = _WorldSpaceCameraPos + wsDir;
half4 scannerCol = half4(0, 0, 0, 0);
float dist = distance(wsPos, _WorldSpaceScannerPos);
//控制扫描线范围 linearDepth<1为了不出现在天空盒上
if (dist < _ScanDistance && dist > _ScanDistance - _ScanWidth && linearDepth < 1)
{
float diff = 1 - (_ScanDistance - dist) / (_ScanWidth);
half4 edge = lerp(_MidColor, _LeadColor, pow(diff, _LeadSharp));
scannerCol = lerp(_TrailColor, edge, diff) + horizBars(i.uv) * _HBarColor;
scannerCol *= diff;
}
return col + scannerCol;
}
ENDCG
}
}
}
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class ScannerEffectDemo : MonoBehaviour
{
public Transform ScannerOrigin;
public Material EffectMaterial;
public float ScanDistance;
private Camera _camera;
void Start()
{
}
void Update()
{
ScanDistance += Time.deltaTime * 12;
if (Input.GetMouseButtonDown(0))
{
Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
ScanDistance = 0;
ScannerOrigin.position = hit.point;
}
}
}
void OnEnable()
{
_camera = GetComponent<Camera>();
_camera.depthTextureMode = DepthTextureMode.Depth;
}
[ImageEffectOpaque]//只对不透明物体施加特效
void OnRenderImage(RenderTexture src, RenderTexture dst)
{
EffectMaterial.SetVector("_WorldSpaceScannerPos", ScannerOrigin.position);
EffectMaterial.SetFloat("_ScanDistance", ScanDistance);
RaycastCornerBlit(src, dst, EffectMaterial);
}
//所以想要获取当前像素在世界空间的坐标,我们需要使用深度贴图来重构世界空间坐标
//不能直接使用Graphics.Bilt(src,dst,mat);
void RaycastCornerBlit(RenderTexture source, RenderTexture dest, Material mat)
{
float camFar = _camera.farClipPlane;
float camFov = _camera.fieldOfView;
float camAspect = _camera.aspect;
float fovWHalf = camFov * 0.5f;
Vector3 toRight = _camera.transform.right * Mathf.Tan(fovWHalf * Mathf.Deg2Rad) * camAspect;
Vector3 toTop = _camera.transform.up * Mathf.Tan(fovWHalf * Mathf.Deg2Rad);
Vector3 topLeft = (_camera.transform.forward - toRight + toTop);
float camScale = topLeft.magnitude * camFar;
topLeft.Normalize();
topLeft *= camScale;
Vector3 topRight = (_camera.transform.forward + toRight + toTop);
topRight.Normalize();
topRight *= camScale;
Vector3 bottomRight = (_camera.transform.forward + toRight - toTop);
bottomRight.Normalize();
bottomRight *= camScale;
Vector3 bottomLeft = (_camera.transform.forward - toRight - toTop);
bottomLeft.Normalize();
bottomLeft *= camScale;
// Custom Blit, encoding Frustum Corners as additional Texture Coordinates
RenderTexture.active = dest;
mat.SetTexture("_MainTex", source);
GL.PushMatrix();
GL.LoadOrtho();
mat.SetPass(0);
GL.Begin(GL.QUADS);
GL.MultiTexCoord2(0, 0.0f, 0.0f);
GL.MultiTexCoord(1, bottomLeft);
GL.Vertex3(0.0f, 0.0f, 0.0f);
GL.MultiTexCoord2(0, 1.0f, 0.0f);
GL.MultiTexCoord(1, bottomRight);
GL.Vertex3(1.0f, 0.0f, 0.0f);
GL.MultiTexCoord2(0, 1.0f, 1.0f);
GL.MultiTexCoord(1, topRight);
GL.Vertex3(1.0f, 1.0f, 0.0f);
GL.MultiTexCoord2(0, 0.0f, 1.0f);
GL.MultiTexCoord(1, topLeft);
GL.Vertex3(0.0f, 1.0f, 0.0f);
GL.End();
GL.PopMatrix();
}
}
使用的时候我们利用上面的shader创建一个材质球,然后在camera上挂载ScannerEffectDemo 脚本,并且把先前创建的材质球赋值到组件上。再创建一个新的空物体赋值到组件上就可以了。我们用鼠标点击场景就出现效果了。

二、使用Projector组件
1.先写一个projector用的shader
Shader "Custom/ProjectorDecal"
{
Properties
{
//调色
_Color("Color", Color) = (1,1,1,1)
//投影图片
_MainTex("Albedo (RGB)", 2D) = "white" {}
//根据投影仪视距渐变的图片
_FalloffTex("Falloff",2D) = "white"{}
}
SubShader
{
Pass
{
ZWrite Off
//解决ZFighting现象
Offset -1, -1
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _Color;
//将投影仪剪辑空间的X和Y轴映射到U和V坐标,这些坐标通常用于对径向衰减纹理进行采样。
float4x4 unity_Projector;
//将投影仪视图空间的Z轴映射到U坐标(可能在V中复制它),该坐标可用于采样渐变纹理,该纹理定义投影仪随距离衰减。u值在投影仪近平面处为0,在投影仪远平面处为1。
float4x4 unity_ProjectorClip;
sampler2D _MainTex;
sampler2D _FalloffTex;
struct v2f {
float4 uvDecal:TEXCOORD0;
float4 uvFalloff:TEXCOORD1;
float4 pos:SV_POSITION;
};
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//四元纹理坐标给UNITY_PROJ_COORD读取
o.uvDecal = mul(unity_Projector, v.vertex);
o.uvFalloff = mul(unity_ProjectorClip, v.vertex);
return o;
}
float4 frag(v2f i) :SV_Target
{
float4 decal;
//解决图片四周拖影
if (i.uvDecal.x / i.uvDecal.w < 0.0001 || i.uvDecal.x / i.uvDecal.w>0.9999 || i.uvDecal.y / i.uvDecal.w < 0.0001 || i.uvDecal.y / i.uvDecal.w>0.9999)
{
decal = float4(0, 0, 0, 0);
}
else
{
//采样齐次uv,分量都除以了w
decal = tex2Dproj(_MainTex, UNITY_PROJ_COORD(i.uvDecal));
}
float falloff = tex2Dproj(_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff)).r;
return float4(decal.rgb * _Color.rgb,decal.a * falloff * _Color.a);
}
ENDCG
}
}
}
2.写一个控制projector缩放的脚本。
using UnityEngine;
public class DecalManager : MonoBehaviour
{
private Projector projector;
[SerializeField]
private bool looping = false;
private float size = 0;
public float spreadSpeed = 1f;
public float maxSize = 10f;
private Color alphaColor;
private void Start()
{
projector = GetComponent<Projector>();
projector.orthographic = true;
projector.orthographicSize = 0;
alphaColor = new Color(projector.material.color.r, projector.material.color.g, projector.material.color.b, 0);
}
private void Update()
{
size += Time.deltaTime * spreadSpeed;
if(size<maxSize)
{
projector.orthographicSize = size;
alphaColor.a = 1 - size / maxSize;
projector.material.color = alphaColor;
}
else
{
if(looping)
{
size = projector.orthographicSize = 0;
}
else
{
Destroy(gameObject);
}
}
}
}
3.在场景中创建一个空物体,然后添加Projector组件,再用上面写的shader创建一个材质球赋值到projector上面。
4.把上面写的脚本挂载到projector上面,运行。

源码地址:http://horse7.cn/download/scenescanner.unitypackage