9#include "graphics/shaders/map_fog_frag_spv.inc"
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);
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);
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;
46float fbmSeamless(
float u,
float v,
int basePeriod, uint32_t
seed,
int octaves) {
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);
58 return (norm > 1e-6f) ? (sum / norm) : 0.f;
61float billow(
float n) {
return 1.f - std::fabs(
n * 2.f - 1.f); }
63float ridged(
float n) {
64 const float r = 1.f - std::fabs(
n * 2.f - 1.f);
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>, };
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);
85fn luma(c: vec3<f32>) -> f32 {
86 return dot(c, vec3<f32>(0.299, 0.587, 0.114));
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);
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)));
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);
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);
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);
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;
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,
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,
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;
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));
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);
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);
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),
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);
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);
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);
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);
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);
269 std::vector<uint32_t> fragment(map_fog_frag_spv, map_fog_frag_spv + map_fog_frag_spv_count);
272 if (!shader_)
throw eve::Exception(
"MapFog: shader creation failed");
303 if (dt > 0.f) time_ += dt;
311 ensureCloudTexture();
312 return cloud_ ? cloud_ : ownedCloud_;
318 tileA_ = std::max(tileA, 0.01f);
319 tileB_ = std::max(tileB, 0.01f);
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);
349 shadowStrength_ = std::clamp(
strength, 0.f, 1.f);
359 densityContrast_ = std::max(
contrast, 0.05f);
360 densityBias_ = std::clamp(
bias, 0.f, 0.9f);
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));
376 std::vector<Puff> puffs;
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);
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;
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);
401 auto wrapDelta = [](
float d) {
402 d -= std::floor(
d + 0.5f);
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);
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;
418 b =
b *
b * (3.f - 2.f *
b);
422 h = std::clamp(
h * 0.90f, 0.f, 1.f);
423 h =
h *
h * (3.f - 2.f *
h);
428 auto sampleH = [&](
int x,
int y) {
429 x = ((
x %
n) +
n) %
n;
430 y = ((
y %
n) +
n) %
n;
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;
439 for (
int y = 0;
y <
n; ++
y) {
440 for (
int x = 0;
x <
n; ++
x) {
441 const float h = sampleH(
x,
y);
445 const float hx = sampleH(
x + 5,
y) - sampleH(
x - 5,
y);
446 const float hy = sampleH(
x,
y + 5) - sampleH(
x,
y - 5);
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);
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);
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);
463 std::clamp(coolR * (1.f -
t) + warmR *
t + ndotl * 0.04f *
h, 0.f, 1.f);
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);
469 float a = std::clamp((
h - 0.07f) / 0.25f, 0.f, 1.f);
470 a =
a *
a * (3.f - 2.f *
a);
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);
479 return graphics_->
newTexture(
n,
n, rgba.data(),
true,
true);
482void MapFog::ensureCloudTexture() {
485 cloud_ = ownedCloud_;
488void MapFog::syncUniforms(
float passMode) {
489 const float pulse = 0.55f + 0.45f * std::sin(time_ * 4.2f);
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_);
517 if (!mask_ ||
width <= 0.f ||
height <= 0.f)
return;
518 ensureCloudTexture();
519 Texture *cloud = cloud_ ? cloud_ : ownedCloud_;
525 if (shadowEnabled_ && shadowStrength_ > 0.f) {
528 Color(1.f, 1.f, 1.f, 1.f));
532 Color(1.f, 1.f, 1.f, 1.f));
std::array< float, 3 > scale
EVENGINE_API_FOUNDATION public API.
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).
void setEdgeSoftness(float softness)
Soft edge half-width around the mask R threshold.
void draw(float x, float y, float width, float height)
Draw fog over a destination rectangle (shadow pass then main pass).
void setCloudMix(float mix)
Mix weight between the two scrolling cloud samples (0..1).
void setCloudTiling(float tileA, float tileB)
Dual-layer cloud tiling (layer A / B).
void setMaskTexture(Texture *mask)
Set the RGBA mask bridge texture.
Texture * makeCloudTexture(int size=128)
Build a seamless procedural cloud noise texture owned by Graphics.
void setFogColor(float r, float g, float b)
Fog tint RGB in 0..1.
void setTime(float time)
Override the animated time used by cloud scroll (seconds).
void setDistortFix(float x, float y)
Correct additive UV drift introduced by distortion.
void setCloudTexture(Texture *cloud)
Set or replace the repeating cloud/color texture.
void setDistort(float amount)
Mask-UV distortion amount from desaturated cloud noise.
void setFogAlpha(float alpha)
Overall fog opacity in 0..1.
void setSelectStrength(float strength)
Selection blink amplitude for mask G.
void setCloudSpeed(float speedA, float speedB)
Dual-layer scroll speeds in UV units per second.
void update(float dt)
Advance dual-scroll / blink timers.
void setCloudDensity(float contrast, float bias)
Shape cloud alpha: contrast (soft→hard) and bias (coverage).
MapFog(Graphics *graphics)
Create a fog overlay renderer backed by the supplied graphics device.
void setShadow(float offsetX, float offsetY, float strength)
Shadow UV offset and strength.
Texture * getCloudTexture()
Return the active cloud texture (never null after first ensureCloudTexture). @ownership Borrowed; Gra...
void setDissolveScale(float scale)
Dissolve noise tiling scale.
void sendFloat(const std::string &name, float x)
int declareFloat(const std::string &name)
Reserve sequential float slots in the push-constant block. Returns start index.
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...