载入中...
搜索中...
未找到
FluidSurfaceRenderer.cpp
浏览该文件的文档.
2
3#include "common/Profile.h"
5#include "fluids/Fluids.h"
7#include "gpgpu/Gpgpu.h"
8#include "gpgpu/GpuBuffer.h"
9#include "gpgpu/Sequence.h"
10#include "graphics/Graphics.h"
11
12#include <glm/gtc/matrix_transform.hpp>
13#include <glm/gtc/quaternion.hpp>
14
15#include <algorithm>
16#include <cmath>
17#include <cstdio>
18#include <cstring>
19#include <fstream>
20#include <stdexcept>
21#include <utility>
22
23namespace eve::fluids {
24namespace {
25
26constexpr int kSsfPushVP0 = 0;
27constexpr int kSsfPushCount = 16;
28constexpr int kSsfPushOrthographic = 17;
29constexpr int kSsfPushNear = 19;
30constexpr int kSsfPushFar = 20;
31constexpr int kSsfPushTanHalf = 21;
32constexpr int kSsfPushAspect = 22;
33constexpr int kSsfPushW = 23;
34constexpr int kSsfPushH = 24;
35constexpr int kSsfPushRadius = 25;
36constexpr int kSsfPushMode = 26;
37constexpr int kSsfPushThick = 27;
38constexpr int kSsfPushFalloff = 28;
39constexpr int kSsfPushBlurRadius = 29;
40
41constexpr uint32_t kEmptyKey = 0xFFFFFFFFu;
42constexpr float kKeyScale = 16777215.f;
43
44int groupsFor(int count, int localSize = 64) { return (count + localSize - 1) / localSize; }
45
46} // namespace
47
49 : params_(params), preferGpu_(preferGpu) {
50 // Keep both CPU allocations and Vulkan image-sized buffers bounded even when
51 // a native caller bypasses Fluids::newSurfaceRenderer().
52 params_.width = std::clamp(params_.width, 8, 1024);
53 params_.height = std::clamp(params_.height, 8, 1024);
54 params_.aspect = float(params_.width) / float(params_.height);
55 const int pixels = params_.width * params_.height;
56 depth_.assign(size_t(pixels), 1e30f);
57 thickness_.assign(size_t(pixels), 0.f);
58 normals_.assign(size_t(pixels), glm::vec3(0.f));
59 color_.assign(size_t(pixels) * 4u, 0);
60}
61
63
64void FluidSurfaceRenderer::releaseGpu() noexcept {
65 delete seq_;
66 seq_ = nullptr;
67 delete shShade_;
68 shShade_ = nullptr;
69 delete shColorSplat_;
70 shColorSplat_ = nullptr;
71 delete shColorClear_;
72 shColorClear_ = nullptr;
73 delete shNormal_;
74 shNormal_ = nullptr;
75 delete shSmooth_;
76 shSmooth_ = nullptr;
77 delete shSplat_;
78 shSplat_ = nullptr;
79 delete shAnisotropicSplat_;
80 shAnisotropicSplat_ = nullptr;
81 delete shClear_;
82 shClear_ = nullptr;
83 delete stColor_;
84 stColor_ = nullptr;
85 delete stNormal_;
86 stNormal_ = nullptr;
87 delete stThick_;
88 stThick_ = nullptr;
89 delete stDepth_;
90 stDepth_ = nullptr;
91 delete bufColor_;
92 bufColor_ = nullptr;
93 delete bufColorAccum_;
94 bufColorAccum_ = nullptr;
95 delete bufParticleColors_;
96 bufParticleColors_ = nullptr;
97 delete bufNormal_;
98 bufNormal_ = nullptr;
99 delete bufThick_;
100 bufThick_ = nullptr;
101 delete bufDepthB_;
102 bufDepthB_ = nullptr;
103 delete bufDepthA_;
104 bufDepthA_ = nullptr;
105 delete bufParts_;
106 bufParts_ = nullptr;
107 delete bufAnisotropicParts_;
108 bufAnisotropicParts_ = nullptr;
109 multicolorGpuReady_ = false;
110 gpuOk_ = false;
111}
112
113void FluidSurfaceRenderer::setCamera(const glm::vec3& eye, const glm::vec3& target, const glm::vec3& up,
114 float fovYDeg) {
115 params_.eye = eye;
116 params_.target = target;
117 params_.up = up;
118 params_.fovYDeg = fovYDeg;
119 params_.aspect = float(params_.width) / float(params_.height);
120 resetReducedRenderers();
121}
122
124 std::vector<glm::vec3> pos;
125 sim.readPositions(pos);
127}
128
130 if (!sim) return;
131 render(*sim);
132}
133
134Result<void> FluidSurfaceRenderer::configureProjection(bool orthographic, float verticalHalfSize) {
135 if (!std::isfinite(verticalHalfSize) || verticalHalfSize < .001f || verticalHalfSize > 10000.f)
137 DiagnosticCode::InvalidArgument, "Invalid orthographic vertical half size", "fluids.surface.projection"));
138 params_.orthographic = orthographic;
139 params_.orthographicSize = verticalHalfSize;
140 resetReducedRenderers();
141 return Result<void>::success();
142}
143
145 if (surfaceDownsample_ > 1) {
146 ensureReducedRenderer();
147 auto prepared = reducedRenderer_->prepare();
148 if (!prepared) return prepared;
149 if (thicknessDownsample_ != surfaceDownsample_) {
150 ensureThicknessRenderer();
151 return thicknessRenderer_->prepare();
152 }
153 return Result<void>::success();
154 }
155 if (preferGpu_ && !gpuOk_) ensureGpu();
156 if (thicknessDownsample_ != 1) {
157 ensureThicknessRenderer();
158 return thicknessRenderer_->prepare();
159 }
160 return Result<void>::success();
161}
162
163void FluidSurfaceRenderer::render(const std::vector<glm::vec3>& positions, float particleRadius) {
164 if (surfaceDownsample_ > 1) {
165 ensureReducedRenderer();
166 reducedRenderer_->render(positions, particleRadius);
167 expandReducedOutputs(false);
168 return;
169 }
170 uniformVolumeGpuShade_ = false;
171 multicolorVolumeGpuShade_ = false;
172 anisotropicFrame_ = false;
173 renderInternal(positions, particleRadius);
174}
175
176void FluidSurfaceRenderer::renderInternal(const std::vector<glm::vec3>& positions, float particleRadius) {
177 EV_PROFILE_MODULE("fluids", "FluidSurfaceRenderer::reconstruct");
178 if (&positions != &positions_) positions_ = positions;
179 params_.particleRadius = particleRadius;
180 auxiliaryCurrent_ = true;
181 residentColorCurrent_ = false;
182 // Empty emitters and expired pools need no compute submission or readback.
183 // Clear every public output so a previously rendered surface cannot persist.
184 if (positions_.empty()) {
185 std::fill(depth_.begin(), depth_.end(), 1e30f);
186 std::fill(thickness_.begin(), thickness_.end(), 0.f);
187 std::fill(normals_.begin(), normals_.end(), glm::vec3(0.f));
188 std::fill(color_.begin(), color_.end(), uint8_t(0));
189 return;
190 }
191 if (preferGpu_ && !gpuOk_) ensureGpu();
192 if (gpuOk_) {
193 // GPU path implemented below; the CPU path is exercised by tests.
194 const int pixels = params_.width * params_.height;
195 if (multicolorVolumeGpuShade_ && !ensureMulticolorGpu()) multicolorVolumeGpuShade_ = false;
196 if (anisotropicFrame_ && !ensureAnisotropyGpu()) {
197 renderCpu();
198 return;
199 }
200 seq_->begin();
201 if (anisotropicFrame_) {
202 seq_->recordUpload(bufAnisotropicParts_, anisotropicSplats_.data(),
203 uint64_t(anisotropicSplats_.size()) * sizeof(float));
204 if (multicolorVolumeGpuShade_) uploadParticles();
205 } else {
206 uploadParticles();
207 }
208 const int groupsPix = groupsFor(pixels);
209 setCommonConstants(shClear_, params_.depthFalloff);
210 seq_->recordDispatch(shClear_, groupsPix);
211 auto* splatShader = anisotropicFrame_ ? shAnisotropicSplat_ : shSplat_;
212 setCommonConstants(splatShader, params_.depthFalloff);
213 seq_->recordDispatch(splatShader, groupsFor(int(positions_.size())));
214 if (multicolorVolumeGpuShade_) {
215 seq_->recordUpload(bufParticleColors_, volumeColors_.data(),
216 uint64_t(volumeColors_.size()) * sizeof(glm::vec4));
217 setCommonConstants(shColorClear_, params_.depthFalloff);
218 seq_->recordDispatch(shColorClear_, groupsPix);
219 setCommonConstants(shColorSplat_, params_.depthFalloff);
220 seq_->recordDispatch(shColorSplat_, groupsFor(int(positions_.size())));
221 }
222 bool inA = true;
223 for (int it = 0; it < params_.smoothIterations; ++it) {
224 shSmooth_->bindBuffer(1, inA ? bufDepthA_ : bufDepthB_);
225 shSmooth_->bindBuffer(2, inA ? bufDepthB_ : bufDepthA_);
226 setCommonConstants(shSmooth_, params_.depthFalloff);
227 seq_->recordDispatch(shSmooth_, groupsPix);
228 inA = !inA;
229 }
230 gpgpu::GpuBuffer* finalDepth = inA ? bufDepthA_ : bufDepthB_;
231 setCommonConstants(shNormal_, params_.depthFalloff);
232 seq_->recordDispatch(shNormal_, groupsPix);
233 setCommonConstants(shShade_, params_.depthFalloff);
234 seq_->recordDispatch(shShade_, groupsPix);
235 const bool deviceShadedVolume = uniformVolumeGpuShade_ || multicolorVolumeGpuShade_;
236 if (!deviceShadedVolume) {
237 seq_->recordDownload(finalDepth, stDepth_, uint64_t(pixels) * sizeof(uint32_t));
238 seq_->recordDownload(bufThick_, stThick_, uint64_t(pixels) * sizeof(uint32_t));
239 seq_->recordDownload(bufNormal_, stNormal_, uint64_t(pixels) * 4u * sizeof(float));
240 }
241 const bool keepColorResident = deviceShadedVolume && skipHostColorReadback_ &&
243 if (!keepColorResident) seq_->recordDownload(bufColor_, stColor_, uint64_t(pixels) * sizeof(uint32_t));
244 seq_->submit();
245
246 static_assert(sizeof(float) == sizeof(uint32_t));
247 if (!keepColorResident) stColor_->downloadBytes(color_.data(), uint64_t(color_.size()));
248 if (deviceShadedVolume) {
249 residentColorCurrent_ = true;
250 return;
251 }
252 gpuNormals_.resize(size_t(pixels));
253 // Reuse final output storage for packed readback, then decode in place.
254 // memcpy avoids aliasing uint keys through float pointers (including the empty sentinel).
255 stDepth_->downloadBytes(depth_.data(), uint64_t(depth_.size()) * sizeof(uint32_t));
256 stThick_->downloadBytes(thickness_.data(), uint64_t(thickness_.size()) * sizeof(uint32_t));
257 stNormal_->downloadBytes(gpuNormals_.data(), uint64_t(gpuNormals_.size()) * sizeof(glm::vec4));
258 const float nearZ = params_.nearZ;
259 const float farZ = params_.farZ;
260 for (int i = 0; i < pixels; ++i) {
261 uint32_t key, thick, rgba;
262 std::memcpy(&key, &depth_[size_t(i)], sizeof(key));
263 std::memcpy(&thick, &thickness_[size_t(i)], sizeof(thick));
264 std::memcpy(&rgba, &color_[size_t(i) * 4u], sizeof(rgba));
265 depth_[size_t(i)] = key == kEmptyKey ? 1e30f : nearZ + (float(key) / kKeyScale) * (farZ - nearZ);
266 thickness_[size_t(i)] = float(thick) / 256.f;
267 normals_[size_t(i)] = glm::vec3(gpuNormals_[size_t(i)]);
268 color_[size_t(i) * 4u + 0] = uint8_t(rgba & 255u);
269 color_[size_t(i) * 4u + 1] = uint8_t((rgba >> 8u) & 255u);
270 color_[size_t(i) * 4u + 2] = uint8_t((rgba >> 16u) & 255u);
271 color_[size_t(i) * 4u + 3] = uint8_t(rgba >> 24u);
272 }
273 if (customShading_) applyConfiguredShading();
274 return;
275 }
276 renderCpu();
277}
278
280 if (!graphics || !texture)
281 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Missing graphics or texture",
282 "fluids.surface.copyToTexture"));
283 if (residentColorCurrent_ && bufColor_) {
284 auto copied =
285 graphics->updateTextureFromResidentRgba8(texture, bufColor_->residentView(), params_.width, params_.height);
286 if (copied) return copied;
287 if (!copied.error() || copied.error()->code() != DiagnosticCode::Unsupported) return copied;
288 }
289 if (!graphics->updateTexture(texture, params_.width, params_.height, color_.data()))
291 Diagnostic::error(DiagnosticCode::Failed, "Fluid texture upload failed", "fluids.surface.copyToTexture"));
292 return Result<void>::success();
293}
294
296 graphics::Texture* texture) {
297 EV_PROFILE_MODULE("fluids", "FluidSurfaceRenderer::renderToTexture");
298 if (!graphics || !texture)
299 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Missing graphics or texture",
300 "fluids.surface.renderVolumeColorToTexture"));
301 skipHostColorReadback_ = true;
302 try {
304 } catch (...) {
305 skipHostColorReadback_ = false;
306 throw;
307 }
308 skipHostColorReadback_ = false;
309 return copyToTexture(graphics, texture);
310}
311
312bool FluidSurfaceRenderer::ensureGpu() {
313 if (gpuOk_) return true;
314 if (gpuAttempted_) return false;
315 gpuAttempted_ = true;
316 gpgpu_ = eve::gpgpu::Gpgpu::create();
317 if (!gpgpu_ || !gpgpu_->isAvailable()) return false;
318 const int pixels = params_.width * params_.height;
319 const int maxP = 65536;
320 try {
321 shClear_ = gpgpu_->newShader(kSsfClear);
322 shSplat_ = gpgpu_->newShader(kSsfSplat);
323 shSmooth_ = gpgpu_->newShader(kSsfSmooth);
324 shNormal_ = gpgpu_->newShader(kSsfNormal);
325 shShade_ = gpgpu_->newShader(kSsfShade);
326 bufParts_ = gpgpu_->newBuffer(maxP * 4 * int(sizeof(float)), "storage");
327 bufDepthA_ = gpgpu_->newBuffer(pixels * int(sizeof(uint32_t)), "storage");
328 bufDepthB_ = gpgpu_->newBuffer(pixels * int(sizeof(uint32_t)), "storage");
329 bufThick_ = gpgpu_->newBuffer(pixels * int(sizeof(uint32_t)), "storage");
330 bufNormal_ = gpgpu_->newBuffer(pixels * 4 * int(sizeof(float)), "storage");
331 bufColor_ = gpgpu_->newBuffer(pixels * int(sizeof(uint32_t)), "storage");
332 // kSsfShade declares binding 7. Keep a valid minimal descriptor until a
333 // multicolor frame transactionally replaces it with the full accumulator.
334 bufColorAccum_ = gpgpu_->newBuffer(5 * int(sizeof(uint32_t)), "storage");
335 stDepth_ = gpgpu_->newBuffer(pixels * int(sizeof(uint32_t)), "staging");
336 stThick_ = gpgpu_->newBuffer(pixels * int(sizeof(uint32_t)), "staging");
337 stNormal_ = gpgpu_->newBuffer(pixels * 4 * int(sizeof(float)), "staging");
338 stColor_ = gpgpu_->newBuffer(pixels * int(sizeof(uint32_t)), "staging");
339 seq_ = gpgpu_->newSequence();
340 } catch (...) {
341 releaseGpu();
342 return false;
343 }
344 if (!seq_ || !seq_->isAvailable()) {
345 releaseGpu();
346 return false;
347 }
348 shClear_->bindBuffer(1, bufDepthA_);
349 shClear_->bindBuffer(2, bufDepthB_);
350 shClear_->bindBuffer(3, bufThick_);
351 shClear_->bindBuffer(4, bufNormal_);
352 shClear_->bindBuffer(5, bufColor_);
353 shSplat_->bindBuffer(0, bufParts_);
354 shSplat_->bindBuffer(1, bufDepthA_);
355 shSplat_->bindBuffer(3, bufThick_);
356 shSmooth_->bindBuffer(1, bufDepthA_);
357 shSmooth_->bindBuffer(2, bufDepthB_);
358 // Iteration count is fixed at construction. Keep these descriptors stable
359 // across frames; rebinding marks them dirty and reallocates descriptor sets.
360 gpgpu::GpuBuffer* finalDepth = std::max(0, params_.smoothIterations) % 2 == 0 ? bufDepthA_ : bufDepthB_;
361 shNormal_->bindBuffer(1, finalDepth);
362 shNormal_->bindBuffer(4, bufNormal_);
363 shShade_->bindBuffer(1, finalDepth);
364 shShade_->bindBuffer(3, bufThick_);
365 shShade_->bindBuffer(4, bufNormal_);
366 shShade_->bindBuffer(5, bufColor_);
367 shShade_->bindBuffer(7, bufColorAccum_);
368 gpuOk_ = true;
369 return true;
370}
371
372bool FluidSurfaceRenderer::ensureMulticolorGpu() {
373 if (multicolorGpuReady_) return true;
374 if (!gpuOk_ || !gpgpu_) return false;
375 gpgpu::ComputeShader* colorClear = nullptr;
376 gpgpu::ComputeShader* colorSplat = nullptr;
377 gpgpu::GpuBuffer* particleColors = nullptr;
378 gpgpu::GpuBuffer* colorAccum = nullptr;
379 try {
380 colorClear = gpgpu_->newShader(kSsfColorClear);
381 colorSplat = gpgpu_->newShader(kSsfColorSplat);
382 particleColors = gpgpu_->newBuffer(65536 * 4 * int(sizeof(float)), "storage");
383 colorAccum = gpgpu_->newBuffer(params_.width * params_.height * 5 * int(sizeof(uint32_t)), "storage");
384 colorClear->bindBuffer(7, colorAccum);
385 colorSplat->bindBuffer(0, bufParts_);
386 colorSplat->bindBuffer(6, particleColors);
387 colorSplat->bindBuffer(7, colorAccum);
388 shShade_->bindBuffer(7, colorAccum);
389 } catch (...) {
390 delete colorAccum;
391 delete particleColors;
392 delete colorSplat;
393 delete colorClear;
394 return false;
395 }
396 delete bufColorAccum_;
397 bufColorAccum_ = colorAccum;
398 bufParticleColors_ = particleColors;
399 shColorSplat_ = colorSplat;
400 shColorClear_ = colorClear;
401 multicolorGpuReady_ = true;
402 return true;
403}
404
405bool FluidSurfaceRenderer::ensureAnisotropyGpu() {
406 if (shAnisotropicSplat_ && bufAnisotropicParts_) return true;
407 if (!gpuOk_ || !gpgpu_) return false;
408 try {
409 shAnisotropicSplat_ = gpgpu_->newShader(kSsfAnisotropicSplat);
410 bufAnisotropicParts_ = gpgpu_->newBuffer(65536 * 12 * int(sizeof(float)), "storage");
411 if (!shAnisotropicSplat_ || !bufAnisotropicParts_) throw std::runtime_error("anisotropy GPU resource");
412 shAnisotropicSplat_->bindBuffer(0, bufAnisotropicParts_);
413 shAnisotropicSplat_->bindBuffer(1, bufDepthA_);
414 shAnisotropicSplat_->bindBuffer(3, bufThick_);
415 return true;
416 } catch (...) {
417 delete shAnisotropicSplat_;
418 shAnisotropicSplat_ = nullptr;
419 delete bufAnisotropicParts_;
420 bufAnisotropicParts_ = nullptr;
421 return false;
422 }
423}
424
425void FluidSurfaceRenderer::buildAnisotropicSplats() {
426 const size_t count = std::min(positions_.size(), size_t(65536));
427 anisotropicSplats_.assign(count * 12u, 0.f);
428 const auto view = glm::lookAtRH(params_.eye, params_.target, params_.up);
429 const glm::mat3 viewRotation(view);
430 const float projectionScale = params_.orthographic ? params_.orthographicSize
431 : std::max(std::tan(glm::radians(params_.fovYDeg) * .5f), 1e-4f);
432 const float focal = float(params_.height) * .5f / projectionScale;
433 for (size_t i = 0; i < count; ++i) {
434 const auto center = view * glm::vec4(positions_[i], 1.f);
435 const float z = -center.z;
436 if (z <= params_.nearZ || z >= params_.farZ) continue;
437 const glm::vec3 radii = glm::max(particleRadii_[i], glm::vec3(.0005f));
438 const auto q = glm::normalize(glm::quat(particleOrientations_[i].w, particleOrientations_[i].x,
439 particleOrientations_[i].y, particleOrientations_[i].z));
440 const glm::mat3 rotation = viewRotation * glm::mat3_cast(q);
441 const glm::mat3 covariance =
442 rotation *
443 glm::mat3(radii.x * radii.x, 0.f, 0.f, 0.f, radii.y * radii.y, 0.f, 0.f, 0.f, radii.z * radii.z) *
444 glm::transpose(rotation);
445 const float invZ = params_.orthographic ? 1.f : 1.f / z;
446 const glm::vec3 jx(focal * invZ, 0.f, params_.orthographic ? 0.f : focal * center.x * invZ * invZ);
447 const glm::vec3 jy(0.f, -focal * invZ, params_.orthographic ? 0.f : -focal * center.y * invZ * invZ);
448 const glm::vec3 cjx = covariance * jx;
449 const glm::vec3 cjy = covariance * jy;
450 const float s00 = glm::dot(jx, cjx);
451 const float s01 = glm::dot(jx, cjy);
452 const float s11 = glm::dot(jy, cjy);
453 const float determinant = s00 * s11 - s01 * s01;
454 if (!std::isfinite(determinant) || determinant <= 1e-12f) continue;
455 const float inv00 = s11 / determinant;
456 const float inv01 = -s01 / determinant;
457 const float inv11 = s00 / determinant;
458 const float covZx = cjx.z;
459 const float covZy = cjy.z;
460 const float slopeX = covZx * inv00 + covZy * inv01;
461 const float slopeY = covZx * inv01 + covZy * inv11;
462 const float conditionalVariance = std::max(covariance[2][2] - (slopeX * covZx + slopeY * covZy), 1e-10f);
463 const float trace = s00 + s11;
464 const float eigenMax = .5f * (trace + std::sqrt(std::max(0.f, (s00 - s11) * (s00 - s11) + 4.f * s01 * s01)));
465 const float bound = std::sqrt(std::max(eigenMax, 0.f)) + 1.f;
466 const float divisor = params_.orthographic ? 1.f : z;
467 const float sx = (.5f + .5f * center.x / (divisor * projectionScale * params_.aspect)) * float(params_.width);
468 const float sy = (.5f - .5f * center.y / (divisor * projectionScale)) * float(params_.height);
469 float* out = anisotropicSplats_.data() + i * 12u;
470 out[0] = sx;
471 out[1] = sy;
472 out[2] = z;
473 out[3] = bound;
474 out[4] = inv00;
475 out[5] = inv01;
476 out[6] = inv11;
477 out[7] = std::sqrt(conditionalVariance);
478 out[8] = slopeX;
479 out[9] = slopeY;
480 }
481}
482
483void FluidSurfaceRenderer::uploadParticles() {
484 if (!gpuOk_) return;
485 const size_t count = std::min(positions_.size(), size_t(65536));
486 gpuParticles_.resize(count * 4u);
487 for (size_t i = 0; i < count; ++i) {
488 gpuParticles_[i * 4u + 0] = positions_[i].x;
489 gpuParticles_[i * 4u + 1] = positions_[i].y;
490 gpuParticles_[i * 4u + 2] = positions_[i].z;
491 gpuParticles_[i * 4u + 3] = params_.particleRadius;
492 }
493 seq_->recordUpload(bufParts_, gpuParticles_.data(), uint64_t(gpuParticles_.size()) * sizeof(float));
494}
495
496Result<void> FluidSurfaceRenderer::occludeWithSceneDepth(std::span<const float> sceneDepth, float depthBias) {
497 const size_t pixels = size_t(params_.width) * size_t(params_.height);
498 if (!auxiliaryCurrent_)
500 "Current color frame has no host-visible depth",
501 "fluids.surface.sceneDepth"));
502 if (sceneDepth.size() != pixels || !std::isfinite(depthBias) || depthBias < 0.f || depthBias > 1.f)
504 "Scene depth dimensions or bias are invalid",
505 "fluids.surface.sceneDepth"));
506 for (float value : sceneDepth) {
507 if (!std::isfinite(value) || value < 0.f)
509 "Scene depth must contain finite nonnegative view depths",
510 "fluids.surface.sceneDepth"));
511 }
512 for (size_t i = 0; i < pixels; ++i) {
513 if (color_[i * 4u + 3u] == 0 || sceneDepth[i] + depthBias > depth_[i]) continue;
514 color_[i * 4u + 0u] = color_[i * 4u + 1u] = color_[i * 4u + 2u] = color_[i * 4u + 3u] = 0;
515 }
516 residentColorCurrent_ = false;
517 return Result<void>::success();
518}
519
520Result<void> FluidSurfaceRenderer::compositeSceneRefraction(std::span<const uint8_t> sceneColor, float distortion,
521 float absorption) {
522 const int W = params_.width;
523 const int H = params_.height;
524 const size_t pixels = size_t(W) * size_t(H);
525 if (!auxiliaryCurrent_)
527 "Current color frame has no host-visible surface auxiliaries",
528 "fluids.surface.refraction"));
529 if (sceneColor.size() != pixels * 4u || !std::isfinite(distortion) || distortion < 0.f || distortion > 64.f ||
530 !std::isfinite(absorption) || absorption < 0.f || absorption > 30.f)
532 "Scene color dimensions or refraction parameters are invalid",
533 "fluids.surface.refraction"));
534
535 for (size_t i = 0; i < pixels; ++i) {
536 const size_t rgba = i * 4u;
537 const float coverage = float(color_[rgba + 3u]) / 255.f;
538 if (coverage <= 0.f) {
539 color_[rgba + 0u] = sceneColor[rgba + 0u];
540 color_[rgba + 1u] = sceneColor[rgba + 1u];
541 color_[rgba + 2u] = sceneColor[rgba + 2u];
542 color_[rgba + 3u] = 255u;
543 continue;
544 }
545 const int x = int(i % size_t(W));
546 const int y = int(i / size_t(W));
547 const float bend = distortion * std::clamp(thickness_[i] * 4.f, 0.f, 1.f);
548 const int sx = std::clamp(int(std::lround(float(x) + normals_[i].x * bend)), 0, W - 1);
549 const int sy = std::clamp(int(std::lround(float(y) - normals_[i].y * bend)), 0, H - 1);
550 const size_t refracted = (size_t(sy) * size_t(W) + size_t(sx)) * 4u;
551 const float transmission = std::exp(-absorption * std::max(thickness_[i], 0.f));
552 for (size_t channel = 0; channel < 3u; ++channel) {
553 const float through = float(sceneColor[refracted + channel]);
554 const float liquid = float(color_[rgba + channel]);
555 const float absorbed = through * transmission + liquid * (1.f - transmission);
556 const float composited = float(sceneColor[rgba + channel]) * (1.f - coverage) + absorbed * coverage;
557 color_[rgba + channel] = uint8_t(std::clamp(composited, 0.f, 255.f));
558 }
559 color_[rgba + 3u] = 255u;
560 }
561 residentColorCurrent_ = false;
562 return Result<void>::success();
563}
564
565Result<void> FluidSurfaceRenderer::configureSurface(float thicknessScale, float thicknessCutoff, float depthFalloff,
566 int smoothIterations) {
567 if (!std::isfinite(thicknessScale) || thicknessScale < 0.f || thicknessScale > 16.f ||
568 !std::isfinite(thicknessCutoff) || thicknessCutoff < 0.f || thicknessCutoff > 5.f ||
569 !std::isfinite(depthFalloff) || depthFalloff <= 0.f || depthFalloff > 1.f || smoothIterations < 0 ||
570 smoothIterations > 8)
572 DiagnosticCode::InvalidArgument, "Invalid fluid surface controls", "fluids.surface.configureSurface"));
573 params_.thicknessScale = thicknessScale;
574 thicknessCutoff_ = thicknessCutoff;
575 params_.depthFalloff = depthFalloff;
576 params_.smoothIterations = smoothIterations;
577 resetReducedRenderers();
578 refreshCustomShadingFlag();
579 return Result<void>::success();
580}
581
586
588 const auto finiteRange = [](float value, float minimum, float maximum) {
589 return std::isfinite(value) && value >= minimum && value <= maximum;
590 };
591 const auto blend = [](int value) { return value >= 0 && value <= 10; };
592 if (!blend(settings.blendSource) || !blend(settings.blendDestination) || !blend(settings.particleBlendSource) ||
593 !blend(settings.particleBlendDestination) || !finiteRange(settings.thicknessCutoff, .01f, 5.f) ||
594 settings.thicknessDownsample < 1 || settings.thicknessDownsample > 4 ||
595 !finiteRange(settings.blurRadius, 0.f, .1f) || settings.surfaceDownsample < 1 ||
596 settings.surfaceDownsample > 4 || !finiteRange(settings.smoothness, 0.f, 1.f) ||
597 !finiteRange(settings.metalness, 0.f, 1.f) || !finiteRange(settings.ambientMultiplier, 0.f, 6.f) ||
598 !finiteRange(settings.reflection, 0.f, 1.f) || !finiteRange(settings.transparency, 0.f, 1.f) ||
599 !finiteRange(settings.absorption, 0.f, 30.f) || !finiteRange(settings.refraction, -.1f, .1f) ||
600 settings.refractionDownsample < 1 || settings.refractionDownsample > 4 || settings.foamDownsample < 1 ||
601 settings.foamDownsample > 4)
603 DiagnosticCode::InvalidArgument, "Invalid Fluid3D renderer settings", "fluids.surface.rendererSettings"));
604
605 surfaceBlendSource_ = static_cast<FluidBlendFactor>(settings.blendSource);
606 surfaceBlendDestination_ = static_cast<FluidBlendFactor>(settings.blendDestination);
607 particleBlendSource_ = static_cast<FluidBlendFactor>(settings.particleBlendSource);
608 particleBlendDestination_ = static_cast<FluidBlendFactor>(settings.particleBlendDestination);
609 particleDepthWrite_ = settings.particleZWrite;
610 particleBlendConfigured_ = true;
611 thicknessCutoff_ = settings.thicknessCutoff;
612 thicknessDownsample_ = settings.thicknessDownsample;
613 surfaceEnabled_ = settings.generateSurface;
614 surfaceBlurRadius_ = settings.blurRadius;
615 surfaceDownsample_ = settings.surfaceDownsample;
616 lighting_ = settings.lighting;
617 smoothness_ = settings.smoothness;
618 metalness_ = settings.metalness;
619 ambientMultiplier_ = settings.ambientMultiplier;
620 reflectionEnabled_ = settings.generateReflection;
621 reflection_ = settings.reflection;
622 refractionEnabled_ = settings.generateRefraction;
623 refractionTransparency_ = settings.transparency;
624 refractionAbsorption_ = settings.absorption;
625 refractionCoefficient_ = settings.refraction;
626 refractionDownsample_ = settings.refractionDownsample;
627 foamEnabled_ = settings.generateFoam;
628 foamDownsample_ = settings.foamDownsample;
629 resetReducedRenderers();
630 refreshCustomShadingFlag();
631 return Result<void>::success();
632}
633
636 settings.blendSource = int(surfaceBlendSource_);
637 settings.blendDestination = int(surfaceBlendDestination_);
638 settings.particleBlendSource = int(particleBlendSource_);
639 settings.particleBlendDestination = int(particleBlendDestination_);
640 settings.particleZWrite = particleDepthWrite_;
641 settings.thicknessCutoff = thicknessCutoff_;
642 settings.thicknessDownsample = thicknessDownsample_;
643 settings.generateSurface = surfaceEnabled_;
644 settings.blurRadius = surfaceBlurRadius_;
645 settings.surfaceDownsample = surfaceDownsample_;
646 settings.lighting = lighting_;
647 settings.smoothness = smoothness_;
648 settings.metalness = metalness_;
649 settings.ambientMultiplier = ambientMultiplier_;
650 settings.generateReflection = reflectionEnabled_;
651 settings.reflection = reflection_;
652 settings.generateRefraction = refractionEnabled_;
653 settings.transparency = refractionTransparency_;
654 settings.absorption = refractionAbsorption_;
655 settings.refraction = refractionCoefficient_;
656 settings.refractionDownsample = refractionDownsample_;
657 settings.generateFoam = foamEnabled_;
658 settings.foamDownsample = foamDownsample_;
659 return settings;
660}
661
662Result<void> FluidSurfaceRenderer::configureMaterial(bool lighting, float smoothness, float metalness,
663 float ambientMultiplier, float reflection, float opacity) {
664 const auto unit = [](float value) { return std::isfinite(value) && value >= 0.f && value <= 1.f; };
665 if (!unit(smoothness) || !unit(metalness) || !std::isfinite(ambientMultiplier) || ambientMultiplier < 0.f ||
666 ambientMultiplier > 6.f || !unit(reflection) || !std::isfinite(opacity) || opacity < 0.f || opacity > 30.f)
668 DiagnosticCode::InvalidArgument, "Invalid fluid material controls", "fluids.surface.configureMaterial"));
669 lighting_ = lighting;
670 smoothness_ = smoothness;
671 metalness_ = metalness;
672 ambientMultiplier_ = ambientMultiplier;
673 reflection_ = reflection;
674 opacity_ = opacity;
675 resetReducedRenderers();
676 refreshCustomShadingFlag();
677 return Result<void>::success();
678}
679
681 if (!std::isfinite(radius) || radius < 0.f || radius > .1f)
683 "Surface blur radius must be finite and within [0,0.1]",
684 "fluids.surface.blurRadius"));
685 surfaceBlurRadius_ = radius;
686 resetReducedRenderers();
687 return Result<void>::success();
688}
689
691 if (reflectionEnabled_ == enabled) return Result<void>::success();
692 reflectionEnabled_ = enabled;
693 resetReducedRenderers();
694 refreshCustomShadingFlag();
695 return Result<void>::success();
696}
697
698Result<void> FluidSurfaceRenderer::configureColors(const glm::vec3& baseColor, const glm::vec3& reflectionColor) {
699 const auto valid = [](const glm::vec3& c) {
700 return std::isfinite(c.x) && std::isfinite(c.y) && std::isfinite(c.z) && c.x >= 0.f && c.y >= 0.f &&
701 c.z >= 0.f && c.x <= 1.f && c.y <= 1.f && c.z <= 1.f;
702 };
703 if (!valid(baseColor) || !valid(reflectionColor))
705 "Fluid colors must be finite linear RGB values in [0,1]",
706 "fluids.surface.configureColors"));
707 baseColor_ = baseColor;
708 reflectionColor_ = reflectionColor;
709 resetReducedRenderers();
710 refreshCustomShadingFlag();
711 return Result<void>::success();
712}
713
715 anisotropyEnabled_ = enabled;
716 if (!enabled) anisotropicFrame_ = false;
717 resetReducedRenderers();
718 return Result<void>::success();
719}
720
722 if (factor < 1 || factor > 4)
724 DiagnosticCode::InvalidArgument, "Surface downsample must be in [1,4]", "fluids.surface.downsample"));
725 if (factor == surfaceDownsample_) return Result<void>::success();
726 surfaceDownsample_ = factor;
727 resetReducedRenderers();
728 return Result<void>::success();
729}
730
732 if (factor < 1 || factor > 4)
734 "Thickness downsample must be in [1,4]",
735 "fluids.surface.thicknessDownsample"));
736 if (factor == thicknessDownsample_) return Result<void>::success();
737 thicknessDownsample_ = factor;
738 thicknessRenderer_.reset();
739 return Result<void>::success();
740}
741
742void FluidSurfaceRenderer::resetReducedRenderers() {
743 reducedRenderer_.reset();
744 thicknessRenderer_.reset();
745}
746
747void FluidSurfaceRenderer::ensureReducedRenderer() {
748 if (reducedRenderer_ || surfaceDownsample_ <= 1) return;
749 auto reduced = params_;
750 reduced.width = std::max(8, (params_.width + surfaceDownsample_ - 1) / surfaceDownsample_);
751 reduced.height = std::max(8, (params_.height + surfaceDownsample_ - 1) / surfaceDownsample_);
752 reduced.aspect = float(reduced.width) / float(reduced.height);
753 reducedRenderer_ = std::make_unique<FluidSurfaceRenderer>(reduced, preferGpu_);
754 reducedRenderer_->thicknessCutoff_ = thicknessCutoff_;
755 reducedRenderer_->surfaceBlurRadius_ = surfaceBlurRadius_;
756 reducedRenderer_->lighting_ = lighting_;
757 reducedRenderer_->smoothness_ = smoothness_;
758 reducedRenderer_->metalness_ = metalness_;
759 reducedRenderer_->ambientMultiplier_ = ambientMultiplier_;
760 reducedRenderer_->reflection_ = reflection_;
761 reducedRenderer_->reflectionEnabled_ = reflectionEnabled_;
762 reducedRenderer_->opacity_ = opacity_;
763 reducedRenderer_->baseColor_ = baseColor_;
764 reducedRenderer_->reflectionColor_ = reflectionColor_;
765 reducedRenderer_->customShading_ = customShading_;
766 reducedRenderer_->anisotropyEnabled_ = anisotropyEnabled_;
767}
768
769void FluidSurfaceRenderer::expandReducedOutputs(bool colorOnly) {
770 const int sourceWidth = reducedRenderer_->getWidth();
771 const int sourceHeight = reducedRenderer_->getHeight();
772 const int width = params_.width;
773 const int height = params_.height;
774 for (int y = 0; y < height; ++y)
775 for (int x = 0; x < width; ++x) {
776 const int sx = std::min(sourceWidth - 1, x * sourceWidth / width);
777 const int sy = std::min(sourceHeight - 1, y * sourceHeight / height);
778 const size_t source = size_t(sy) * size_t(sourceWidth) + size_t(sx);
779 const size_t target = size_t(y) * size_t(width) + size_t(x);
780 std::memcpy(color_.data() + target * 4u, reducedRenderer_->color().data() + source * 4u, 4u);
781 if (!colorOnly) {
782 depth_[target] = reducedRenderer_->depth()[source];
783 thickness_[target] = reducedRenderer_->thickness()[source];
784 normals_[target] = reducedRenderer_->normals()[source];
785 }
786 }
787 auxiliaryCurrent_ = !colorOnly;
788}
789
790void FluidSurfaceRenderer::ensureThicknessRenderer() {
791 if (thicknessRenderer_) return;
792 auto reduced = params_;
793 reduced.width = std::max(8, (params_.width + thicknessDownsample_ - 1) / thicknessDownsample_);
794 reduced.height = std::max(8, (params_.height + thicknessDownsample_ - 1) / thicknessDownsample_);
795 reduced.aspect = float(reduced.width) / float(reduced.height);
796 thicknessRenderer_ = std::make_unique<FluidSurfaceRenderer>(reduced, preferGpu_);
797 thicknessRenderer_->anisotropyEnabled_ = anisotropyEnabled_;
798}
799
800void FluidSurfaceRenderer::replaceThicknessFromReduced() {
801 const int sourceWidth = thicknessRenderer_->getWidth();
802 const int sourceHeight = thicknessRenderer_->getHeight();
803 for (int y = 0; y < params_.height; ++y)
804 for (int x = 0; x < params_.width; ++x) {
805 const int sx = std::min(sourceWidth - 1, x * sourceWidth / params_.width);
806 const int sy = std::min(sourceHeight - 1, y * sourceHeight / params_.height);
807 thickness_[size_t(y) * size_t(params_.width) + size_t(x)] =
808 thicknessRenderer_->thickness()[size_t(sy) * size_t(sourceWidth) + size_t(sx)];
809 }
810}
811
812void FluidSurfaceRenderer::refreshCustomShadingFlag() {
813 customShading_ = thicknessCutoff_ != 0.f || !lighting_ || smoothness_ != .8f || metalness_ != 0.f ||
814 ambientMultiplier_ != .55f || reflection_ != .75f || opacity_ != .35f || !reflectionEnabled_ ||
815 baseColor_ != glm::vec3(.05f, .32f, .72f) || reflectionColor_ != glm::vec3(.55f, .72f, 1.f);
816}
817
818void FluidSurfaceRenderer::applyConfiguredShading() {
819 const glm::vec3 L = glm::normalize(glm::vec3(.35f, .65f, .55f));
820 const glm::vec3 V(0.f, 0.f, 1.f);
821 for (size_t i = 0; i < thickness_.size(); ++i) {
822 const size_t rgba = i * 4u;
823 if (depth_[i] >= 1e29f || thickness_[i] * 10.f < thicknessCutoff_) {
824 color_[rgba] = color_[rgba + 1u] = color_[rgba + 2u] = color_[rgba + 3u] = 0;
825 continue;
826 }
827 if (params_.mode == 1) continue;
828 const glm::vec3 n = normals_[i];
829 const float diff = std::max(glm::dot(n, L), 0.f);
830 const float lit =
831 lighting_ ? std::clamp(ambientMultiplier_ + (1.f - std::min(ambientMultiplier_, 1.f)) * diff, 0.f, 6.f)
832 : 1.f;
833 const float fresnel = .04f + .96f * std::pow(1.f - std::max(glm::dot(n, V), 0.f), 5.f);
834 const glm::vec3 hv = glm::normalize(L + V);
835 const float exponent = 4.f + 124.f * smoothness_;
836 const float spec = lighting_ ? std::pow(std::max(glm::dot(n, hv), 0.f), exponent) * smoothness_ * .56f : 0.f;
837 const glm::vec3 reflected = glm::mix(reflectionColor_, baseColor_, metalness_);
838 const float reflectionStrength = reflectionEnabled_ ? reflection_ : 0.f;
839 const glm::vec3 shaded = baseColor_ * lit + reflected * fresnel * reflectionStrength + glm::vec3(spec);
840 for (size_t c = 0; c < 3u; ++c) color_[rgba + c] = uint8_t(255.f * std::clamp(shaded[int(c)], 0.f, 1.f));
841 color_[rgba + 3u] = uint8_t(255.f * std::clamp(thickness_[i] * opacity_, 0.f, 1.f));
842 }
843}
844
845void FluidSurfaceRenderer::setCommonConstants(gpgpu::ComputeShader* shader, float falloff) {
846 if (!shader) return;
847 const glm::mat4 view = glm::lookAtRH(params_.eye, params_.target, params_.up);
848 const glm::mat4 proj =
849 params_.orthographic
850 ? glm::orthoRH(-params_.orthographicSize * params_.aspect, params_.orthographicSize * params_.aspect,
851 -params_.orthographicSize, params_.orthographicSize, params_.nearZ, params_.farZ)
852 : glm::perspectiveRH(glm::radians(params_.fovYDeg), params_.aspect, params_.nearZ, params_.farZ);
853 const glm::mat4 vp = proj * view;
854 const float* vpPtr = &vp[0][0];
855 for (int i = 0; i < 16; ++i) shader->setFloat(kSsfPushVP0 + i, vpPtr[i]);
856 shader->setFloat(kSsfPushCount, float(std::min(positions_.size(), size_t(65536))));
857 shader->setFloat(kSsfPushOrthographic, params_.orthographic ? 1.f : 0.f);
858 shader->setFloat(kSsfPushNear, params_.nearZ);
859 shader->setFloat(kSsfPushFar, params_.farZ);
860 shader->setFloat(kSsfPushTanHalf,
861 params_.orthographic ? params_.orthographicSize : std::tan(glm::radians(params_.fovYDeg) * .5f));
862 shader->setFloat(kSsfPushAspect, params_.aspect);
863 shader->setFloat(kSsfPushW, float(params_.width));
864 shader->setFloat(kSsfPushH, float(params_.height));
865 shader->setFloat(kSsfPushRadius, params_.particleRadius);
866 shader->setFloat(kSsfPushMode, float(params_.mode));
867 shader->setFloat(kSsfPushThick, params_.thicknessScale);
868 shader->setFloat(kSsfPushFalloff, falloff);
869 shader->setFloat(kSsfPushBlurRadius, surfaceBlurRadius_);
870 if (shader == shShade_ && (uniformVolumeGpuShade_ || multicolorVolumeGpuShade_)) {
871 shader->setFloat(kSsfPushMode, float(params_.mode + (multicolorVolumeGpuShade_ ? 20 : 10)));
872 shader->setFloat(0, uniformVolumeColor_.r);
873 shader->setFloat(1, uniformVolumeColor_.g);
874 shader->setFloat(2, uniformVolumeColor_.b);
875 shader->setFloat(3, uniformVolumeColor_.a);
876 shader->setFloat(4, reflectionColor_.r);
877 shader->setFloat(5, reflectionColor_.g);
878 shader->setFloat(6, reflectionColor_.b);
879 shader->setFloat(7, lighting_ ? 1.f : 0.f);
880 shader->setFloat(8, smoothness_);
881 shader->setFloat(9, metalness_);
882 shader->setFloat(10, ambientMultiplier_);
883 shader->setFloat(11, reflectionEnabled_ ? reflection_ : 0.f);
884 shader->setFloat(12, opacity_);
885 shader->setFloat(13, thicknessCutoff_);
886 }
887}
888
889void FluidSurfaceRenderer::renderCpu() {
890 const int W = params_.width;
891 const int H = params_.height;
892 const int pixels = W * H;
893 const float nearZ = params_.nearZ;
894 const float farZ = params_.farZ;
895 const float tanHalf =
896 params_.orthographic ? params_.orthographicSize : std::tan(glm::radians(params_.fovYDeg) * .5f);
897 const glm::mat4 view = glm::lookAtRH(params_.eye, params_.target, params_.up);
898 const glm::mat4 proj =
899 params_.orthographic
900 ? glm::orthoRH(-tanHalf * params_.aspect, tanHalf * params_.aspect, -tanHalf, tanHalf, nearZ, farZ)
901 : glm::perspectiveRH(glm::radians(params_.fovYDeg), params_.aspect, nearZ, farZ);
902 std::fill(depth_.begin(), depth_.end(), 1e30f);
903 std::fill(thickness_.begin(), thickness_.end(), 0.f);
904 depthScratch_.resize(size_t(pixels));
905
906 // 1. Splat.
907 if (anisotropicFrame_) {
908 for (size_t i = 0; i < positions_.size(); ++i) {
909 const float* splat = anisotropicSplats_.data() + i * 12u;
910 const float sx = splat[0], sy = splat[1], depthVal = splat[2], bound = splat[3];
911 if (bound <= 0.f) continue;
912 const int x0 = std::max(int(std::floor(sx - bound)), 0), x1 = std::min(int(std::ceil(sx + bound)), W - 1);
913 const int y0 = std::max(int(std::floor(sy - bound)), 0), y1 = std::min(int(std::ceil(sy + bound)), H - 1);
914 for (int yy = y0; yy <= y1; ++yy)
915 for (int xx = x0; xx <= x1; ++xx) {
916 const float dx = float(xx) + .5f - sx, dy = float(yy) + .5f - sy;
917 const float q = splat[4] * dx * dx + 2.f * splat[5] * dx * dy + splat[6] * dy * dy;
918 if (q >= 1.f) continue;
919 const float cap = splat[7] * std::sqrt(std::max(0.f, 1.f - q));
920 const float centerDepth = depthVal + splat[8] * dx + splat[9] * dy;
921 const size_t at = size_t(yy) * size_t(W) + size_t(xx);
922 depth_[at] = std::min(depth_[at], std::clamp(centerDepth - cap, nearZ, farZ));
923 thickness_[at] += 2.f * cap * params_.thicknessScale;
924 }
925 }
926 } else
927 for (const glm::vec3& p : positions_) {
928 const auto viewPosition = view * glm::vec4(p, 1.f);
929 const glm::vec4 clip = proj * viewPosition;
930 if (!params_.orthographic && clip.w <= 1e-4f) continue;
931 const glm::vec3 ndc = glm::vec3(clip) / clip.w;
932 if (glm::any(glm::lessThan(ndc, glm::vec3(-1.f))) || glm::any(glm::greaterThan(ndc, glm::vec3(1.f))))
933 continue;
934 const float sx = (ndc.x * 0.5f + 0.5f) * float(W);
935 const float sy = (0.5f - ndc.y * 0.5f) * float(H);
936 const float depthVal = -viewPosition.z;
937 const float radiusPx = (params_.particleRadius * (float(H) * .5f) / tanHalf) /
938 (params_.orthographic ? 1.f : std::max(depthVal, 1e-4f));
939 if (radiusPx < 0.5f) continue;
940 const int x0 = std::max(int(std::floor(sx - radiusPx)), 0);
941 const int x1 = std::min(int(std::ceil(sx + radiusPx)), W - 1);
942 const int y0 = std::max(int(std::floor(sy - radiusPx)), 0);
943 const int y1 = std::min(int(std::ceil(sy + radiusPx)), H - 1);
944 const float r2 = radiusPx * radiusPx;
945 for (int yy = y0; yy <= y1; ++yy) {
946 for (int xx = x0; xx <= x1; ++xx) {
947 const float ddx = float(xx) + 0.5f - sx;
948 const float ddy = float(yy) + 0.5f - sy;
949 const float q = (ddx * ddx + ddy * ddy) / r2;
950 if (q >= 1.f) continue;
951 const size_t idx = size_t(yy) * size_t(W) + size_t(xx);
952 const float cap = params_.particleRadius * std::sqrt(1.f - q);
953 const float surfaceDepth = std::clamp(depthVal - cap, params_.nearZ, params_.farZ);
954 if (surfaceDepth < depth_[idx]) depth_[idx] = surfaceDepth;
955 thickness_[idx] += 2.f * cap * params_.thicknessScale;
956 }
957 }
958 }
959
960 // 2. Bilateral smooth (ping-pong).
961 for (int it = 0; it < params_.smoothIterations; ++it) {
962 const std::vector<float>& src = (it % 2 == 0) ? depth_ : depthScratch_;
963 std::vector<float>& dst = (it % 2 == 0) ? depthScratch_ : depth_;
964 for (int y = 0; y < H; ++y) {
965 for (int x = 0; x < W; ++x) {
966 const size_t idx = size_t(y) * size_t(W) + size_t(x);
967 if (src[idx] >= 1e29f) {
968 dst[idx] = src[idx];
969 continue;
970 }
971 float sum = src[idx];
972 float wsum = 1.f;
973 const float projectedBlur =
974 surfaceBlurRadius_ < 0.f
975 ? 2.f
976 : surfaceBlurRadius_ * float(H) / (2.f * tanHalf * (params_.orthographic ? 1.f : src[idx]));
977 const int kernelRadius = std::clamp(int(std::ceil(projectedBlur)), 0, 4);
978 const float sigma = std::max(.5f, projectedBlur * .5f);
979 for (int oy = -kernelRadius; oy <= kernelRadius; ++oy) {
980 for (int ox = -kernelRadius; ox <= kernelRadius; ++ox) {
981 if (ox == 0 && oy == 0) continue;
982 const int xx = x + ox;
983 const int yy = y + oy;
984 if (xx < 0 || yy < 0 || xx >= W || yy >= H) continue;
985 const size_t nidx = size_t(yy) * size_t(W) + size_t(xx);
986 if (src[nidx] >= 1e29f) continue;
987 const float spatial = std::exp(-float(ox * ox + oy * oy) / (2.f * sigma * sigma));
988 const float wDepth = std::exp(-std::fabs(src[nidx] - src[idx]) / params_.depthFalloff);
989 const float w = spatial * wDepth;
990 sum += src[nidx] * w;
991 wsum += w;
992 }
993 }
994 dst[idx] = sum / std::max(wsum, 1e-5f);
995 }
996 }
997 }
998 const std::vector<float>& smooth = (params_.smoothIterations % 2 == 0) ? depth_ : depthScratch_;
999
1000 // 3. Normals from depth gradients (view space).
1001 for (int y = 0; y < H; ++y) {
1002 for (int x = 0; x < W; ++x) {
1003 const size_t idx = size_t(y) * size_t(W) + size_t(x);
1004 if (smooth[idx] >= 1e29f || thickness_[idx] * 10.f < thicknessCutoff_) {
1005 normals_[idx] = glm::vec3(0.f);
1006 continue;
1007 }
1008 const auto viewPos = [&](int px, int py, float d) {
1009 const float u = (float(px) + 0.5f) / float(W);
1010 const float v = (float(py) + 0.5f) / float(H);
1011 const float scale = params_.orthographic ? 1.f : d;
1012 return glm::vec3((u * 2.f - 1.f) * params_.aspect * tanHalf * scale, (1.f - v * 2.f) * tanHalf * scale,
1013 -d);
1014 };
1015 const auto sd = [&](int ox, int oy) {
1016 const int xx = x + ox, yy = y + oy;
1017 if (xx < 0 || yy < 0 || xx >= W || yy >= H) return 1e30f;
1018 return smooth[size_t(yy) * size_t(W) + size_t(xx)];
1019 };
1020 const auto derivative = [&](glm::vec3 forward, glm::vec3 backward, bool forwardValid, bool backwardValid) {
1021 if (!forwardValid && backwardValid) return backward;
1022 if (!backwardValid && forwardValid) return forward;
1023 const float tolerance = 2.f * (params_.farZ - params_.nearZ) / 16777215.f;
1024 if ((!forwardValid && !backwardValid) ||
1025 std::abs(std::abs(forward.z) - std::abs(backward.z)) <= tolerance)
1026 return (forward + backward) * .5f;
1027 return std::abs(forward.z) < std::abs(backward.z) ? forward : backward;
1028 };
1029 const float dl = sd(-1, 0), dr = sd(1, 0), dt = sd(0, -1), db = sd(0, 1);
1030 const auto center = viewPos(x, y, smooth[idx]);
1031 const auto pL = viewPos(x - 1, y, dl < 1e29f ? dl : smooth[idx]);
1032 const auto pR = viewPos(x + 1, y, dr < 1e29f ? dr : smooth[idx]);
1033 const auto pT = viewPos(x, y - 1, dt < 1e29f ? dt : smooth[idx]);
1034 const auto pB = viewPos(x, y + 1, db < 1e29f ? db : smooth[idx]);
1035 const auto dpx = derivative(pR - center, center - pL, dr < 1e29f, dl < 1e29f);
1036 const auto dpy = derivative(pB - center, center - pT, db < 1e29f, dt < 1e29f);
1037 glm::vec3 n = glm::normalize(glm::cross(dpx, dpy));
1038 if (n.z < 0.f) n = -n;
1039 normals_[idx] = n;
1040 }
1041 }
1042
1043 // 4. Shade.
1044 const glm::vec3 L(0.35f, 0.65f, 0.55f);
1045 const glm::vec3 V(0.f, 0.f, 1.f);
1046 for (int y = 0; y < H; ++y) {
1047 for (int x = 0; x < W; ++x) {
1048 const size_t idx = size_t(y) * size_t(W) + size_t(x);
1049 if (smooth[idx] >= 1e29f || thickness_[idx] * 10.f < thicknessCutoff_) {
1050 color_[idx * 4u + 0] = 0;
1051 color_[idx * 4u + 1] = 0;
1052 color_[idx * 4u + 2] = 0;
1053 color_[idx * 4u + 3] = 0;
1054 continue;
1055 }
1056 const glm::vec3 n = normals_[idx];
1057 const float diff = std::max(glm::dot(n, L), 0.f);
1058 glm::vec3 outC;
1059 float alpha;
1060 if (params_.mode == 1) {
1061 const glm::vec3 base = glm::vec3(0.36f, 0.23f, 0.12f) * (0.45f + 0.55f * diff);
1062 const float attenuation = std::exp(-thickness_[idx] * 1.8f);
1063 const glm::vec3 hv = glm::normalize(L + V);
1064 const float spec = std::pow(std::max(glm::dot(n, hv), 0.f), 8.f) * 0.12f;
1065 outC = base * attenuation + glm::vec3(spec);
1066 alpha = std::clamp(thickness_[idx] * 0.6f, 0.f, 1.f);
1067 } else {
1068 const float lit =
1069 lighting_
1070 ? std::clamp(ambientMultiplier_ + (1.f - std::min(ambientMultiplier_, 1.f)) * diff, 0.f, 6.f)
1071 : 1.f;
1072 const glm::vec3 base = baseColor_ * lit;
1073 const float fresnel = 0.04f + 0.96f * std::pow(1.f - std::max(glm::dot(n, V), 0.f), 5.f);
1074 const glm::vec3 hv = glm::normalize(L + V);
1075 const float exponent = 4.f + 124.f * smoothness_;
1076 const float spec =
1077 lighting_ ? std::pow(std::max(glm::dot(n, hv), 0.f), exponent) * smoothness_ * .56f : 0.f;
1078 const glm::vec3 reflected = glm::mix(reflectionColor_, baseColor_, metalness_);
1079 const float reflectionStrength = reflectionEnabled_ ? reflection_ : 0.f;
1080 outC = base + reflected * fresnel * reflectionStrength + glm::vec3(spec);
1081 alpha = std::clamp(thickness_[idx] * opacity_, 0.f, 1.f);
1082 }
1083 color_[idx * 4u + 0] = uint8_t(std::clamp(outC.x, 0.f, 1.f) * 255.f);
1084 color_[idx * 4u + 1] = uint8_t(std::clamp(outC.y, 0.f, 1.f) * 255.f);
1085 color_[idx * 4u + 2] = uint8_t(std::clamp(outC.z, 0.f, 1.f) * 255.f);
1086 color_[idx * 4u + 3] = uint8_t(std::clamp(alpha, 0.f, 1.f) * 255.f);
1087 }
1088 }
1089 // Expose the same smoothed depth used for normals/shading, including odd pass counts.
1090 if (params_.smoothIterations % 2 != 0) depth_.swap(depthScratch_);
1091}
1092
1093void FluidSurfaceRenderer::writePpm(const std::string& path) const {
1094 std::ofstream out(path, std::ios::binary);
1095 if (!out) return;
1096 out << "P6\n" << params_.width << " " << params_.height << "\n255\n";
1097 for (size_t i = 0; i < color_.size(); i += 4) out << char(color_[i]) << char(color_[i + 1]) << char(color_[i + 2]);
1098}
1099
1100} // namespace eve::fluids
LogicalId target
double value
Trace trace
Definition Agent.cpp:49
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
eve::action::ActionSpatialBinding spatial
Vec3 radii
Definition CaveMesh.cpp:56
float py
glm::vec4 p[6]
float maximum[3]
float minimum[3]
std::uint32_t key
glm::vec4 clip
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::array< double, 10 > q
std::vector< float > positions
float v
HexVec3 up
std::int32_t c
float blend
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
std::array< float, 4 > rotation
std::array< float, 3 > scale
bool valid
int idx
float radius
std::string path
Definition PlayHost.cpp:110
#define EV_PROFILE_MODULE(module, name)
Profile the enclosing scope, tagged with a module for grouping.
Definition Profile.h:140
float d
uint8_t * pixels
glm::vec3 eye
Shader * shader
Lighting3DPack lighting
glm::mat4 view
glm::mat4 proj
float dy
float dx
std::uint32_t count
TacticalUnit * unit
TerrainThermalSettings settings
float opacity
const UnitySourceAsset & source
std::size_t at
double oy
double ox
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
GPU-backed surface fluid simulator (falls back to the CPU solver).
Definition Fluids.h:44
FluidParams & params()
Params.
Definition Fluids.h:123
void readPositions(std::vector< glm::vec3 > &out) const
Copy live particle positions out (CPU mirror; GPU path downloads).
Definition Fluids.cpp:230
Result< void > configureSurfaceEnabled(bool enabled)
Enables or disables Fluid3D's generateSurface policy for renderConfiguredVolume.
Result< void > configureThicknessDownsample(int factor)
Sets the independently reconstructed liquid-thickness target divisor.
Result< void > configureSurfaceDownsample(int factor)
Configures Fluid3D-style surface target downsampling.
Result< void > occludeWithSceneDepth(std::span< const float > sceneDepth, float depthBias=0.001f)
Removes fluid and diffuse color hidden by borrowed linear scene depth.
void render(const std::vector< glm::vec3 > &positions, float particleRadius)
Reconstruct a frame from particle positions.
Result< void > configureSurfaceBlurRadius(float radius)
Sets Fluid3D's world-space surface blur radius for subsequent reconstruction.
Result< void > configureSurface(float thicknessScale, float thicknessCutoff, float depthFalloff, int smoothIterations)
Atomically configures thickness reconstruction and surface smoothing.
Result< void > configureAnisotropy(bool enabled)
Enables oriented-ellipsoid projection for volume-fluid particles.
Result< void > copyToTexture(graphics::Graphics *graphics, graphics::Texture *texture)
Present the current RGBA8 result into an existing graphics texture.
Result< void > configureReflection(bool enabled)
Enables or disables the configured reflection contribution without losing its coefficient.
Result< void > configureMaterial(bool lighting, float smoothness, float metalness, float ambientMultiplier, float reflection, float opacity)
Atomically configures lighting and material response for subsequent liquid frames.
void renderFrom(FluidSimulator *sim)
Script-friendly wrapper: render(*sim) with a null check.
Result< void > configureColors(const glm::vec3 &baseColor, const glm::vec3 &reflectionColor)
Sets the linear RGB base and reflection colors atomically.
Result< void > configureProjection(bool orthographic, float verticalHalfSize=1.f)
Selects perspective or orthographic reconstruction for subsequent frames.
Result< void > renderVolumeColorToTexture(const VolumeFluid &sim, graphics::Graphics *graphics, graphics::Texture *texture)
Reconstruct and present a uniform-color volume without host color readback when supported.
void setCamera(const glm::vec3 &eye, const glm::vec3 &target, const glm::vec3 &up, float fovYDeg)
Update the camera.
Result< void > configureRendererSettings(const FluidRendererSettings &settings)
Validates and atomically applies every Fluid3DRendererSettings field.
FluidSurfaceRenderer(const FluidSurfaceParams &params, bool preferGpu)
Fluid surface renderer.
void writePpm(const std::string &path) const
Write the color buffer as a PPM image (debug artifact).
FluidRendererSettings rendererSettings() const noexcept
Returns an owning snapshot of the current Fluid3D renderer settings without allocation.
Result< void > prepare()
Prepares the preferred reconstruction backend before frame timing begins.
void renderVolumeColorOnly(const VolumeFluid &sim)
Reconstructs only the RGBA output for display, skipping auxiliary GPU readback when color is uniform.
Result< void > compositeSceneRefraction(std::span< const uint8_t > sceneColor, float distortion=8.f, float absorption=3.f)
Refracts a matching opaque RGBA8 scene through the current liquid surface.
~FluidSurfaceRenderer()
Fluid surface renderer.
CPU position-based free-volume fluid with a bounded spatial grid. @ownership Owns all particle state;...
virtual void bindBuffer(int binding, GpuBuffer *buffer)=0
Bind a storage buffer to set=0 binding. binding in [0, kMaxBindings).
ComputeShader * newShader(const std::string &source)
Compatibility-only raw-owning shader factory (Vulkan: GLSL; WebGPU: WGSL). Vulkan delegates to the ch...
Definition Gpgpu.cpp:324
bool isAvailable() const
True when the active Graphics backend can run compute (device initialized).
Definition Gpgpu.cpp:284
GpuBuffer * newBuffer(int byteSize, const std::string &usage="storage")
Allocate a GPU buffer. usage: "storage" (SSBO, device-local) | "staging" (host-visible transfer).
Definition Gpgpu.cpp:372
Sequence * newSequence()
Create a Kompute-style command Sequence: record buffer transfers and compute dispatches into one comm...
Definition Gpgpu.cpp:384
virtual void downloadBytes(void *dst, uint64_t nbytes, uint64_t srcOffset=0) const =0
Downloads bytes.
virtual GpuResidentBufferView residentView() const
Return a transient non-owning native view for same-device rendering. Unsupported/test buffers return ...
Definition GpuBuffer.h:60
void recordUpload(GpuBuffer *dst, const void *src, uint64_t nbytes, uint64_t dstOffset=0)
Record upload.
Definition Sequence.cpp:77
void recordDownload(GpuBuffer *src, GpuBuffer *staging, uint64_t nbytes, uint64_t srcOffset=0)
Record download.
Definition Sequence.cpp:82
void submit()
Submit.
Definition Sequence.cpp:92
void recordDispatch(ComputeShader *shader, int groupsX, int groupsY=1, int groupsZ=1)
Record dispatch.
Definition Sequence.cpp:87
void begin()
Begins begin.
Definition Sequence.cpp:75
bool isAvailable() const
True when available.
Definition Sequence.cpp:66
virtual bool updateTexture(Texture *texture, int width, int height, const uint8_t *rgba)=0
Replace an existing texture's pixels in place (pointer stays stable).
virtual eve::Result< void > updateTextureFromResidentRgba8(Texture *texture, const GpuResidentBufferView &source, int width, int height)
Copy a same-device resident RGBA8 buffer into an existing texture.
Definition Graphics.h:643
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:18
std::vector< ParamSpec > params
std::vector< double > forward(const Policy &p, const Observation &o)
Forward.
Definition Learning.h:65
GLSL compute kernels for the GPU surface-flow solver.
Definition FluidTarget.h:12
const char * kSsfColorSplat
Accumulate isotropic particle colors using the CPU tint pass' disc weights. @lifetime The returned po...
const char * kSsfSmooth
One bilateral smoothing pass (read depthA, write depthB).
const char * kSsfShade
Water/mud shading from depth + normal + thickness.
const char * kSsfClear
Reset depth (0xFFFFFFFF), thickness, normal and color buffers.
const char * kSsfSplat
Splat particles into depth + thickness (atomic min / add).
FluidBlendFactor
Unity/Fluid3D blend factors used by fluid render settings.
const char * kSsfNormal
Reconstruct view-space normals from the smoothed depth.
const char * kSsfColorClear
Clear lazily-created fixed-point multicolor accumulation buffers. @lifetime The returned pointer rema...
constexpr auto kSsfAnisotropicSplat
int groupsFor(int count)
Groups for.
bool enabled
float particleRadius
Resting particle radius in world units.
Definition FluidMath.h:28
Complete owning mirror of Fluid3DRendererSettings with package defaults.
Camera + reconstruction tuning for the SSF pipeline.