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

Unity Shader Graph水面模拟:木桶漂浮的水面高度计算与脚本应用

Unity Shader Graph水面下的木桶漂浮实现方案

一、提取水面高度的两种核心方法

因为你的水面效果基于Shader Graph,核心是让C#脚本能获取到对应位置的水面高度,这里提供两种常用方案:

1. 脚本复现Shader的高度计算逻辑(推荐高效方案)

如果你的Shader Graph里的水面高度是通过公式(比如噪声、正弦波叠加)计算的,直接在C#里写完全一致的逻辑即可,避免额外渲染开销。

比如Shader Graph中你用了这类波浪计算:
float height = _Amplitude * sin(_Time.y * _Speed + worldPos.x * _FreqX + worldPos.z * _FreqZ);

对应C#代码:

// 注意:以下参数要和Shader Graph里的变量完全匹配
public float waveAmplitude = 0.5f;
public float waveSpeed = 2f;
public float waveFreqX = 1f;
public float waveFreqZ = 1f;

// 根据世界坐标x/z获取对应水面高度
private float GetWaterHeight(Vector3 worldPos)
{
    float timeFactor = Time.time * waveSpeed;
    float xFactor = worldPos.x * waveFreqX;
    float zFactor = worldPos.z * waveFreqZ;
    float height = waveAmplitude * Mathf.Sin(timeFactor + xFactor + zFactor);
    
    // 如果是多层波浪叠加,继续追加计算即可
    // height += waveAmplitude2 * Mathf.Sin(timeFactor * speed2 + xFactor * freqX2 + zFactor * freqZ2);
    return height;
}

2. 用RenderTexture采样高度值(复杂Shader适配方案)

如果你的水面Shader逻辑太复杂(比如用了复杂节点组合、纹理采样),没法轻易复现,就把高度值渲染到RenderTexture中,再用脚本采样:

  • 在Shader Graph中添加自定义输出,将高度值输出到RenderTexture的R通道;
  • 创建一张合适分辨率的RenderTexture,用正交相机仅渲染水面层,把RenderTexture设为相机的目标纹理;
  • 脚本中通过Texture2D.ReadPixels或RenderTexture.active读取对应世界坐标的像素值,转换为高度。

二、脚本中运用高度实现漂浮效果

拿到高度后,就能控制木桶的位置和旋转,模拟真实漂浮:

1. 基础位置同步

public Transform barrel;
public float barrelYOffset = 0.3f; // 木桶底部到水面的偏移量,根据模型尺寸调整

void Update()
{
    Vector3 barrelWorldPos = barrel.position;
    // 获取木桶底部x/z对应的水面高度
    float waterHeight = GetWaterHeight(new Vector3(barrelWorldPos.x, 0, barrelWorldPos.z));
    // 更新木桶Y轴位置
    barrel.position = new Vector3(barrelWorldPos.x, waterHeight + barrelYOffset, barrelWorldPos.z);
}

2. 进阶:模拟水面倾斜

采样木桶周围几个点的高度,计算水面法线,让木桶随波浪倾斜:

void Update()
{
    Vector3 barrelWorldPos = barrel.position;
    float waterHeight = GetWaterHeight(new Vector3(barrelWorldPos.x, 0, barrelWorldPos.z));
    barrel.position = new Vector3(barrelWorldPos.x, waterHeight + barrelYOffset, barrelWorldPos.z);

    // 采样周围4个点计算水面法线
    float sampleRadius = 0.5f;
    float hRight = GetWaterHeight(new Vector3(barrelWorldPos.x + sampleRadius, 0, barrelWorldPos.z));
    float hLeft = GetWaterHeight(new Vector3(barrelWorldPos.x - sampleRadius, 0, barrelWorldPos.z));
    float hForward = GetWaterHeight(new Vector3(barrelWorldPos.x, 0, barrelWorldPos.z + sampleRadius));
    float hBack = GetWaterHeight(new Vector3(barrelWorldPos.x, 0, barrelWorldPos.z - sampleRadius));

    // 计算切线、副切线和法线
    Vector3 tangent = new Vector3(2 * sampleRadius, hLeft - hRight, 0);
    Vector3 bitangent = new Vector3(0, hBack - hForward, 2 * sampleRadius);
    Vector3 waterNormal = Vector3.Cross(tangent, bitangent).normalized;

    // 让木桶朝向法线方向
    barrel.rotation = Quaternion.FromToRotation(Vector3.up, waterNormal);
}

3. 超真实:加入物理浮力

给木桶添加Rigidbody,通过计算浸入体积施加浮力,模拟真实物理效果:

public Rigidbody barrelRb;
public float waterDensity = 1000f; // 水的密度,单位kg/m³
public float barrelVolume = 1f; // 木桶总体积

void FixedUpdate()
{
    Vector3 barrelPos = barrelRb.position;
    float waterHeight = GetWaterHeight(new Vector3(barrelPos.x, 0, barrelPos.z));
    // 计算浸入深度
    float submergeDepth = waterHeight - barrelPos.y + barrelYOffset;
    submergeDepth = Mathf.Clamp(submergeDepth, 0f, barrel.transform.localScale.y);
    
    // 计算排开水的体积
    float displacedVolume = barrelVolume * (submergeDepth / barrel.transform.localScale.y);
    // 浮力公式:浮力 = 水密度 * 排开体积 * 重力加速度
    float buoyancyForce = waterDensity * displacedVolume * Physics.gravity.magnitude;
    
    // 施加向上的浮力
    barrelRb.AddForce(Vector3.up * buoyancyForce);
}

三、关键注意事项

  • 确保C#脚本和Shader Graph中的参数(如振幅、速度、频率)完全一致,否则水面和木桶运动不同步;
  • 如果水面是局部空间计算(比如绑定在某个Plane下),要把木桶的世界坐标转换为水面的局部坐标再计算高度;
  • 多个漂浮物时,建议用Coroutine或固定时间间隔批量计算,避免每帧重复运算消耗性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 23:50:10