Rain and more

This commit is contained in:
Gabrieal Jimmy
2026-10-02 13:04:49 -05:00
parent 2c99305fb0
commit 16c03afd16
15 changed files with 374 additions and 46 deletions

View File

@@ -95,6 +95,9 @@ public:
// Strength of the contact shadow baked into the ground under each instance
// (see GroundOcclusion); 0 disables. Its size follows the model's footprint.
float groundOcclusion = 0.0f;
// Dither this model away where it hides the player (tall scenery like
// trees). Turn off for low props the camera sees over (bridges, rocks).
bool occluderFade = true;
private:
std::vector<GLuint> vaos_;

View File

@@ -8,19 +8,6 @@
namespace hd2d {
namespace {
glm::vec3 flameColor(float t) {
// Yellow core -> orange -> deep red, in linear HDR so the core blooms like
// HD-2D's torches. Kept saturated: overlapping tongues add up quickly.
const glm::vec3 c0(4.0f, 2.3f, 0.6f), c1(3.6f, 1.0f, 0.15f), c2(1.8f, 0.26f, 0.04f), c3(0.3f, 0.03f, 0.01f);
if (t < 0.25f) return glm::mix(c0, c1, t / 0.25f);
if (t < 0.6f) return glm::mix(c1, c2, (t - 0.25f) / 0.35f);
return glm::mix(c2, c3, (t - 0.6f) / 0.4f);
}
} // namespace
ParticleSystem::~ParticleSystem() {
if (vbo_) glDeleteBuffers(1, &vbo_);
if (vao_) glDeleteVertexArrays(1, &vao_);
@@ -79,11 +66,14 @@ void ParticleSystem::spawnFire(const Fire& f, int fireIndex, Kind kind) {
p.age = 0.0f;
float a = u(rng_) * 6.2832f;
if (kind == Kind::Flame) {
float r = 0.2f * k * std::sqrt(u(rng_));
// Chunky licks that fill the bowl and stay low and dense, like the
// painterly brazier fires of HD-2D.
float r = 0.3f * k * std::sqrt(u(rng_));
p.pos = f.pos + glm::vec3(std::cos(a) * r, u(rng_) * 0.05f * k, std::sin(a) * r);
p.vel = glm::vec3(s(rng_) * 0.15f, 1.3f + 0.8f * u(rng_), s(rng_) * 0.15f) * std::sqrt(k);
p.life = 0.5f + 0.4f * u(rng_);
p.size = 0.42f * k * (0.8f + 0.4f * u(rng_));
p.vel = glm::vec3(s(rng_) * 0.12f, 0.6f + 0.45f * u(rng_), s(rng_) * 0.12f) * std::sqrt(k);
p.life = 0.45f + 0.35f * u(rng_);
p.size = 0.55f * k * (0.8f + 0.4f * u(rng_)) * (1.0f - 0.35f * r / (0.3f * k + 1e-4f)); // smaller at the rim
p.color = glm::vec3(0.9f + 0.2f * u(rng_));
} else if (kind == Kind::Ember) {
float r = 0.15f * k * u(rng_);
p.pos = f.pos + glm::vec3(std::cos(a) * r, 0.1f * k, std::sin(a) * r);
@@ -132,11 +122,11 @@ void ParticleSystem::update(float dt, float time) {
spawnFire(f, int(i), kind);
}
};
emit(f.flameAccum, 45.0f * f.scale, Kind::Flame);
emit(f.flameAccum, 70.0f * f.scale, Kind::Flame);
emit(f.emberAccum, 14.0f * f.scale, Kind::Ember);
emit(f.smokeAccum, 6.0f * f.scale, Kind::Smoke);
glm::vec3 glow = glm::vec3(1.0f, 0.45f, 0.13f) * 0.9f * f.flicker;
gpu_.push_back({glm::vec4(f.pos + glm::vec3(0, 0.35f * f.scale, 0), 1.1f * f.scale * f.flicker),
glm::vec3 glow = glm::vec3(1.0f, 0.42f, 0.1f) * 0.8f * f.flicker;
gpu_.push_back({glm::vec4(f.pos + glm::vec3(0, 0.3f * f.scale, 0), 1.5f * f.scale * f.flicker),
glm::vec4(glow, 1.0f), glm::vec4(float(int(Kind::Glow)), 0, 0, 0)});
}
@@ -184,13 +174,15 @@ void ParticleSystem::update(float dt, float time) {
break;
case Kind::Flame: {
const Fire& f = fires_[p.fire];
p.vel.y += 1.6f * dt; // buoyancy
p.vel.x += (std::sin(t * 9.0f) * 0.9f - (p.pos.x - f.pos.x) * 5.0f) * dt; // flicker + taper
p.vel.z += (std::cos(t * 7.3f) * 0.9f - (p.pos.z - f.pos.z) * 5.0f) * dt;
p.vel.y += 0.9f * dt; // buoyancy (kept low: compact, bowl-filling fire)
p.vel.x += (std::sin(t * 9.0f) * 0.9f - (p.pos.x - f.pos.x) * 3.5f) * dt; // flicker + taper
p.vel.z += (std::cos(t * 7.3f) * 0.9f - (p.pos.z - f.pos.z) * 3.5f) * dt;
p.pos += p.vel * dt;
color = flameColor(lifeT) * 0.22f; // tongues overlap a lot; keep the sum orange, not white
alpha = std::min(1.0f, lifeT / 0.08f) * std::pow(1.0f - lifeT, 1.3f);
size = p.size * (0.75f + 0.5f * std::sin(3.1416f * std::min(lifeT * 1.2f, 1.0f))) * (1.0f - 0.5f * lifeT);
// Intensity only: the yellow -> orange -> red palette is applied
// per pixel in particles.frag from heat (center + youth).
color = p.color * f.flicker;
alpha = std::min(1.0f, lifeT / 0.06f) * std::pow(1.0f - lifeT, 0.6f);
size = p.size * (1.0f - 0.55f * lifeT) * (0.7f + 0.3f * std::min(1.0f, lifeT / 0.12f));
break;
}
case Kind::Ember: {
@@ -222,6 +214,13 @@ void ParticleSystem::update(float dt, float time) {
++i;
}
// Flames are alpha-blended (not additive), so draw each fire's oldest,
// reddest tongues first and the young bright core on top.
auto firstFlame = std::stable_partition(gpu_.begin(), gpu_.end(), [](const GpuParticle& g) {
return int(g.params.x) != int(Kind::Flame);
});
std::sort(firstFlame, gpu_.end(), [](const GpuParticle& a, const GpuParticle& b) { return a.params.y > b.params.y; });
if (!vao_) {
glCreateVertexArrays(1, &vao_);
glCreateBuffers(1, &vbo_);

View File

@@ -185,12 +185,13 @@ struct Renderer::Impl {
void drawModels(const Scene& scene, int pass, bool reflectionPass) {
Shader& s = pass == 1 ? meshDepth : mesh;
s.use();
if (pass == 0) s.set("uOccluderFade", reflectionPass || !settings->occluderFade ? 0 : 1);
const bool fadeView = pass == 0 && !reflectionPass && settings->occluderFade;
for (const ModelBatch* b : scene.models) {
if (!b || !b->model) continue;
if (pass == 1 && !b->castShadows) continue;
if (reflectionPass && !b->inReflections) continue;
s.set("uWindScale", b->windScale);
if (pass == 0) s.set("uOccluderFade", fadeView && b->occluderFade ? 1 : 0);
for (size_t i = 0; i < b->model->primitives.size(); ++i) {
const ModelMaterial& m = *b->model->primitives[i].material;
s.set("uAlphaMask", m.alphaMask ? 1 : 0);

View File

@@ -25,7 +25,7 @@ Weather::Weather() {
storm.windStrength = 0.2f;
storm.windGustSpeed = 3.0f;
storm.bloomStrength = 0.06f;
storm.saturation = 0.82f;
storm.saturation = 0.92f;
}
WeatherLook Weather::capture(const Scene& s, const RenderSettings& r) {