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

813 lines
32 KiB
C++

#include "graphics/Volumetric.h"
#include "graphics/AtmosphereVolume.h"
#include "graphics/FogVolume.h"
#include "common/Exception.h"
#include "graphics/Canvas.h"
#include "graphics/ClipSpace.h"
#include "graphics/Drawable.h"
#include "graphics/Graphics.h"
#include "graphics/RenderSystem.h"
#include "graphics/Shader.h"
#include "graphics/Texture.h"
#include "graphics/shaders/volumetric_post_frag_spv.inc"
#include "graphics/shaders/volumetric_raymarch_frag_spv.inc"
#include "graphics/shaders/volumetric_fog_frag_spv.inc"
#include "graphics/shaders/volumetric_froxel_frag_spv.inc"
#include "graphics/shaders/volumetric_cloud_frag_spv.inc"
#include "graphics/shaders/VolumetricWgsl.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <optional>
#include <vector>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
namespace eve::graphics {
namespace {
Shader *newVolShader(Graphics *gfx, const std::vector<uint32_t> &frag, const char *wgsl) {
if (gfx->getBackendName() == "webgpu")
return gfx->newShaderFromWgsl({}, std::string(shaders::kVolCommon) + wgsl);
return gfx->newShaderFromSpv({}, frag);
}
Shader *createScreenspaceShader(Graphics *gfx) {
if (!gfx) throw eve::Exception("Volumetric: null graphics");
std::vector<uint32_t> frag(volumetric_post_frag_spv,
volumetric_post_frag_spv + volumetric_post_frag_spv_count);
std::vector<uint32_t> vert;
Shader *sh = newVolShader(gfx, frag, shaders::kVolScreen);
sh->declareFloat("lightX");
sh->declareFloat("lightY");
sh->declareFloat("exposure");
sh->declareFloat("decay");
sh->declareFloat("density");
sh->declareFloat("weight");
sh->declareFloat("sampleCount");
sh->declareFloat("dustAmount");
sh->declareFloat("fogAmount");
sh->declareFloat("fogR");
sh->declareFloat("fogG");
sh->declareFloat("fogB");
sh->declareFloat("shaftR");
sh->declareFloat("shaftG");
sh->declareFloat("shaftB");
sh->declareFloat("time");
sh->declareFloat("compositeMode");
sh->declareFloat("intensity");
sh->sendFloat("lightX", 0.5f);
sh->sendFloat("lightY", 0.35f);
sh->sendFloat("exposure", 0.35f);
sh->sendFloat("decay", 0.96f);
sh->sendFloat("density", 0.85f);
sh->sendFloat("weight", 0.55f);
sh->sendFloat("sampleCount", 48.f);
sh->sendFloat("dustAmount", 0.25f);
sh->sendFloat("fogAmount", 0.18f);
sh->sendFloat("fogR", 0.55f);
sh->sendFloat("fogG", 0.62f);
sh->sendFloat("fogB", 0.75f);
sh->sendFloat("shaftR", 1.f);
sh->sendFloat("shaftG", 0.95f);
sh->sendFloat("shaftB", 0.85f);
sh->sendFloat("time", 0.f);
sh->sendFloat("compositeMode", 0.f);
sh->sendFloat("intensity", 1.f);
return sh;
}
Shader *createRayMarchShader(Graphics *gfx) {
if (!gfx) throw eve::Exception("Volumetric: null graphics");
std::vector<uint32_t> frag(volumetric_raymarch_frag_spv,
volumetric_raymarch_frag_spv + volumetric_raymarch_frag_spv_count);
std::vector<uint32_t> vert;
Shader *sh = newVolShader(gfx, frag, shaders::kVolRay);
sh->declareMatrix("invViewProj");
sh->declareFloat("lightDx");
sh->declareFloat("lightDy");
sh->declareFloat("lightDz");
sh->declareFloat("density");
sh->declareFloat("shaftR");
sh->declareFloat("shaftG");
sh->declareFloat("shaftB");
sh->declareFloat("intensity");
sh->declareFloat("nearZ");
sh->declareFloat("farZ");
sh->declareFloat("sampleCount");
sh->declareFloat("dustAmount");
sh->declareFloat("fogAmount");
sh->declareFloat("shadowSteps");
sh->declareFloat("lightU");
sh->declareFloat("lightV");
sh->sendMatrix("invViewProj", glm::mat4(1.f));
sh->sendFloat("lightDx", 0.4f);
sh->sendFloat("lightDy", 1.f);
sh->sendFloat("lightDz", 0.3f);
sh->sendFloat("density", 0.35f);
sh->sendFloat("shaftR", 1.f);
sh->sendFloat("shaftG", 0.95f);
sh->sendFloat("shaftB", 0.85f);
sh->sendFloat("intensity", 1.f);
sh->sendFloat("nearZ", 0.1f);
sh->sendFloat("farZ", 100.f);
sh->sendFloat("sampleCount", 24.f);
sh->sendFloat("dustAmount", 0.25f);
sh->sendFloat("fogAmount", 0.2f);
sh->sendFloat("shadowSteps", 8.f);
sh->sendFloat("lightU", 0.7f);
sh->sendFloat("lightV", 0.2f);
return sh;
}
Shader *createFogShader(Graphics *gfx) {
if (!gfx) throw eve::Exception("Volumetric: null graphics");
std::vector<uint32_t> frag(volumetric_fog_frag_spv,
volumetric_fog_frag_spv + volumetric_fog_frag_spv_count);
std::vector<uint32_t> vert;
Shader *sh = newVolShader(gfx, frag, shaders::kVolFog);
sh->declareMatrix("invViewProj");
sh->declareFloat("lightDx");
sh->declareFloat("lightDy");
sh->declareFloat("lightDz");
sh->declareFloat("density");
sh->declareFloat("fogR");
sh->declareFloat("fogG");
sh->declareFloat("fogB");
sh->declareFloat("intensity");
sh->declareFloat("nearZ");
sh->declareFloat("farZ");
sh->declareFloat("sampleCount");
sh->declareFloat("fogHeight");
sh->declareFloat("heightFalloff");
sh->declareFloat("fogStart");
sh->declareFloat("fogEnd");
sh->declareFloat("noiseAmount");
sh->sendMatrix("invViewProj", glm::mat4(1.f));
sh->sendFloat("lightDx", 0.4f);
sh->sendFloat("lightDy", 1.f);
sh->sendFloat("lightDz", 0.3f);
sh->sendFloat("density", 0.25f);
sh->sendFloat("fogR", 0.55f);
sh->sendFloat("fogG", 0.62f);
sh->sendFloat("fogB", 0.75f);
sh->sendFloat("intensity", 1.f);
sh->sendFloat("nearZ", 0.1f);
sh->sendFloat("farZ", 100.f);
sh->sendFloat("sampleCount", 24.f);
sh->sendFloat("fogHeight", 0.f);
sh->sendFloat("heightFalloff", 0.15f);
sh->sendFloat("fogStart", 2.f);
sh->sendFloat("fogEnd", 40.f);
sh->sendFloat("noiseAmount", 0.35f);
return sh;
}
Shader *createFroxelShader(Graphics *gfx) {
if (!gfx) throw eve::Exception("Volumetric: null graphics");
std::vector<uint32_t> frag(volumetric_froxel_frag_spv,
volumetric_froxel_frag_spv + volumetric_froxel_frag_spv_count);
std::vector<uint32_t> vert;
Shader *sh = newVolShader(gfx, frag, shaders::kVolFroxel);
sh->declareFloat("atlasCols");
sh->declareFloat("atlasRows");
sh->declareFloat("sliceCount");
sh->declareFloat("nearDistance");
sh->declareFloat("farDistance");
sh->sendFloat("atlasCols", 1.f);
sh->sendFloat("atlasRows", 1.f);
sh->sendFloat("sliceCount", 1.f);
sh->sendFloat("nearDistance", 0.1f);
sh->sendFloat("farDistance", 100.f);
return sh;
}
Shader *createCloudShader(Graphics *gfx) {
if (!gfx) throw eve::Exception("Volumetric: null graphics");
std::vector<uint32_t> frag(volumetric_cloud_frag_spv,
volumetric_cloud_frag_spv + volumetric_cloud_frag_spv_count);
std::vector<uint32_t> vert;
Shader *sh = newVolShader(gfx, frag, shaders::kVolCloud);
sh->declareMatrix("invViewProj");
sh->declareFloat("lightDx");
sh->declareFloat("lightDy");
sh->declareFloat("lightDz");
sh->declareFloat("time");
sh->declareFloat("cloudBottom");
sh->declareFloat("cloudTop");
sh->declareFloat("cloudCoverage");
sh->declareFloat("cloudDensity");
sh->declareFloat("cloudScale");
sh->declareFloat("cloudWindX");
sh->declareFloat("cloudWindZ");
sh->declareFloat("sampleCount");
sh->declareFloat("shadowSteps");
sh->declareFloat("cloudLightR");
sh->declareFloat("cloudLightG");
sh->declareFloat("cloudLightB");
sh->sendMatrix("invViewProj", glm::mat4(1.f));
sh->sendFloat("lightDx", 0.4f);
sh->sendFloat("lightDy", 1.f);
sh->sendFloat("lightDz", 0.3f);
sh->sendFloat("time", 0.f);
sh->sendFloat("cloudBottom", 8.f);
sh->sendFloat("cloudTop", 16.f);
sh->sendFloat("cloudCoverage", 0.55f);
sh->sendFloat("cloudDensity", 1.f);
sh->sendFloat("cloudScale", 18.f);
sh->sendFloat("cloudWindX", 1.5f);
sh->sendFloat("cloudWindZ", 0.4f);
sh->sendFloat("sampleCount", 32.f);
sh->sendFloat("shadowSteps", 6.f);
sh->sendFloat("cloudLightR", 1.f);
sh->sendFloat("cloudLightG", 0.92f);
sh->sendFloat("cloudLightB", 0.78f);
return sh;
}
} // namespace
Volumetric::Volumetric(Graphics *gfx) : gfx_(gfx) {
shader_ = createScreenspaceShader(gfx);
rayShader_ = createRayMarchShader(gfx);
fogShader_ = createFogShader(gfx);
cloudShader_ = createCloudShader(gfx);
froxelShader_ = createFroxelShader(gfx);
atmosphereVolume_ = std::make_unique<AtmosphereVolume>();
applyQualityDefaults();
}
Volumetric::~Volumetric() = default;
void Volumetric::applyQualityDefaults() {
if (quality_ == "low") {
downscale_ = 4.f;
if (mode_ == "raymarch") {
rayShader_->sendFloat("sampleCount", 8.f);
rayShader_->sendFloat("shadowSteps", 4.f);
rayShader_->sendFloat("dustAmount", 0.12f);
rayShader_->sendFloat("fogAmount", 0.12f);
} else if (mode_ == "fog") {
fogShader_->sendFloat("sampleCount", 8.f);
fogShader_->sendFloat("noiseAmount", 0.15f);
fogShader_->sendFloat("density", 0.18f);
} else if (mode_ == "cloud") {
cloudShader_->sendFloat("sampleCount", 16.f);
cloudShader_->sendFloat("shadowSteps", 3.f);
} else {
setFloat("sampleCount", 16.f);
setFloat("dustAmount", 0.12f);
setFloat("fogAmount", 0.10f);
setFloat("exposure", 0.40f);
}
} else if (quality_ == "high") {
downscale_ = 1.f;
if (mode_ == "raymarch") {
rayShader_->sendFloat("sampleCount", 48.f);
rayShader_->sendFloat("shadowSteps", 16.f);
rayShader_->sendFloat("dustAmount", 0.35f);
rayShader_->sendFloat("fogAmount", 0.25f);
} else if (mode_ == "fog") {
fogShader_->sendFloat("sampleCount", 48.f);
fogShader_->sendFloat("noiseAmount", 0.45f);
fogShader_->sendFloat("density", 0.32f);
} else if (mode_ == "cloud") {
cloudShader_->sendFloat("sampleCount", 64.f);
cloudShader_->sendFloat("shadowSteps", 10.f);
} else {
setFloat("sampleCount", 96.f);
setFloat("dustAmount", 0.35f);
setFloat("fogAmount", 0.22f);
setFloat("exposure", 0.32f);
}
} else {
quality_ = "medium";
downscale_ = 2.f;
if (mode_ == "raymarch") {
rayShader_->sendFloat("sampleCount", 24.f);
rayShader_->sendFloat("shadowSteps", 8.f);
rayShader_->sendFloat("dustAmount", 0.25f);
rayShader_->sendFloat("fogAmount", 0.2f);
} else if (mode_ == "fog") {
fogShader_->sendFloat("sampleCount", 24.f);
fogShader_->sendFloat("noiseAmount", 0.35f);
fogShader_->sendFloat("density", 0.25f);
} else if (mode_ == "cloud") {
cloudShader_->sendFloat("sampleCount", 32.f);
cloudShader_->sendFloat("shadowSteps", 6.f);
} else {
setFloat("sampleCount", 48.f);
setFloat("dustAmount", 0.25f);
setFloat("fogAmount", 0.18f);
setFloat("exposure", 0.35f);
}
}
}
void Volumetric::setQuality(const std::string &quality) {
quality_ = quality;
applyQualityDefaults();
}
void Volumetric::setMode(const std::string &mode) {
if (mode == "raymarch")
mode_ = "raymarch";
else if (mode == "fog")
mode_ = "fog";
else if (mode == "froxel")
mode_ = "froxel";
else if (mode == "cloud")
mode_ = "cloud";
else
mode_ = "screenspace";
applyQualityDefaults();
}
void Volumetric::setLightScreenUV(float u, float v) {
setFloat("lightX", u);
setFloat("lightY", v);
rayShader_->sendFloat("lightU", u);
rayShader_->sendFloat("lightV", v);
}
float Volumetric::getLightScreenU() const { return getFloat("lightX"); }
float Volumetric::getLightScreenV() const { return getFloat("lightY"); }
void Volumetric::setLightScreenPos(float x, float y, float width, float height) {
if (width < 1.f) width = 1.f;
if (height < 1.f) height = 1.f;
setLightScreenUV(x / width, y / height);
}
void Volumetric::setLightDirection(float dx, float dy, float dz) {
glm::vec3 d(dx, dy, dz);
if (glm::length(d) < 1e-6f) d = glm::vec3(0.f, 1.f, 0.f);
else d = glm::normalize(d);
lightDir_ = d;
rayShader_->sendFloat("lightDx", d.x);
rayShader_->sendFloat("lightDy", d.y);
rayShader_->sendFloat("lightDz", d.z);
fogShader_->sendFloat("lightDx", d.x);
fogShader_->sendFloat("lightDy", d.y);
fogShader_->sendFloat("lightDz", d.z);
cloudShader_->sendFloat("lightDx", d.x);
cloudShader_->sendFloat("lightDy", d.y);
cloudShader_->sendFloat("lightDz", d.z);
}
void Volumetric::setInvViewProj(const glm::mat4 &invViewProj) {
invViewProj_ = invViewProj;
rayShader_->sendMatrix("invViewProj", invViewProj_);
fogShader_->sendMatrix("invViewProj", invViewProj_);
cloudShader_->sendMatrix("invViewProj", invViewProj_);
}
void Volumetric::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) {
nearZ_ = nearZ > 1e-4f ? nearZ : 0.1f;
farZ_ = farZ > nearZ_ ? farZ : nearZ_ + 1.f;
const float aspectSafe = aspect > 1e-4f ? aspect : 1.f;
const float fovRad = fovYDeg * 0.017453292519943295f;
const glm::mat4 view =
glm::lookAtRH(glm::vec3(eyeX, eyeY, eyeZ), glm::vec3(targetX, targetY, targetZ),
glm::vec3(upX, upY, upZ));
const glm::mat4 proj = perspectiveVulkanRH_ZO(fovRad, aspectSafe, nearZ_, farZ_);
setInvViewProj(glm::inverse(proj * view));
rayShader_->sendFloat("nearZ", nearZ_);
rayShader_->sendFloat("farZ", farZ_);
fogShader_->sendFloat("nearZ", nearZ_);
fogShader_->sendFloat("farZ", farZ_);
}
void Volumetric::setShaftColor(float r, float g, float b) {
setFloat("shaftR", r);
setFloat("shaftG", g);
setFloat("shaftB", b);
rayShader_->sendFloat("shaftR", r);
rayShader_->sendFloat("shaftG", g);
rayShader_->sendFloat("shaftB", b);
}
void Volumetric::setFogColor(float r, float g, float b) {
setFloat("fogR", r);
setFloat("fogG", g);
setFloat("fogB", b);
fogShader_->sendFloat("fogR", r);
fogShader_->sendFloat("fogG", g);
fogShader_->sendFloat("fogB", b);
}
void Volumetric::setIntensity(float intensity) {
setFloat("intensity", intensity);
rayShader_->sendFloat("intensity", intensity);
fogShader_->sendFloat("intensity", intensity);
}
void Volumetric::setTime(float seconds) { setFloat("time", seconds); }
void Volumetric::setDensity(float density) {
setFloat("density", density);
rayShader_->sendFloat("density", density);
fogShader_->sendFloat("density", density);
}
void Volumetric::setFogHeight(float worldY) {
fogHeight_ = worldY;
fogShader_->sendFloat("fogHeight", fogHeight_);
}
void Volumetric::setFogHeightFalloff(float falloff) {
fogHeightFalloff_ = falloff < 0.f ? 0.f : falloff;
fogShader_->sendFloat("heightFalloff", fogHeightFalloff_);
}
void Volumetric::setFogStart(float startDistance) {
fogStart_ = startDistance < 0.f ? 0.f : startDistance;
fogShader_->sendFloat("fogStart", fogStart_);
}
void Volumetric::setFogEnd(float endDistance) {
fogEnd_ = endDistance <= fogStart_ ? fogStart_ + 1.f : endDistance;
fogShader_->sendFloat("fogEnd", fogEnd_);
}
void Volumetric::setFogNoise(float amount) {
fogNoise_ = amount < 0.f ? 0.f : amount;
fogShader_->sendFloat("noiseAmount", fogNoise_);
}
void Volumetric::setCloudLayer(float bottom, float top) {
cloudBottom_ = bottom;
cloudTop_ = top > bottom + 0.01f ? top : bottom + 0.01f;
cloudShader_->sendFloat("cloudBottom", cloudBottom_);
cloudShader_->sendFloat("cloudTop", cloudTop_);
}
void Volumetric::setCloudCoverage(float coverage) {
cloudCoverage_ = std::clamp(coverage, 0.f, 1.f);
cloudShader_->sendFloat("cloudCoverage", cloudCoverage_);
}
void Volumetric::setCloudDensity(float density) {
cloudDensity_ = std::max(density, 0.f);
cloudShader_->sendFloat("cloudDensity", cloudDensity_);
}
void Volumetric::setCloudScale(float worldScale) {
cloudScale_ = std::max(worldScale, 0.01f);
cloudShader_->sendFloat("cloudScale", cloudScale_);
}
void Volumetric::setCloudWind(float x, float z) {
cloudWind_ = glm::vec3(x, 0.f, z);
cloudShader_->sendFloat("cloudWindX", x);
cloudShader_->sendFloat("cloudWindZ", z);
}
void Volumetric::setCloudLightColor(float r, float g, float b) {
cloudLightColor_ = glm::vec3(std::max(r, 0.f), std::max(g, 0.f), std::max(b, 0.f));
cloudShader_->sendFloat("cloudLightR", cloudLightColor_.x);
cloudShader_->sendFloat("cloudLightG", cloudLightColor_.y);
cloudShader_->sendFloat("cloudLightB", cloudLightColor_.z);
}
bool Volumetric::hasParam(const std::string &name) const {
return (shader_ && shader_->hasUniform(name)) || (rayShader_ && rayShader_->hasUniform(name)) ||
(fogShader_ && fogShader_->hasUniform(name)) ||
(cloudShader_ && cloudShader_->hasUniform(name));
}
void Volumetric::setFloat(const std::string &name, float value) {
if (!shader_) throw eve::Exception("Volumetric.setFloat: null shader");
if (shader_->hasUniform(name)) shader_->sendFloat(name, value);
if (rayShader_ && rayShader_->hasUniform(name)) rayShader_->sendFloat(name, value);
if (fogShader_ && fogShader_->hasUniform(name)) fogShader_->sendFloat(name, value);
if (cloudShader_ && cloudShader_->hasUniform(name)) cloudShader_->sendFloat(name, value);
}
float Volumetric::getFloat(const std::string &name) const {
// Prefer the active mode's shader so quality/sample queries match setMode.
if (mode_ == "raymarch" && rayShader_ && rayShader_->hasUniform(name)) {
float v = 0.f;
if (rayShader_->getFromVar(name, &v, sizeof(v)) == int(sizeof(v))) return v;
}
if (mode_ == "fog" && fogShader_ && fogShader_->hasUniform(name)) {
float v = 0.f;
if (fogShader_->getFromVar(name, &v, sizeof(v)) == int(sizeof(v))) return v;
}
if (mode_ == "cloud" && cloudShader_ && cloudShader_->hasUniform(name)) {
float v = 0.f;
if (cloudShader_->getFromVar(name, &v, sizeof(v)) == int(sizeof(v))) return v;
}
if (shader_ && shader_->hasUniform(name)) {
float v = 0.f;
if (shader_->getFromVar(name, &v, sizeof(v)) == int(sizeof(v))) return v;
}
if (rayShader_ && rayShader_->hasUniform(name)) {
float v = 0.f;
if (rayShader_->getFromVar(name, &v, sizeof(v)) == int(sizeof(v))) return v;
}
if (fogShader_ && fogShader_->hasUniform(name)) {
float v = 0.f;
if (fogShader_->getFromVar(name, &v, sizeof(v)) == int(sizeof(v))) return v;
}
if (cloudShader_ && cloudShader_->hasUniform(name)) {
float v = 0.f;
if (cloudShader_->getFromVar(name, &v, sizeof(v)) == int(sizeof(v))) return v;
}
throw eve::Exception("Volumetric.getFloat: missing param '%s'", name.c_str());
}
int Volumetric::getSampleCount() const { return int(std::lround(getFloat("sampleCount"))); }
int Volumetric::resolutionFor(int fullSize) const {
if (fullSize < 1) return 1;
const float ds = std::max(downscale_, 1.f);
return std::max(1, int(std::floor(float(fullSize) / ds)));
}
void Volumetric::uploadCommon(bool compositeFromScene) {
setFloat("compositeMode", compositeFromScene ? 1.f : 0.f);
}
void Volumetric::uploadRayMarchCommon() {
rayShader_->sendMatrix("invViewProj", invViewProj_);
rayShader_->sendFloat("lightDx", lightDir_.x);
rayShader_->sendFloat("lightDy", lightDir_.y);
rayShader_->sendFloat("lightDz", lightDir_.z);
rayShader_->sendFloat("nearZ", nearZ_);
rayShader_->sendFloat("farZ", farZ_);
}
void Volumetric::uploadFogCommon() {
fogShader_->sendMatrix("invViewProj", invViewProj_);
fogShader_->sendFloat("lightDx", lightDir_.x);
fogShader_->sendFloat("lightDy", lightDir_.y);
fogShader_->sendFloat("lightDz", lightDir_.z);
fogShader_->sendFloat("nearZ", nearZ_);
fogShader_->sendFloat("farZ", farZ_);
fogShader_->sendFloat("fogHeight", fogHeight_);
fogShader_->sendFloat("heightFalloff", fogHeightFalloff_);
fogShader_->sendFloat("fogStart", fogStart_);
fogShader_->sendFloat("fogEnd", fogEnd_);
fogShader_->sendFloat("noiseAmount", fogNoise_);
}
void Volumetric::uploadCloudCommon() {
cloudShader_->sendMatrix("invViewProj", invViewProj_);
cloudShader_->sendFloat("lightDx", lightDir_.x);
cloudShader_->sendFloat("lightDy", lightDir_.y);
cloudShader_->sendFloat("lightDz", lightDir_.z);
cloudShader_->sendFloat("cloudBottom", cloudBottom_);
cloudShader_->sendFloat("cloudTop", cloudTop_);
cloudShader_->sendFloat("cloudCoverage", cloudCoverage_);
cloudShader_->sendFloat("cloudDensity", cloudDensity_);
cloudShader_->sendFloat("cloudScale", cloudScale_);
cloudShader_->sendFloat("cloudWindX", cloudWind_.x);
cloudShader_->sendFloat("cloudWindZ", cloudWind_.z);
cloudShader_->sendFloat("cloudLightR", cloudLightColor_.x);
cloudShader_->sendFloat("cloudLightG", cloudLightColor_.y);
cloudShader_->sendFloat("cloudLightB", cloudLightColor_.z);
}
void Volumetric::drawFullscreen(Graphics *gfx, Texture *source, Shader *shader) {
if (!gfx) throw eve::Exception("Volumetric: null graphics");
if (!source) throw eve::Exception("Volumetric: null source texture");
if (!shader) throw eve::Exception("Volumetric: null shader");
const float dw = gfx->getCanvas() ? float(gfx->getCanvas()->getWidth()) : float(gfx->getWidth());
const float dh =
gfx->getCanvas() ? float(gfx->getCanvas()->getHeight()) : float(gfx->getHeight());
gfx->drawTexturedRectShader(source, shader, 0.f, 0.f, dw, dh, Color(1.f, 1.f, 1.f, 1.f));
}
void Volumetric::beginOcclusionMap(Graphics *gfx, float lightPixelX, float lightPixelY,
float lightRadiusPixels) {
if (!gfx) throw eve::Exception("Volumetric.beginOcclusionMap: null graphics");
gfx->clear(Color(0.f, 0.f, 0.f, 1.f), std::nullopt, std::nullopt);
const float r = std::max(lightRadiusPixels, 1.f);
gfx->drawSolidRect(lightPixelX - r, lightPixelY - r, r * 2.f, r * 2.f,
Color(1.f, 1.f, 1.f, 1.f));
const float w = gfx->getCanvas() ? float(gfx->getCanvas()->getWidth()) : float(gfx->getWidth());
const float h = gfx->getCanvas() ? float(gfx->getCanvas()->getHeight()) : float(gfx->getHeight());
setLightScreenPos(lightPixelX, lightPixelY, w, h);
}
void Volumetric::drawOccluder(Graphics *gfx, Drawable *drawable, const glm::mat4 &matrix) {
if (!gfx || !drawable) return;
gfx->drawOcclusion(drawable, matrix);
}
void Volumetric::drawOccluderSolid(Graphics *gfx, float x, float y, float w, float h) {
if (!gfx) return;
gfx->drawOcclusionSolid(x, y, w, h);
}
void Volumetric::drawOccluderTexture(Graphics *gfx, Texture *texture, float x, float y, float w,
float h) {
if (!gfx) return;
gfx->drawOcclusionTexture(texture, x, y, w, h);
}
void Volumetric::drawOccluders2D(Graphics *gfx) {
if (!gfx) return;
if (ecs::current()->getManager<Renderable2D>() == nullptr) return;
auto view = ecs::View<Renderable2D, Renderable2D::Transform2D, Renderable2D::Sprite>();
for (auto it = view.begin(); it != view.end(); ++it) {
auto [xf, sp] = *it;
if (!sp->visible || !sp->castOcclusion) continue;
const float w = sp->width * (xf->sx == 0.f ? 1.f : xf->sx);
const float h = sp->height * (xf->sy == 0.f ? 1.f : xf->sy);
if (sp->texture)
gfx->drawOcclusionTexture(sp->texture, xf->x, xf->y, w, h);
else
gfx->drawOcclusionSolid(xf->x, xf->y, w, h);
}
}
void Volumetric::scatter(Graphics *gfx, Texture *occlusion) {
uploadCommon(false);
drawFullscreen(gfx, occlusion, shader_);
}
void Volumetric::scatterTo(Graphics *gfx, Texture *occlusion, Canvas *dest) {
if (!gfx) throw eve::Exception("Volumetric.scatterTo: null graphics");
if (!dest) throw eve::Exception("Volumetric.scatterTo: null dest");
Canvas *prev = gfx->getCanvas();
gfx->setCanvas(dest);
scatter(gfx, occlusion);
gfx->setCanvas(prev == gfx ? nullptr : prev);
}
void Volumetric::applyFromScene(Graphics *gfx, Texture *scene) {
uploadCommon(true);
drawFullscreen(gfx, scene, shader_);
}
void Volumetric::applyFromSceneTo(Graphics *gfx, Texture *scene, Canvas *dest) {
if (!gfx) throw eve::Exception("Volumetric.applyFromSceneTo: null graphics");
if (!dest) throw eve::Exception("Volumetric.applyFromSceneTo: null dest");
Canvas *prev = gfx->getCanvas();
gfx->setCanvas(dest);
applyFromScene(gfx, scene);
gfx->setCanvas(prev == gfx ? nullptr : prev);
}
void Volumetric::rayMarch(Graphics *gfx, Texture *linearDepth) {
uploadRayMarchCommon();
drawFullscreen(gfx, linearDepth, rayShader_);
}
void Volumetric::rayMarchTo(Graphics *gfx, Texture *linearDepth, Canvas *dest) {
if (!gfx) throw eve::Exception("Volumetric.rayMarchTo: null graphics");
if (!dest) throw eve::Exception("Volumetric.rayMarchTo: null dest");
Canvas *prev = gfx->getCanvas();
gfx->setCanvas(dest);
rayMarch(gfx, linearDepth);
gfx->setCanvas(prev == gfx ? nullptr : prev);
}
void Volumetric::applyFog(Graphics *gfx, Texture *linearDepth) {
uploadFogCommon();
drawFullscreen(gfx, linearDepth, fogShader_);
}
void Volumetric::applyFogTo(Graphics *gfx, Texture *linearDepth, Canvas *dest) {
if (!gfx) throw eve::Exception("Volumetric.applyFogTo: null graphics");
if (!dest) throw eve::Exception("Volumetric.applyFogTo: null dest");
Canvas *prev = gfx->getCanvas();
gfx->setCanvas(dest);
applyFog(gfx, linearDepth);
gfx->setCanvas(prev == gfx ? nullptr : prev);
}
void Volumetric::renderClouds(Graphics *gfx, Texture *linearDepth) {
uploadCloudCommon();
drawFullscreen(gfx, linearDepth, cloudShader_);
}
void Volumetric::renderCloudsTo(Graphics *gfx, Texture *linearDepth, Canvas *dest) {
if (!gfx) throw eve::Exception("Volumetric.renderCloudsTo: null graphics");
if (!dest) throw eve::Exception("Volumetric.renderCloudsTo: null dest");
Canvas *prev = gfx->getCanvas();
gfx->setCanvas(dest);
renderClouds(gfx, linearDepth);
gfx->setCanvas(prev == gfx ? nullptr : prev);
}
void Volumetric::configureFroxelGrid(int width, int height, int depth, float nearDistance,
float farDistance) {
atmosphereVolume_->resize(width, height, depth);
atmosphereVolume_->setDepthRange(nearDistance, farDistance);
froxelAtlasCols_ = std::max(1, int(std::ceil(std::sqrt(float(atmosphereVolume_->getDepth())))));
froxelAtlasRows_ = std::max(1, (atmosphereVolume_->getDepth() + froxelAtlasCols_ - 1) /
froxelAtlasCols_);
}
void Volumetric::clearFroxelGrid() { atmosphereVolume_->clear(); }
void Volumetric::injectFroxelHeightFog(float extinction, float albedoR, float albedoG,
float albedoB, float baseHeight, float heightFalloff,
float minWorldY, float maxWorldY) {
atmosphereVolume_->injectHeightFog(extinction, glm::vec3(albedoR, albedoG, albedoB),
baseHeight, heightFalloff, minWorldY, maxWorldY);
}
void Volumetric::injectFroxelLocalVolume(FogVolume *volume) {
if (!volume) throw eve::Exception("Volumetric.injectFroxelLocalVolume: null volume");
atmosphereVolume_->injectLocalVolumeFrustum(*volume, invViewProj_);
}
void Volumetric::integrateFroxel(float lightR, float lightG, float lightB, float phaseScale) {
atmosphereVolume_->integrate(glm::vec3(lightR, lightG, lightB), phaseScale);
}
void Volumetric::uploadFroxel(Graphics *gfx) {
if (!gfx) throw eve::Exception("Volumetric.uploadFroxel: null graphics");
const int w = atmosphereVolume_->getWidth();
const int h = atmosphereVolume_->getHeight();
const int d = atmosphereVolume_->getDepth();
if (w <= 0 || h <= 0 || d <= 0)
throw eve::Exception("Volumetric.uploadFroxel: grid is not configured");
const int atlasW = w * froxelAtlasCols_;
const int atlasH = h * froxelAtlasRows_;
std::vector<uint8_t> rgba(std::size_t(atlasW) * std::size_t(atlasH) * 4u, 0u);
for (int z = 0; z < d; ++z) {
const int ox = (z % froxelAtlasCols_) * w;
const int oy = (z / froxelAtlasCols_) * h;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
const glm::vec4 value = atmosphereVolume_->integratedAt(x, y, z);
const std::size_t offset = (std::size_t(oy + y) * std::size_t(atlasW) +
std::size_t(ox + x)) * 4u;
const glm::vec3 mapped = glm::vec3(value) / (glm::vec3(1.f) + glm::vec3(value));
rgba[offset + 0] = uint8_t(std::lround(glm::clamp(mapped.r, 0.f, 1.f) * 255.f));
rgba[offset + 1] = uint8_t(std::lround(glm::clamp(mapped.g, 0.f, 1.f) * 255.f));
rgba[offset + 2] = uint8_t(std::lround(glm::clamp(mapped.b, 0.f, 1.f) * 255.f));
rgba[offset + 3] = uint8_t(std::lround(glm::clamp(value.a, 0.f, 1.f) * 255.f));
}
}
}
if (!froxelAtlas_ || froxelAtlas_->getWidth() != atlasW || froxelAtlas_->getHeight() != atlasH)
froxelAtlas_ = gfx->newTexture(atlasW, atlasH, rgba.data());
else
gfx->updateTexture(froxelAtlas_, atlasW, atlasH, rgba.data());
froxelShader_->sendFloat("atlasCols", float(froxelAtlasCols_));
froxelShader_->sendFloat("atlasRows", float(froxelAtlasRows_));
froxelShader_->sendFloat("sliceCount", float(d));
froxelShader_->sendFloat("nearDistance", atmosphereVolume_->getNearDistance());
froxelShader_->sendFloat("farDistance", atmosphereVolume_->getFarDistance());
}
void Volumetric::applyFroxel(Graphics *gfx, Texture *linearDepth) {
if (!gfx) throw eve::Exception("Volumetric.applyFroxel: null graphics");
if (!linearDepth) throw eve::Exception("Volumetric.applyFroxel: null depth");
if (!froxelAtlas_) throw eve::Exception("Volumetric.applyFroxel: uploadFroxel not called");
const float dw = gfx->getCanvas() ? float(gfx->getCanvas()->getWidth()) : float(gfx->getWidth());
const float dh = gfx->getCanvas() ? float(gfx->getCanvas()->getHeight()) : float(gfx->getHeight());
gfx->drawTexturedRectShaderDepth(linearDepth, froxelAtlas_, froxelShader_, 0.f, 0.f, dw, dh,
Color(1.f, 1.f, 1.f, 1.f));
}
void Volumetric::applyFroxelTo(Graphics *gfx, Texture *linearDepth, Canvas *dest) {
if (!gfx || !dest) throw eve::Exception("Volumetric.applyFroxelTo: null argument");
Canvas *prev = gfx->getCanvas();
gfx->setCanvas(dest);
applyFroxel(gfx, linearDepth);
gfx->setCanvas(prev == gfx ? nullptr : prev);
}
Texture *Volumetric::newLinearDepthTexture(Graphics *gfx, int width, int height,
float (*depth01)(int x, int y, void *userdata),
void *userdata) {
if (!gfx) throw eve::Exception("Volumetric.newLinearDepthTexture: null graphics");
if (width < 1 || height < 1)
throw eve::Exception("Volumetric.newLinearDepthTexture: invalid size");
if (!depth01) throw eve::Exception("Volumetric.newLinearDepthTexture: null depth fn");
std::vector<uint8_t> rgba(size_t(width) * size_t(height) * 4);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
float d = depth01(x, y, userdata);
if (d < 0.f) d = 0.f;
if (d > 1.f) d = 1.f;
const uint8_t b = uint8_t(std::lround(d * 255.f));
const size_t i = size_t(y * width + x) * 4;
rgba[i + 0] = b;
rgba[i + 1] = b;
rgba[i + 2] = b;
rgba[i + 3] = 255;
}
}
return gfx->newTexture(width, height, rgba.data());
}
} // namespace eve::graphics