fix(render): Fix 3D bed positioning and implement dynamic lighting for custom models
This commit is contained in:
parent
b3b69f997e
commit
527b3e4c7d
5 changed files with 219 additions and 2 deletions
78
assets/shaders/lighting.fs
Normal file
78
assets/shaders/lighting.fs
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
30
assets/shaders/lighting.vs
Normal file
30
assets/shaders/lighting.vs
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -137,6 +137,10 @@ void DebugSystem::applyWarp() {
|
||||||
m_game->startNewRun();
|
m_game->startNewRun();
|
||||||
m_game->m_player.setGridPosition(2, 4);
|
m_game->m_player.setGridPosition(2, 4);
|
||||||
m_game->m_player.setFacing(Direction::EAST);
|
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") {
|
} else if (w == "EXPLORATION") {
|
||||||
m_game->startNewRun();
|
m_game->startNewRun();
|
||||||
if (m_config.targetFloor > 1) {
|
if (m_config.targetFloor > 1) {
|
||||||
|
|
|
||||||
|
|
@ -1228,6 +1228,33 @@ void CyberRenderer::generateTextures() {
|
||||||
m_texAncientCeil = createAncientCeilTexture();
|
m_texAncientCeil = createAncientCeilTexture();
|
||||||
m_texAncientWood = createAncientWoodTexture();
|
m_texAncientWood = createAncientWoodTexture();
|
||||||
|
|
||||||
|
// Load custom 3D model dynamic lighting shader
|
||||||
|
std::vector<std::pair<std::string, std::string>> 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
|
// Load custom imported 3D Bed model
|
||||||
std::vector<std::string> bedModelPaths = {
|
std::vector<std::string> bedModelPaths = {
|
||||||
"assets/models/Bed.obj",
|
"assets/models/Bed.obj",
|
||||||
|
|
@ -1250,6 +1277,11 @@ void CyberRenderer::generateTextures() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_bedModelLoaded) {
|
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) {
|
for (const auto& tp : bedTexPaths) {
|
||||||
if (FileExists(tp.c_str())) {
|
if (FileExists(tp.c_str())) {
|
||||||
m_texBedModel = LoadTexture(tp.c_str());
|
m_texBedModel = LoadTexture(tp.c_str());
|
||||||
|
|
@ -1268,6 +1300,11 @@ void CyberRenderer::generateTextures() {
|
||||||
void CyberRenderer::unloadTextures() {
|
void CyberRenderer::unloadTextures() {
|
||||||
if (!m_texturesLoaded) return;
|
if (!m_texturesLoaded) return;
|
||||||
|
|
||||||
|
if (m_modelLightingLoaded) {
|
||||||
|
UnloadShader(m_shaderModelLighting);
|
||||||
|
m_modelLightingLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_bedModelLoaded) {
|
if (m_bedModelLoaded) {
|
||||||
UnloadModel(m_bedModel);
|
UnloadModel(m_bedModel);
|
||||||
if (m_texBedModel.id != 0) {
|
if (m_texBedModel.id != 0) {
|
||||||
|
|
@ -1947,6 +1984,60 @@ void CyberRenderer::renderCombat3D(const Dungeon& dungeon, const Player& player,
|
||||||
endPostProcess(animTime);
|
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) {
|
void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float animTime) {
|
||||||
if (!m_texturesLoaded) {
|
if (!m_texturesLoaded) {
|
||||||
generateTextures();
|
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 };
|
m_camera.target = { eye.x + dirX, eye.y, eye.z + dirZ };
|
||||||
|
|
||||||
|
updateHubModelLighting(m_camera, animTime);
|
||||||
|
|
||||||
BeginMode3D(m_camera);
|
BeginMode3D(m_camera);
|
||||||
|
|
||||||
const float tileSize = 2.0f;
|
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])
|
// 8. BED (South-East corner: X in [8.5f, 11.2f], Z in [8.8f, 11.2f])
|
||||||
if (m_bedModelLoaded) {
|
if (m_bedModelLoaded) {
|
||||||
// Draw user custom imported 3D Bed model
|
// 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;
|
float customBedScale = 0.024f;
|
||||||
DrawModelEx(m_bedModel, customBedPos, Vector3{ 0.0f, 1.0f, 0.0f }, 0.0f,
|
DrawModelEx(m_bedModel, customBedPos, Vector3{ 0.0f, 1.0f, 0.0f }, 0.0f,
|
||||||
Vector3{ customBedScale, customBedScale, customBedScale }, WHITE);
|
Vector3{ customBedScale, customBedScale, customBedScale }, WHITE);
|
||||||
|
|
|
||||||
|
|
@ -71,10 +71,22 @@ private:
|
||||||
Texture2D m_texWindowView{};
|
Texture2D m_texWindowView{};
|
||||||
Texture2D m_texCyberGate{};
|
Texture2D m_texCyberGate{};
|
||||||
|
|
||||||
// Custom 3D Models
|
// Custom 3D Models & Dynamic Lighting
|
||||||
Model m_bedModel{};
|
Model m_bedModel{};
|
||||||
Texture2D m_texBedModel{};
|
Texture2D m_texBedModel{};
|
||||||
bool m_bedModelLoaded = false;
|
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
|
// Ancient Palace Storeroom Procedural Textures
|
||||||
Texture2D m_texAncientWall{};
|
Texture2D m_texAncientWall{};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue