mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 22:04:56 +00:00
refactor(gameengine): extract spotlight.update step, distance fog, volumetric improvements
- Extract spotlight logic from render.prepare into dedicated spotlight.update step - render.prepare now only handles camera, shadow, and lighting uniforms - spotlight.update runs per-frame after render.prepare, reads spotlight.state from context - Aim distance configurable via JSON (aim_distance parameter) - Camera-local offset for spotlight origin (matches viewmodel position) - Direction computed from torch position toward camera aim point (natural beam alignment) - Add distance fog to whole room (exponential, dark blue-grey) - Volumetric beam: 48 steps, UE4 interleaved gradient noise, cubic cone falloff - Fog density increased for visible beam effect Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -303,6 +303,7 @@ if(BUILD_SDL3_APP)
|
||||
src/services/impl/workflow/rendering/workflow_shadow_pass_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_shadow_setup_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_spotlight_setup_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_spotlight_update_step.cpp
|
||||
src/services/impl/workflow/graphics/workflow_gpu_pipeline_create_step.cpp
|
||||
src/services/impl/workflow/graphics/workflow_gpu_shader_compile_step.cpp
|
||||
src/services/impl/workflow/graphics/workflow_graphics_buffer_create_index_step.cpp
|
||||
|
||||
@@ -83,36 +83,39 @@ float SpotlightAtten(vec3 lightToFrag, vec3 spotDir, float cosInner, float cosOu
|
||||
// Simulates light scattering through dust particles in the air
|
||||
vec3 VolumetricBeam(vec3 camPos, vec3 fragPos, vec3 flashPos, vec3 flashDir,
|
||||
float cosInner, float cosOuter, vec3 flashColor, float flashRange) {
|
||||
const int NUM_STEPS = 16;
|
||||
const float FOG_DENSITY = 0.007; // dust density in the air
|
||||
const int NUM_STEPS = 48;
|
||||
const float FOG_DENSITY = 0.05;
|
||||
|
||||
vec3 rayDir = fragPos - camPos;
|
||||
float rayLen = length(rayDir);
|
||||
vec3 rayNorm = rayDir / rayLen;
|
||||
float stepSize = rayLen / float(NUM_STEPS);
|
||||
|
||||
// Dithered start offset to reduce banding artifacts
|
||||
float dither = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
// Interleaved gradient noise - much smoother than sin-based dithering
|
||||
// (same technique used by UE4/UE5 volumetric fog)
|
||||
vec2 fc = gl_FragCoord.xy;
|
||||
float dither = fract(52.9829189 * fract(0.06711056 * fc.x + 0.00583715 * fc.y));
|
||||
|
||||
// Mie-like forward scattering (compute once, constant along ray)
|
||||
float viewDot = max(dot(rayNorm, flashDir), 0.0);
|
||||
float scatter = mix(1.0, 2.5, viewDot * viewDot);
|
||||
|
||||
vec3 accumulated = vec3(0.0);
|
||||
for (int i = 0; i < NUM_STEPS; ++i) {
|
||||
float t = (float(i) + dither) * stepSize;
|
||||
vec3 samplePos = camPos + normalize(rayDir) * t;
|
||||
vec3 samplePos = camPos + rayNorm * t;
|
||||
|
||||
// Check if this point in space is inside the spotlight cone
|
||||
vec3 toSample = samplePos - flashPos;
|
||||
float dist = length(toSample);
|
||||
|
||||
// Distance attenuation
|
||||
// Smooth distance falloff
|
||||
float distAtten = clamp(1.0 - dist / flashRange, 0.0, 1.0);
|
||||
distAtten *= distAtten;
|
||||
|
||||
// Cone attenuation
|
||||
float cosAngle = dot(normalize(toSample), flashDir);
|
||||
// Cone with smooth cubic falloff at edges
|
||||
float cosAngle = dot(toSample / dist, flashDir);
|
||||
float coneAtten = clamp((cosAngle - cosOuter) / max(cosInner - cosOuter, 0.001), 0.0, 1.0);
|
||||
|
||||
// Mie-like forward scattering: brighter when looking into the beam
|
||||
float viewDot = max(dot(normalize(rayDir), flashDir), 0.0);
|
||||
float scatter = mix(1.0, 3.0, viewDot * viewDot);
|
||||
coneAtten *= coneAtten; // smoother edge
|
||||
|
||||
accumulated += flashColor * distAtten * coneAtten * scatter * FOG_DENSITY * stepSize;
|
||||
}
|
||||
@@ -190,7 +193,14 @@ void main() {
|
||||
// === Ambient ===
|
||||
vec3 ambient = u_ambient.rgb * albedo;
|
||||
|
||||
// Combine surface + volumetric fog (fog is additive, not affected by surface)
|
||||
// Surface color
|
||||
vec3 color = (Lo + ambient) * exposure + volumetric;
|
||||
|
||||
// === Distance fog (whole room) ===
|
||||
float dist = length(v_worldPos - v_cameraPos);
|
||||
float fogFactor = 1.0 - exp(-dist * 0.06); // exponential fog density
|
||||
vec3 fogColor = vec3(0.02, 0.02, 0.03); // dark blue-grey fog
|
||||
color = mix(color, fogColor, fogFactor);
|
||||
|
||||
o_color = vec4(color, 1.0);
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -75,6 +75,13 @@
|
||||
"typeVersion": 1,
|
||||
"position": [750, 0]
|
||||
},
|
||||
{
|
||||
"id": "spotlight_update",
|
||||
"name": "Update Spotlight",
|
||||
"type": "spotlight.update",
|
||||
"typeVersion": 1,
|
||||
"position": [760, 0]
|
||||
},
|
||||
{
|
||||
"id": "frame_begin",
|
||||
"name": "Begin Offscreen Frame",
|
||||
@@ -472,6 +479,11 @@
|
||||
}
|
||||
},
|
||||
"render_prepare": {
|
||||
"main": {
|
||||
"0": [{ "node": "spotlight_update", "type": "main", "index": 0 }]
|
||||
}
|
||||
},
|
||||
"spotlight_update": {
|
||||
"main": {
|
||||
"0": [{ "node": "frame_begin", "type": "main", "index": 0 }]
|
||||
}
|
||||
|
||||
@@ -862,14 +862,16 @@
|
||||
"position": [1125, 200],
|
||||
"parameters": {
|
||||
"attach": "camera",
|
||||
"inner_cone": 6,
|
||||
"outer_cone": 16,
|
||||
"inner_cone": 3,
|
||||
"outer_cone": 12,
|
||||
"intensity": 3.0,
|
||||
"range": 25,
|
||||
"color_r": 1.0,
|
||||
"color_g": 0.95,
|
||||
"color_b": 0.85,
|
||||
"offset_y": -0.15
|
||||
"offset_x": 0.15,
|
||||
"offset_y": -0.1,
|
||||
"offset_z": -0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -52,17 +52,16 @@ void WorkflowRenderPrepareStep::Execute(const WorkflowStepDefinition& step, Work
|
||||
// --- Fragment uniforms from lighting ---
|
||||
rendering::FragmentUniformData fu = {};
|
||||
|
||||
// Defaults: downward white light, subtle ambient, full exposure
|
||||
fu.light_dir[1] = -1.0f;
|
||||
fu.light_color[0] = 1.0f;
|
||||
fu.light_color[1] = 1.0f;
|
||||
fu.light_color[2] = 1.0f;
|
||||
fu.light_color[3] = 1.0f; // exposure
|
||||
fu.light_color[3] = 1.0f;
|
||||
fu.ambient[0] = 0.2f;
|
||||
fu.ambient[1] = 0.2f;
|
||||
fu.ambient[2] = 0.2f;
|
||||
fu.material[0] = 0.8f; // roughness default
|
||||
fu.material[1] = 0.0f; // metallic default
|
||||
fu.material[0] = 0.8f;
|
||||
fu.material[1] = 0.0f;
|
||||
|
||||
const auto* lighting = context.TryGet<nlohmann::json>("lighting.directional");
|
||||
if (lighting) {
|
||||
@@ -93,40 +92,6 @@ void WorkflowRenderPrepareStep::Execute(const WorkflowStepDefinition& step, Work
|
||||
fu.light_color[3] = lighting->value("exposure", 1.0f);
|
||||
}
|
||||
|
||||
// --- Spotlight / flashlight (reads from context, set by spotlight.setup step) ---
|
||||
const auto* spot = context.TryGet<nlohmann::json>("spotlight.state");
|
||||
if (spot) {
|
||||
std::string attach = spot->value("attach", "camera");
|
||||
glm::vec3 spotPos, spotDir;
|
||||
auto offset = spot->value("offset", std::vector<float>{0,0,0});
|
||||
glm::vec3 off(offset.size()>0?offset[0]:0, offset.size()>1?offset[1]:0, offset.size()>2?offset[2]:0);
|
||||
|
||||
if (attach == "camera") {
|
||||
spotPos = cameraPos + off;
|
||||
spotDir = -glm::vec3(viewMatrix[0][2], viewMatrix[1][2], viewMatrix[2][2]);
|
||||
} else {
|
||||
auto p = spot->value("position", std::vector<float>{0,0,0});
|
||||
auto d = spot->value("direction", std::vector<float>{0,0,-1});
|
||||
spotPos = glm::vec3(p[0], p[1], p[2]) + off;
|
||||
spotDir = glm::normalize(glm::vec3(d[0], d[1], d[2]));
|
||||
}
|
||||
|
||||
fu.flash_pos[0] = spotPos.x;
|
||||
fu.flash_pos[1] = spotPos.y;
|
||||
fu.flash_pos[2] = spotPos.z;
|
||||
fu.flash_pos[3] = std::cos(glm::radians(spot->value("inner_cone", 12.0f)));
|
||||
fu.flash_dir[0] = spotDir.x;
|
||||
fu.flash_dir[1] = spotDir.y;
|
||||
fu.flash_dir[2] = spotDir.z;
|
||||
fu.flash_dir[3] = std::cos(glm::radians(spot->value("outer_cone", 25.0f)));
|
||||
auto col = spot->value("color", std::vector<float>{1,1,1});
|
||||
float spotIntensity = spot->value("intensity", 2.5f);
|
||||
fu.flash_color[0] = (col.size() > 0 ? col[0] : 1.0f) * spotIntensity;
|
||||
fu.flash_color[1] = (col.size() > 1 ? col[1] : 1.0f) * spotIntensity;
|
||||
fu.flash_color[2] = (col.size() > 2 ? col[2] : 1.0f) * spotIntensity;
|
||||
fu.flash_color[3] = spot->value("range", 20.0f);
|
||||
}
|
||||
|
||||
context.Set<rendering::FragmentUniformData>("render.frag_uniforms", fu);
|
||||
|
||||
if (logger_) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "services/interfaces/workflow/rendering/workflow_spotlight_update_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/rendering_types.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <cmath>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
WorkflowSpotlightUpdateStep::WorkflowSpotlightUpdateStep(std::shared_ptr<ILogger> logger)
|
||||
: logger_(std::move(logger)) {}
|
||||
|
||||
std::string WorkflowSpotlightUpdateStep::GetPluginId() const {
|
||||
return "spotlight.update";
|
||||
}
|
||||
|
||||
void WorkflowSpotlightUpdateStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) {
|
||||
const auto* spot = context.TryGet<nlohmann::json>("spotlight.state");
|
||||
if (!spot) return;
|
||||
|
||||
auto fu = context.Get<rendering::FragmentUniformData>("render.frag_uniforms", rendering::FragmentUniformData{});
|
||||
|
||||
std::string attach = spot->value("attach", "camera");
|
||||
auto offset = spot->value("offset", std::vector<float>{0, 0, 0});
|
||||
glm::vec3 off(offset.size() > 0 ? offset[0] : 0,
|
||||
offset.size() > 1 ? offset[1] : 0,
|
||||
offset.size() > 2 ? offset[2] : 0);
|
||||
|
||||
glm::vec3 spotPos, spotDir;
|
||||
|
||||
if (attach == "camera") {
|
||||
auto viewMatrix = context.Get<glm::mat4>("render.view_matrix", glm::mat4(1.0f));
|
||||
auto cameraPos = context.Get<glm::vec3>("render.camera_pos", glm::vec3(0.0f));
|
||||
|
||||
glm::vec3 camRight = glm::vec3(viewMatrix[0][0], viewMatrix[1][0], viewMatrix[2][0]);
|
||||
glm::vec3 camUp = glm::vec3(viewMatrix[0][1], viewMatrix[1][1], viewMatrix[2][1]);
|
||||
glm::vec3 camFwd = -glm::vec3(viewMatrix[0][2], viewMatrix[1][2], viewMatrix[2][2]);
|
||||
|
||||
spotPos = cameraPos + camRight * off.x + camUp * off.y + camFwd * (-off.z);
|
||||
|
||||
// Aim toward a far point on camera center axis (natural torch aim)
|
||||
float aimDist = spot->value("aim_distance", 50.0f);
|
||||
glm::vec3 aimTarget = cameraPos + camFwd * aimDist;
|
||||
spotDir = glm::normalize(aimTarget - spotPos);
|
||||
} else {
|
||||
auto p = spot->value("position", std::vector<float>{0, 0, 0});
|
||||
auto d = spot->value("direction", std::vector<float>{0, 0, -1});
|
||||
spotPos = glm::vec3(p[0], p[1], p[2]) + off;
|
||||
spotDir = glm::normalize(glm::vec3(d[0], d[1], d[2]));
|
||||
}
|
||||
|
||||
fu.flash_pos[0] = spotPos.x;
|
||||
fu.flash_pos[1] = spotPos.y;
|
||||
fu.flash_pos[2] = spotPos.z;
|
||||
fu.flash_pos[3] = std::cos(glm::radians(spot->value("inner_cone", 12.0f)));
|
||||
fu.flash_dir[0] = spotDir.x;
|
||||
fu.flash_dir[1] = spotDir.y;
|
||||
fu.flash_dir[2] = spotDir.z;
|
||||
fu.flash_dir[3] = std::cos(glm::radians(spot->value("outer_cone", 25.0f)));
|
||||
|
||||
auto col = spot->value("color", std::vector<float>{1, 1, 1});
|
||||
float intensity = spot->value("intensity", 2.5f);
|
||||
fu.flash_color[0] = (col.size() > 0 ? col[0] : 1.0f) * intensity;
|
||||
fu.flash_color[1] = (col.size() > 1 ? col[1] : 1.0f) * intensity;
|
||||
fu.flash_color[2] = (col.size() > 2 ? col[2] : 1.0f) * intensity;
|
||||
fu.flash_color[3] = spot->value("range", 20.0f);
|
||||
|
||||
context.Set<rendering::FragmentUniformData>("render.frag_uniforms", fu);
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "services/interfaces/workflow/rendering/workflow_draw_textured_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_lighting_setup_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_spotlight_setup_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_spotlight_update_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_model_load_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_draw_viewmodel_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_geometry_create_flashlight_step.hpp"
|
||||
@@ -271,6 +272,7 @@ void WorkflowRegistrar::RegisterSteps(std::shared_ptr<IWorkflowStepRegistry> reg
|
||||
registry->RegisterStep(std::make_shared<WorkflowDrawTexturedBoxStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowLightingSetupStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowSpotlightSetupStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowSpotlightUpdateStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowModelLoadStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowDrawViewmodelStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowGeometryCreateFlashlightStep>(logger_));
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "services/interfaces/i_workflow_step.hpp"
|
||||
#include "services/interfaces/i_logger.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
class WorkflowSpotlightUpdateStep : public IWorkflowStep {
|
||||
public:
|
||||
explicit WorkflowSpotlightUpdateStep(std::shared_ptr<ILogger> logger);
|
||||
std::string GetPluginId() const override;
|
||||
void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) override;
|
||||
private:
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
Reference in New Issue
Block a user