跳到正文
Qiita · Unity 开发社区· 来源· YYYGame·· 1 小时前AI 评分42

Unity URP 中实现任意角度不崩坏的“顶点切片故障”特效及解决 Alpha 为 0 变黑的方法

【Unity URP】どの角度でも破綻しない「頂点スライスグリッチ」の数式と、Alphaを0にすると真っ黒になる罠の解決法

正文 · AI 翻译

在制作科幻游戏的全息通信或赛博朋克风格的噪点效果时,将模型躯干水平切片(slice)并向横向大幅偏移的顶点切片故障是一种非常有效的方法[cite: 1]。

然而,一旦尝试用着色器(HLSL)实现,就经常会遇到以下3个麻烦。

  1. 当摄像机绕到模型侧面时,顶点看起来不是横向偏移,而是向“深度方向(前后)”飞出
  2. 在使用sin()的随机数公式时,如果输入较大的Seed值,会出现规则的条纹图案,或者数值会粘附在0上[cite: 1]
  3. 即使已将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

查看点赞用户列表

0

注册新用户以更便捷地使用Qiita

  1. 获取符合您需求的文章
  2. 您可以高效地回顾有用信息
  3. 您可以使用深色主题

注册后您可以做什么

来源:Qiita · Unity 开发社区 · qiita.com