12#include <glm/gtc/matrix_transform.hpp>
13#include <glm/gtc/quaternion.hpp>
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;
41constexpr uint32_t kEmptyKey = 0xFFFFFFFFu;
42constexpr float kKeyScale = 16777215.f;
49 : params_(
params), preferGpu_(preferGpu) {
52 params_.
width = std::clamp(params_.
width, 8, 1024);
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);
64void FluidSurfaceRenderer::releaseGpu() noexcept {
70 shColorSplat_ =
nullptr;
72 shColorClear_ =
nullptr;
79 delete shAnisotropicSplat_;
80 shAnisotropicSplat_ =
nullptr;
93 delete bufColorAccum_;
94 bufColorAccum_ =
nullptr;
95 delete bufParticleColors_;
96 bufParticleColors_ =
nullptr;
102 bufDepthB_ =
nullptr;
104 bufDepthA_ =
nullptr;
107 delete bufAnisotropicParts_;
108 bufAnisotropicParts_ =
nullptr;
109 multicolorGpuReady_ =
false;
120 resetReducedRenderers();
124 std::vector<glm::vec3>
pos;
135 if (!std::isfinite(verticalHalfSize) || verticalHalfSize < .001f || verticalHalfSize > 10000.f)
140 resetReducedRenderers();
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();
155 if (preferGpu_ && !gpuOk_) ensureGpu();
156 if (thicknessDownsample_ != 1) {
157 ensureThicknessRenderer();
158 return thicknessRenderer_->prepare();
164 if (surfaceDownsample_ > 1) {
165 ensureReducedRenderer();
166 reducedRenderer_->render(
positions, particleRadius);
167 expandReducedOutputs(
false);
170 uniformVolumeGpuShade_ =
false;
171 multicolorVolumeGpuShade_ =
false;
172 anisotropicFrame_ =
false;
173 renderInternal(
positions, particleRadius);
176void FluidSurfaceRenderer::renderInternal(
const std::vector<glm::vec3>&
positions,
float particleRadius) {
180 auxiliaryCurrent_ =
true;
181 residentColorCurrent_ =
false;
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));
191 if (preferGpu_ && !gpuOk_) ensureGpu();
195 if (multicolorVolumeGpuShade_ && !ensureMulticolorGpu()) multicolorVolumeGpuShade_ =
false;
196 if (anisotropicFrame_ && !ensureAnisotropyGpu()) {
201 if (anisotropicFrame_) {
202 seq_->
recordUpload(bufAnisotropicParts_, anisotropicSplats_.data(),
203 uint64_t(anisotropicSplats_.size()) *
sizeof(
float));
204 if (multicolorVolumeGpuShade_) uploadParticles();
211 auto* splatShader = anisotropicFrame_ ? shAnisotropicSplat_ : shSplat_;
214 if (multicolorVolumeGpuShade_) {
215 seq_->
recordUpload(bufParticleColors_, volumeColors_.data(),
216 uint64_t(volumeColors_.size()) *
sizeof(glm::vec4));
217 setCommonConstants(shColorClear_, params_.
depthFalloff);
219 setCommonConstants(shColorSplat_, params_.
depthFalloff);
224 shSmooth_->
bindBuffer(1, inA ? bufDepthA_ : bufDepthB_);
225 shSmooth_->
bindBuffer(2, inA ? bufDepthB_ : bufDepthA_);
230 gpgpu::GpuBuffer* finalDepth = inA ? bufDepthA_ : bufDepthB_;
235 const bool deviceShadedVolume = uniformVolumeGpuShade_ || multicolorVolumeGpuShade_;
236 if (!deviceShadedVolume) {
241 const bool keepColorResident = deviceShadedVolume && skipHostColorReadback_ &&
243 if (!keepColorResident) seq_->
recordDownload(bufColor_, stColor_, uint64_t(
pixels) *
sizeof(uint32_t));
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;
252 gpuNormals_.resize(
size_t(
pixels));
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);
273 if (customShading_) applyConfiguredShading();
280 if (!graphics || !texture)
282 "fluids.surface.copyToTexture"));
283 if (residentColorCurrent_ && bufColor_) {
286 if (copied)
return copied;
298 if (!graphics || !texture)
300 "fluids.surface.renderVolumeColorToTexture"));
301 skipHostColorReadback_ =
true;
305 skipHostColorReadback_ =
false;
308 skipHostColorReadback_ =
false;
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;
319 const int maxP = 65536;
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");
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");
360 gpgpu::GpuBuffer* finalDepth = std::max(0, params_.
smoothIterations) % 2 == 0 ? bufDepthA_ : bufDepthB_;
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;
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);
391 delete particleColors;
396 delete bufColorAccum_;
397 bufColorAccum_ = colorAccum;
398 bufParticleColors_ = particleColors;
399 shColorSplat_ = colorSplat;
400 shColorClear_ = colorClear;
401 multicolorGpuReady_ =
true;
405bool FluidSurfaceRenderer::ensureAnisotropyGpu() {
406 if (shAnisotropicSplat_ && bufAnisotropicParts_)
return true;
407 if (!gpuOk_ || !gpgpu_)
return false;
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_);
417 delete shAnisotropicSplat_;
418 shAnisotropicSplat_ =
nullptr;
419 delete bufAnisotropicParts_;
420 bufAnisotropicParts_ =
nullptr;
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);
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);
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 =
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;
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;
477 out[7] = std::sqrt(conditionalVariance);
483void FluidSurfaceRenderer::uploadParticles() {
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;
493 seq_->
recordUpload(bufParts_, gpuParticles_.data(), uint64_t(gpuParticles_.size()) *
sizeof(
float));
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) {
509 "Scene depth must contain finite nonnegative view depths",
510 "fluids.surface.sceneDepth"));
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 + 0
u] = color_[i * 4u + 1u] = color_[i * 4u + 2u] = color_[i * 4u + 3u] = 0;
516 residentColorCurrent_ =
false;
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"));
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 + 0
u] = sceneColor[rgba + 0
u];
540 color_[rgba + 1u] = sceneColor[rgba + 1u];
541 color_[rgba + 2u] = sceneColor[rgba + 2u];
542 color_[rgba + 3u] = 255u;
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));
559 color_[rgba + 3u] = 255u;
561 residentColorCurrent_ =
false;
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)
574 thicknessCutoff_ = thicknessCutoff;
577 resetReducedRenderers();
578 refreshCustomShadingFlag();
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) ||
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;
619 ambientMultiplier_ =
settings.ambientMultiplier;
620 reflectionEnabled_ =
settings.generateReflection;
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();
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_;
649 settings.ambientMultiplier = ambientMultiplier_;
650 settings.generateReflection = reflectionEnabled_;
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_;
663 float ambientMultiplier,
float reflection,
float opacity) {
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)
670 smoothness_ = smoothness;
671 metalness_ = metalness;
672 ambientMultiplier_ = ambientMultiplier;
673 reflection_ = reflection;
675 resetReducedRenderers();
676 refreshCustomShadingFlag();
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();
693 resetReducedRenderers();
694 refreshCustomShadingFlag();
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;
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();
716 if (!
enabled) anisotropicFrame_ =
false;
717 resetReducedRenderers();
722 if (factor < 1 || factor > 4)
726 surfaceDownsample_ = factor;
727 resetReducedRenderers();
732 if (factor < 1 || factor > 4)
734 "Thickness downsample must be in [1,4]",
735 "fluids.surface.thicknessDownsample"));
737 thicknessDownsample_ = factor;
738 thicknessRenderer_.reset();
742void FluidSurfaceRenderer::resetReducedRenderers() {
743 reducedRenderer_.reset();
744 thicknessRenderer_.reset();
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_;
769void FluidSurfaceRenderer::expandReducedOutputs(
bool colorOnly) {
770 const int sourceWidth = reducedRenderer_->getWidth();
771 const int sourceHeight = reducedRenderer_->getHeight();
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);
780 std::memcpy(color_.data() +
target * 4u, reducedRenderer_->color().data() +
source * 4u, 4u);
783 thickness_[
target] = reducedRenderer_->thickness()[
source];
787 auxiliaryCurrent_ = !colorOnly;
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_;
800void FluidSurfaceRenderer::replaceThicknessFromReduced() {
801 const int sourceWidth = thicknessRenderer_->getWidth();
802 const int sourceHeight = thicknessRenderer_->getHeight();
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)];
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);
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;
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);
831 lighting_ ? std::clamp(ambientMultiplier_ + (1.f - std::min(ambientMultiplier_, 1.f)) * diff, 0.f, 6.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));
845void FluidSurfaceRenderer::setCommonConstants(gpgpu::ComputeShader*
shader,
float falloff) {
847 const glm::mat4
view = glm::lookAtRH(params_.
eye, params_.
target, params_.
up);
848 const glm::mat4
proj =
852 : glm::perspectiveRH(glm::radians(params_.fovYDeg), params_.aspect, params_.nearZ, params_.farZ);
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))));
860 shader->setFloat(kSsfPushTanHalf,
866 shader->setFloat(kSsfPushMode,
float(params_.
mode));
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_);
889void FluidSurfaceRenderer::renderCpu() {
890 const int W = params_.
width;
891 const int H = params_.
height;
893 const float nearZ = params_.
nearZ;
894 const float farZ = params_.
farZ;
895 const float tanHalf =
897 const glm::mat4
view = glm::lookAtRH(params_.
eye, params_.
target, params_.
up);
898 const glm::mat4
proj =
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));
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));
927 for (
const glm::vec3&
p : positions_) {
928 const auto viewPosition =
view * glm::vec4(
p, 1.f);
929 const glm::vec4
clip =
proj * viewPosition;
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))))
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);
953 const float surfaceDepth = std::clamp(depthVal - cap, params_.
nearZ, params_.
farZ);
954 if (surfaceDepth < depth_[
idx]) depth_[
idx] = surfaceDepth;
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) {
971 float sum = src[
idx];
973 const float projectedBlur =
974 surfaceBlurRadius_ < 0.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);
990 sum += src[nidx] *
w;
994 dst[
idx] = sum / std::max(wsum, 1e-5f);
998 const std::vector<float>& smooth = (params_.
smoothIterations % 2 == 0) ? depth_ : depthScratch_;
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);
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);
1012 return glm::vec3((
u * 2.f - 1.f) * params_.
aspect * tanHalf *
scale, (1.f -
v * 2.f) * tanHalf *
scale,
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)];
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;
1029 const float dl = sd(-1, 0), dr = sd(1, 0), dt = sd(0, -1), db = sd(0, 1);
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;
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;
1056 const glm::vec3
n = normals_[
idx];
1057 const float diff = std::max(glm::dot(
n, L), 0.f);
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);
1070 ? std::clamp(ambientMultiplier_ + (1.f - std::min(ambientMultiplier_, 1.f)) * diff, 0.f, 6.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_;
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);
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);
1094 std::ofstream out(
path, std::ios::binary);
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]);
eve::action::ActionSpatialBinding spatial
std::array< double, 10 > q
std::vector< float > positions
std::array< float, 4 > rotation
std::array< float, 3 > scale
#define EV_PROFILE_MODULE(module, name)
Profile the enclosing scope, tagged with a module for grouping.
TerrainThermalSettings settings
const UnitySourceAsset & source
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.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
GPU-backed surface fluid simulator (falls back to the CPU solver).
FluidParams & params()
Params.
void readPositions(std::vector< glm::vec3 > &out) const
Copy live particle positions out (CPU mirror; GPU path downloads).
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 ¶ms, 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...
bool isAvailable() const
True when the active Graphics backend can run compute (device initialized).
GpuBuffer * newBuffer(int byteSize, const std::string &usage="storage")
Allocate a GPU buffer. usage: "storage" (SSBO, device-local) | "staging" (host-visible transfer).
Sequence * newSequence()
Create a Kompute-style command Sequence: record buffer transfers and compute dispatches into one comm...
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 ...
void recordUpload(GpuBuffer *dst, const void *src, uint64_t nbytes, uint64_t dstOffset=0)
Record upload.
void recordDownload(GpuBuffer *src, GpuBuffer *staging, uint64_t nbytes, uint64_t srcOffset=0)
Record download.
void recordDispatch(ComputeShader *shader, int groupsX, int groupsY=1, int groupsZ=1)
Record dispatch.
void begin()
Begins begin.
bool isAvailable() const
True when available.
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.
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
std::vector< ParamSpec > params
std::vector< double > forward(const Policy &p, const Observation &o)
Forward.
GLSL compute kernels for the GPU surface-flow solver.
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.
GpuResidentBackend backend
float particleRadius
Resting particle radius in world units.
Complete owning mirror of Fluid3DRendererSettings with package defaults.
Camera + reconstruction tuning for the SSF pipeline.
int mode
0 = water, 1 = mud.