Files
EVEngine/src/modules/graphics/SparseVolumeTexture.cpp
T

182 lines
7.3 KiB
C++

#include "graphics/SparseVolumeTexture.h"
#include "graphics/VolumeDensityGraph.h"
#include <algorithm>
#include <cmath>
#include <fstream>
#include <glm/common.hpp>
namespace eve::graphics {
namespace {
constexpr char kMagic[4] = {'E', 'V', 'S', 'V'};
constexpr std::uint32_t kVersion = 1;
template <typename T>
bool writeValue(std::ofstream &stream, const T &value) {
stream.write(reinterpret_cast<const char *>(&value), sizeof(T));
return bool(stream);
}
template <typename T>
bool readValue(std::ifstream &stream, T &value) {
stream.read(reinterpret_cast<char *>(&value), sizeof(T));
return bool(stream);
}
bool isVacuum(const FogFroxel &f) {
return f.extinction == 0.f && f.scattering == glm::vec3(0.f) && f.emissive == glm::vec3(0.f);
}
} // namespace
void SparseVolumeTexture::resize(int width, int height, int depth, int brickSize) {
width_ = std::max(width, 1);
height_ = std::max(height, 1);
depth_ = std::max(depth, 1);
brickSize_ = std::clamp(brickSize, 2, 32);
bricks_.clear();
}
void SparseVolumeTexture::clear() { bricks_.clear(); }
std::size_t SparseVolumeTexture::getAllocatedVoxelCount() const {
const std::size_t perBrick = std::size_t(brickSize_) * std::size_t(brickSize_) *
std::size_t(brickSize_);
return bricks_.size() * perBrick;
}
bool SparseVolumeTexture::inBounds(int x, int y, int z) const {
return x >= 0 && y >= 0 && z >= 0 && x < width_ && y < height_ && z < depth_;
}
std::uint64_t SparseVolumeTexture::brickKey(int bx, int by, int bz) const {
return (std::uint64_t(std::uint32_t(bx)) << 42) |
(std::uint64_t(std::uint32_t(by)) << 21) | std::uint64_t(std::uint32_t(bz));
}
std::size_t SparseVolumeTexture::localIndex(int x, int y, int z) const {
const int lx = x % brickSize_;
const int ly = y % brickSize_;
const int lz = z % brickSize_;
return (std::size_t(lz) * std::size_t(brickSize_) + std::size_t(ly)) *
std::size_t(brickSize_) + std::size_t(lx);
}
void SparseVolumeTexture::setVoxel(int x, int y, int z, const FogFroxel &voxel) {
if (!inBounds(x, y, z)) return;
const std::uint64_t key = brickKey(x / brickSize_, y / brickSize_, z / brickSize_);
auto found = bricks_.find(key);
if (found == bricks_.end()) {
if (isVacuum(voxel)) return;
Brick brick;
brick.voxels.resize(std::size_t(brickSize_) * std::size_t(brickSize_) *
std::size_t(brickSize_));
found = bricks_.emplace(key, std::move(brick)).first;
}
found->second.voxels[localIndex(x, y, z)] = voxel;
}
FogFroxel SparseVolumeTexture::getVoxel(int x, int y, int z) const {
if (!inBounds(x, y, z)) return {};
const auto found = bricks_.find(brickKey(x / brickSize_, y / brickSize_, z / brickSize_));
return found == bricks_.end() ? FogFroxel{} : found->second.voxels[localIndex(x, y, z)];
}
FogFroxel SparseVolumeTexture::sample(float u, float v, float w) const {
return getVoxel(std::clamp(int(u * float(width_)), 0, width_ - 1),
std::clamp(int(v * float(height_)), 0, height_ - 1),
std::clamp(int(w * float(depth_)), 0, depth_ - 1));
}
void SparseVolumeTexture::bake(const VolumeDensityGraph &graph, const glm::vec3 &worldMin,
const glm::vec3 &worldMax, float extinctionScale,
const glm::vec3 &albedo, float emptyThreshold, float time) {
clear();
const glm::vec3 omega = glm::clamp(albedo, glm::vec3(0.f), glm::vec3(1.f));
for (int z = 0; z < depth_; ++z) {
for (int y = 0; y < height_; ++y) {
for (int x = 0; x < width_; ++x) {
const glm::vec3 uvw((float(x) + 0.5f) / float(width_),
(float(y) + 0.5f) / float(height_),
(float(z) + 0.5f) / float(depth_));
const float extinction = std::max(0.f, graph.evaluate(
worldMin + (worldMax - worldMin) * uvw, time) * extinctionScale);
if (extinction <= emptyThreshold) continue;
FogFroxel voxel;
voxel.extinction = extinction;
voxel.scattering = omega * extinction;
setVoxel(x, y, z, voxel);
}
}
}
}
bool SparseVolumeTexture::save(const std::string &path) const {
std::ofstream stream(path, std::ios::binary | std::ios::trunc);
if (!stream) return false;
stream.write(kMagic, sizeof(kMagic));
const std::uint32_t width = std::uint32_t(width_), height = std::uint32_t(height_);
const std::uint32_t depth = std::uint32_t(depth_), brickSize = std::uint32_t(brickSize_);
const std::uint64_t brickCount = std::uint64_t(bricks_.size());
if (!writeValue(stream, kVersion) || !writeValue(stream, width) || !writeValue(stream, height) ||
!writeValue(stream, depth) || !writeValue(stream, brickSize) ||
!writeValue(stream, brickCount)) return false;
std::vector<std::uint64_t> keys;
keys.reserve(bricks_.size());
for (const auto &entry : bricks_) keys.push_back(entry.first);
std::sort(keys.begin(), keys.end());
for (std::uint64_t key : keys) {
if (!writeValue(stream, key)) return false;
const Brick &brick = bricks_.at(key);
for (const FogFroxel &f : brick.voxels) {
const float values[9] = {f.scattering.x, f.scattering.y, f.scattering.z, f.extinction,
f.emissive.x, f.emissive.y, f.emissive.z, f.anisotropy,
f.lightVisibility};
stream.write(reinterpret_cast<const char *>(values), sizeof(values));
if (!stream) return false;
}
}
return true;
}
bool SparseVolumeTexture::load(const std::string &path) {
std::ifstream stream(path, std::ios::binary);
char magic[4]{};
stream.read(magic, sizeof(magic));
std::uint32_t version = 0, width = 0, height = 0, depth = 0, brickSize = 0;
std::uint64_t brickCount = 0;
if (!stream || !std::equal(std::begin(magic), std::end(magic), std::begin(kMagic)) ||
!readValue(stream, version) || version != kVersion || !readValue(stream, width) ||
!readValue(stream, height) || !readValue(stream, depth) || !readValue(stream, brickSize) ||
!readValue(stream, brickCount) || width == 0 || height == 0 || depth == 0 ||
brickSize < 2 || brickSize > 32) return false;
SparseVolumeTexture loaded;
loaded.resize(int(width), int(height), int(depth), int(brickSize));
const std::size_t voxelsPerBrick = std::size_t(brickSize) * std::size_t(brickSize) *
std::size_t(brickSize);
for (std::uint64_t i = 0; i < brickCount; ++i) {
std::uint64_t key = 0;
if (!readValue(stream, key)) return false;
Brick brick;
brick.voxels.resize(voxelsPerBrick);
for (FogFroxel &f : brick.voxels) {
float values[9]{};
stream.read(reinterpret_cast<char *>(values), sizeof(values));
if (!stream) return false;
f.scattering = {values[0], values[1], values[2]};
f.extinction = values[3];
f.emissive = {values[4], values[5], values[6]};
f.anisotropy = values[7];
f.lightVisibility = values[8];
}
loaded.bricks_.emplace(key, std::move(brick));
}
*this = std::move(loaded);
return true;
}
} // namespace eve::graphics