Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ target_link_libraries(w3d_lib PUBLIC
# Library compile definitions
target_compile_definitions(w3d_lib PUBLIC
VULKAN_HPP_HANDLE_ERROR_OUT_OF_DATE_AS_SUCCESS
GLFW_INCLUDE_NONE # Suppress GLFW's OpenGL header inclusion (we use Vulkan only)
$<$<CONFIG:Debug>:W3D_DEBUG>
)

Expand Down
26 changes: 18 additions & 8 deletions shaders/basic.frag
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ layout(location = 3) in vec3 fragWorldPos;

layout(location = 0) out vec4 outColor;

// UBO – now includes scene lighting (Phase 6.1)
layout(set = 0, binding = 0) uniform UniformBufferObject {
mat4 model;
mat4 view;
mat4 proj;
vec4 lightDirection; // xyz = direction toward light source (not normalised here)
vec4 ambientColor; // xyz = RGB ambient
vec4 diffuseColor; // xyz = RGB diffuse
} ubo;

// Texture sampler
layout(set = 0, binding = 1) uniform sampler2D texSampler;

Expand All @@ -27,11 +37,6 @@ const uint FLAG_HAS_ALPHA_TEST = 2u;
const uint FLAG_TWO_SIDED = 4u;
const uint FLAG_UNLIT = 8u;

// Simple directional light
const vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
const vec3 lightColor = vec3(1.0, 1.0, 1.0);
const float ambientStrength = 0.3;

void main() {
vec3 normal = normalize(fragNormal);

Expand Down Expand Up @@ -60,15 +65,20 @@ void main() {
if ((material.flags & FLAG_UNLIT) != 0u) {
result = baseColor.rgb + material.emissiveColor.rgb;
} else {
// Use scene lighting from UBO (populated by LightingState / Renderer).
// lightDirection.xyz points *toward* the light, so negate for the
// diffuse dot product (which expects a vector from surface to light).
vec3 lightDir = normalize(ubo.lightDirection.xyz);

// Ambient
vec3 ambient = ambientStrength * lightColor;
vec3 ambient = ubo.ambientColor.rgb * baseColor.rgb;

// Diffuse
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
vec3 diffuse = ubo.diffuseColor.rgb * diff * baseColor.rgb;

// Combine lighting with base color
result = (ambient + diffuse) * baseColor.rgb;
result = ambient + diffuse;

// Add emissive
result += material.emissiveColor.rgb;
Expand Down
57 changes: 57 additions & 0 deletions shaders/terrain.frag
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ layout(location = 0) in vec3 fragNormal;
layout(location = 1) in vec2 fragTexCoord;
layout(location = 2) in vec3 fragWorldPos;
layout(location = 3) in vec2 fragAtlasCoord;
layout(location = 4) in vec2 fragCloudCoord; // Phase 6.3: scrolled cloud UV

layout(location = 0) out vec4 outColor;

Expand All @@ -14,8 +15,44 @@ layout(push_constant) uniform TerrainMaterial {
vec4 diffuseColor;
vec3 lightDirection;
uint useTexture;
// Phase 6.2 – shadow colour decoded from GlobalLighting::shadowColor (ARGB)
vec4 shadowColor;
// Phase 6.3 – cloud shadow animation (scroll speeds + time stored in .vert)
float cloudScrollU;
float cloudScrollV;
float cloudTime;
float cloudStrength; // 0 = disabled, 1 = full shadow
} material;

// ---------------------------------------------------------------------------
// Simple hash-based 2D noise for procedural cloud shadows.
// Produces smooth values in [0, 1].
// ---------------------------------------------------------------------------
float hash21(vec2 p) {
p = fract(p * vec2(127.1, 311.7));
p += dot(p, p + 19.19);
return fract(p.x * p.y);
}

float smoothNoise(vec2 uv) {
vec2 i = floor(uv);
vec2 f = fract(uv);
vec2 u = f * f * (3.0 - 2.0 * f); // smoothstep

float a = hash21(i);
float b = hash21(i + vec2(1.0, 0.0));
float c = hash21(i + vec2(0.0, 1.0));
float d = hash21(i + vec2(1.0, 1.0));

return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}

// Two-octave FBM for a more cloud-like pattern.
float cloudPattern(vec2 uv) {
float v = smoothNoise(uv) * 0.6 + smoothNoise(uv * 2.1 + 4.7) * 0.4;
return v;
}

void main() {
vec3 normal = normalize(fragNormal);

Expand All @@ -32,12 +69,32 @@ void main() {

vec3 lightDir = normalize(-material.lightDirection);

// Ambient
vec3 ambient = material.ambientColor.rgb * baseColor;

// Diffuse
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = material.diffuseColor.rgb * diff * baseColor;

vec3 result = ambient + diffuse;

// Phase 6.2 – shadow colour tint.
// Apply the shadow colour as a lerp based on its alpha when the surface is
// facing away from the light (diff == 0 → fully in shadow).
if (material.shadowColor.a > 0.0) {
float shadowFactor = (1.0 - diff) * material.shadowColor.a;
result = mix(result, result * material.shadowColor.rgb, shadowFactor);
}

// Phase 6.3 – cloud shadow overlay.
// Sample a procedural cloud pattern using scrolled world-space UVs and
// darken the lit surface proportionally to cloudStrength.
if (material.cloudStrength > 0.0) {
float cloud = cloudPattern(fragCloudCoord);
// cloud ∈ [0, 1]; values > 0.5 are "under cloud", values ≤ 0.5 are "in sun".
float shadow = smoothstep(0.45, 0.65, cloud) * material.cloudStrength;
result *= (1.0 - shadow * 0.6); // attenuate by up to 60 % (matches original look)
}

outColor = vec4(result, 1.0);
}
28 changes: 28 additions & 0 deletions shaders/terrain.vert
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,26 @@ layout(set = 0, binding = 0) uniform UniformBufferObject {
mat4 model;
mat4 view;
mat4 proj;
// Scene lighting fields (Phase 6.1, present in UBO but not used by the
// terrain vertex shader – consumed by the fragment shader via push constant).
vec4 lightDirection;
vec4 ambientColor;
vec4 diffuseColor;
} ubo;

layout(push_constant) uniform TerrainMaterial {
vec4 ambientColor;
vec4 diffuseColor;
vec3 lightDirection;
uint useTexture;
vec4 shadowColor;
// Phase 6.3 – cloud shadow animation parameters.
float cloudScrollU;
float cloudScrollV;
float cloudTime;
float cloudStrength;
} material;

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inTexCoord;
Expand All @@ -15,6 +33,11 @@ layout(location = 0) out vec3 fragNormal;
layout(location = 1) out vec2 fragTexCoord;
layout(location = 2) out vec3 fragWorldPos;
layout(location = 3) out vec2 fragAtlasCoord;
layout(location = 4) out vec2 fragCloudCoord; // Phase 6.3: animated cloud UV

// World-space scale for the cloud UV. Smaller values tile the cloud pattern
// more coarsely, matching the original engine's feel.
const float kCloudUVScale = 0.002;

void main() {
vec4 worldPos = ubo.model * vec4(inPosition, 1.0);
Expand All @@ -24,4 +47,9 @@ void main() {
fragTexCoord = inTexCoord;
fragWorldPos = worldPos.xyz;
fragAtlasCoord = inAtlasCoord;

// Phase 6.3: derive cloud UV from world-space X/Z then animate with time.
vec2 cloudBase = worldPos.xz * kCloudUVScale;
fragCloudCoord = cloudBase + vec2(material.cloudScrollU * material.cloudTime,
material.cloudScrollV * material.cloudTime);
}
12 changes: 12 additions & 0 deletions src/core/renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ void Renderer::updateUniformBuffer(uint32_t frameIndex, const Camera &camera) {
0.01f, 10000.0f);
ubo.proj[1][1] *= -1; // Flip Y for Vulkan

// Phase 6.1 – scene lighting. Populate from LightingState if available,
// otherwise use the hard-coded defaults that match the pre-Phase-6 behaviour.
if (lightingState_ != nullptr) {
ubo.lightDirection = glm::vec4(lightingState_->objectLightDirection(), 0.0f);
ubo.ambientColor = glm::vec4(lightingState_->objectAmbient(), 1.0f);
ubo.diffuseColor = glm::vec4(lightingState_->objectDiffuse(), 1.0f);
} else {
ubo.lightDirection = glm::vec4(LightingState::kDefaultLightDirection, 0.0f);
ubo.ambientColor = glm::vec4(LightingState::kDefaultAmbient, 1.0f);
ubo.diffuseColor = glm::vec4(LightingState::kDefaultDiffuse, 1.0f);
}

uniformBuffers_.update(frameIndex, ubo);
}

Expand Down
9 changes: 9 additions & 0 deletions src/core/renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "lib/gfx/texture.hpp"
#include "render/bone_buffer.hpp"
#include "render/hover_detector.hpp"
#include "render/lighting_state.hpp"
#include "render/material.hpp"
#include "render/renderable_mesh.hpp"
#include "render/skeleton_renderer.hpp"
Expand Down Expand Up @@ -89,6 +90,13 @@ class Renderer {
*/
uint32_t currentFrame() const { return currentFrame_; }

/**
* Set the active scene lighting state (Phase 6.1).
* The pointer is non-owning and must outlive the Renderer.
* Pass nullptr to revert to hard-coded defaults.
*/
void setLighting(const LightingState *lighting) { lightingState_ = lighting; }

// Accessors
gfx::Pipeline &pipeline() { return pipeline_; }
gfx::Pipeline &skinnedPipeline() { return skinnedPipeline_; }
Expand All @@ -109,6 +117,7 @@ class Renderer {
ImGuiBackend *imguiBackend_ = nullptr;
gfx::TextureManager *textureManager_ = nullptr;
BoneMatrixBuffer *boneMatrixBuffer_ = nullptr;
const LightingState *lightingState_ = nullptr; // optional scene lighting (Phase 6.1)

// Pipelines and descriptors
gfx::Pipeline pipeline_;
Expand Down
23 changes: 18 additions & 5 deletions src/lib/gfx/pipeline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ struct UniformBufferObject {
alignas(16) glm::mat4 model;
alignas(16) glm::mat4 view;
alignas(16) glm::mat4 proj;
// Scene lighting (populated from map GlobalLighting; defaults used when no map is loaded)
alignas(16) glm::vec4 lightDirection; // xyz = direction toward light, w = unused
alignas(16) glm::vec4 ambientColor; // xyz = RGB ambient, w = unused
alignas(16) glm::vec4 diffuseColor; // xyz = RGB diffuse, w = unused
};

struct MaterialPushConstant {
Expand All @@ -78,6 +82,13 @@ struct TerrainPushConstant {
alignas(16) glm::vec4 diffuseColor;
alignas(16) glm::vec3 lightDirection;
alignas(4) uint32_t useTexture;
// Phase 6.2 – shadow color (decoded from GlobalLighting::shadowColor ARGB uint32)
alignas(16) glm::vec4 shadowColor;
// Phase 6.3 – cloud shadow animation parameters
alignas(4) float cloudScrollU; // cloud UV horizontal scroll speed (world units/sec)
alignas(4) float cloudScrollV; // cloud UV vertical scroll speed (world units/sec)
alignas(4) float cloudTime; // accumulated time for cloud UV offset
alignas(4) float cloudStrength; // 0 = no shadow, 1 = full shadow intensity
};

struct WaterPushConstant {
Expand Down Expand Up @@ -122,9 +133,10 @@ struct PipelineCreateInfo {

info.descriptorBindings = {
vk::DescriptorSetLayoutBinding{0, vk::DescriptorType::eUniformBuffer, 1,
vk::ShaderStageFlagBits::eVertex },
vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eFragment},
vk::DescriptorSetLayoutBinding{1, vk::DescriptorType::eCombinedImageSampler, 1,
vk::ShaderStageFlagBits::eFragment}
vk::ShaderStageFlagBits::eFragment }
};

info.pushConstants = {
Expand All @@ -147,11 +159,12 @@ struct PipelineCreateInfo {

info.descriptorBindings = {
vk::DescriptorSetLayoutBinding{0, vk::DescriptorType::eUniformBuffer, 1,
vk::ShaderStageFlagBits::eVertex },
vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eFragment},
vk::DescriptorSetLayoutBinding{1, vk::DescriptorType::eCombinedImageSampler, 1,
vk::ShaderStageFlagBits::eFragment},
vk::ShaderStageFlagBits::eFragment },
vk::DescriptorSetLayoutBinding{2, vk::DescriptorType::eStorageBuffer, 1,
vk::ShaderStageFlagBits::eVertex }
vk::ShaderStageFlagBits::eVertex }
};

info.pushConstants = {
Expand Down
Loading