You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity Shadergraph与C#波浪高度计算结果不一致问题排查

问题背景

我正在开发一个浮力脚本,该脚本会采样点的位置,判断其是否低于某个“水位”,并根据深度在该位置施加力。同时,我用Shadergraph制作了视觉效果良好的水面Shader,通过Simple Noise节点结合顶点位移实现波浪效果。为将位移后的波浪值作为“水位”用于浮力计算,我把Shadergraph的节点逻辑完整重写为C#代码,以此采样指定位置的水位。

核心问题

最终位移后的水面网格与C#计算出的水位位置存在差异,导致浮力脚本判断的水位与实际水面高低不符。差异不大,我推测问题出在C#对节点图或Simple Noise的转换实现中。请问我的推测是否正确?若正确,问题出在哪?我的理解误区是什么?若不正确,还有哪些可能的原因?

实现细节

节点图逻辑

  • 将世界坐标作为UV,进行偏移和平铺处理
  • 将UV输入Simple Noise节点,将噪声值乘以强度
  • 对输出结果进行钳制
  • 重复步骤1-3并将结果相加以增加细节
  • 将顶点位置的Y值替换为合并后的波浪值

C#脚本

通过获取材质参数,实现GetWaveHeightAtPosition方法计算指定位置的波浪高度,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WaveHeightCalculator : MonoBehaviour
{
    // Step 1
    [SerializeField] Material _waterMaterial;

    [Header("Waves")]
    [SerializeField] float _waveTiling;
    [SerializeField] float _waveOffset;
    [SerializeField] float _waveMin;
    [SerializeField] float _waveMax;
    [Header("Small Waves")]
    [SerializeField] float _wavesSmallScale;
    [SerializeField] float _wavesSmallStrength;
    [SerializeField] Vector2 _wavesSmallVelocity;
    [Header("Large Waves")]
    [SerializeField] float _wavesLargeScale;
    [SerializeField] float _wavesLargeStrength;
    [SerializeField] Vector2 _wavesLargeVelocity;
    
    // Step 2
    private void OnValidate()
    {
        _waterMaterial = GetComponent<Renderer>().sharedMaterial;
        SetVariables();
    }
    void SetVariables()
    {
        _waveTiling = _waterMaterial.GetFloat("_Wave_Tiling");
        _waveOffset = _waterMaterial.GetFloat("_Wave_Offset");
        _waveMin = _waterMaterial.GetFloat("_Wave_Min");
        _waveMax = _waterMaterial.GetFloat("_Wave_Max");
        _wavesSmallScale = _waterMaterial.GetFloat("_Waves_Small_Scale");
        _wavesSmallStrength = _waterMaterial.GetFloat("_Waves_Small_Strength");
        _wavesSmallVelocity = _waterMaterial.GetVector("_Waves_Small_Velocity");
        _wavesLargeScale = _waterMaterial.GetFloat("_Waves_Large_Scale");
        _wavesLargeStrength = _waterMaterial.GetFloat("_Waves_Large_Strength");
        _wavesLargeVelocity = _waterMaterial.GetVector("_Waves_Large_Velocity");
    }

    // Step 3
    public float GetWaveHeightAtPosition(Vector3 position)
    {
        Vector2 noiseMapUV;
        noiseMapUV = new Vector2(position.x, position.z) * _waveTiling;
        // Calculate Small Waves
        Vector2 wavesSmallUVOffset = (Time.time / 20) * _wavesSmallVelocity;
        float noiseValueAtUVPlusOffset = UnitySimpleNoiseAtUV(noiseMapUV + wavesSmallUVOffset, _wavesSmallScale);
        float wavesSmall = noiseValueAtUVPlusOffset * _wavesSmallStrength;
        // Calculate Large Waves
        Vector2 wavesLargeUVOffset = (Time.time / 20) * _wavesLargeVelocity;
        noiseValueAtUVPlusOffset = UnitySimpleNoiseAtUV(noiseMapUV + wavesLargeUVOffset, _wavesLargeScale);
        float wavesLarge = noiseValueAtUVPlusOffset * _wavesLargeStrength;
        // Combine
        float waveHeight = wavesSmall + wavesLarge;
        // Clamp
        waveHeight = Mathf.Clamp(waveHeight, _waveMin, _waveMax);
        // Offset
        waveHeight += _waveOffset;
        return waveHeight;
    }
}

转换的Simple Noise代码

将Shadergraph生成的Simple Noise代码转换为C#实现,代码如下:

