1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
| // FluidSimulation.compute
#pragma kernel InitFluid // 初始化 #pragma kernel AdvectVelocity // 速度平流 #pragma kernel DiffuseVelocity // 速度扩散 #pragma kernel PressureSolve // 压力求解 #pragma kernel ProjectVelocity // 速度投影(确保不可压缩) #pragma kernel AdvectDye // 染料(可视化用)平流 #pragma kernel AddForce // 外力注入
// ---- 数据布局 ---- // velocity: .xy = 速度 (vx, vy), .z = 压力, .w = 染料浓度
RWTexture2D<float4> _Result; Texture2D<float4> _Source; Texture2D<float4> _PressureField; // 独立的压力缓冲(Jacobi 迭代用)
uint2 _Resolution; float _DeltaTime; float _Viscosity; // 运动黏度 float _DyeDiffusion; // 染料扩散系数 float _VorticityStr; // 涡旋限制强度
// 外力参数(由 C# 每帧传入) float2 _ForcePos; // 外力作用位置(像素坐标) float2 _ForceDir; // 外力方向和强度 float _ForceRadius; // 外力影响半径 float2 _DyeSource; // 染料注入位置 float _DyeAmount; // 染料注入量
SamplerState sampler_linear_clamp; // 双线性采样,钳制边界
// 安全的纹理采样(带边界检测) float4 SampleField(Texture2D<float4> field, float2 pos) { float2 uv = (pos + 0.5) / float2(_Resolution); return field.SampleLevel(sampler_linear_clamp, uv, 0); }
// 从像素坐标获取场值(整数寻址) float4 GetField(Texture2D<float4> field, int2 pos) { // 边界处理:速度为零(无滑边界条件) int2 clamped = clamp(pos, int2(0, 0), int2(_Resolution) - 1); return field[clamped]; }
// ---- 初始化 ---- [numthreads(8, 8, 1)] void InitFluid(uint3 id : SV_DispatchThreadID) { if (any(id.xy >= _Resolution)) return; // 初始状态:极小噪声(打破对称性) float noise = frac(sin(dot(float2(id.xy), float2(12.9898, 78.233))) * 43758.5); _Result[id.xy] = float4(0, 0, 1.0, 0) + float4(noise, noise, 0, 0) * 0.0001; }
// ---- 半拉格朗日平流(速度)---- // 逆向追踪:从当前位置沿负速度方向采样上一帧的值 // 无条件稳定,即使 dt 较大也不会发散 [numthreads(8, 8, 1)] void AdvectVelocity(uint3 id : SV_DispatchThreadID) { if (any(id.xy >= _Resolution)) return;
float2 pos = float2(id.xy); float4 curr = _Source[id.xy];
// 逆向追踪位置(从当前位置沿速度方向回溯) float2 prevPos = pos - _DeltaTime * curr.xy;
// 在上游位置插值采样 float4 advected = SampleField(_Source, prevPos); _Result[id.xy] = float4(advected.xy, curr.z, curr.w); // 更新速度,保留压力 }
// ---- 黏性扩散(显式差分,速度扩散)---- [numthreads(8, 8, 1)] void DiffuseVelocity(uint3 id : SV_DispatchThreadID) { if (any(id.xy >= _Resolution)) return;
int2 p = int2(id.xy); float4 c = _Source[p]; float4 n = GetField(_Source, p + int2(0, 1)); float4 s = GetField(_Source, p - int2(0, 1)); float4 e = GetField(_Source, p + int2(1, 0)); float4 w = GetField(_Source, p - int2(1, 0));
// Laplacian(中心差分) float4 laplacian = n + s + e + w - 4.0 * c;
// 显式黏性扩散 float2 newVel = c.xy + _DeltaTime * _Viscosity * laplacian.xy;
_Result[p] = float4(newVel, c.z, c.w); }
// ---- 压力 Jacobi 迭代(一次迭代,需要多次 Dispatch 调用收敛)---- [numthreads(8, 8, 1)] void PressureSolve(uint3 id : SV_DispatchThreadID) { if (any(id.xy >= _Resolution)) return;
int2 p = int2(id.xy); float4 c = _Source[p]; float4 n = GetField(_Source, p + int2(0, 1)); float4 s = GetField(_Source, p - int2(0, 1)); float4 e = GetField(_Source, p + int2(1, 0)); float4 w = GetField(_Source, p - int2(1, 0));
// 速度散度(不可压缩约束) float divV = 0.5 * ((e.x - w.x) + (n.y - s.y));
// Jacobi 迭代:p_new = (p_n + p_s + p_e + p_w - divV) / 4 float newPressure = (n.z + s.z + e.z + w.z - divV) * 0.25;
_Result[p] = float4(c.xy, newPressure, c.w); }
// ---- 速度投影(减去压力梯度,确保 div(v)=0)---- [numthreads(8, 8, 1)] void ProjectVelocity(uint3 id : SV_DispatchThreadID) { if (any(id.xy >= _Resolution)) return;
int2 p = int2(id.xy); float4 c = _Source[p]; float4 n = GetField(_Source, p + int2(0, 1)); float4 s = GetField(_Source, p - int2(0, 1)); float4 e = GetField(_Source, p + int2(1, 0)); float4 w = GetField(_Source, p - int2(1, 0));
// 压力梯度 float gradPx = 0.5 * (e.z - w.z); float gradPy = 0.5 * (n.z - s.z);
// 从速度中减去压力梯度 float2 projectedVel = c.xy - float2(gradPx, gradPy);
// 涡旋限制(Vorticity Confinement) // 旋度场:curl = dVy/dx - dVx/dy float curlC = 0.5 * ((e.y - w.y) - (n.x - s.x)); float curlN = 0.5 * ((GetField(_Source, p + int2(1,1)).y - GetField(_Source, p + int2(-1,1)).y) - (GetField(_Source, p + int2(0,2)).x - c.x)); float curlS = 0.5 * ((GetField(_Source, p + int2(1,-1)).y - GetField(_Source, p + int2(-1,-1)).y) - (c.x - GetField(_Source, p + int2(0,-2)).x)); float curlE = 0.5 * ((GetField(_Source, p + int2(2,0)).y - c.y) - (GetField(_Source, p + int2(1,1)).x - GetField(_Source, p + int2(1,-1)).x)); float curlW = 0.5 * ((c.y - GetField(_Source, p + int2(-2,0)).y) - (GetField(_Source, p + int2(-1,1)).x - GetField(_Source, p + int2(-1,-1)).x));
// 涡旋力方向(梯度指向旋度最大的方向) float2 eta = normalize(float2(abs(curlE) - abs(curlW), abs(curlN) - abs(curlS)) + 1e-5); projectedVel += _DeltaTime * _VorticityStr * float2(eta.y, -eta.x) * curlC;
// 无滑边界(边缘速度为零) if (p.x == 0 || p.y == 0 || p.x == (int)_Resolution.x - 1 || p.y == (int)_Resolution.y - 1) projectedVel = float2(0, 0);
_Result[p] = float4(projectedVel, c.z, c.w); }
// ---- 染料平流(可视化用)---- [numthreads(8, 8, 1)] void AdvectDye(uint3 id : SV_DispatchThreadID) { if (any(id.xy >= _Resolution)) return;
float2 pos = float2(id.xy); float4 curr = _Source[id.xy]; float2 prevPos = pos - _DeltaTime * curr.xy; float advDye = SampleField(_Source, prevPos).w;
// 染料扩散衰减 advDye *= exp(-_DeltaTime * _DyeDiffusion);
// 染料注入(自动发射源) float2 diff = pos - _DyeSource; float inject = exp(-dot(diff, diff) / (_ForceRadius * _ForceRadius)) * _DyeAmount; advDye += inject;
_Result[id.xy] = float4(curr.xy, curr.z, saturate(advDye)); }
// ---- 外力注入(鼠标交互或自动发射)---- [numthreads(8, 8, 1)] void AddForce(uint3 id : SV_DispatchThreadID) { if (any(id.xy >= _Resolution)) return;
float4 curr = _Source[id.xy]; float2 pos = float2(id.xy);
// 高斯衰减的力场 float2 diff = pos - _ForcePos; float influence = exp(-dot(diff, diff) / (_ForceRadius * _ForceRadius));
float2 newVel = curr.xy + influence * _ForceDir * _DeltaTime;
_Result[id.xy] = float4(newVel, curr.z, curr.w); }
|