载入中...
搜索中...
未找到
GlobalIllumination.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4#include "graphics/Canvas.h"
6#include "graphics/Graphics.h"
7#include "graphics/Shader.h"
8#include "graphics/Texture.h"
9#include "graphics/shaders/gi_ssgi_frag_spv.inc"
10#include "graphics/shaders/ssr_temporal_frag_spv.inc"
12
13#include <algorithm>
14#include <cmath>
15#include <cstdint>
16#include <vector>
17
18#include <glm/glm.hpp>
19#include <glm/gtc/matrix_transform.hpp>
20
21namespace eve::graphics {
22namespace {
23
24Shader *createSsgiShader(Graphics *gfx) {
25 if (!gfx) throw eve::Exception("GlobalIllumination: null graphics");
26 std::vector<uint32_t> frag(gi_ssgi_frag_spv, gi_ssgi_frag_spv + gi_ssgi_frag_spv_count);
27 Shader *sh = gfx->getBackendName() == "webgpu"
28 ? gfx->newShaderFromWgsl({}, std::string(shaders::kScreenEffectCommon) +
30 : gfx->newShaderFromSpv({}, frag);
31 if (!sh || !sh->gpuHandle) throw eve::Exception("GlobalIllumination: failed to create SSGI shader");
32 sh->declareMatrix("invViewProj");
33 sh->declareFloat("nearZ");
34 sh->declareFloat("farZ");
35 sh->declareFloat("radius");
36 sh->declareFloat("intensity");
37 sh->declareFloat("sampleCount");
38 sh->declareFloat("lightDirX");
39 sh->declareFloat("lightDirY");
40 sh->declareFloat("lightDirZ");
41 sh->declareFloat("lightR");
42 sh->declareFloat("lightG");
43 sh->declareFloat("lightB");
44 sh->declareFloat("texelW");
45 sh->declareFloat("texelH");
46 sh->declareFloat("useNdcDepth");
47 sh->declareFloat("normalValid");
48 sh->declareFloat("thickness");
49 sh->sendMatrix("invViewProj", glm::mat4(1.f));
50 sh->sendFloat("nearZ", 0.1f);
51 sh->sendFloat("farZ", 100.f);
52 sh->sendFloat("radius", 1.25f);
53 sh->sendFloat("intensity", 0.45f);
54 sh->sendFloat("sampleCount", 12.f);
55 sh->sendFloat("lightDirX", 0.4f);
56 sh->sendFloat("lightDirY", 1.f);
57 sh->sendFloat("lightDirZ", 0.3f);
58 sh->sendFloat("lightR", 1.f);
59 sh->sendFloat("lightG", 1.f);
60 sh->sendFloat("lightB", 1.f);
61 sh->sendFloat("texelW", 1.f / 256.f);
62 sh->sendFloat("texelH", 1.f / 256.f);
63 sh->sendFloat("useNdcDepth", 0.f);
64 sh->sendFloat("normalValid", 0.f);
65 sh->sendFloat("thickness", 0.2f);
66 return sh;
67}
68
69Shader *createTemporalShader(Graphics *gfx) {
70 std::vector<uint32_t> frag(ssr_temporal_frag_spv,
71 ssr_temporal_frag_spv + ssr_temporal_frag_spv_count);
72 Shader *sh = gfx->getBackendName() == "webgpu"
73 ? gfx->newShaderFromWgsl({}, std::string(shaders::kScreenEffectCommon) +
75 : gfx->newShaderFromSpv({}, frag);
76 for (int i = 0; i < 16; ++i) sh->declareFloat("reprojection" + std::to_string(i));
77 sh->declareFloat("temporalTexelW");
78 sh->declareFloat("temporalTexelH");
79 sh->declareFloat("temporalHistoryValid");
80 sh->declareFloat("temporalNearZ");
81 sh->declareFloat("temporalFarZ");
82 sh->declareFloat("temporalCurrentWeight");
83 sh->declareFloat("spatialFilterStrength");
84 sh->declareFloat("temporalEnergyEncoded");
85 sh->declareFloat("spatialStep");
86 return sh;
87}
88
89} // namespace
90
92 ssgi_ = createSsgiShader(gfx);
93 temporal_ = createTemporalShader(gfx);
94 applyQualityDefaults();
95}
96
98
99void GlobalIllumination::applyQualityDefaults() {
100 if (quality_ == "low") {
101 radius_ = 0.9f;
102 thickness_ = 0.32f;
103 temporalCurrentWeight_ = 0.3f;
104 spatialFilterStrength_ = 0.8f;
105 resolutionScale_ = 0.5f;
106 setFloat("sampleCount", 8.f);
107 } else if (quality_ == "high") {
108 radius_ = 1.6f;
109 thickness_ = 0.14f;
110 temporalCurrentWeight_ = 0.12f;
111 spatialFilterStrength_ = 0.4f;
112 resolutionScale_ = 1.f;
113 setFloat("sampleCount", 24.f);
114 } else if (quality_ == "ultra") {
115 radius_ = 1.8f;
116 thickness_ = 0.1f;
117 temporalCurrentWeight_ = 0.08f;
118 spatialFilterStrength_ = 0.3f;
119 resolutionScale_ = 1.f;
120 setFloat("sampleCount", 32.f);
121 } else {
122 radius_ = 1.25f;
123 thickness_ = 0.2f;
124 temporalCurrentWeight_ = 0.16f;
125 spatialFilterStrength_ = 0.5f;
126 resolutionScale_ = 1.f;
127 setFloat("sampleCount", 16.f);
128 }
129 setFloat("radius", radius_);
130 setFloat("intensity", intensity_);
131}
132
133void GlobalIllumination::setQuality(const std::string &quality) {
134 if (quality == "low" || quality == "medium" || quality == "high" || quality == "ultra")
135 quality_ = quality;
136 else
137 quality_ = "medium";
138 applyQualityDefaults();
139 historyValid_ = false;
140}
141
143 const glm::mat4 nextViewProj = glm::inverse(invViewProj);
144 if (viewProjValid_) {
145 float maxDelta = 0.f;
146 for (int c = 0; c < 4; ++c)
147 for (int r = 0; r < 4; ++r)
148 maxDelta = std::max(maxDelta,
149 std::abs(nextViewProj[c][r] - previousViewProj_[c][r]));
150 if (maxDelta > 2.f) historyValid_ = false;
151 }
152 invViewProj_ = invViewProj;
153 ssgi_->sendMatrix("invViewProj", invViewProj_);
154}
155
156void GlobalIllumination::setCamera(float eyeX, float eyeY, float eyeZ, float targetX, float targetY,
157 float targetZ, float upX, float upY, float upZ, float fovYDeg,
158 float aspect, float nearZ, float farZ) {
159 nearZ_ = nearZ > 1e-4f ? nearZ : 0.1f;
160 farZ_ = farZ > nearZ_ ? farZ : nearZ_ + 1.f;
161 const float aspectSafe = aspect > 1e-4f ? aspect : 1.f;
162 const float fovRad = fovYDeg * 0.017453292519943295f;
163 const glm::vec3 eye(eyeX, eyeY, eyeZ);
164 glm::vec3 forward = glm::vec3(targetX, targetY, targetZ) - eye;
165 forward = glm::length(forward) > 1e-5f ? glm::normalize(forward)
166 : glm::vec3(0.f, 0.f, -1.f);
167 if (cameraValid_ &&
168 (glm::distance(eye, previousEye_) > std::max(1.f, farZ_ * 0.1f) ||
169 glm::dot(forward, previousForward_) < 0.65f ||
170 std::abs(fovYDeg - previousFovY_) > 3.f ||
171 std::abs(aspectSafe - previousAspect_) > std::max(0.05f, previousAspect_ * 0.1f)))
172 historyValid_ = false;
173 previousEye_ = eye;
174 previousForward_ = forward;
175 previousFovY_ = fovYDeg;
176 previousAspect_ = aspectSafe;
177 cameraValid_ = true;
178 const glm::mat4 view =
179 glm::lookAtRH(eye, glm::vec3(targetX, targetY, targetZ),
180 glm::vec3(upX, upY, upZ));
181 const glm::mat4 proj = perspectiveVulkanRH_ZO(fovRad, aspectSafe, nearZ_, farZ_);
182 setInvViewProj(glm::inverse(proj * view));
183 ssgi_->sendFloat("nearZ", nearZ_);
184 ssgi_->sendFloat("farZ", farZ_);
185}
186
188 radius_ = radius > 1e-4f ? radius : 1e-4f;
189 setFloat("radius", radius_);
190}
191
192void GlobalIllumination::setIntensity(float intensity) {
193 intensity_ = std::max(intensity, 0.f);
194 setFloat("intensity", intensity_);
195}
196
198 thickness_ = std::clamp(thickness, 0.01f, 0.99f);
199 historyValid_ = false;
200}
201
203 const float next = std::clamp(scale, 0.25f, 1.f);
204 if (std::abs(next - resolutionScale_) < 1e-4f) return;
205 resolutionScale_ = next;
206 giCanvas_ = nullptr;
207 historyValid_ = false;
208}
209
211 glm::vec3 d(dx, dy, dz);
212 if (glm::length(d) < 1e-6f) d = glm::vec3(0.f, 1.f, 0.f);
213 else d = glm::normalize(d);
214 lightDir_ = d;
215 historyValid_ = false;
216 ssgi_->sendFloat("lightDirX", lightDir_.x);
217 ssgi_->sendFloat("lightDirY", lightDir_.y);
218 ssgi_->sendFloat("lightDirZ", lightDir_.z);
219}
220
221void GlobalIllumination::setLightColor(float r, float g, float b) {
222 lightColor_ = glm::vec3(std::max(r, 0.f), std::max(g, 0.f), std::max(b, 0.f));
223 historyValid_ = false;
224 ssgi_->sendFloat("lightR", lightColor_.x);
225 ssgi_->sendFloat("lightG", lightColor_.y);
226 ssgi_->sendFloat("lightB", lightColor_.z);
227}
228
229bool GlobalIllumination::hasParam(const std::string &name) const {
230 return ssgi_ && ssgi_->hasUniform(name);
231}
232
233void GlobalIllumination::setFloat(const std::string &name, float value) {
234 if (!ssgi_ || !ssgi_->hasUniform(name))
235 throw eve::Exception("GlobalIllumination.setFloat: missing param '%s'", name.c_str());
236 if (name == "thickness") {
238 ssgi_->sendFloat("thickness", float(samplingFrame_ & 7u) + thickness_);
239 return;
240 }
241 ssgi_->sendFloat(name, value);
242 if (name == "radius" || name == "intensity" || name == "sampleCount")
243 historyValid_ = false;
244 if (name == "radius") radius_ = value;
245 if (name == "intensity") intensity_ = value;
246}
247
248float GlobalIllumination::getFloat(const std::string &name) const {
249 if (!ssgi_ || !ssgi_->hasUniform(name))
250 throw eve::Exception("GlobalIllumination.getFloat: missing param '%s'", name.c_str());
251 if (name == "thickness") return thickness_;
252 float v = 0.f;
253 if (ssgi_->getFromVar(name, &v, sizeof(v)) == int(sizeof(v))) return v;
254 throw eve::Exception("GlobalIllumination.getFloat: missing param '%s'", name.c_str());
255}
256
258 return int(std::lround(getFloat("sampleCount")));
259}
260
261Canvas *GlobalIllumination::getGiCanvas() {
262 if (gfx_) {
263 const int fullW = std::max(1, gfx_->getPixelWidth() > 0 ? gfx_->getPixelWidth() : gfx_->getWidth());
264 const int fullH = std::max(1, gfx_->getPixelHeight() > 0 ? gfx_->getPixelHeight() : gfx_->getHeight());
265 const int targetW = std::max(1, int(std::lround(float(fullW) * resolutionScale_)));
266 const int targetH = std::max(1, int(std::lround(float(fullH) * resolutionScale_)));
267 if (!giCanvas_ || giCanvas_->getWidth() != targetW || giCanvas_->getHeight() != targetH)
268 giCanvas_ = gfx_->newHDRCanvas(targetW, targetH);
269 }
270 return giCanvas_;
271}
272
273void GlobalIllumination::uploadUniforms(int width, int height) {
274 if (width < 1) width = 1;
275 if (height < 1) height = 1;
276 ssgi_->sendMatrix("invViewProj", invViewProj_);
277 ssgi_->sendFloat("nearZ", nearZ_);
278 ssgi_->sendFloat("farZ", farZ_);
279 ssgi_->sendFloat("radius", radius_);
280 ssgi_->sendFloat("intensity", intensity_);
281 ssgi_->sendFloat("lightDirX", lightDir_.x);
282 ssgi_->sendFloat("lightDirY", lightDir_.y);
283 ssgi_->sendFloat("lightDirZ", lightDir_.z);
284 ssgi_->sendFloat("lightR", lightColor_.x);
285 ssgi_->sendFloat("lightG", lightColor_.y);
286 ssgi_->sendFloat("lightB", lightColor_.z);
287 ssgi_->sendFloat("texelW", 1.f / float(width));
288 ssgi_->sendFloat("texelH", 1.f / float(height));
289 ssgi_->sendFloat("normalValid", worldNormal_ ? 1.f : 0.f);
290 // Push constants are capped at 32 floats. Pack the 8-phase sampling index in
291 // the integer part and world-space thickness in the fractional part.
292 const uint32_t packedPhase = (samplingFrame_ & 7u) +
293 uint32_t(std::max(depthPyramidLevels_, 0)) * 8u;
294 ssgi_->sendFloat("thickness", float(packedPhase) +
295 std::clamp(thickness_, 0.01f, 0.99f));
296 ++samplingFrame_;
297}
298
299void GlobalIllumination::drawFullscreen(Graphics *gfx, Texture *source, Shader *shader,
300 Texture *hwDepth) {
301 if (!gfx) throw eve::Exception("GlobalIllumination: null graphics");
302 if (!source) throw eve::Exception("GlobalIllumination: null source texture");
303 if (!shader) throw eve::Exception("GlobalIllumination: null shader");
304 const float dw = gfx->getCanvas() ? float(gfx->getCanvas()->getWidth()) : float(gfx->getWidth());
305 const float dh =
306 gfx->getCanvas() ? float(gfx->getCanvas()->getHeight()) : float(gfx->getHeight());
307 if (hwDepth)
308 gfx->drawTexturedRectShader4(source, hwDepth, worldNormal_ ? worldNormal_ : source,
309 albedo_ ? albedo_ : source, shader, 0.f, 0.f, dw, dh,
310 Color(1.f, 1.f, 1.f, 1.f));
311 else
312 gfx->drawTexturedRectShader(source, shader, 0.f, 0.f, dw, dh, Color(1.f, 1.f, 1.f, 1.f));
313}
314
316 if (!packedAlbedo) throw eve::Exception("GlobalIllumination.applyFromDepth: null packedAlbedo");
317 ssgi_->sendFloat("useNdcDepth", 0.f);
318 uploadUniforms(packedAlbedo->getWidth(), packedAlbedo->getHeight());
319 ssgi_->sendFloat("normalValid", 0.f);
320 drawFullscreen(gfx, packedAlbedo, ssgi_, nullptr);
321}
322
324 if (!color) throw eve::Exception("GlobalIllumination.applyFromScene: null color");
325 if (!hwDepth) throw eve::Exception("GlobalIllumination.applyFromScene: null hwDepth");
326 ssgi_->sendFloat("useNdcDepth", 1.f);
327 uploadUniforms(color->getWidth(), color->getHeight());
328 ssgi_->sendFloat("normalValid", worldNormal_ ? 1.f : 0.f);
329 Texture *traceDepth = depthPyramid_ && depthPyramidLevels_ > 0 ? depthPyramid_ : hwDepth;
330 drawFullscreen(gfx, color, ssgi_, traceDepth);
331}
332
334 if (!gfx) throw eve::Exception("GlobalIllumination.applyFromDepthTo: null graphics");
335 if (!dest) throw eve::Exception("GlobalIllumination.applyFromDepthTo: null dest");
336 Canvas *prev = gfx->getCanvas();
337 gfx->setCanvas(dest);
338 applyFromDepth(gfx, packedAlbedo);
339 gfx->setCanvas(prev);
340}
341
343 return getGiCanvas();
344}
345
347 getGiCanvas();
348 return historyValid_ && historyRead_ ? historyRead_->getTexture()
349 : (giCanvas_ ? giCanvas_->getTexture() : nullptr);
350}
351
352void GlobalIllumination::ensureHistory(int width, int height) {
353 width = std::max(width, 1);
354 height = std::max(height, 1);
355 if (!historyA_ || !historyB_ || historyWidth_ != width || historyHeight_ != height) {
356 historyA_ = gfx_->newHDRCanvas(width, height);
357 historyB_ = gfx_->newHDRCanvas(width, height);
358 spatialA_ = gfx_->newHDRCanvas(width, height);
359 spatialB_ = gfx_->newHDRCanvas(width, height);
360 historyRead_ = historyA_;
361 historyWrite_ = historyB_;
362 historyWidth_ = width;
363 historyHeight_ = height;
364 historyValid_ = false;
365 }
366}
367
368void GlobalIllumination::resolveTemporal(Graphics *gfx, Texture *current) {
369 if (!gfx || !current || !temporalMotionDepth_ || !temporal_) {
370 historyValid_ = false;
371 return;
372 }
373 ensureHistory(current->getWidth(), current->getHeight());
374 const glm::mat4 currentViewProj = glm::inverse(invViewProj_);
375 const glm::mat4 reprojection = previousViewProj_ * invViewProj_;
376 const float *matrix = &reprojection[0][0];
377 for (int i = 0; i < 16; ++i)
378 temporal_->sendFloat("reprojection" + std::to_string(i), matrix[i]);
379 temporal_->sendFloat("temporalTexelW", 1.f / float(historyWidth_));
380 temporal_->sendFloat("temporalTexelH", 1.f / float(historyHeight_));
381 temporal_->sendFloat("temporalNearZ", nearZ_);
382 temporal_->sendFloat("temporalFarZ", farZ_);
383 temporal_->sendFloat("temporalCurrentWeight", temporalCurrentWeight_);
384 temporal_->sendFloat("temporalEnergyEncoded", 1.f);
385
386 const int spatialPasses = getSpatialPassCount();
387 Texture *filtered = current;
388 for (int pass = 0; pass < spatialPasses; ++pass) {
389 Canvas *target = (pass & 1) == 0 ? spatialA_ : spatialB_;
390 temporal_->sendFloat("temporalHistoryValid", 0.f);
391 temporal_->sendFloat("spatialFilterStrength", spatialFilterStrength_);
392 temporal_->sendFloat("spatialStep", float(1 << pass));
393 gfx->setCanvas(target);
394 gfx->drawTexturedRectShaderDepthMotion(
395 filtered, filtered, temporalMotionDepth_, temporal_, 0.f, 0.f, float(historyWidth_),
396 float(historyHeight_), Color(1.f, 1.f, 1.f, 1.f));
397 filtered = target->getTexture();
398 }
399
400 temporal_->sendFloat("temporalHistoryValid", historyValid_ && viewProjValid_ ? 1.f : 0.f);
401 temporal_->sendFloat("spatialFilterStrength", spatialPasses == 0 ? spatialFilterStrength_ : 0.f);
402 temporal_->sendFloat("spatialStep", 1.f);
403 gfx->setCanvas(historyWrite_);
404 Texture *history = historyValid_ ? historyRead_->getTexture() : filtered;
405 gfx->drawTexturedRectShaderDepthMotion(
406 filtered, history, temporalMotionDepth_, temporal_, 0.f, 0.f, float(historyWidth_),
407 float(historyHeight_), Color(1.f, 1.f, 1.f, 1.f));
408 std::swap(historyRead_, historyWrite_);
409 historyValid_ = true;
410 viewProjValid_ = true;
411 previousViewProj_ = currentViewProj;
412}
413
415 if (!gfx) throw eve::Exception("GlobalIllumination.applyFromSceneTo: null graphics");
416 if (!dest) throw eve::Exception("GlobalIllumination.applyFromSceneTo: null dest");
417 Canvas *prev = gfx->getCanvas();
418 gfx->setCanvas(dest);
419 applyFromScene(gfx, color, hwDepth);
420 resolveTemporal(gfx, dest->getTexture());
421 gfx->setCanvas(prev);
422}
423
425 if (!color) throw eve::Exception("GlobalIllumination.applyFromSceneTo: null color");
426 if (!hwDepth) throw eve::Exception("GlobalIllumination.applyFromSceneTo: null hwDepth");
427 Canvas *canvas = getGiCanvas();
428 if (!canvas) throw eve::Exception("GlobalIllumination.applyFromSceneTo: failed to create canvas");
429 applyFromSceneTo(gfx, color, hwDepth, canvas);
430}
431
432} // namespace eve::graphics
LogicalId target
double value
float thickness
Definition CaveMesh.cpp:83
tensor::Graph g
Definition GpuGraph.cpp:7
vk::ShaderModule frag
double r
float v
std::int32_t c
std::uint32_t height
std::uint32_t width
std::array< float, 3 > scale
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
float radius
float d
glm::vec3 eye
float fovRad
Shader * shader
glm::mat4 view
glm::mat4 proj
double current
float dz
float dy
float dx
float targetX
const UnitySourceAsset & source
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Canvas public API.
Definition Canvas.h:17
virtual int getWidth() const =0
Returns the width.
virtual Texture * getTexture()=0
Sampleable color buffer; screen Canvas returns nullptr.
virtual int getHeight() const =0
Returns the height.
void applyFromDepth(Graphics *gfx, Texture *packedAlbedo)
Overlay bounced light onto the currently bound canvas / screen. Packed path (tests): RGB=albedo/lit,...
Canvas * getWorkingCanvas()
Returns the working canvas.
void applyFromScene(Graphics *gfx, Texture *color, Texture *hwDepth)
Applies from scene.
void setResolutionScale(float scale)
Set internal GI resolution scale in the range 0.25 to 1.0.
bool hasParam(const std::string &name) const
True when param.
void applyFromSceneTo(Graphics *gfx, Texture *color, Texture *hwDepth, Canvas *dest)
Applies from scene to.
void setLightDirection(float dx, float dy, float dz)
Sets the light direction.
Texture * getWorkingTexture()
Temporally resolved GI texture, or the raw working texture before history exists. @lifetime Returned ...
~GlobalIllumination()
Global illumination.
void setQuality(const std::string &quality)
"low" | "medium" | "high" | "ultra" (unknown becomes medium).
void setCamera(float eyeX, float eyeY, float eyeZ, float targetX, float targetY, float targetZ, float upX, float upY, float upZ, float fovYDeg, float aspect, float nearZ, float farZ)
Camera for depth reconstruction (RH + ZO). Builds inv(viewProj) and near/far used by applyFromDepth.
int getSampleCount() const
Returns the sample count.
void setFloat(const std::string &name, float value)
Sets the float.
void setRadius(float radius)
Sets the radius.
GlobalIllumination(Graphics *gfx)
Global illumination.
void setThickness(float thickness)
Set world-space hit thickness used to reject screen-space GI leaks.
void setIntensity(float intensity)
Sets the intensity.
void setInvViewProj(const glm::mat4 &invViewProj)
Sets the inv view proj.
int getSpatialPassCount() const
Number of explicit A-trous prefilter passes for the active quality.
void setLightColor(float r, float g, float b)
Sets the light color.
void applyFromDepthTo(Graphics *gfx, Texture *packedAlbedo, Canvas *dest)
Applies from depth to.
float getFloat(const std::string &name) const
Returns the float.
int getPixelHeight() const
Returns the pixel height.
Definition Graphics.h:508
virtual Canvas * getCanvas() const =0
Returns the canvas.
virtual void setCanvas(Canvas *canvas)=0
nullptr or this → screen. Switching flushes pending draws to the previous target.
virtual Canvas * newHDRCanvas(int width, int height)=0
Create an engine-internal linear RGBA16F post-process target.
int getPixelWidth() const
Returns the pixel width.
Definition Graphics.h:506
bool hasUniform(const std::string &name) const
Definition Shader.cpp:59
void sendFloat(const std::string &name, float x)
Definition Shader.cpp:95
void sendMatrix(const std::string &name, const glm::mat4 &m)
Definition Shader.cpp:112
int getFromVar(const std::string &name, void *data, size_t size) const
Definition Shader.cpp:84
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:18
int getWidth() const
Returns the width.
Definition Texture.cpp:28
int getHeight() const
Returns the height.
Definition Texture.cpp:33
constexpr const char * kSsrTemporal
constexpr const char * kSsgi
constexpr const char * kScreenEffectCommon
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
glm::mat4 perspectiveVulkanRH_ZO(float fovyRad, float aspect, float zNear, float zFar)
Right-handed, zero-to-one depth perspective for Vulkan swapchains.
Definition ClipSpace.h:22
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13
glm::mat4 invViewProj
glm::vec4 color