9#include <glm/common.hpp>
14constexpr char kMagic[4] = {
'E',
'V',
'S',
'V'};
15constexpr std::uint32_t kVersion = 1;
18bool writeValue(std::ofstream &stream,
const T &
value) {
19 stream.write(
reinterpret_cast<const char *
>(&
value),
sizeof(T));
24bool readValue(std::ifstream &stream, T &
value) {
25 stream.read(
reinterpret_cast<char *
>(&
value),
sizeof(T));
29bool isVacuum(
const FogFroxel &
f) {
30 return f.extinction == 0.f &&
f.scattering == glm::vec3(0.f) &&
f.emissive == glm::vec3(0.f);
36 width_ = std::max(
width, 1);
37 height_ = std::max(
height, 1);
38 depth_ = std::max(
depth, 1);
39 brickSize_ = std::clamp(brickSize, 2, 32);
46 const std::size_t perBrick = std::size_t(brickSize_) * std::size_t(brickSize_) *
47 std::size_t(brickSize_);
48 return bricks_.size() * perBrick;
51bool SparseVolumeTexture::inBounds(
int x,
int y,
int z)
const {
52 return x >= 0 &&
y >= 0 &&
z >= 0 &&
x < width_ &&
y < height_ &&
z < depth_;
55std::uint64_t SparseVolumeTexture::brickKey(
int bx,
int by,
int bz)
const {
56 return (std::uint64_t(std::uint32_t(
bx)) << 42) |
57 (std::uint64_t(std::uint32_t(
by)) << 21) | std::uint64_t(std::uint32_t(
bz));
60std::size_t SparseVolumeTexture::localIndex(
int x,
int y,
int z)
const {
61 const int lx =
x % brickSize_;
62 const int ly =
y % brickSize_;
63 const int lz =
z % brickSize_;
64 return (std::size_t(lz) * std::size_t(brickSize_) + std::size_t(ly)) *
65 std::size_t(brickSize_) + std::size_t(lx);
69 if (!inBounds(
x,
y,
z))
return;
70 const std::uint64_t
key = brickKey(
x / brickSize_,
y / brickSize_,
z / brickSize_);
72 if (
found == bricks_.end()) {
73 if (isVacuum(voxel))
return;
75 brick.voxels.resize(std::size_t(brickSize_) * std::size_t(brickSize_) *
76 std::size_t(brickSize_));
77 found = bricks_.emplace(
key, std::move(brick)).first;
79 found->second.voxels[localIndex(
x,
y,
z)] = voxel;
83 if (!inBounds(
x,
y,
z))
return {};
84 const auto found = bricks_.find(brickKey(
x / brickSize_,
y / brickSize_,
z / brickSize_));
89 return getVoxel(std::clamp(
int(
u *
float(width_)), 0, width_ - 1),
90 std::clamp(
int(
v *
float(height_)), 0, height_ - 1),
91 std::clamp(
int(
w *
float(depth_)), 0, depth_ - 1));
95 const glm::vec3 &worldMax,
float extinctionScale,
96 const glm::vec3 &
albedo,
float emptyThreshold,
float time) {
98 const glm::vec3 omega = glm::clamp(
albedo, glm::vec3(0.f), glm::vec3(1.f));
99 for (
int z = 0;
z < depth_; ++
z) {
100 for (
int y = 0;
y < height_; ++
y) {
101 for (
int x = 0;
x < width_; ++
x) {
102 const glm::vec3 uvw((
float(
x) + 0.5f) /
float(width_),
103 (
float(
y) + 0.5f) /
float(height_),
104 (
float(
z) + 0.5f) /
float(depth_));
106 worldMin + (worldMax - worldMin) * uvw,
time) * extinctionScale);
118 std::ofstream stream(
path, std::ios::binary | std::ios::trunc);
119 if (!stream)
return false;
120 stream.write(kMagic,
sizeof(kMagic));
121 const std::uint32_t
width = std::uint32_t(width_),
height = std::uint32_t(height_);
122 const std::uint32_t
depth = std::uint32_t(depth_), brickSize = std::uint32_t(brickSize_);
123 const std::uint64_t brickCount = std::uint64_t(bricks_.size());
124 if (!writeValue(stream, kVersion) || !writeValue(stream,
width) || !writeValue(stream,
height) ||
125 !writeValue(stream,
depth) || !writeValue(stream, brickSize) ||
126 !writeValue(stream, brickCount))
return false;
127 std::vector<std::uint64_t> keys;
128 keys.reserve(bricks_.size());
129 for (
const auto &entry : bricks_) keys.push_back(entry.first);
130 std::sort(keys.begin(), keys.end());
131 for (std::uint64_t
key : keys) {
132 if (!writeValue(stream,
key))
return false;
133 const Brick &brick = bricks_.at(
key);
135 const float values[9] = {
f.scattering.x,
f.scattering.y,
f.scattering.z,
f.extinction,
136 f.emissive.x,
f.emissive.y,
f.emissive.z,
f.anisotropy,
138 stream.write(
reinterpret_cast<const char *
>(
values),
sizeof(
values));
139 if (!stream)
return false;
146 std::ifstream stream(
path, std::ios::binary);
148 stream.read(magic,
sizeof(magic));
150 std::uint64_t brickCount = 0;
151 if (!stream || !std::equal(std::begin(magic), std::end(magic), std::begin(kMagic)) ||
152 !readValue(stream, version) || version != kVersion || !readValue(stream,
width) ||
153 !readValue(stream,
height) || !readValue(stream,
depth) || !readValue(stream, brickSize) ||
154 !readValue(stream, brickCount) ||
width == 0 ||
height == 0 ||
depth == 0 ||
155 brickSize < 2 || brickSize > 32)
return false;
158 const std::size_t voxelsPerBrick = std::size_t(brickSize) * std::size_t(brickSize) *
159 std::size_t(brickSize);
160 for (std::uint64_t i = 0; i < brickCount; ++i) {
161 std::uint64_t
key = 0;
162 if (!readValue(stream,
key))
return false;
164 brick.voxels.resize(voxelsPerBrick);
167 stream.read(
reinterpret_cast<char *
>(
values),
sizeof(
values));
168 if (!stream)
return false;
175 loaded.bricks_.emplace(
key, std::move(brick));
177 *
this = std::move(loaded);
graphics::Texture * albedo
std::map< std::string, Var > values
std::map< std::string, std::vector< std::string > > graph
Brick-sparse, pre-bakeable participating-media texture.
bool save(const std::string &path) const
Save the deterministic EVSV v1 binary format.
FogFroxel sample(float u, float v, float w) const
Nearest sample using normalized coordinates.
std::size_t getAllocatedVoxelCount() const
Returns the allocated voxel count.
void resize(int width, int height, int depth, int brickSize=8)
Allocate a logical volume; bricks are created only for non-empty voxels.
void bake(const VolumeDensityGraph &graph, const glm::vec3 &worldMin, const glm::vec3 &worldMax, float extinctionScale, const glm::vec3 &albedo, float emptyThreshold=1e-5f, float time=0.f)
Pre-bake a procedural graph into sparse bricks.
FogFroxel getVoxel(int x, int y, int z) const
Read one voxel; missing bricks return vacuum.
void setVoxel(int x, int y, int z, const FogFroxel &voxel)
Store one voxel; all-zero voxels do not allocate a new brick.
bool load(const std::string &path)
Load the deterministic EVSV v1 binary format, replacing current data.
Compact acyclic program that evaluates procedural volume density.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Participating-media coefficients stored in one froxel.