Unity URP 中实现任意角度不崩坏的“顶点切片故障”特效及解决 Alpha 为 0 变黑的方法
【Unity URP】どの角度でも破綻しない「頂点スライスグリッチ」の数式と、Alphaを0にすると真っ黒になる罠の解決法
在制作科幻游戏的全息通信或赛博朋克风格的噪点效果时,将模型躯干水平切片(slice)并向横向大幅偏移的顶点切片故障是一种非常有效的方法[cite: 1]。
然而,一旦尝试用着色器(HLSL)实现,就经常会遇到以下3个麻烦。
- 当摄像机绕到模型侧面时,顶点看起来不是横向偏移,而是向“深度方向(前后)”飞出
-
在使用
sin()的随机数公式时,如果输入较大的Seed值,会出现规则的条纹图案,或者数值会粘附在0上[cite: 1] - 即使已将URP中的着色器设置为透明,但当Alpha为
0时,并不会变透明,而是变成“纯黑色的剪影”
本文将针对这3个问题,从数学和规格两个角度讲解实用的解决技巧。
整体处理流程
顶点着色器中根据相机朝向进行切片变形,然后在片段着色器中进行安全的透明度合成,流程如下。
1. 不使用 sin!即使数值很大也不会崩溃的伪随机数(Hash2D)
在着色器中常用的 frac(sin(dot(co, float2(12.9898, 78.233))) * 43758.5453) 随机数公式存在弱点:当输入值或种子值(_Seed)变大时,会超出32位浮点数的精度极限,导致出现规律性,或者仅返回 0[cite: 1]。
为了获得不受环境或输入值大小影响的稳定噪声,应使用仅由四则运算和 frac / dot 构成的哈希公式[cite: 1]。此外,为确保即使 _Seed 被设置为巨大数值(例如 314159265.312 等)也不会发生溢出,最好在开头对种子值进行归一化处理[cite: 1]。
// 基本的な四則演算のみを使用した、環境非依存の2Dハッシュ乱数
float Hash2D(float2 co, float seed)
{
float safeSeed = frac(seed * 0.00137) * 100.0 + 1.0;
float3 p3 = frac(float3(co.xyx * safeSeed) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return frac((p3.x + p3.y) * p3.z);
}
2. 从任何角度观看都会整齐横向偏移的“相机右向量”提取
如果将顶点沿世界空间的X轴(float3(1, 0, 0))方向偏移,那么从侧面(X轴方向)观察相机时,顶点会朝相机前后移动,从而看不出切片错位。
要始终相对于当前相机画面水平方向平移切片,需从视图矩阵 UNITY_MATRIX_V 的第0行(UNITY_MATRIX_V[0].xyz)获取相机的右方向向量,将Y分量设为 0 并在水平面上归一化[cite: 1]。
Varyings vert(Attributes input)
{
Varyings output;
// 時間ステップの離散化(カクカクしたデジタルな切り替わりを作る)
float steppedTime = floor(_Time.y * _GlitchSpeed);
// ワールド座標への変換
float3 posWS = TransformObjectToWorld(input.positionOS.xyz);
// ビュー行列からカメラの水平右ベクトルを抽出(ゼロ除算防止の微小値を加算)
float3 cameraRightWS = UNITY_MATRIX_V[0].xyz;
cameraRightWS.y = 0.0;
cameraRightWS = normalize(cameraRightWS + float3(1e-5, 0.0, 0.0));
// Y座標をスライス分割し、確率(_GlitchProbability)に応じて発火判定(0 or 1)
float sliceY = floor(posWS.y * _GlitchFrequency);
float isGlitch = step(1.0 - _GlitchProbability, Hash2D(float2(sliceY, steppedTime), _Seed));
// -1.0 〜 +1.0 のランダムな横シフト量を算出して加算
float shiftX = (Hash2D(float2(sliceY, steppedTime + 1.0), _Seed) - 0.5) * 2.0 * _GlitchAmount;
posWS += cameraRightWS * (shiftX * isGlitch);
output.positionWS = posWS;
output.positionCS = TransformWorldToHClip(posWS);
output.uv = input.uv;
return output;
}
3. URP中“Alpha为0时变黑”Bug的原因与永久对策
试图通过自定义着色器在 Alpha 混合与加法混合之间切换时,如果像下面这样定义名为 _DstBlend 的属性,就会陷入意想不到的陷阱。
// 【危険】URP標準シェーダーと同じ名前を使うと値が汚染される
Properties {
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend", Float) = 10
}
Pass {
Blend SrcAlpha [_DstBlend]
}
为什么会变得漆黑?
在Unity(URP)中新建材质时,最初会分配Universal Render Pipeline/Lit(Opaque:不透明),并且材质内部会保存_DstBlend = 0(BlendMode.Zero)。
之后切换到自定义着色器时,如果使用相同的_DstBlend变量名,着色器侧的初始值(10 = OneMinusSrcAlpha)会被忽略,0(将背景色乘以0涂黑的设置)会原样继承下来。
解决方案:更改为固有属性名称
为避免与URP标准着色器的内部属性名(_DstBlend、_SrcBlend、_Cull 等)发生冲突,只需将其更改为带有前缀的固有名称(如_HoloDstBlend),即可从创建新材质开始100%确保实现透明效果。
Properties {
[Enum(AlphaBlend_OneMinusSrcAlpha, 10, Additive_One, 1)]
_HoloDstBlend ("Blend Mode (透過ブレンド方式)", Float) = 10
}
Pass {
Blend SrcAlpha [_HoloDstBlend]
ZWrite Off
}
如何制作更专业的“扫描线×故障艺术集成全息图”?
在本文介绍的“跟随摄像机的顶点切片故障艺术”[cite: 1]基础上,将以下元素整合到单个Pass中,即可完成达到科幻电影或商业游戏级别的全息图表现。
- 具有不同速度和宽度的“双扫描线(Scanline 0 / 1)”干涉滚动[cite: 2]
- UV水平跳跃、区块缺失(信号丢失)、数字雪花噪声的合成[cite: 1]
- 突出轮廓的菲涅尔边缘光(Rim Lighting)与双层Alpha控制(Master Alpha / Body Alpha)
- 通过
MaterialPropertyBlock实现零 GC 控制,以及用TriggerGlitchBurst()一行代码在受到伤害时播放剧烈噪声的 C# 控制器
📦 开箱即用的成品包(BOOTH)
面向「希望立刻将本方案在30秒内导入自己的游戏或VRChat化身!」的用户,我们在BOOTH上发布了整合了统一着色器、C# Burst控制脚本及专用Inspector扩展(含三种一键预设)的成品Asset。
👉 【Unity URP】HoloFX - 扫描线×故障风集成全息着色器(BOOTH链接)
查看点赞用户列表
0
注册新用户以更便捷地使用Qiita
- 获取符合您需求的文章
- 您可以高效地回顾有用信息
- 您可以使用深色主题
来源:Qiita · Unity 开发社区 · qiita.com