Unity从零开始实现一个全息效果Shader
本文欢迎转载,转载请标明出处!
前言
本次笔者想分享的是最近实现出来的全息模型效果。
全息的意思我就不详细讲了,因为讲了也是我百度的~
可以通过全息效果实现很赛博很科幻的模型投影特效,可以用于一些比较科幻的怪物或者npc做游戏表现。本次笔者分享的效果图如下:
如果读者觉得不够赛博不够炫,那肯定…肯定是模型的问题!(x
开始捣鼓
一、准备阶段
笔者使用的Unity版本还是2018.4.16f,按一般情况,版本不要太低就行了。
找一个目标模型和相关纹理,笔者就不放出示例中所使用的模型了,读者可以自行找一个模型尝试。
(Ps:模型的面片节点需要保证没有错误的旋转。什么是错误的旋转,就是模型有渲染面片的节点不会出现XYZ轴莫名其妙的90度旋转,这样会导致在shader处理中的模型定点方向问题。笔者的模型拿到手的时候,mesh对象节点就有莫名其妙的旋转)
像以前一样,做好文件夹分类,让对应的资源都能被放到它应该在的目录中去。
新建一个Shader HologramEffect,把里面的Properties和Subshader内容全部删掉,我们要从头开始写。
Shader "SaberShad/HologramEffect"
{
Properties
{
}
SubShader
{
}
}
创建一个材质球 HologramMat,不着急替换shader,因为前面的HologramEffect肯定会报错…
因为是详细的效果实现,篇幅较长,还希望读者们能耐心看完。笔者也还在龟速学习中,如果有错误或者建议欢迎指出~
二、先从透明效果开始
首先,我个人的理解是,全息效果是要透明的,因为全息效果是一种光线投影,不应该会说挡到模型背后的对象。
同时,它不显示原来模型的颜色,而是使用单一的光色。为什么呢,因为显示原本颜色的效果我没做…
其实也不是说不能实现,但咱们先把透明效果实现了先~
修改HologramEffect,我们增加几个颜色字段,这里笔者把全局透明跟颜色透明区分开,因为颜色的透明度有其他作用,而区分开的透明度,决定了整个模型的透明度。然后我们简单实现一个标准的顶点和片元着色器处理函数,别忘了我们前面把Pass删除了,所以要在SubShader中新开出一个Pass来包含这块逻辑。最后,我们的渲染类型是透明对象,要加上对应的渲染类型RenderType,以及关闭深度写入。
相关的逻辑如下:
Shader "SaberShad/HologramEffect"
{
Properties
{
[HDR]_HologramColor("Hologram Color", Color) = (1, 1, 1, 0)
_HologramAlpha("Hologram Alpha", Range(0.0, 1.0)) = 1.0
}
SubShader
{
Tags{
"Queue" = "Transparent" "RenderType" = "Transparent"}
CGINCLUDE
struct a2v_hg
{
float4 vertex : POSITION;
};
struct v2f_hg
{
float4 pos : SV_POSITION;
};
float4 _HologramColor;
fixed _HologramAlpha;
ENDCG
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
CGPROGRAM
#pragma target 3.0
#pragma vertex HologramVertex
#pragma fragment HologramFragment
v2f_hg HologramVertex(a2v_hg v)
{
v2f_hg o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
float4 HologramFragment(v2f_hg i) : SV_Target
{
return float4(_HologramColor.rgb, _HologramAlpha);
}
ENDCG
}
}
}
把HologramMat的shader改成HologramEffect,赋值给我们的目标模型。
为了看到透明的效果,我在模型的后面放了一个球,如下图所示。我们可以看到模型确实是透明了,而且能看到身后的小球。
可是,我们这种透明效果没有用到目标模型的纹理,所以模型的细节都消失了。所以我们还是需要使用原先模型的UV纹理,但我们这种纯色的效果不需要完全使用UV纹理的颜色,而是使用其中的某个通道调整最终的透明度就可以了。
我们修改HologramEffect,把UV采样相关的东西添加进去。
Shader "SaberShad/HologramEffect"
{
Properties
{
...
// 主纹理充当颜色蒙版
_HologramMaskMap("Hologram Mask", 2D) = "white"{
}
_HologramMaskAffect("Hologram Mask Affect", Range(0.0, 1.0)) = 0.5
}
SubShader
{
...
struct a2v_hg
{
...
float2 uv : TEXCOORD0;
};
struct v2f_hg
{
...
float2 uv : TEXCOORD0;
};
...
// 全息蒙版
sampler2D _HologramMaskMap;
float4 _HologramMaskMap_ST;
half _HologramMaskAffect;
ENDCG
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
CGPROGRAM
...
v2f_hg HologramVertex(a2v_hg v)
{
...
o.uv = v.uv;
return o;
}
float4 HologramFragment(v2f_hg i) : SV_Target
{
float4 main_color = _HologramColor;
// 用主纹理的r通道做颜色蒙版
float2 mask_uv = i.uv.xy * _HologramMaskMap_ST.xy + _HologramMaskMap_ST.zw;
float4 mask = tex2D(_HologramMaskMap, mask_uv);
// 追加一个参数用来控制遮罩效果
float mask_alpha = lerp(1, mask.r, _HologramMaskAffect);
float4 resultColor = float4(main_color.rgb, _HologramAlpha * mask_alpha);
return resultColor;
}
ENDCG
}
}
}
可以看到,增加了新的逻辑之后,UV上覆盖到了的位置都会出现相应的细节,但是我们也可以发现,原本在模型背面的顶点也被渲染进去了。由于我们是一整个模型作为主体,同时没有剔除背面和写入深度,导致了同一模型下在透明效果中出现的自己穿透自己显示的这么一个问题。
为了解决这个问题,我们可以额外使用一个Pass提前进行深度写入但是不渲染任何颜色到深度缓存,然后后续的Pass以深度写入的顶点进行渲染。修改HologramEffect:
...
SubShader
{
Tags{
"Queue" = "Transparent" "RenderType" = "Transparent"}
CGINCLUDE
...
// 因为顶点深入写入的Pass也是使用跟全息Pass一样的顶点函数,所以挪到这边来
v2f_hg HologramVertex(a2v_hg v)
{
v2f_hg o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
ENDCG
Pass
{
Name "Depth Mask"
// 打开深度写入,并且设置颜色遮罩,0代表什么颜色都不输出
ZWrite On
ColorMask 0
CGPROGRAM
#pragma target 3.0
#pragma vertex HologramVertex
#pragma fragment HologramMaskFragment
float4 HologramMaskFragment(v2f_hg i) : SV_TARGET
{
return 0;
}
ENDCG
}
Pass
{
Name "Hologram Effect"
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
CGPROGRAM
#pragma target 3.0
#pragma vertex HologramVertex
#pragma fragment HologramFragment
float4 HologramFragment(v2f_hg i) : SV_Target
{
...
}
ENDCG
}
}
怎么样,效果是不是好多了,那可不,性能换的…
由于我们多了一个Pass,我们在一次完整的渲染中就要多计算一次模型的顶点和三角面,下图就是单Pass和双Pass下的顶点和三角面差异,多出来的部分正好是笔者项目中的模型顶点数和三角面数。
因此,我们在设计模型Shader的时候,尽可能的把不透明和透明的效果分别整合到单个Pass中去实现。同时,也尽量避免在一个模型面片对象上挂多个材质球,避免渲染多次顶点,这也是项目场景顶点和三角面的优化策略之一。
解决了最基本的透明问题,接下来开始实现全息特效的效果啦。
三、顶点故障效果
从最上面的GIF图中可以看到,模型会随机出现左右抽动的效果。这个就是模拟全息广播传输的时候出现的信号不稳定导致的影响波动的效果(说的好像真的一样…)。而这个效果的实现,就需要依赖修改模型的顶点坐标制作随机动画。笔者这边就直接搬出动画函数,修改HologramEffect:
Properties
{
...
// 全息抖动参数设置,x代表速度,y代表抖动范围,z代表抖动偏移量,w代表频率(0~0.99)
_HologramGliterData1("Hologram Gliter Data1", Vector) = (0, 1, 0, 0)
_HologramGliterData2("Hologram Gliter Data2", Vector) = (0, 1, 0, 0)
}
SubShader
{
Tags{
"Queue" = "Transparent" "RenderType" = "Transparent"}
CGINCLUDE
...
half4 _HologramGliterData1, _HologramGliterData2;
half3 VertexHologramOffset(float3 vertex, half4 offsetData)
{
half speed = offsetData.x;
half range = offsetData.y