载入中...
搜索中...
未找到
MapFog.cpp
浏览该文件的文档.
1#include "graphics/MapFog.h"
2
3#include "common/Exception.h"
4#include "graphics/Color.h"
5#include "graphics/Graphics.h"
6#include "graphics/Shader.h"
7#include "graphics/Texture.h"
8
9#include "graphics/shaders/map_fog_frag_spv.inc"
10
11#include <algorithm>
12#include <cmath>
13#include <cstdint>
14#include <vector>
15
16namespace eve::graphics {
17namespace {
18
19float hashWrap(int x, int y, int period, uint32_t seed) {
20 x = ((x % period) + period) % period;
21 y = ((y % period) + period) % period;
22 uint32_t n = uint32_t(x) * 374761393u + uint32_t(y) * 668265263u + seed;
23 n = (n ^ (n >> 13u)) * 1274126177u;
24 return float(n & 0x00ffffffu) / float(0x00ffffffu);
25}
26
27float valueNoiseWrap(float x, float y, int period, uint32_t seed) {
28 const int x0 = int(std::floor(x));
29 const int y0 = int(std::floor(y));
30 const float fx = x - float(x0);
31 const float fy = y - float(y0);
32 // Quintic fade — softer ridges than the classic cubic hermite.
33 const float ux = fx * fx * fx * (fx * (fx * 6.f - 15.f) + 10.f);
34 const float uy = fy * fy * fy * (fy * (fy * 6.f - 15.f) + 10.f);
35 const float n00 = hashWrap(x0, y0, period, seed);
36 const float n10 = hashWrap(x0 + 1, y0, period, seed);
37 const float n01 = hashWrap(x0, y0 + 1, period, seed);
38 const float n11 = hashWrap(x0 + 1, y0 + 1, period, seed);
39 const float nx0 = n00 + (n10 - n00) * ux;
40 const float nx1 = n01 + (n11 - n01) * ux;
41 return nx0 + (nx1 - nx0) * uy;
42}
43
44// True tileable fBm: each octave period is an integer multiple of the base so
45// u=0 and u=1 hit identical lattice corners (no fmod phase-shift seams).
46float fbmSeamless(float u, float v, int basePeriod, uint32_t seed, int octaves) {
47 float sum = 0.f;
48 float amp = 0.55f;
49 float norm = 0.f;
50 int period = std::max(basePeriod, 2);
51 for (int i = 0; i < octaves; ++i) {
52 sum += amp * valueNoiseWrap(u * float(period), v * float(period), period,
53 seed + uint32_t(i) * 97u);
54 norm += amp;
55 period *= 2;
56 amp *= 0.48f;
57 }
58 return (norm > 1e-6f) ? (sum / norm) : 0.f;
59}
60
61float billow(float n) { return 1.f - std::fabs(n * 2.f - 1.f); }
62
63float ridged(float n) {
64 const float r = 1.f - std::fabs(n * 2.f - 1.f);
65 return r * r;
66}
67
68} // namespace
69
70MapFog::MapFog(Graphics *graphics) : graphics_(graphics) {
71 if (!graphics_) throw eve::Exception("MapFog: null graphics");
72 if (graphics_->getBackendName() == "webgpu") {
73 static const std::string fragment = R"(
74struct Externals { data: array<f32, 32>, };
75@group(0) @binding(0) var mainTex: texture_2d<f32>;
76@group(0) @binding(1) var maskTex: texture_2d<f32>;
77@group(0) @binding(2) var mainSampler: sampler;
78@group(0) @binding(3) var maskSampler: sampler;
79@group(0) @binding(4) var<uniform> u: Externals;
80struct In { @location(0) color: vec4<f32>, @location(1) uv: vec2<f32>, };
81
82fn scrollUV(uv: vec2<f32>, tile: f32, speed: f32, time: f32, dir: f32) -> vec2<f32> {
83 return uv * max(tile, 1e-4) + vec2<f32>(dir * speed * time, dir * speed * time * 0.73);
84}
85fn luma(c: vec3<f32>) -> f32 {
86 return dot(c, vec3<f32>(0.299, 0.587, 0.114));
87}
88fn hash22(p: vec2<f32>) -> vec2<f32> {
89 return fract(sin(vec2<f32>(dot(p, vec2<f32>(127.1, 311.7)),
90 dot(p, vec2<f32>(269.5, 183.3)))) * 43758.5453);
91}
92fn softenEdge(v: f32, soft: f32) -> f32 {
93 let lo = clamp(0.5 - soft, 0.0, 1.0);
94 let hi = clamp(0.5 + soft, 0.0, 1.0);
95 let t = smoothstep(lo, hi, v);
96 return pow(t, mix(1.0, 0.50, clamp(soft * 3.0, 0.0, 1.0)));
97}
98fn sampleUnlockSoft(uv: vec2<f32>, softRadius: f32) -> f32 {
99 return (textureSample(maskTex, maskSampler, uv).r
100 + textureSample(maskTex, maskSampler, uv + vec2<f32>(softRadius, 0.0)).r
101 + textureSample(maskTex, maskSampler, uv - vec2<f32>(softRadius, 0.0)).r
102 + textureSample(maskTex, maskSampler, uv + vec2<f32>(0.0, softRadius)).r
103 + textureSample(maskTex, maskSampler, uv - vec2<f32>(0.0, softRadius)).r
104 + textureSample(maskTex, maskSampler, uv + vec2<f32>(softRadius, softRadius) * 0.707).r
105 + textureSample(maskTex, maskSampler, uv + vec2<f32>(-softRadius, softRadius) * 0.707).r
106 + textureSample(maskTex, maskSampler, uv + vec2<f32>(softRadius, -softRadius) * 0.707).r
107 + textureSample(maskTex, maskSampler, uv + vec2<f32>(-softRadius, -softRadius) * 0.707).r) * (1.0 / 9.0);
108}
109
110@fragment fn fs_main(input: In) -> @location(0) vec4<f32> {
111 let time = u.data[0];
112 let tileA = u.data[1];
113 let tileB = u.data[2];
114 let speedA = u.data[3];
115 let speedB = u.data[4];
116 let distort = u.data[5];
117 let fix = vec2<f32>(u.data[6], u.data[7]);
118 let fogRgb = vec3<f32>(u.data[8], u.data[9], u.data[10]);
119 let fogAlpha = clamp(u.data[11], 0.0, 1.0);
120 let shadowOff = vec2<f32>(u.data[12], u.data[13]);
121 let shadowStrength = clamp(u.data[14], 0.0, 1.0);
122 let passMode = u.data[15];
123 let edgeSoft = max(u.data[16], 1e-4);
124 let selectStrength = max(u.data[17], 0.0);
125 let selectPulse = clamp(u.data[18], 0.0, 1.0);
126 let dissolveScale = max(u.data[19], 0.25);
127 let cloudMix = clamp(u.data[20], 0.0, 1.0);
128 let densityContrast = max(u.data[21], 0.05);
129 let densityBias = clamp(u.data[22], 0.0, 0.9);
130 let aspect = max(u.data[23], 1e-4);
131
132 let cloudUv = vec2<f32>(input.uv.x * aspect, input.uv.y);
133 let sampleA = textureSample(mainTex, mainSampler, scrollUV(cloudUv, tileA, speedA, time, 1.0));
134 let sampleB = textureSample(mainTex, mainSampler, scrollUV(cloudUv, tileB, speedB, time, -1.0));
135 let cloudBase = mix(sampleA.rgb, sampleB.rgb, cloudMix);
136 let cloudMul = clamp(sampleA.rgb * sampleB.rgb * 1.12, vec3<f32>(0.0), vec3<f32>(1.0));
137 let cloud = mix(cloudBase, cloudMul, 0.28);
138 let noise = luma(cloud);
139 var cover = max(sampleA.a, sampleB.a) + sampleA.a * sampleB.a * 0.45;
140 cover = clamp(cover, 0.0, 1.0);
141
142 let softRadius = max(edgeSoft * 0.35, 0.008);
143 let baseUv = input.uv + fix;
144 // Hard unwarped clear core — warp must not drag fog into the unlock hole.
145 let clearCore = smoothstep(0.90, 0.985,
146 sampleUnlockSoft(baseUv, max(softRadius * 2.6, 0.018)));
147 let warpScale = 1.0 - clearCore;
148
149 let warpNoise = luma(mix(
150 textureSample(mainTex, mainSampler, scrollUV(cloudUv, max(tileA * 0.20, 0.12), speedA * 0.28, time, 1.0)).rgb,
151 textureSample(mainTex, mainSampler, scrollUV(cloudUv, max(tileB * 0.20, 0.12), speedB * 0.28, time, -1.0)).rgb,
152 0.5));
153 let warpFine = luma(mix(
154 textureSample(mainTex, mainSampler, scrollUV(cloudUv, max(tileA * 0.55, 0.25), speedA * 0.55, time, 1.0)).rgb,
155 textureSample(mainTex, mainSampler, scrollUV(cloudUv, max(tileB * 0.55, 0.25), speedB * 0.55, time, -1.0)).rgb,
156 0.5));
157 let maskUv = input.uv +
158 ((warpNoise - 0.5) * distort + (warpFine - 0.5) * (distort * 0.35)) * warpScale + fix;
159 let maskSample = textureSample(maskTex, maskSampler, maskUv);
160 let selected = maskSample.g;
161 let dissolve = maskSample.b;
162
163 // Build one implicit surface from map distance + cloud height. The mask
164 // moves the cloud boundary; it never clips individual lobes.
165 let unlockedAmt = softenEdge(sampleUnlockSoft(maskUv, softRadius), edgeSoft);
166 let wideUnlock = softenEdge(
167 sampleUnlockSoft(maskUv, max(softRadius * 2.4, 0.040)),
168 min(edgeSoft * 1.35, 0.48));
169 let frontier = smoothstep(0.02, 0.20, wideUnlock) *
170 (1.0 - smoothstep(0.985, 1.0, wideUnlock));
171 let puffGrid = 9.0;
172 let drift = vec2<f32>(time * 0.035, time * 0.021);
173 let puffPos = floor((cloudUv * puffGrid + drift) * 28.0) / 28.0;
174 let puffCell = floor(puffPos);
175 var puffField = 0.0;
176 var puffShadow = 0.0;
177 for (var py: i32 = -1; py <= 1; py = py + 1) {
178 for (var px: i32 = -1; px <= 1; px = px + 1) {
179 let cell = puffCell + vec2<f32>(f32(px), f32(py));
180 let rnd = hash22(cell);
181 let centre = cell + vec2<f32>(0.18) + rnd * 0.64;
182 let radius = 0.62 + hash22(cell + vec2<f32>(19.7)).x * 0.18;
183 let centreCloud = (centre - drift) / puffGrid;
184 let centreUv = vec2<f32>(centreCloud.x / aspect, centreCloud.y) + fix;
185 let centreMask = textureSample(maskTex, maskSampler, centreUv);
186 // Keep the cluster alive while its Gaussian core contracts. The
187 // reveal channel only releases it at the very end of the motion.
188 let anchored = 1.0 - smoothstep(0.90, 0.995, centreMask.r);
189 let dissolveAtCentre = clamp(centreMask.b, 0.0, 1.0);
190 // Raising this Gaussian isosurface removes the low-density rim
191 // first, so the cloud contracts organically toward its core.
192 let contraction = smoothstep(0.0, 0.82, dissolveAtCentre);
193 let cloudThreshold = mix(0.18, 0.92, contraction);
194 let clusterScale = mix(1.0, 0.18, contraction);
195 let axis = normalize(vec2<f32>(rnd.x - 0.5, rnd.y - 0.5) + vec2<f32>(0.17, 0.08));
196 let side = vec2<f32>(-axis.y, axis.x);
197 let centres = array<vec2<f32>, 4>(centre,
198 centre + axis * radius * 0.62,
199 centre - axis * radius * 0.55 + side * radius * 0.24,
200 centre - side * radius * 0.58);
201 let radii = array<f32, 4>(radius, radius * 0.72, radius * 0.66, radius * 0.58);
202 var cluster = 0.0;
203 var shadowCluster = 0.0;
204 for (var ci: i32 = 0; ci < 4; ci = ci + 1) {
205 let lobeCentre = centre + (centres[ci] - centre) * clusterScale;
206 let sphereXY = (puffPos - lobeCentre) / max(radii[ci], 0.05);
207 let gaussian = exp(-2.2 * dot(sphereXY, sphereXY));
208 let lobe = smoothstep(cloudThreshold,
209 min(cloudThreshold + 0.12, 0.98), gaussian);
210 cluster = max(cluster, lobe);
211 let shadowXY = (puffPos - vec2<f32>(0.0, 0.12) - lobeCentre) /
212 max(radii[ci], 0.05);
213 let shadowGaussian = exp(-2.2 * dot(shadowXY, shadowXY));
214 shadowCluster = max(shadowCluster,
215 smoothstep(cloudThreshold, min(cloudThreshold + 0.12, 0.98),
216 shadowGaussian));
217 }
218 // Fade throughout the contraction, not only in its last third.
219 // Multiplying the complete cluster field preserves its silhouette:
220 // lobes become smaller and paler together instead of being clipped.
221 let earlyFade = 1.0 - 0.42 * contraction;
222 let finalFade = 1.0 - smoothstep(0.88, 1.0, dissolveAtCentre);
223 let alive = earlyFade * finalFade * anchored;
224 puffField = max(puffField, cluster * alive);
225 puffShadow = max(puffShadow, shadowCluster * alive);
226 }
227 }
228 let bottomEdge = max(puffShadow - puffField, 0.0);
229 // Coverage comes only from complete cloud clusters. A continuous backing
230 // sheet reads as a white mask beneath the clouds and makes reveals abrupt.
231 var fogKeep = max(puffField, bottomEdge);
232
233 let density = smoothstep(densityBias, min(densityBias + densityContrast, 0.98),
234 mix(noise, cover, 0.82));
235 let body = mix(0.96, 1.0, density);
236
237 if (passMode < 0.5) {
238 // Drop-shadow of the puff silhouette onto unlocked ground only:
239 // sample cloud cover at uv - shadowOff so the cast matches the fog lobes.
240 let groundVisible = 1.0 - fogKeep;
241 let unlocked = smoothstep(0.38, 0.80, textureSample(maskTex, maskSampler, baseUv).r);
242 let sUnlock = textureSample(maskTex, maskSampler, baseUv + shadowOff).r;
243 let overhang = 1.0 - smoothstep(0.48, 0.84, sUnlock);
244 let castUv = cloudUv - vec2<f32>(shadowOff.x * aspect, shadowOff.y);
245 let castA = textureSample(mainTex, mainSampler, scrollUV(castUv, tileA, speedA, time, 1.0));
246 let castB = textureSample(mainTex, mainSampler, scrollUV(castUv, tileB, speedB, time, -1.0));
247 let castCover = clamp(max(castA.a, castB.a) + castA.a * castB.a * 0.45, 0.0, 1.0);
248 let puffCast = smoothstep(0.12, 0.52, castCover);
249 let a = groundVisible * unlocked * overhang * puffCast *
250 shadowStrength * fogAlpha * input.color.a;
251 return vec4<f32>(0.24, 0.29, 0.38, a * 0.42);
252 }
253
254 let cloudTop = vec3<f32>(0.945, 0.965, 0.965);
255 let cloudBottom = vec3<f32>(0.737, 0.784, 0.800);
256 let sculpted = cloudTop;
257 let frontierPuffs = puffField * smoothstep(0.04, 0.46, wideUnlock);
258 let sculptAmount = mix(0.04, 1.0, max(frontier, frontierPuffs));
259 var col = mix(fogRgb, sculpted, sculptAmount);
260 col = mix(col, cloudBottom, clamp(bottomEdge * 0.88, 0.0, 1.0));
261 let blink = selected * selectStrength * selectPulse;
262 col = mix(col, col * 1.08 + vec3<f32>(0.06, 0.10, 0.14), clamp(blink, 0.0, 1.0));
263 let a = fogKeep * body * fogAlpha * input.color.a;
264 return vec4<f32>(col, a);
265}
266)";
267 shader_ = graphics_->newShaderFromWgsl({}, fragment);
268 } else {
269 std::vector<uint32_t> fragment(map_fog_frag_spv, map_fog_frag_spv + map_fog_frag_spv_count);
270 shader_ = graphics_->newShaderFromSpv({}, fragment);
271 }
272 if (!shader_) throw eve::Exception("MapFog: shader creation failed");
273
274 shader_->declareFloat("time");
275 shader_->declareFloat("tileA");
276 shader_->declareFloat("tileB");
277 shader_->declareFloat("speedA");
278 shader_->declareFloat("speedB");
279 shader_->declareFloat("distort");
280 shader_->declareFloat("fixX");
281 shader_->declareFloat("fixY");
282 shader_->declareFloat("fogR");
283 shader_->declareFloat("fogG");
284 shader_->declareFloat("fogB");
285 shader_->declareFloat("fogAlpha");
286 shader_->declareFloat("shadowOffX");
287 shader_->declareFloat("shadowOffY");
288 shader_->declareFloat("shadowStrength");
289 shader_->declareFloat("passMode");
290 shader_->declareFloat("edgeSoft");
291 shader_->declareFloat("selectStrength");
292 shader_->declareFloat("selectPulse");
293 shader_->declareFloat("dissolveScale");
294 shader_->declareFloat("cloudMix");
295 shader_->declareFloat("densityContrast");
296 shader_->declareFloat("densityBias");
297 shader_->declareFloat("aspect");
298}
299
300MapFog::~MapFog() = default;
301
302void MapFog::update(float dt) {
303 if (dt > 0.f) time_ += dt;
304}
305
306void MapFog::setTime(float time) { time_ = time; }
307
308void MapFog::setCloudTexture(Texture *cloud) { cloud_ = cloud; }
309
311 ensureCloudTexture();
312 return cloud_ ? cloud_ : ownedCloud_;
313}
314
316
317void MapFog::setCloudTiling(float tileA, float tileB) {
318 tileA_ = std::max(tileA, 0.01f);
319 tileB_ = std::max(tileB, 0.01f);
320}
321
322void MapFog::setCloudSpeed(float speedA, float speedB) {
323 speedA_ = speedA;
324 speedB_ = speedB;
325}
326
327void MapFog::setDistort(float amount) { distort_ = std::max(amount, 0.f); }
328
329void MapFog::setDistortFix(float x, float y) {
330 fixX_ = x;
331 fixY_ = y;
332}
333
334void MapFog::setFogColor(float r, float g, float b) {
335 fogR_ = std::clamp(r, 0.f, 1.f);
336 fogG_ = std::clamp(g, 0.f, 1.f);
337 fogB_ = std::clamp(b, 0.f, 1.f);
338}
339
340void MapFog::setFogAlpha(float alpha) { fogAlpha_ = std::clamp(alpha, 0.f, 1.f); }
341
342void MapFog::setEdgeSoftness(float softness) { edgeSoft_ = std::clamp(softness, 0.001f, 0.5f); }
343
344void MapFog::setShadowEnabled(bool enabled) { shadowEnabled_ = enabled; }
345
346void MapFog::setShadow(float offsetX, float offsetY, float strength) {
347 shadowOffX_ = offsetX;
348 shadowOffY_ = offsetY;
349 shadowStrength_ = std::clamp(strength, 0.f, 1.f);
350}
351
352void MapFog::setSelectStrength(float strength) { selectStrength_ = std::max(strength, 0.f); }
353
354void MapFog::setDissolveScale(float scale) { dissolveScale_ = std::max(scale, 0.25f); }
355
356void MapFog::setCloudMix(float mix) { cloudMix_ = std::clamp(mix, 0.f, 1.f); }
357
359 densityContrast_ = std::max(contrast, 0.05f);
360 densityBias_ = std::clamp(bias, 0.f, 0.9f);
361}
362
364 const int n = std::clamp(size, 16, 512);
365 std::vector<float> height(size_t(n * n), 0.f);
366 std::vector<uint8_t> rgba(size_t(n * n * 4));
367 // Reference FoW look: overlapping circular "cotton" puffs with per-blob
368 // lighting (bright top-left / gray bottom-right), not continuous fBm wallpaper.
369 // Toroidal stamps keep the map seamless for dual reverse-scroll.
370
371 struct Puff {
372 float u = 0.f;
373 float v = 0.f;
374 float r = 0.f;
375 };
376 std::vector<Puff> puffs;
377 puffs.reserve(96);
378
379 auto addLayer = [&](int gridX, int gridY, float radiusMin, float radiusMax, uint32_t seed) {
380 for (int j = 0; j < gridY; ++j) {
381 for (int i = 0; i < gridX; ++i) {
382 const float ju = hashWrap(i, j, 1024, seed);
383 const float jv = hashWrap(i, j, 1024, seed + 17u);
384 const float jr = hashWrap(i, j, 1024, seed + 91u);
385 Puff p;
386 p.u = (float(i) + 0.20f + 0.60f * ju) / float(gridX);
387 p.v = (float(j) + 0.20f + 0.60f * jv) / float(gridY);
388 p.r = radiusMin + (radiusMax - radiusMin) * jr;
389 puffs.push_back(p);
390 }
391 }
392 };
393 // Separated lobe families. The previous 4x4 layer used radii up to 0.30,
394 // covering almost every texel several times and collapsing the alpha into
395 // a featureless sheet. Deep fog is filled by the shader; this texture is
396 // deliberately porous so the frontier can expose individual cotton puffs.
397 addLayer(3, 3, 0.16f, 0.23f, 0xA11CE001u);
398 addLayer(5, 5, 0.070f, 0.115f, 0xBEEF42u);
399 addLayer(7, 7, 0.030f, 0.052f, 0xC0FFEEu);
400
401 auto wrapDelta = [](float d) {
402 d -= std::floor(d + 0.5f);
403 return d;
404 };
405
406 for (int y = 0; y < n; ++y) {
407 for (int x = 0; x < n; ++x) {
408 const float u = (float(x) + 0.5f) / float(n);
409 const float v = (float(y) + 0.5f) / float(n);
410 float h = 0.f;
411 for (const Puff &p : puffs) {
412 const float dx = wrapDelta(u - p.u);
413 const float dy = wrapDelta(v - p.v);
414 const float d = std::sqrt(dx * dx + dy * dy) / std::max(p.r, 1e-4f);
415 if (d >= 1.f) continue;
416 // Hard-core cotton lobe (reference FoW): flat top, short soft rim.
417 float b = 1.f - d;
418 b = b * b * (3.f - 2.f * b); // smoothstep
419 h += b;
420 }
421 // Soft-max plateau: merged sheet with deeper valleys (terrain peeks).
422 h = std::clamp(h * 0.90f, 0.f, 1.f);
423 h = h * h * (3.f - 2.f * h);
424 height[size_t(y * n + x)] = h;
425 }
426 }
427
428 auto sampleH = [&](int x, int y) {
429 x = ((x % n) + n) % n;
430 y = ((y % n) + n) % n;
431 return height[size_t(y * n + x)];
432 };
433
434 // Key light from upper-left — matches reference puff self-shadowing.
435 const float lx = 0.62f, ly = 0.72f, lz = 0.35f;
436 const float invLen = 1.f / std::sqrt(lx * lx + ly * ly + lz * lz);
437 const float Lx = lx * invLen, Ly = ly * invLen, Lz = lz * invLen;
438
439 for (int y = 0; y < n; ++y) {
440 for (int x = 0; x < n; ++x) {
441 const float h = sampleH(x, y);
442 // A one-texel derivative is nearly flat on a 512px texture whose
443 // major lobes span 70-100px. A wider footprint restores the
444 // directional light/dark roll visible on the reference clouds.
445 const float hx = sampleH(x + 5, y) - sampleH(x - 5, y);
446 const float hy = sampleH(x, y + 5) - sampleH(x, y - 5);
447
448 // Directional lobe shading — milky top-left, cool gray underside.
449 float nx = -hx * 4.8f, ny = -hy * 4.8f, nz = 0.42f;
450 const float nlen = std::sqrt(nx * nx + ny * ny + nz * nz);
451 nx /= nlen;
452 ny /= nlen;
453 nz /= nlen;
454 const float ndotl = std::max(nx * Lx + ny * Ly + nz * Lz, 0.f);
455 const float lit = 0.42f + 0.58f * ndotl;
456 float shade = std::clamp(0.28f + h * lit * 0.72f, 0.f, 1.f);
457 shade = std::pow(shade, 0.78f);
458
459 const float coolR = 0.68f, coolG = 0.74f, coolB = 0.86f;
460 const float warmR = 1.00f, warmG = 1.00f, warmB = 0.995f;
461 const float t = std::clamp(shade, 0.f, 1.f);
462 const float r =
463 std::clamp(coolR * (1.f - t) + warmR * t + ndotl * 0.04f * h, 0.f, 1.f);
464 const float g =
465 std::clamp(coolG * (1.f - t) + warmG * t + ndotl * 0.025f * h, 0.f, 1.f);
466 const float b = std::clamp(coolB * (1.f - t) + warmB * t, 0.f, 1.f);
467
468 // Opaque cores, clearer valleys — terrain peeks only in gaps.
469 float a = std::clamp((h - 0.07f) / 0.25f, 0.f, 1.f);
470 a = a * a * (3.f - 2.f * a); // smoothstep
471
472 const size_t i = size_t((y * n + x) * 4);
473 rgba[i + 0] = uint8_t(r * 255.f + 0.5f);
474 rgba[i + 1] = uint8_t(g * 255.f + 0.5f);
475 rgba[i + 2] = uint8_t(b * 255.f + 0.5f);
476 rgba[i + 3] = uint8_t(a * 255.f + 0.5f);
477 }
478 }
479 return graphics_->newTexture(n, n, rgba.data(), true, true);
480}
481
482void MapFog::ensureCloudTexture() {
483 if (cloud_) return;
484 if (!ownedCloud_) ownedCloud_ = makeCloudTexture(256);
485 cloud_ = ownedCloud_;
486}
487
488void MapFog::syncUniforms(float passMode) {
489 const float pulse = 0.55f + 0.45f * std::sin(time_ * 4.2f);
490 shader_->sendFloat("time", time_);
491 shader_->sendFloat("tileA", tileA_);
492 shader_->sendFloat("tileB", tileB_);
493 shader_->sendFloat("speedA", speedA_);
494 shader_->sendFloat("speedB", speedB_);
495 shader_->sendFloat("distort", distort_);
496 shader_->sendFloat("fixX", fixX_);
497 shader_->sendFloat("fixY", fixY_);
498 shader_->sendFloat("fogR", fogR_);
499 shader_->sendFloat("fogG", fogG_);
500 shader_->sendFloat("fogB", fogB_);
501 shader_->sendFloat("fogAlpha", fogAlpha_);
502 shader_->sendFloat("shadowOffX", shadowOffX_);
503 shader_->sendFloat("shadowOffY", shadowOffY_);
504 shader_->sendFloat("shadowStrength", shadowStrength_);
505 shader_->sendFloat("passMode", passMode);
506 shader_->sendFloat("edgeSoft", edgeSoft_);
507 shader_->sendFloat("selectStrength", selectStrength_);
508 shader_->sendFloat("selectPulse", pulse);
509 shader_->sendFloat("dissolveScale", dissolveScale_);
510 shader_->sendFloat("cloudMix", cloudMix_);
511 shader_->sendFloat("densityContrast", densityContrast_);
512 shader_->sendFloat("densityBias", densityBias_);
513 shader_->sendFloat("aspect", drawAspect_);
514}
515
516void MapFog::draw(float x, float y, float width, float height) {
517 if (!mask_ || width <= 0.f || height <= 0.f) return;
518 ensureCloudTexture();
519 Texture *cloud = cloud_ ? cloud_ : ownedCloud_;
520 if (!cloud) return;
521
522 drawAspect_ = width / std::max(height, 1e-4f);
523
524 // Article: shadow pass must run before the main cloud pass.
525 if (shadowEnabled_ && shadowStrength_ > 0.f) {
526 syncUniforms(0.f);
527 graphics_->drawTexturedRectShaderDepth(cloud, mask_, shader_, x, y, width, height,
528 Color(1.f, 1.f, 1.f, 1.f));
529 }
530 syncUniforms(1.f);
531 graphics_->drawTexturedRectShaderDepth(cloud, mask_, shader_, x, y, width, height,
532 Color(1.f, 1.f, 1.f, 1.f));
533}
534
535} // namespace eve::graphics
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
int mask
float contrast
float nx
float nz
float ny
glm::vec4 p[6]
tensor::Graph g
Definition GpuGraph.cpp:7
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
double r
float v
int h
std::uint32_t height
std::uint32_t width
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::uint32_t seed
Definition PointSet.cpp:807
float d
float t
float dy
float dx
float bias
float offsetX
float offsetY
float size
Definition TreeMesh.cpp:156
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
virtual std::string getBackendName() const =0
Renderer backend id used by sibling modules (e.g. Gpgpu).
virtual Shader * newShaderFromWgsl(const std::string &vertWgsl, const std::string &fragWgsl)=0
Create a 2D custom shader from WGSL source (WebGPU backend). Empty vert → default textured vertex sha...
virtual Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false)=0
Creates a texture. @ownership Caller deletes unless documented otherwise.
virtual Shader * newShaderFromSpv(const std::vector< uint32_t > &vertSpv, const std::vector< uint32_t > &fragSpv)=0
Create a custom 2D shader from SPIR-V words (vert + frag). Owned by Graphics. Vertex stage may be emp...
virtual void drawTexturedRectShaderDepth(Texture *color, Texture *depth, Shader *shader, float x, float y, float w, float h, const Color &tint)=0
Fullscreen/post draw sampling color at binding 0 and depth at binding 1 (hardware D32,...
void setShadowEnabled(bool enabled)
Enable the pre-pass cloud shadow (must draw before the main pass).
Definition MapFog.cpp:344
void setEdgeSoftness(float softness)
Soft edge half-width around the mask R threshold.
Definition MapFog.cpp:342
void draw(float x, float y, float width, float height)
Draw fog over a destination rectangle (shadow pass then main pass).
Definition MapFog.cpp:516
void setCloudMix(float mix)
Mix weight between the two scrolling cloud samples (0..1).
Definition MapFog.cpp:356
void setCloudTiling(float tileA, float tileB)
Dual-layer cloud tiling (layer A / B).
Definition MapFog.cpp:317
void setMaskTexture(Texture *mask)
Set the RGBA mask bridge texture.
Definition MapFog.cpp:315
Texture * makeCloudTexture(int size=128)
Build a seamless procedural cloud noise texture owned by Graphics.
Definition MapFog.cpp:363
void setFogColor(float r, float g, float b)
Fog tint RGB in 0..1.
Definition MapFog.cpp:334
void setTime(float time)
Override the animated time used by cloud scroll (seconds).
Definition MapFog.cpp:306
void setDistortFix(float x, float y)
Correct additive UV drift introduced by distortion.
Definition MapFog.cpp:329
void setCloudTexture(Texture *cloud)
Set or replace the repeating cloud/color texture.
Definition MapFog.cpp:308
void setDistort(float amount)
Mask-UV distortion amount from desaturated cloud noise.
Definition MapFog.cpp:327
void setFogAlpha(float alpha)
Overall fog opacity in 0..1.
Definition MapFog.cpp:340
void setSelectStrength(float strength)
Selection blink amplitude for mask G.
Definition MapFog.cpp:352
void setCloudSpeed(float speedA, float speedB)
Dual-layer scroll speeds in UV units per second.
Definition MapFog.cpp:322
void update(float dt)
Advance dual-scroll / blink timers.
Definition MapFog.cpp:302
void setCloudDensity(float contrast, float bias)
Shape cloud alpha: contrast (soft→hard) and bias (coverage).
Definition MapFog.cpp:358
MapFog(Graphics *graphics)
Create a fog overlay renderer backed by the supplied graphics device.
Definition MapFog.cpp:70
void setShadow(float offsetX, float offsetY, float strength)
Shadow UV offset and strength.
Definition MapFog.cpp:346
Texture * getCloudTexture()
Return the active cloud texture (never null after first ensureCloudTexture). @ownership Borrowed; Gra...
Definition MapFog.cpp:310
void setDissolveScale(float scale)
Dissolve noise tiling scale.
Definition MapFog.cpp:354
void sendFloat(const std::string &name, float x)
Definition Shader.cpp:95
int declareFloat(const std::string &name)
Reserve sequential float slots in the push-constant block. Returns start index.
Definition Shader.cpp:43
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:18
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13
bool enabled