9#include <glm/common.hpp>
10#include <glm/geometric.hpp>
15 width_ = std::max(
width, 1);
16 height_ = std::max(
height, 1);
17 depth_ = std::max(
depth, 1);
18 media_.assign(std::size_t(width_) * std::size_t(height_) * std::size_t(depth_), {});
19 integrated_.assign(media_.size(), glm::vec4(0.f, 0.f, 0.f, 1.f));
23 std::fill(media_.begin(), media_.end(),
FogFroxel{});
24 std::fill(integrated_.begin(), integrated_.end(), glm::vec4(0.f, 0.f, 0.f, 1.f));
28 nearDistance_ = std::max(nearDistance, 1e-3f);
29 farDistance_ = std::max(farDistance, nearDistance_ + 1e-3f);
33 if (depth_ <= 0)
return nearDistance_;
34 const float t = (float(std::clamp(
z, 0, depth_ - 1)) + 0.5f) /
float(depth_);
35 return nearDistance_ * std::pow(farDistance_ / nearDistance_,
t);
39 if (depth_ <= 1)
return 0;
40 const float d = std::clamp(
distance, nearDistance_, farDistance_);
41 const float t = std::log(
d / nearDistance_) / std::log(farDistance_ / nearDistance_);
42 return std::clamp(
int(std::floor(
t *
float(depth_))), 0, depth_ - 1);
45std::size_t AtmosphereVolume::index(
int x,
int y,
int z)
const {
46 x = std::clamp(
x, 0, width_ - 1);
47 y = std::clamp(
y, 0, height_ - 1);
48 z = std::clamp(
z, 0, depth_ - 1);
49 return (std::size_t(
z) * std::size_t(height_) + std::size_t(
y)) * std::size_t(width_) +
57 float baseHeight,
float heightFalloff,
float minWorldY,
59 const float sigma = std::max(baseExtinction, 0.f);
60 const glm::vec3 omega = glm::clamp(
albedo, glm::vec3(0.f), glm::vec3(1.f));
61 const float falloff = std::max(heightFalloff, 0.f);
62 for (
int y = 0;
y < height_; ++
y) {
63 const float ty = (float(
y) + 0.5f) /
float(height_);
64 const float worldY = minWorldY + (maxWorldY - minWorldY) * ty;
65 const float extinction = sigma * std::exp(-falloff * std::max(worldY - baseHeight, 0.f));
66 for (
int z = 0;
z < depth_; ++
z) {
67 for (
int x = 0;
x < width_; ++
x) {
77 float baseHeight,
float heightFalloff,
78 float minWorldY,
float maxWorldY,
80 const float sigma = std::max(baseExtinction, 0.f);
81 const glm::vec3 omega = glm::clamp(
albedo, glm::vec3(0.f), glm::vec3(1.f));
82 const float falloff = std::max(heightFalloff, 0.f);
83 auto unproject = [&](
float ndcX,
float ndcY,
float ndcZ) {
84 const glm::vec4 homogeneous =
invViewProj * glm::vec4(ndcX, ndcY, ndcZ, 1.f);
85 return glm::vec3(homogeneous) / homogeneous.w;
87 for (
int y = 0;
y < height_; ++
y) {
88 const float ndcY = ((float(
y) + 0.5f) /
float(height_)) * 2.f - 1.f;
89 for (
int x = 0;
x < width_; ++
x) {
90 const float ndcX = ((float(
x) + 0.5f) /
float(width_)) * 2.f - 1.f;
91 const glm::vec3 nearPoint = unproject(ndcX, ndcY, 0.f);
92 const glm::vec3 farPoint = unproject(ndcX, ndcY, 1.f);
93 for (
int z = 0;
z < depth_; ++
z) {
95 (farDistance_ - nearDistance_);
96 const glm::vec3
world = glm::mix(nearPoint, farPoint, depth01);
97 if (
world.y < minWorldY ||
world.y > maxWorldY)
continue;
98 const float extinction = sigma * std::exp(-falloff * std::max(
world.y - baseHeight, 0.f));
108 const glm::vec3 &worldMax) {
109 for (
int z = 0;
z < depth_; ++
z) {
110 for (
int y = 0;
y < height_; ++
y) {
111 for (
int x = 0;
x < width_; ++
x) {
112 const glm::vec3 uvw((
float(
x) + 0.5f) /
float(width_),
113 (
float(
y) + 0.5f) /
float(height_),
114 (
float(
z) + 0.5f) /
float(depth_));
115 const glm::vec3
world = worldMin + (worldMax - worldMin) * uvw;
116 const float signedExtinction =
volume.sampleExtinction(
world);
117 if (std::fabs(signedExtinction) <= 1e-8f)
continue;
120 f.extinction = std::max(0.f,
previous + signedExtinction);
121 if (signedExtinction > 0.f) {
122 f.scattering +=
volume.getAlbedo() * signedExtinction;
123 f.emissive +=
volume.getEmissive() * signedExtinction;
124 f.anisotropy =
volume.getAnisotropy();
137 auto unproject = [&](
float ndcX,
float ndcY,
float ndcZ) {
141 for (
int y = 0;
y < height_; ++
y) {
142 const float v = (float(
y) + 0.5f) /
float(height_);
143 for (
int x = 0;
x < width_; ++
x) {
144 const float u = (float(
x) + 0.5f) /
float(width_);
145 const glm::vec3 nearPoint = unproject(
u * 2.f - 1.f,
v * 2.f - 1.f, 0.f);
146 const glm::vec3 farPoint = unproject(
u * 2.f - 1.f,
v * 2.f - 1.f, 1.f);
147 for (
int z = 0;
z < depth_; ++
z) {
149 (farDistance_ - nearDistance_);
150 const glm::vec3
world = glm::mix(nearPoint, farPoint, depth01);
151 const float signedExtinction =
volume.sampleExtinction(
world);
152 if (std::fabs(signedExtinction) <= 1e-8f)
continue;
155 f.extinction = std::max(0.f,
previous + signedExtinction);
156 if (signedExtinction > 0.f) {
157 f.scattering +=
volume.getAlbedo() * signedExtinction;
158 f.emissive +=
volume.getEmissive() * signedExtinction;
159 f.anisotropy =
volume.getAnisotropy();
171 const glm::vec3 &worldMin,
172 const glm::vec3 &worldMax,
float extinctionScale,
174 const glm::vec3 omega = glm::clamp(
albedo, glm::vec3(0.f), glm::vec3(1.f));
175 for (
int z = 0;
z < depth_; ++
z) {
176 for (
int y = 0;
y < height_; ++
y) {
177 for (
int x = 0;
x < width_; ++
x) {
178 const glm::vec3 uvw((
float(
x) + 0.5f) /
float(width_),
179 (
float(
y) + 0.5f) /
float(height_),
180 (
float(
z) + 0.5f) /
float(depth_));
181 const glm::vec3
world = worldMin + (worldMax - worldMin) * uvw;
196 float extinctionScale) {
197 for (
int z = 0;
z < depth_; ++
z) {
198 for (
int y = 0;
y < height_; ++
y) {
199 for (
int x = 0;
x < width_; ++
x) {
201 (
float(
y) + 0.5f) /
float(height_), (
float(
z) + 0.5f) /
float(depth_));
202 const float scale = std::max(extinctionScale, 0.f);
214 const glm::vec3
light = glm::max(
lightColor, glm::vec3(0.f)) * std::max(phaseScale, 0.f);
215 for (
int y = 0;
y < height_; ++
y) {
216 for (
int x = 0;
x < width_; ++
x) {
217 glm::vec3 radiance(0.f);
218 float transmittance = 1.f;
219 float previousDistance = nearDistance_;
220 for (
int z = 0;
z < depth_; ++
z) {
222 const float stepLength = std::max(
distance - previousDistance, 0.f);
225 const float opticalDepth =
f.extinction * stepLength;
226 const float stepTransmittance = std::exp(-opticalDepth);
227 const float integral =
f.extinction > 1e-6f
228 ? (1.f - stepTransmittance) /
f.extinction
230 radiance += transmittance *
231 (
f.scattering *
light *
f.lightVisibility +
f.emissive) * integral;
232 transmittance *= stepTransmittance;
233 integrated_[
index(
x,
y,
z)] = glm::vec4(radiance, transmittance);
240 const glm::vec3 &worldMin,
241 const glm::vec3 &worldMax,
242 const glm::vec3 &ambientLight) {
243 for (
int y = 0;
y < height_; ++
y) {
244 for (
int x = 0;
x < width_; ++
x) {
245 glm::vec3 radiance(0.f);
246 float transmittance = 1.f;
247 float previousDistance = nearDistance_;
248 for (
int z = 0;
z < depth_; ++
z) {
249 const glm::vec3 uvw((
float(
x) + 0.5f) /
float(width_),
250 (
float(
y) + 0.5f) /
float(height_),
251 (
float(
z) + 0.5f) /
float(depth_));
252 const glm::vec3
world = worldMin + (worldMax - worldMin) * uvw;
253 glm::vec3 incident = glm::max(ambientLight, glm::vec3(0.f));
255 if (!
light.enabled ||
light.radius <= 0.f ||
light.intensity <= 0.f)
continue;
256 const float d = glm::length(
world -
light.position);
257 if (
d >=
light.radius)
continue;
258 const float radial = 1.f -
d /
light.radius;
259 incident += glm::max(
light.color, glm::vec3(0.f)) *
light.intensity *
263 const float stepLength = std::max(
distance - previousDistance, 0.f);
266 const float opticalDepth =
f.extinction * stepLength;
267 const float stepTransmittance = std::exp(-opticalDepth);
268 const float integral =
f.extinction > 1e-6f
269 ? (1.f - stepTransmittance) /
f.extinction
271 radiance += transmittance *
272 (
f.scattering * incident *
f.lightVisibility +
f.emissive) * integral;
273 transmittance *= stepTransmittance;
274 integrated_[
index(
x,
y,
z)] = glm::vec4(radiance, transmittance);
281 at(
x,
y,
z).lightVisibility = std::clamp(visibility, 0.f, 1.f);
285 float rejectionThreshold) {
286 if (history.width_ != width_ || history.height_ != height_ || history.depth_ != depth_)
287 return integrated_.size();
288 const float baseWeight = std::clamp(historyWeight, 0.f, 1.f);
289 const float threshold = std::max(rejectionThreshold, 0.f);
290 std::size_t rejected = 0;
291 for (std::size_t i = 0; i < integrated_.size(); ++i) {
292 const glm::vec4
current = integrated_[i];
293 const glm::vec4
previous = history.integrated_[i];
294 const float currentLuma = glm::dot(glm::vec3(
current), glm::vec3(0.2126f, 0.7152f, 0.0722f));
295 const float previousLuma = glm::dot(glm::vec3(
previous), glm::vec3(0.2126f, 0.7152f, 0.0722f));
296 const float relativeDelta = std::fabs(currentLuma - previousLuma) /
297 std::max(std::max(currentLuma, previousLuma), 1e-4f);
298 const bool reject = relativeDelta > threshold;
299 if (reject) ++rejected;
300 const float weight = reject ? 0.f : baseWeight;
311 if (integrated_.empty())
return glm::vec4(0.f, 0.f, 0.f, 1.f);
312 const int x = std::clamp(
int(
u *
float(width_)), 0, width_ - 1);
313 const int y = std::clamp(
int(
v *
float(height_)), 0, height_ - 1);
318 if (media_.empty())
return {};
319 const int x = std::clamp(
int(
u *
float(width_)), 0, width_ - 1);
320 const int y = std::clamp(
int(
v *
float(height_)), 0, height_ - 1);
321 const int z = std::clamp(
int(
w *
float(depth_)), 0, depth_ - 1);
graphics::Texture * albedo
std::vector< eve::ProcgenProbeDesc > lights
std::array< float, 3 > scale
graphics::Canvas * previous
std::map< std::string, std::vector< std::string > > graph
const UnitySourceAsset & source
Camera-frustum volume used by the volumetric fog passes and CPU references.
void clear()
Remove all media and integrated lighting without changing dimensions.
void setDepthRange(float nearDistance, float farDistance)
Configure the world-distance range represented by logarithmic Z slices.
void setLightVisibility(int x, int y, int z, float visibility)
Set the main-light shadow visibility for one froxel.
FogFroxel & at(int x, int y, int z)
Read or write one froxel; indices are clamped to the grid.
void injectSparseVolume(const SparseVolumeTexture &texture, float extinctionScale=1.f)
Resample a pre-baked sparse volume into this working volume.
void injectHeightFogFrustum(float baseExtinction, const glm::vec3 &albedo, float baseHeight, float heightFalloff, float minWorldY, float maxWorldY, const glm::mat4 &invViewProj)
Add exponential-height fog by reconstructing each froxel in the active camera frustum.
void injectLocalVolumeFrustum(const FogVolume &volume, const glm::mat4 &invViewProj)
Voxelize one analytic local volume in the active camera frustum.
void injectHeightFog(float baseExtinction, const glm::vec3 &albedo, float baseHeight, float heightFalloff, float minWorldY, float maxWorldY)
Add a global exponential-height medium to every froxel.
const glm::vec4 & integratedAt(int x, int y, int z) const
Cumulative RGB in-scattering and A transmittance at a froxel.
std::size_t blendHistory(const AtmosphereVolume &history, float historyWeight, float rejectionThreshold)
Blend integrated lighting with a reprojected history volume.
void resize(int width, int height, int depth)
Resize the froxel grid and clear all media.
float sliceDistance(int z) const
World distance at the center of a Z slice.
void injectDensityGraph(const VolumeDensityGraph &graph, const glm::vec3 &worldMin, const glm::vec3 &worldMax, float extinctionScale, const glm::vec3 &albedo, float time=0.f)
Voxelize a procedural density graph into this volume.
void injectLocalVolume(const FogVolume &volume, const glm::vec3 &worldMin, const glm::vec3 &worldMax)
Voxelize one analytic local volume over the supplied world bounds.
FogFroxel sampleMedia(float u, float v, float w) const
Sample raw media with normalized volume coordinates.
int sliceForDistance(float distance) const
Convert a world distance into a clamped slice index.
glm::vec4 sampleIntegrated(float u, float v, float distance) const
Query cumulative fog for a transparent fragment.
void integrateLocalLights(const std::vector< VolumetricLight > &lights, const glm::vec3 &worldMin, const glm::vec3 &worldMax, const glm::vec3 &ambientLight)
Inject local lights and integrate the volume in one reference pass.
void integrate(const glm::vec3 &lightColor, float phaseScale=1.f)
Integrate scattering and transmittance along every view ray.
Analytic local participating-media volume injected into atmospheric fog.
Brick-sparse, pre-bakeable participating-media texture.
FogFroxel sample(float u, float v, float w) const
Nearest sample using normalized coordinates.
Compact acyclic program that evaluates procedural volume density.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Participating-media coefficients stored in one froxel.
Local light injected into participating media.