diff --git a/assets/shaders/lighting.fs b/assets/shaders/lighting.fs new file mode 100644 index 0000000..1da3044 --- /dev/null +++ b/assets/shaders/lighting.fs @@ -0,0 +1,78 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec3 fragPosition; +in vec2 fragTexCoord; +in vec4 fragColor; +in vec3 fragNormal; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +out vec4 finalColor; + +#define MAX_LIGHTS 4 +#define LIGHT_DIRECTIONAL 0 +#define LIGHT_POINT 1 + +struct Light { + int enabled; + int type; + vec3 position; + vec3 target; + vec4 color; +}; + +uniform Light lights[MAX_LIGHTS]; +uniform vec4 ambient; +uniform vec3 viewPos; + +void main() +{ + vec4 texelColor = texture(texture0, fragTexCoord); + if (texelColor.a < 0.1) discard; + + vec3 normal = normalize(fragNormal); + vec3 viewD = normalize(viewPos - fragPosition); + + vec3 lightTotal = ambient.rgb; + vec3 specularTotal = vec3(0.0); + + for (int i = 0; i < MAX_LIGHTS; i++) + { + if (lights[i].enabled == 1) + { + vec3 lightDir = vec3(0.0); + float attenuation = 1.0; + + if (lights[i].type == LIGHT_DIRECTIONAL) + { + lightDir = -normalize(lights[i].target - lights[i].position); + } + else // LIGHT_POINT + { + vec3 lightVec = lights[i].position - fragPosition; + float dist = length(lightVec); + lightDir = normalize(lightVec); + attenuation = 1.0 / (1.0 + 0.10 * dist + 0.03 * dist * dist); + } + + float NdotL = max(dot(normal, lightDir), 0.0); + lightTotal += lights[i].color.rgb * NdotL * attenuation; + + if (NdotL > 0.0) + { + vec3 reflectDir = reflect(-lightDir, normal); + float spec = pow(max(dot(viewD, reflectDir), 0.0), 16.0); + specularTotal += lights[i].color.rgb * spec * attenuation * 0.25; + } + } + } + + vec4 tint = colDiffuse * fragColor; + vec3 result = (texelColor.rgb * tint.rgb) * lightTotal + specularTotal; + + finalColor = vec4(result, texelColor.a * tint.a); +} diff --git a/assets/shaders/lighting.vs b/assets/shaders/lighting.vs new file mode 100644 index 0000000..61fa43e --- /dev/null +++ b/assets/shaders/lighting.vs @@ -0,0 +1,30 @@ +#version 330 + +// Input vertex attributes +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexColor; + +// Input uniform values +uniform mat4 mvp; +uniform mat4 matModel; +uniform mat4 matNormal; + +// Output vertex attributes (to fragment shader) +out vec3 fragPosition; +out vec2 fragTexCoord; +out vec4 fragColor; +out vec3 fragNormal; + +void main() +{ + // Send vertex attributes to fragment shader + fragPosition = vec3(matModel*vec4(vertexPosition, 1.0)); + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + fragNormal = normalize(vec3(matNormal*vec4(vertexNormal, 1.0))); + + // Calculate final vertex position + gl_Position = mvp*vec4(vertexPosition, 1.0); +} diff --git a/src/debug/DebugSystem.cpp b/src/debug/DebugSystem.cpp index 7dd99c6..75e3f42 100644 --- a/src/debug/DebugSystem.cpp +++ b/src/debug/DebugSystem.cpp @@ -137,6 +137,10 @@ void DebugSystem::applyWarp() { m_game->startNewRun(); m_game->m_player.setGridPosition(2, 4); m_game->m_player.setFacing(Direction::EAST); + } else if (w == "HUB_BED_FRONT") { + m_game->startNewRun(); + m_game->m_player.setGridPosition(4, 3); + m_game->m_player.setFacing(Direction::SOUTH); } else if (w == "EXPLORATION") { m_game->startNewRun(); if (m_config.targetFloor > 1) { diff --git a/src/render/CyberRenderer.cpp b/src/render/CyberRenderer.cpp index 23e915d..989eb6a 100644 --- a/src/render/CyberRenderer.cpp +++ b/src/render/CyberRenderer.cpp @@ -1228,6 +1228,33 @@ void CyberRenderer::generateTextures() { m_texAncientCeil = createAncientCeilTexture(); m_texAncientWood = createAncientWoodTexture(); + // Load custom 3D model dynamic lighting shader + std::vector> lightingShaderPaths = { + { "assets/shaders/lighting.vs", "assets/shaders/lighting.fs" }, + { "../assets/shaders/lighting.vs", "../assets/shaders/lighting.fs" }, + { "shaders/lighting.vs", "shaders/lighting.fs" } + }; + m_modelLightingLoaded = false; + for (const auto& sp : lightingShaderPaths) { + if (FileExists(sp.first.c_str()) && FileExists(sp.second.c_str())) { + m_shaderModelLighting = LoadShader(sp.first.c_str(), sp.second.c_str()); + if (m_shaderModelLighting.id != 0) { + m_shaderModelLighting.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(m_shaderModelLighting, "viewPos"); + m_locLightViewPos = m_shaderModelLighting.locs[SHADER_LOC_VECTOR_VIEW]; + m_locLightAmbient = GetShaderLocation(m_shaderModelLighting, "ambient"); + for (int i = 0; i < 4; ++i) { + m_modelLights[i].enabled = GetShaderLocation(m_shaderModelLighting, TextFormat("lights[%d].enabled", i)); + m_modelLights[i].type = GetShaderLocation(m_shaderModelLighting, TextFormat("lights[%d].type", i)); + m_modelLights[i].position = GetShaderLocation(m_shaderModelLighting, TextFormat("lights[%d].position", i)); + m_modelLights[i].target = GetShaderLocation(m_shaderModelLighting, TextFormat("lights[%d].target", i)); + m_modelLights[i].color = GetShaderLocation(m_shaderModelLighting, TextFormat("lights[%d].color", i)); + } + m_modelLightingLoaded = true; + break; + } + } + } + // Load custom imported 3D Bed model std::vector bedModelPaths = { "assets/models/Bed.obj", @@ -1250,6 +1277,11 @@ void CyberRenderer::generateTextures() { } if (m_bedModelLoaded) { + if (m_modelLightingLoaded) { + for (int i = 0; i < m_bedModel.materialCount; ++i) { + m_bedModel.materials[i].shader = m_shaderModelLighting; + } + } for (const auto& tp : bedTexPaths) { if (FileExists(tp.c_str())) { m_texBedModel = LoadTexture(tp.c_str()); @@ -1268,6 +1300,11 @@ void CyberRenderer::generateTextures() { void CyberRenderer::unloadTextures() { if (!m_texturesLoaded) return; + if (m_modelLightingLoaded) { + UnloadShader(m_shaderModelLighting); + m_modelLightingLoaded = false; + } + if (m_bedModelLoaded) { UnloadModel(m_bedModel); if (m_texBedModel.id != 0) { @@ -1947,6 +1984,60 @@ void CyberRenderer::renderCombat3D(const Dungeon& dungeon, const Player& player, endPostProcess(animTime); } +void CyberRenderer::updateHubModelLighting(const Camera3D& camera, float animTime) { + if (!m_modelLightingLoaded) return; + + // Camera view position for specular highlights + float camPos[3] = { camera.position.x, camera.position.y, camera.position.z }; + SetShaderValue(m_shaderModelLighting, m_locLightViewPos, camPos, SHADER_UNIFORM_VEC3); + + // Cyber-noir ambient illumination (subtle slate blue-gray base) + float ambient[4] = { 0.18f, 0.18f, 0.22f, 1.0f }; + SetShaderValue(m_shaderModelLighting, m_locLightAmbient, ambient, SHADER_UNIFORM_VEC4); + + struct HubLight { + int enabled; + int type; + Vector3 pos; + Vector3 target; + float color[4]; + }; + + float deskFlicker = 0.90f + 0.10f * std::sin(animTime * 6.0f); + float portalPulse = 0.75f + 0.25f * std::sin(animTime * 3.5f); + + HubLight lights[4] = { + // Light 0: Megacity Skyline Window glow (East wall) - cool neon cobalt/violet + { 1, 1, { 11.0f, 1.8f, 6.0f }, { 0, 0, 0 }, { 0.35f, 0.40f, 0.85f, 1.0f } }, + // Light 1: Workstation CRT monitors (West desk) - cyan flicker + { 1, 1, { 1.3f, 1.3f, 5.0f }, { 0, 0, 0 }, { 0.15f * deskFlicker, 0.55f * deskFlicker, 0.85f * deskFlicker, 1.0f } }, + // Light 2: Cyberspace Gateway Portal (North wall) - emerald pulse + { 1, 1, { 6.0f, 1.8f, 1.0f }, { 0, 0, 0 }, { 0.10f * portalPulse, 0.80f * portalPulse, 0.40f * portalPulse, 1.0f } }, + // Light 3: Ceiling warm ambient lamp (Above room center/bed approach) - warm tungsten + { 1, 1, { 8.5f, 2.7f, 8.5f }, { 0, 0, 0 }, { 0.70f, 0.58f, 0.42f, 1.0f } } + }; + + for (int i = 0; i < 4; ++i) { + if (m_modelLights[i].enabled != -1) { + SetShaderValue(m_shaderModelLighting, m_modelLights[i].enabled, &lights[i].enabled, SHADER_UNIFORM_INT); + } + if (m_modelLights[i].type != -1) { + SetShaderValue(m_shaderModelLighting, m_modelLights[i].type, &lights[i].type, SHADER_UNIFORM_INT); + } + if (m_modelLights[i].position != -1) { + float p[3] = { lights[i].pos.x, lights[i].pos.y, lights[i].pos.z }; + SetShaderValue(m_shaderModelLighting, m_modelLights[i].position, p, SHADER_UNIFORM_VEC3); + } + if (m_modelLights[i].target != -1) { + float t[3] = { lights[i].target.x, lights[i].target.y, lights[i].target.z }; + SetShaderValue(m_shaderModelLighting, m_modelLights[i].target, t, SHADER_UNIFORM_VEC3); + } + if (m_modelLights[i].color != -1) { + SetShaderValue(m_shaderModelLighting, m_modelLights[i].color, lights[i].color, SHADER_UNIFORM_VEC4); + } + } +} + void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float animTime) { if (!m_texturesLoaded) { generateTextures(); @@ -1963,6 +2054,8 @@ void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float an m_camera.target = { eye.x + dirX, eye.y, eye.z + dirZ }; + updateHubModelLighting(m_camera, animTime); + BeginMode3D(m_camera); const float tileSize = 2.0f; @@ -2136,7 +2229,7 @@ void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float an // 8. BED (South-East corner: X in [8.5f, 11.2f], Z in [8.8f, 11.2f]) if (m_bedModelLoaded) { // Draw user custom imported 3D Bed model - Vector3 customBedPos = { 9.8f, 0.0f, 10.1f }; + Vector3 customBedPos = { 9.7f, 0.0f, 9.75f }; float customBedScale = 0.024f; DrawModelEx(m_bedModel, customBedPos, Vector3{ 0.0f, 1.0f, 0.0f }, 0.0f, Vector3{ customBedScale, customBedScale, customBedScale }, WHITE); diff --git a/src/render/CyberRenderer.hpp b/src/render/CyberRenderer.hpp index d20a83a..59c149a 100644 --- a/src/render/CyberRenderer.hpp +++ b/src/render/CyberRenderer.hpp @@ -71,10 +71,22 @@ private: Texture2D m_texWindowView{}; Texture2D m_texCyberGate{}; - // Custom 3D Models + // Custom 3D Models & Dynamic Lighting Model m_bedModel{}; Texture2D m_texBedModel{}; bool m_bedModelLoaded = false; + Shader m_shaderModelLighting{}; + bool m_modelLightingLoaded = false; + int m_locLightAmbient = -1; + int m_locLightViewPos = -1; + struct ModelLightLocs { + int enabled = -1; + int type = -1; + int position = -1; + int target = -1; + int color = -1; + } m_modelLights[4]; + void updateHubModelLighting(const Camera3D& camera, float animTime); // Ancient Palace Storeroom Procedural Textures Texture2D m_texAncientWall{};