float float_frac(float x) { return x - Mathf.Floor(x);}
    Vector2 frac(Vector2 x) { return x - new Vector2(Mathf.Floor(x.x), Mathf.Floor(x.y));}
    float sin(float x) { return Mathf.Sin(x);}
    float dot(Vector2 a, Vector2 b) { return a.x * b.x + a.y * b.y;}
    float float_floor(float x) { return Mathf.Floor(x);}
    Vector2 floor(Vector2 x) { return new Vector2(Mathf.Floor(x.x), Mathf.Floor(x.y));}
    float float_abs(float x) { return Mathf.Abs(x);}
    Vector2 abs(Vector2 x) { return new Vector2(Mathf.Abs(x.x), Mathf.Abs(x.y));}
    float pow (float x, float y) { return Mathf.Pow(x, y);}

    float Unity_SimpleNoise_RandomValue_float (Vector2 uv)
    {
        float angle = dot(uv, new Vector2(12.9898f, 78.233f));
        return float_frac(sin(angle) * 43758.5453f);
    }
    float Unity_SimpleNnoise_Interpolate_float (float a, float b, float t)
    {
        return (1.0f - t) * a + (t * b);
    }
    float Unity_SimpleNoise_ValueNoise_float (Vector2 uv)
    {
        Vector2 i = floor(uv);
        Vector2 f = frac(uv);
        f = (f * f) * (new Vector2 (3.0f, 3.0f) - new Vector2(2.0f, 2.0f) * f);

        uv = abs(frac(uv) - new Vector2 (0.5f, 0.5f));
        Vector2 c0 = i + new Vector2(0.0f, 0.0f);
        Vector2 c1 = i + new Vector2(1.0f, 0.0f);
        Vector2 c2 = i + new Vector2(0.0f, 1.0f);
        Vector2 c3 = i + new Vector2(1.0f, 1.0f);
        float r0 = Unity_SimpleNoise_RandomValue_float(c0);
        float r1 = Unity_SimpleNoise_RandomValue_float(c1);
        float r2 = Unity_SimpleNoise_RandomValue_float(c2);
        float r3 = Unity_SimpleNoise_RandomValue_float(c3);

        float bottomOfGrid = Unity_SimpleNnoise_Interpolate_float(r0, r1, f.x);
        float topOfGrid = Unity_SimpleNnoise_Interpolate_float(r2, r3, f.x);
        float t = Unity_SimpleNnoise_Interpolate_float(bottomOfGrid, topOfGrid, f.y);
        return t;
    }
    float UnitySimpleNoiseAtUV(Vector2 UV, float Scale)
    {
        float t = 0.0f;

        float freq = pow(2.0f, 0);
        float amp = pow(0.5f, 3-0);
        t += Unity_SimpleNoise_ValueNoise_float(new Vector2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;

        freq = pow(2.0f, 1);
        amp = pow(0.5f, 3-1);
        t += Unity_SimpleNoise_ValueNoise_float(new Vector2(UV.x * Scale / freq, UV.y * Scale / freq)) * amp;

        freq = pow(2.0f, 2);
        amp = pow(0.5f, 3-2);
        t += Unity_SimpleNoise_ValueNoise_float(new Vector2(UV.x * Scale / freq, UV.y * Scale / freq)) * amp;

        return t;
    }
解答

你的推测是正确的,问题主要出在Simple Noise的C#转换实现和逻辑细节上:

1. Simple Noise实现的核心错误

在Unity_SimpleNoise_ValueNoise_float函数中,你额外添加了一行uv = abs(frac(uv) - new Vector2 (0.5f, 0.5f));,这行代码在ShaderGraph的原生Simple Noise节点中不存在。它会修改后续计算网格坐标c0-c3的基准,直接导致噪声输出结果和Shader中的逻辑完全偏离,这是差异产生的主要原因。删除这行多余代码后,噪声计算逻辑就能和ShaderGraph对齐。

2. 逻辑顺序的潜在偏差

确认ShaderGraph中的步骤顺序:是否是合并波浪值 → 钳制 → 加偏移?你的C#代码逻辑是符合这个顺序的,但如果Shader中是先加偏移再钳制,就会出现数值差异。不过从你的描述来看,这部分不是主要问题。

3. 其他次要可能原因

  • 浮点精度差异:GPU和CPU的浮点计算精度存在细微差别,多次噪声叠加后可能积累极小误差。可以用Mathf.Approximately判断近似相等,或调整精度容忍度。
  • UV坐标空间不一致:确认Shader中使用的是世界坐标的XZ轴作为UV,和C#中new Vector2(position.x, position.z)的处理一致。如果Shader用的是模型空间坐标,会直接导致偏差。
  • 时间同步问题:C#中的Time.time和Shader中的_Time.y是否同步?若项目使用了Time.timeScale,要确保两者使用的是同一时间源(比如都用缩放时间或都用 unscaled 时间)。

内容的提问来源于stack exchange,提问作者feenix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 17:40:25