Unity3D Shader系列之全息投影

1 效果展示

全息投影效果

2 实现原理

全息投影其实是几个效果的叠加:①半透明效果②上下条纹的扫描效果③边缘光效果④模拟信号传输不稳定的顶点偏移效果。
咱们依次来看看这几个效果背后的原理。
①半透明效果
在Unity中实现半透明效果其实很简单,只需添加以下标签、关闭深度写入、开启混合即可。
渲染队列设置为Transparent,值为3000,可以保证使用该Shader的物体再所有不透明物体渲染之后再渲染。

Tags {
    
     "RenderType"="Transparent" "IgnoreProjector"="true" "Queue"="Transparent"}

关闭深度写入。

ZWrite Off

设置混合状态。

Blend SrcAlpha OneMinusSrcAlpha

但是这样设置后会有个问题,就是我们能透过模型看到被自己遮挡的部分,就像下面这样。
透明效果的bug
这个问题出现的原因是我们关闭了深度写入(半透明物体是一定要关闭深度写入的),所以被我们自己挡住的部分依然能通过深度测试从而绘制出来。为了解决这个问题,我们需要额外增加一个Pass,此Pass只用来深度写入不输出颜色。这样就能保证此物体仍是半透明效果,但又不会出现上面的问题。

Pass
{
    
    
	ZWrite On
	ColorMask 0
}

额外增加Pass后的效果
②条纹上下扫描效果
使用到的扫描纹理如下。
扫描条纹图
重点是需要使用顶点对应的视口坐标对扫描纹理进行采样。
在Shader中获取顶点对应的视口坐标过程如下:
在顶点着色器中,使用ComputeScreenPos方法获取屏幕坐标,其参数为顶点在裁剪空间中的坐标。

o.screenPos = ComputeScreenPos(o.vertex);

然后需要在片元着色器中进行透视除法,即可得到顶点对应的视口坐标。

float2 wcoord = i.screenPos.xy / i.screenPos.w;

为什么需要这样做,具体参见《Unity Shader入门精要》4.9.3 Unity中的屏幕坐标:ComputeScreenPos/VPOS/WPOS节 ,说得特别清楚了。为了方便查阅,我这儿就直接复制过来了。
视口坐标系求法01
视口坐标系求法02
然后将视口坐标的y值按时间增加,并对扫描纹理进行采样。我们这里增加了两个变量,一是_ScanSpeed用来控制扫描的速度,二是_ScanScale用来控制两条纹之间的间隔大小。

wcoord.y += _Time.y * _ScanSpeed;
fixed4 scanColor = tex2D(_ScanTex, wcoord * _ScanScale);

条纹间隔可通过_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

		// Glitch
		_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 参考文章

猜你喜欢

转载自blog.csdn.net/sinat_25415095/article/details/123908969