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

Unity URP 实现怪物咆哮特效:解决 atan2 接缝与 3D 坐标追踪

【Unity URP】モンスターの咆哮エフェクトを作る!極座標(atan2)の切れ目解消と3D座標追従の数学

正文 · AI 翻译

在动作游戏中,当怪物咆哮或发生大爆炸时,加入「以Boss嘴部为中心的空间呈放射状扭曲的特效」,能瞬间大幅提升画面的冲击力。

然而,一旦尝试用着色器(HLSL)通过从中心出发的角度(atan2)来制作放射状噪声,就经常会陷入以下3个陷阱。

  1. 受屏幕宽高比(如16:9等)影响,扭曲会变成横向拉长的椭圆而非正圆
  2. atan2 返回值($-\pi \sim \pi$)的边界处,即“左侧水平方向(180度)”,会出现明显的噪声断裂(接缝)
  3. 3D空间中Boss的位置与着色器内的2D屏幕UV坐标不一致

本文将数学性地解决这三大课题,讲解实现无破绽的“集中线条纹扭曲”的核心技术。


整体处理流程

从C#端传递的3D目标坐标转换为屏幕UV,并在片段着色器内计算极坐标噪声的流程如下。


1. 纵横比校正与“中心清除蒙版”的计算

全屏着色器的UV坐标无论屏幕尺寸如何,都被归一化为0.0 〜 1.0。如果直接计算距离(length),在16:9的屏幕上会变成横向拉伸的椭圆。

将_ScreenParams.x / _ScreenParams.y乘以X分量进行校正,并创建“让Boss的脸(中心)清晰可见,仅对外围进行扭曲”的甜甜圈状蒙版。

// 発生中心(_RoarCenter.xy)から現在のピクセル(uv)へのベクトル
float2 dir = uv - _RoarCenter.xy;

// アスペクト比補正(16:9等の画面でも正円の距離場を作る)
float aspect = _ScreenParams.x / max(_ScreenParams.y, 1.0);
float2 dirAspect = float2(dir.x * aspect, dir.y);
float dist = length(dirAspect);

// 中心(_CenterClearRadius)は0、外周に向かって1になる滑らかなマスク
float radialMask = smoothstep(_CenterClearRadius, _CenterClearRadius + _MaskSmoothness, dist);
float activeEffect = _Intensity * radialMask;

2. atan2 边界接缝(左端的切口)完全消除的公式

要创建放射状集中线,需通过atan2(y, x)求出角度,并将其输入到一维噪声函数(Noise1D)中。
然而,atan2的值域为 $-\pi \sim \pi$。如果直接像Noise1D(angle * _StreakFrequency)那样编写,在左端($-\pi$ 与 $\pi$ 相接处)会出现相位不连续,导致边界线清晰可见。

为防止此问题,必须遵循以下原则:①将角度归一化至0.0 〜 1.0,随后②将乘上的频率(Frequency)在floor处四舍五入为整数。

// 1次元バリューノイズ関数
float Hash(float co)
{
    float3 p3 = frac(float3(co, co, co) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return frac((p3.x + p3.y) * p3.z);
}

float Noise1D(float x)
{
    float i = floor(x);
    float f = frac(x);
    f = f * f * (3.0 - 2.0 * f); // エルミート補間(Smoothstep)
    return lerp(Hash(i), Hash(i + 1.0), f);
}

// --- フラグメントシェーダー内 ---
// 1. atan2(-PI〜PI) を 0.0〜1.0 の正規化角度に変換
float normalizedAngle = (atan2(dirAspect.y, dirAspect.x) + PI) / TWO_PI;

// 2. 周波数を整数に丸めることで、0.0と1.0の境界での位相ズレを抑える
float freq = floor(_StreakFrequency);
float t = _Time.y * _StreakSpeed;

// 異なる周期・逆方向の2つのノイズを合成して単調さを消す
float streakNoise  = Noise1D(normalizedAngle * freq + t) * 2.0 - 1.0;
float streakNoise2 = Noise1D(normalizedAngle * (freq * 0.5) - t * 1.3) * 2.0 - 1.0;
float combinedStreak = (streakNoise * 0.6 + streakNoise2 * 0.4);

此外,在使用求得的噪声值偏移UV时,除了从中心出发的「法线方向(指向外侧的向量)」外,还混合约0.5的正交「切线方向(旋转方向的向量)」,从而产生如空气漩涡般起伏的真实扭曲效果。

// ゼロ除算防止の微小値(1e-5)を足して正規化
float2 normDir = normalize(dir + 1e-5);
// 法線に直交する接線ベクトル (-y, x)
float2 tangentDir = float2(-normDir.y, normDir.x);

// 放射方向+回転方向にUVオフセットを合成
float2 streakOffset = (normDir + tangentDir * 0.5) * (combinedStreak * _StreakStrength * activeEffect);

3. C#端的3D世界坐标 → 屏幕UV自动转换

要以Boss的口部(Transform)为中心生成特效,需使用Camera.WorldToViewportPoint。视口坐标范围为左下(0, 0)至右上(1, 1),因此可直接将其作为Shader的UV坐标传入。

不过,当目标绕至相机背面(z < 0)时,坐标会发生翻转的问题,因此务必加入viewportPos.z的符号检查。

private static readonly int PropRoarCenter = Shader.PropertyToID("_RoarCenter");

private void UpdateRoarCenter(Material mat, Camera cam, Transform target)
{
    Vector3 viewportPos = cam.WorldToViewportPoint(target.position);
    
    // カメラの背後にいる場合は画面中央(0.5, 0.5)へフォールバック
    Vector2 centerUV = (viewportPos.z < 0f) 
        ? new Vector2(0.5f, 0.5f) 
        : new Vector2(viewportPos.x, viewportPos.x); // ※yはviewportPos.y

    centerUV.y = (viewportPos.z < 0f) ? 0.5f : viewportPos.y;
    mat.SetVector(PropRoarCenter, new Vector4(centerUV.x, centerUV.y, 0f, 0f));
}

要进一步完善为更实用的“咆哮・冲击波系统”,该怎么做?

将以下元素与本文介绍的「集中线 streak 扭曲」结合,即可实现与商业动作游戏同等水平的画面演出。

  • 集成带脉冲振动的径向模糊(放射状模糊)
  • 边缘区域 RGB 分离更明显的多采样色差(Chromatic Aberration)
  • 通过 #pragma multi_compile_local 和 [unroll] 进行静态循环展开,确保在移动端或 Switch 上也不会增加负担
  • 零 GC Alloc、PlayRoar() 一行即可实现衰减播放的 C# 控制器,以及带一键预设的 CustomEditor

📦 开箱即用的成品包(BOOTH)

面向「想在30秒内立刻将特效导入自己的游戏!」的用户,我们在BOOTH上发布了整合了着色器、3D坐标跟随脚本以及专用Inspector扩展(包含3种预设和测试播放按钮)的成品包。
👉 【URP】RoarFX - 咆哮/冲击波后处理着色器(BOOTH链接)

📘 全部代码与优化手法的完整解说书(Zenn Book)

此外,我们还从径向模糊和色差的一体化算法到ShaderGUI / CustomEditor 的实现代码全文,在Zenn上以逐步讲解的方式发布了一本技术书籍。想要深入了解其原理的朋友请务必查阅!
👉 Unity URP 实战后处理开发:咆哮/冲击波着色器与编辑器扩展的制作方法(Zenn链接)

0

查看点赞用户列表

0

注册为新用户并更便捷地使用Qiita

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

注册后您可以做什么

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