1 效果展示
2 实现原理
全息投影其实是几个效果的叠加:①半透明效果②上下条纹的扫描效果③边缘光效果④模拟信号传输不稳定的顶点偏移效果。 咱们依次来看看这几个效果背后的原理。 ①半透明效果 在Unity中实现半透明效果其实很简单,只需添加以下标签、关闭深度写入、开启混合即可。 渲染队列设置为Transparent,值为3000,可以保证使用该Shader的物体再所有不透明物体渲染之后再渲染。
Tags { "RenderType"="Transparent" "IgnoreProjector"="true" "Queue"="Transparent"}
关闭深度写入。
ZWrite Off
设置混合状态。
Blend SrcAlpha OneMinusSrcAlpha
但是这样设置后会有个问题,就是我们能透过模型看到被自己遮挡的部分,就像下面这样。 这个问题出现的原因是我们关闭了深度写入(半透明物体是一定要关闭深度写入的),所以被我们自己挡住的部分依然能通过深度测试从而绘制出来。为了解决这个问题,我们需要额外增加一个Pass,此Pass只用来深度写入不输出颜色。这样就能保证此物体仍是半透明效果,但又不会出现上面的问题。
Pass
{
ZWrite On
ColorMask 0
}
②条纹上下扫描效果 使用到的扫描纹理如下。 重点是需要使用顶点对应的视口坐标对扫描纹理进行采样。 在Shader中获取顶点对应的视口坐标过程如下: 在顶点着色器中,使用ComputeScreenPos方法获取屏幕坐标,其参数为顶点在裁剪空间中的坐标。
o.screenPos = ComputeScreenPos(o.vertex);
然后需要在片元着色器中进行透视除法,即可得到顶点对应的视口坐标。
float2 wcoord = i.screenPos.xy / i.screenPos.w;
为什么需要这样做,具体参见《Unity Shader入门精要》4.9.3 Unity中的屏幕坐标:ComputeScreenPos/VPOS/WPOS节 ,说得特别清楚了。为了方便查阅,我这儿就直接复制过来了。 然后将视口坐标的y值按时间增加,并对扫描纹理进行采样。我们这里增加了两个变量,一是_ScanSpeed用来控制扫描的速度,二是_ScanScale用来控制两条纹之间的间隔大小。
wcoord.y += _Time.y * _ScanSpeed;
fixed4 scanColor = tex2D(_ScanTex, wcoord * _ScanScale);
为什么对视口坐标乘以_ScanScale可以用来控制条纹间的宽度呢? 视口坐标的范围为(0,0)到(1,1),当_ScanScale小于1时,视口坐标相当于被缩小了,那么采样得到纹理范围也被缩小了。假设之前是整个纹理映射到模型,现在是半个纹理映射到模型,从模型上看来,就相当于纹理被放大了,对应到我们这里的扫描纹理,其实就是灰白条纹部分被拉大了。 最后,需要将采样到的灰色部分给Clip掉。
clip(scanColor.r - _CutOut);
③叠加边缘光 这个我们在《Unity3D Shader系列之边缘光RimLight》说过,这里就不多说了。 ④模拟信号传输不稳定的顶点偏移效果
这部分是直接使用了https://sharpcoderblog.com/blog/create-a-hologram-effect-in-unity-3d的代码。
实际上是通过代码控制顶点的偏移。 由于要进行顶点偏移,所以需要在Tags中增加关闭批处理的标签。
"DisableBatching"="true"
顶点偏移在Shader中的代码就下面这一句。通过使用sin函数来控制顶点的来回往复移动,_GlitchSpeed(故障速度)用来控制顶点偏移速度。_GlitchIntensity(故障强度)用来控制顶点偏移的大小,为0是不偏移,即为正常效果。 (我们经常性的使用sin、cos函数来实现往复运动的效果,因为随着时间的流动,它们的值在-1到1之间不断循环,可参考《Unity3D C#数学系列之三角函数》第3节 三角函数图像性质)
v.vertex.z += sin(_Time.y * _GlitchSpeed * 5 * v.vertex.y) * _GlitchIntensity;
然后需要用脚本定期改变_GlitchIntensity值来实现随机传输不稳定的效果。 GlitchControl.cs
using System.Collections;
using UnityEngine;
public class GlitchControl : MonoBehaviour
{
public float glitchChance = 0.1f;
Material hologramMaterial;
WaitForSeconds glitchLoopWait = new WaitForSeconds(0.2f);
void Awake()
{
hologramMaterial = GetComponent<Renderer>().material;
}
IEnumerator Start()
{
while (true)
{
float glitchTest = Random.Range(0f, 1f);
if (glitchTest <= glitchChance)
{
float originalGlowIntensity = hologramMaterial.GetFloat("_GlowIntensity");
hologramMaterial.SetFloat("_GlitchIntensity", Random.Range(0.07f, 0.1f));
hologramMaterial.SetFloat("_GlowIntensity", originalGlowIntensity * Random.Range(0.14f, 0.44f));
yield return new WaitForSeconds(Random.Range(0.05f, 0.1f));
hologramMaterial.SetFloat("_GlitchIntensity", 0f);
hologramMaterial.SetFloat("_GlowIntensity", originalGlowIntensity);
}
yield return glitchLoopWait;
}
}
}
3 源码
这个效果并不是通用的,需要根据自己的项目和要求稍微进行调整。 Hologram.shader
Shader "LaoWang/Hologram"
{
Properties
{
_Color ("Color", Color) = (0, 1, 1, 1)
_MainTex ("Texture", 2D) = "white" {}
_HologramAlpha ("Alpha", Range(0.0, 1.0)) = 0.5
_ScanTex ("Scan Tex", 2D) = "white" {}
_ScanSpeed ("Scan Speed", Range(0, 2.0)) = 1.0
_ScanScale ("Scan Tiling", Range(0.0, 3.0)) = 0.6
_CutOut ("Cut Out", Range(0.0, 1.0)) = 0.5
_RimColor ("Rim Color", Color) = (1,1,1,1)
_RimPower ("Rim Power", Range(0.001, 3.0)) = 0.5
_GlowIntensity ("Glow Intensity", Range(0.01, 2.0)) = 1.0
_GlitchSpeed ("Glitch Speed", Range(0, 50)) = 50.0
_GlitchIntensity ("Glitch Intensity", Range(0.0, 0.1)) = 0
}
SubShader
{
Tags { "RenderType"="Transparent" "IgnoreProjector"="true" "Queue"="AlphaTest" "DisableBatching"="true"}
LOD 100
Pass
{
ZWrite On
ColorMask 0
}
Pass
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 screenPos : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
float3 worldViewDir : TEXCOORD3;
};
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _HologramAlpha;
sampler2D _ScanTex;
float _ScanSpeed;
half _ScanScale;
fixed _CutOut;
float _RimPower;
fixed4 _RimColor;
half _GlowIntensity;
half _GlitchSpeed, _GlitchIntensity;
v2f vert (appdata v)
{
v.vertex.z += sin(_Time.y * _GlitchSpeed * 5 * v.vertex.y) * _GlitchIntensity;
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.screenPos = ComputeScreenPos(o.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldViewDir = WorldSpaceViewDir(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 wcoord = i.screenPos.xy / i.screenPos.w;
wcoord.y += _Time.y * _ScanSpeed;
fixed4 scanColor = tex2D(_ScanTex, wcoord * _ScanScale);
clip(scanColor.r - _CutOut);
fixed3 worldNormalDir = normalize(i.worldNormal);
fixed3 worldViewDir = normalize(i.worldViewDir);
half rim = 1.0 - saturate(dot(i.worldNormal, i.worldViewDir));
fixed4 rimColor = _RimColor * pow(rim, _RimPower);
fixed4 col = tex2D(_MainTex, i.uv);
col = col * _Color * _GlowIntensity + rimColor;
col.a = _HologramAlpha;
return fixed4(col.rgb * _Color.rgb , _HologramAlpha);
}
ENDCG
}
}
}
GlitchControl.cs
using System.Collections;
using UnityEngine;
public class GlitchControl : MonoBehaviour
{
public float glitchChance = 0.1f;
Material hologramMaterial;
WaitForSeconds glitchLoopWait = new WaitForSeconds(0.2f);
void Awake()
{
hologramMaterial = GetComponent<Renderer>().material;
}
IEnumerator Start()
{
while (true)
{
float glitchTest = Random.Range(0f, 1f);
if (glitchTest <= glitchChance)
{
float originalGlowIntensity = hologramMaterial.GetFloat("_GlowIntensity");
hologramMaterial.SetFloat("_GlitchIntensity", Random.Range(0.07f, 0.1f));
hologramMaterial.SetFloat("_GlowIntensity", originalGlowIntensity * Random.Range(0.14f, 0.44f));
yield return new WaitForSeconds(Random.Range(0.05f, 0.1f));
hologramMaterial.SetFloat("_GlitchIntensity", 0f);
hologramMaterial.SetFloat("_GlowIntensity", originalGlowIntensity);
}
yield return glitchLoopWait;
}
}
}
完整项目。 链接:https://pan.baidu.com/s/1YAoG6oiLZOHYjk5V5JPBBg 提取码:d8xd 博主本文博客链接。
4 参考文章
|