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
相关产品推荐
相关产品推荐

