小伙伴提供的素材:
hologram sticker效果,简称holo:
成果:
基于URP的shader graph实现,以及基于built-in管线的实现
1. 效果分析
在这里可以看到csgo的贴纸效果:StatTrak™ AWP | Man-o'-war (Minimal Wear) | 3D Skin Viewer
根据观察,发现:
a. 全息颜色是根据视角变化的,所以会利用到view direction dot normal direction(视线方向点乘法线方向,即VdotN),即下图的V和N。两个单位向量点乘得到它们夹角的cos值,值越大,夹角越小(想象一下cos曲线)。
b. 全息是多层叠加的,每层的颜色以及颜色的变化速度都是不一样的,所以我们会将全息效果写成一个函数,然后调用若干次
2. 实现
基于Unity 2019.4.23
大致流程是,我们将上图作为色板,将VdotN作为横坐标,纵坐标取固定值。随着视角的变化,shader会在图中横着采样从左到右的彩虹色。我们给贴纸的背景部分使用小的y值,主体使用高的y值,这样背景的颜色发灰,主体颜色很亮。
↑这两张图作为mask。根据观察,认为是三层holo叠加,对应mask的rgb通道。
2.1 全息效果
我们给全息效果制作了一个sub graph:
讲解下参数:
- Mask:哪里有全息效果(1),哪里没有效果(0),会把mask图的某个通道传进来
- Scale:采样色板的快慢,即holo颜色变化快慢
- Offset:颜色偏移
- Y Sample Value:色板取色的y值
- Color Multiplier:取到的颜色的乘数
- LUT:look up table,色板
如下图所示,比起通常用uv采样贴图的做法,我们会在uv上叠加VdotN去采样色板,这样视角变化会影响取色。add节点的输入是VdotN和uv。VdotN注意要在object space,如果在world space变化会很鬼畜,可能是数值太大导致的精度问题。uv这里有两个操作。第一个操作是因为色板直接采样,颜色的变化方向是左右,我们需要上下变化,所以把uv split掉后取的是y。第二个操作是scale & offset。我这里控制的是原始uv,读者也可以把它放在别的地方。
如下图所示,采样LUT后,乘以mask和色彩乘数得到最终结果。
2.2 多层holo叠加
接下来读取两张mask的三个通道。
这两张图怎么使用读者可以*决定,我的做法是这样的,并不是所有图都用上了:
参数:
- R Scale Offset Y Mult:对应holo节点的四个参数
单层
叠加
2.3 转为built-in管线shader
Shader "Unlit/UnlitShader card"
{
Properties
{
_MaskA ("mask a", 2D) = "white" {}
_MaskB ("mask b", 2D) = "white" {}
_LUT ("LUT", 2D) = "white" {}
RParam("R param", Vector) = (1, 0, 0, 1)
GParam("G param", Vector) = (1, 0, 0, 1)
BParam("B param", Vector) = (1, 0, 0, 1)
_ClipValue("AlphaClipThreshold", Float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float3 normal : TEXCOORD1;
};
sampler2D _MaskA;
sampler2D _MaskB;
sampler2D _LUT;
float4 RParam;
float4 GParam;
float4 BParam;
float _ClipValue;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;//TRANSFORM_TEX(v.uv, _MaskA);
o.normal = v.normal;
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 Holo(half2 uv, float3 normal, half mask, half4 param)
{
half viewAngle=dot(_WorldSpaceCameraPos.xyz, normal);
float2 calc_uv = float2(uv.y*param.x + param.y+viewAngle, param.z);
return tex2D(_LUT, calc_uv) * mask * param.w;
}
fixed4 frag (v2f i) : SV_Target
{
half4 maskB = tex2D(_MaskB, i.uv);
clip(maskB.a - _ClipValue);
half4 maskA = tex2D(_MaskA, i.uv);
half4 holoR = Holo(i.uv, i.normal, maskA.r * maskB.r, RParam);
half4 holoG = Holo(i.uv, i.normal, maskA.g, GParam);
half4 holoB = Holo(i.uv, i.normal, maskA.b, BParam);
fixed4 col = half4((holoR+holoG+holoB).rgb, maskB.a);
return col;
}
ENDCG
}
}
}
4. 总结与回顾
视角的计算:用sin值也可以表示视角变化,但是点乘用起来很方便,所以用cos
GitHub - MidoriMeng/URP-Hologram-Sticker-Effect