载入中...
搜索中...
未找到
SkyAtmosphereLuts.cpp
浏览该文件的文档.
2#include <algorithm>
3#include <cmath>
4#include <cstdint>
5#include <glm/glm.hpp>
6#include <limits>
8
9namespace eve::graphics {
10namespace {
11using Vec = glm::dvec3;
12constexpr double Pi = 3.14159265358979323846;
13Vec vector(const std::array<float, 3>& v) { return {v[0], v[1], v[2]}; }
14bool range(float v, float lo, float hi) { return std::isfinite(v) && v >= lo && v <= hi; }
15bool coefficients(const std::array<float, 3>& values, float maximum = 100) {
16 return std::all_of(values.begin(), values.end(), [maximum](float v) { return range(v, 0, maximum); });
17}
18struct Medium {
20};
21Medium medium(const SkyAtmosphereParameters& p, Vec point) {
22 double altitude = std::max(0.0, glm::length(point) - p.groundRadiusKm);
23 Vec ray = vector(p.rayleigh) * std::exp(-altitude / p.rayleighHeightKm);
24 double density = std::exp(-altitude / p.mieHeightKm);
25 Vec mie = vector(p.mieScattering) * density;
26 double ozone = std::max(0.0, 1 - std::abs(altitude - p.ozoneCenterKm) / p.ozoneHalfWidthKm);
27 return {ray + mie, ray + mie + vector(p.mieAbsorption) * density + vector(p.ozoneAbsorption) * ozone};
28}
29glm::dvec2 sphere(Vec point, Vec direction, double radius) {
30 double b = glm::dot(point, direction), d = b * b - glm::dot(point, point) + radius * radius;
31 if (d < 0) return {-1, -1};
32 double root = std::sqrt(d);
33 return {-b - root, -b + root};
34}
35glm::dvec2 transmissionUv(const SkyAtmosphereParameters& p, double height, double cosine) {
36 double bottom = p.groundRadiusKm, top = bottom + p.atmosphereHeightKm;
37 double h = std::sqrt(top * top - bottom * bottom),
38 rho = std::sqrt(std::max(0.0, height * height - bottom * bottom));
39 double distance =
40 std::max(0.0, -height * cosine + std::sqrt(std::max(0.0, height * height * (cosine * cosine - 1) + top * top)));
41 double minimum = top - height, maximum = rho + h;
42 return glm::clamp(glm::dvec2((distance - minimum) / (maximum - minimum), rho / h), glm::dvec2(0), glm::dvec2(1));
43}
44Vec sample(const std::vector<float>& image, unsigned width, unsigned height, glm::dvec2 uv) {
45 auto p = glm::clamp(uv * glm::dvec2(width, height) - .5, glm::dvec2(0), glm::dvec2(width - 1, height - 1));
46 unsigned x = unsigned(p.x), y = unsigned(p.y), x1 = std::min(x + 1, width - 1), y1 = std::min(y + 1, height - 1);
47 auto at = [&](unsigned a, unsigned b) {
48 size_t i = (size_t(b) * width + a) * 4;
49 return Vec(image[i], image[i + 1], image[i + 2]);
50 };
51 return glm::mix(glm::mix(at(x, y), at(x1, y), p.x - x), glm::mix(at(x, y1), at(x1, y1), p.x - x), p.y - y);
52}
53void store(std::vector<float>& image, size_t texel, Vec value) {
54 for (int c = 0; c < 3; ++c) {
55 // Match the reference R11G11B10 LUT storage before bilinear filtering.
56 // Keeping the decoded values in RGBA32F preserves the shared volume ABI.
57 const double maximum = c == 2 ? 64512.0 : 65024.0;
58 const double v = std::clamp(value[c], 0.0, maximum);
59 const double exponent = std::max(-14.0, std::floor(std::log2(std::max(v, 1e-30))));
60 const double quantum = std::exp2(exponent - (c == 2 ? 5 : 6));
61 const double scaled = v / quantum, lower = std::floor(scaled);
62 const double fraction = scaled - lower;
63 const double rounded = lower + (fraction > .5 || (fraction == .5 && std::fmod(lower, 2.0) != 0));
64 image[texel * 4 + size_t(c)] = float(rounded * quantum);
65 }
66 image[texel * 4 + 3] = 1;
67}
68struct Integrated {
69 Vec luminance{0}, feedback{0};
70};
71Integrated integrate(const SkyAtmosphereParameters& p, const detail::SkyAtmosphereLuts& tables, Vec origin,
72 Vec direction, Vec light) {
73 double top = double(p.groundRadiusKm) + p.atmosphereHeightKm;
74 auto ground = sphere(origin, direction, p.groundRadiusKm);
75 double end = sphere(origin, direction, top).y;
76 bool hitsGround = ground.x > 0 && ground.x < end;
77 if (hitsGround) end = ground.x;
78 double step = end / 15;
79 Integrated result;
80 Vec throughput(1);
81 auto transmission = [&](Vec point) {
82 double h = glm::length(point);
83 return sample(tables.transmittance, tables.TransmittanceWidth, tables.TransmittanceHeight,
84 transmissionUv(p, h, glm::dot(point, light) / h));
85 };
86 for (int i = 0; i < 15; ++i) {
87 Vec point = origin + direction * ((i + .3) * step);
88 auto m = medium(p, point);
89 Vec segment = glm::exp(-m.extinction * step);
90 bool shadow = sphere(point, light, p.groundRadiusKm).x > 0;
91 Vec source = shadow ? Vec(0) : transmission(point) * m.scattering / (4 * Pi);
92 result.luminance += throughput * source * (Vec(1) - segment) / glm::max(m.extinction, Vec(1e-9));
93 // UE's finite-order approximation uses a rectangle integral for feedback.
94 result.feedback += throughput * m.scattering * step;
95 throughput *= segment;
96 }
97 if (hitsGround) {
98 Vec point = origin + direction * end;
99 double cosine = std::max(0.0, glm::dot(glm::normalize(point), light));
100 result.luminance += transmission(point) * throughput * cosine * vector(p.groundAlbedo) / Pi;
101 }
102 return result;
103}
104} // namespace
105
107 auto fog = heightFog.validate();
108 if (!fog) return fog;
109 if (!coefficients(rayleigh) || !coefficients(mieScattering) || !coefficients(mieAbsorption) ||
110 !coefficients(ozoneAbsorption) || !coefficients(groundAlbedo, 1) || !range(rayleighHeightKm, 1e-5f, 1e6f) ||
111 !range(mieHeightKm, 1e-5f, 1e6f) || !range(ozoneHalfWidthKm, 1e-5f, 1e6f) || !range(ozoneCenterKm, 0, 1e6f) ||
112 !range(groundRadiusKm, 1, 1e6f) || !range(atmosphereHeightKm, .001f, 1e4f) ||
116 "Invalid atmospheric coefficient, geometry or energy control",
117 "sky.atmosphere"));
118 return Result<void>::success();
119}
120
122 if (!range(directionalElevationRange[0], -1, 1) || !range(directionalElevationRange[1], -1, 1) ||
124 !range(heightFalloffPerMetre, 0, 1) || !range(baseHeightMetres, -1e6f, 1e6f) ||
125 !range(startDistanceMetres, 0, 1e6f) || !range(maximumOpacity, 0, 1) || !coefficients(inscattering) ||
126 !coefficients(directionalInscattering) || !range(directionalExponent, .001f, 100) ||
130 "Invalid height fog density, color, height or distance",
131 "sky.height-fog"));
132 return Result<void>::success();
133}
134
136 using Baked = Result<SkyAtmosphereLuts>;
137 auto valid = p.validate();
138 if (!valid) return Baked::failure(valid.status());
139 try {
140 SkyAtmosphereLuts tables;
141 tables.transmittance.resize(size_t(tables.TransmittanceWidth) * tables.TransmittanceHeight * 4);
142 tables.multiScattering.resize(size_t(tables.MultiWidth) * tables.MultiHeight * 4);
143 double bottom = p.groundRadiusKm, top = bottom + p.atmosphereHeightKm,
144 h = std::sqrt(top * top - bottom * bottom);
145 for (unsigned y = 0; y < tables.TransmittanceHeight; ++y)
146 for (unsigned x = 0; x < tables.TransmittanceWidth; ++x) {
147 double rho = h * (y + .5) / tables.TransmittanceHeight, height = std::sqrt(rho * rho + bottom * bottom);
148 double minimum = top - height,
149 distance = minimum + (x + .5) / tables.TransmittanceWidth * (rho + h - minimum);
150 double cosine =
151 std::clamp((h * h - rho * rho - distance * distance) / (2 * height * distance), -1.0, 1.0);
152 Vec origin(0, height, 0), direction(std::sqrt(std::max(0.0, 1 - cosine * cosine)), cosine, 0);
153 double step = distance / 10;
154 Vec depth(0);
155 for (int i = 0; i < 10; ++i)
156 depth += medium(p, origin + direction * ((i + .3) * step)).extinction * step;
157 store(tables.transmittance, size_t(y) * tables.TransmittanceWidth + x, glm::exp(-depth));
158 }
159 for (unsigned y = 0; y < tables.MultiHeight; ++y)
160 for (unsigned x = 0; x < tables.MultiWidth; ++x) {
161 double cosine = 2 * (x + .5) / tables.MultiWidth - 1;
162 Vec light(std::sqrt(std::max(0.0, 1 - cosine * cosine)), cosine, 0);
163 Vec origin(0, bottom + (y + .5) / tables.MultiHeight * (top - bottom), 0);
164 auto up = integrate(p, tables, origin, Vec(0, 1, 0), light),
165 down = integrate(p, tables, origin, Vec(0, -1, 0), light);
166 Vec r = (up.feedback + down.feedback) * .5, r2 = r * r;
167 Vec luminance = (up.luminance + down.luminance) * .5 * (Vec(1) + r + r2 + r2 * r + r2 * r2) *
168 double(p.multiScatteringFactor);
169 if (!std::isfinite(luminance.x) || !std::isfinite(luminance.y) || !std::isfinite(luminance.z) ||
170 glm::any(glm::greaterThan(luminance, Vec(std::numeric_limits<float>::max()))))
171 return Baked::failure(
172 Diagnostic::error(DiagnosticCode::Failed, "Nonfinite atmospheric lookup table"));
173 store(tables.multiScattering, size_t(y) * tables.MultiWidth + x, luminance);
174 }
175 return Baked::success(std::move(tables));
176 } catch (const std::exception& error) {
177 return Baked::failure(Diagnostic::error(DiagnosticCode::Failed, error.what(), "sky.atmosphere"));
178 }
179}
180
182 const SkyAtmosphereLuts& tables,
183 const std::array<float, 3>& lightDirection) {
184 using Ambient = Result<std::array<float, 3>>;
185 auto valid = p.validate();
186 if (!valid) return Ambient::failure(valid.status());
187 Vec light = vector(lightDirection);
188 const double lightLength = glm::length(light);
189 const auto finite = [](const std::vector<float>& values) {
190 return std::all_of(values.begin(), values.end(), [](float v) { return std::isfinite(v) && v >= 0; });
191 };
192 if (!std::isfinite(lightLength) || lightLength < 1e-6 || p.atmosphereHeightKm <= 6 ||
193 tables.transmittance.size() != size_t(tables.TransmittanceWidth) * tables.TransmittanceHeight * 4 ||
194 tables.multiScattering.size() != size_t(tables.MultiWidth) * tables.MultiHeight * 4 ||
195 !finite(tables.transmittance) || !finite(tables.multiScattering))
197 "Distant sky requires finite matching tables, a nonzero light "
198 "direction and atmosphere above six kilometres",
199 "sky.atmosphere"));
200 light /= lightLength;
201 const double bottom = p.groundRadiusKm, top = bottom + p.atmosphereHeightKm;
202 const Vec origin(0, bottom + 6, 0);
203 // Local fixed stream reproduces the reference's stratification without consuming simulation RNG.
204 uint32_t seed = 0xde4dc0deu;
205 const auto fraction = [&seed]() {
206 seed = seed * 196314165u + 907633515u;
207 return double(seed >> 9) / 8388608.0;
208 };
209 Vec sum(0);
210 for (unsigned i = 0; i < 8; ++i) {
211 for (unsigned j = 0; j < 8; ++j) {
212 const double u = (i + fraction()) / 8, v = (j + fraction()) / 8;
213 const double y = 1 - 2 * u, radius = std::sqrt(std::max(0.0, 1 - y * y));
214 const Vec direction(radius * std::cos(2 * Pi * v), y, radius * std::sin(2 * Pi * v));
215 double end = sphere(origin, direction, top).y;
216 const auto ground = sphere(origin, direction, bottom);
217 if (ground.x > 0) end = std::min(end, ground.x);
218 const double step = end / 10;
219 Vec throughput(1), radiance(0);
220 for (int k = 0; k < 10; ++k) {
221 const Vec point = origin + direction * ((k + .3) * step);
222 const double height = glm::length(point), cosine = glm::dot(point, light) / height;
223 const auto m = medium(p, point);
224 const Vec segment = glm::exp(-m.extinction * step);
225 const Vec sunlight = sphere(point, light, bottom).x > 0
226 ? Vec(0)
227 : sample(tables.transmittance, tables.TransmittanceWidth,
228 tables.TransmittanceHeight, transmissionUv(p, height, cosine));
229 const Vec multiple = sample(tables.multiScattering, tables.MultiWidth, tables.MultiHeight,
230 glm::clamp(glm::dvec2(cosine * .5 + .5, (height - bottom) / (top - bottom)),
231 glm::dvec2(0), glm::dvec2(1)));
232 const Vec source = m.scattering * (sunlight / (4 * Pi) + multiple);
233 radiance += throughput * source * (Vec(1) - segment) / glm::max(m.extinction, Vec(1e-9));
234 throughput *= segment;
235 }
236 sum += radiance;
237 }
238 }
239 const Vec average = sum / 64.0;
240 std::array<float, 3> result{};
241 for (int axis = 0; axis < 3; ++axis) {
242 if (!std::isfinite(average[axis]) || average[axis] < 0 || average[axis] > std::numeric_limits<float>::max())
243 return Ambient::failure(Diagnostic::error(DiagnosticCode::Failed, "Nonfinite distant sky ambient"));
244 result[size_t(axis)] = static_cast<float>(average[axis]);
245 }
246 return Ambient::success(result);
247}
248} // namespace eve::graphics
double value
SQInteger top
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
int root
Definition AnimSmr.cpp:119
float uv
glm::vec4 p[6]
std::map< std::string, Var > values
float maximum[3]
float minimum[3]
vk::UniqueImage image
float u
Definition Grass.cpp:233
double r
float v
HexVec3 up
std::int32_t c
int h
std::uint32_t height
std::uint32_t width
Range range
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
bool finite
std::string error
Definition Package.cpp:60
float radius
std::uint32_t seed
Definition PointSet.cpp:807
float d
V3 origin
Definition RoadBake.cpp:138
RoadLaneDirection direction
Light3D::Data * light
Vec scattering
Vec extinction
Vec feedback
Vec luminance
float step
Definition TreeMesh.cpp:314
const UnitySourceAsset & source
std::uint32_t depth
std::size_t at
float m[16]
glm::vec3 point
float bottom
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
EVENGINE_API_BACKENDS Result< SkyAtmosphereLuts > bakeSkyAtmosphereLuts(const SkyAtmosphereParameters &parameters)
Bake coefficients using UE's default 10/15-sample, two-direction approximation.
EVENGINE_API_BACKENDS Result< std::array< float, 3 > > bakeDistantSkyAmbient(const SkyAtmosphereParameters &parameters, const SkyAtmosphereLuts &tables, const std::array< float, 3 > &lightDirection)
Integrate distant-sky ambient radiance per unit incident RGB irradiance at six kilometres.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
double sample(const Heightmap &map, double u, double v)
Sample.
Spherical atmospheric coefficients in inverse kilometres and kilometre heights.
EVENGINE_API_BACKENDS Result< void > validate() const
Validate finite optical coefficients, bounded geometry and energy controls without mutation.
std::array< float, 3 > directionalInscattering
EVENGINE_API_BACKENDS Result< void > validate() const
Validate finite bounded fog coefficients without mutation.
std::array< float, 2 > directionalElevationRange
Optional directional fog fade over sun direction Y in [-1,1]. Equal endpoints disable fading; otherwi...
Internal owning RGBA32F table candidates, never partially published.
static constexpr unsigned TransmittanceWidth
static constexpr unsigned TransmittanceHeight
std::vector< float > transmittance
Owned table pixels, allocated during the explicit bake. @cost Copies scale with the fixed LUT texel c...