Working on the night mode

This commit is contained in:
Vladislav Khorev 2026-06-04 13:27:52 +03:00
parent e6bfe85e2e
commit 442d3a7945
18 changed files with 1060 additions and 2 deletions

View File

@ -0,0 +1,22 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
varying vec2 texCoord;
varying float fogDistance;
varying vec3 fragViewPos;
varying vec3 fragNormal;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform vec3 uPlayerEyePos;
void main()
{
vec4 eyePos = ModelViewMatrix * vec4(vPosition.xyz, 1.0);
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * vec4(vPosition.xyz, 1.0);
texCoord = vTexCoord;
fragViewPos = eyePos.xyz;
fragNormal = mat3(ModelViewMatrix) * vNormal;
}

View File

@ -0,0 +1,56 @@
uniform sampler2D Texture;
uniform float uAlpha;
uniform int uPointLightCount;
uniform vec3 uPointLightPos[4];
uniform vec3 uPointLightDir[4];
uniform vec3 uPointLightColor[4];
varying vec2 texCoord;
varying float fogDistance;
varying vec3 fragViewPos;
varying vec3 fragNormal;
// cos(45 deg) = half-angle for a 90-degree full-angle spotlight
const float SPOT_COS_OUTER = 0.7071;
const float SPOT_COS_INNER = 0.82; // slightly tighter inner cone for smooth edge
void main()
{
vec4 color = texture2D(Texture, texCoord);
if (color.a < 0.1)
discard;
float ambient = 0.37;
vec3 lighting = vec3(ambient);
bool hasNormal = dot(fragNormal, fragNormal) > 0.001;
vec3 N = hasNormal ? normalize(fragNormal) : vec3(0.0);
for (int i = 0; i < 4; i++)
{
if (i >= uPointLightCount) break;
vec3 lightVec = uPointLightPos[i] - fragViewPos;
float dist = length(lightVec);
vec3 lightDir = normalize(lightVec);
// Spotlight cone: angle between the cone axis and the ray from light to fragment
float cosTheta = dot(-lightDir, normalize(uPointLightDir[i]));
float spotAtten = smoothstep(SPOT_COS_OUTER, SPOT_COS_INNER, cosTheta);
if (spotAtten <= 0.0) continue;
float atten = 1.0 / (1.0 + 0.35 * dist + 0.15 * dist * dist);
float diff = hasNormal ? max(dot(N, lightDir), 0.0) : 1.0;
lighting += uPointLightColor[i] * diff * atten * spotAtten;
}
color.rgb *= lighting;
vec3 fogColor = vec3(0.01, 0.01, 0.05);
float fogFactor = clamp((fogDistance - 15.0) / 8.0, 0.0, 1.0);
color.rgb = mix(color.rgb, fogColor, fogFactor);
gl_FragColor = vec4(color.rgb, color.a * uAlpha);
}

View File

@ -0,0 +1,25 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
varying vec2 texCoord;
varying vec4 fragPosLightSpace;
varying vec3 fragNormal;
varying float fogDistance;
varying vec3 fragViewPos;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uLightFromCamera;
uniform vec3 uPlayerEyePos;
void main()
{
vec4 eyePos = ModelViewMatrix * vec4(vPosition, 1.0);
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * vec4(vPosition, 1.0);
texCoord = vTexCoord;
fragViewPos = eyePos.xyz;
fragNormal = mat3(ModelViewMatrix) * vNormal;
fragPosLightSpace = uLightFromCamera * eyePos;
}

View File

@ -0,0 +1,95 @@
uniform sampler2D Texture;
uniform sampler2D uShadowMap;
uniform vec3 uLightDir;
uniform float uAlpha;
uniform int uPointLightCount;
uniform vec3 uPointLightPos[4];
uniform vec3 uPointLightDir[4];
uniform vec3 uPointLightColor[4];
varying vec2 texCoord;
varying vec4 fragPosLightSpace;
varying vec3 fragNormal;
varying float fogDistance;
varying vec3 fragViewPos;
const float SPOT_COS_OUTER = 0.7071;
const float SPOT_COS_INNER = 0.82;
float computeShadow(vec4 lightSpacePos, vec3 normal)
{
vec3 projCoords = lightSpacePos.xyz / lightSpacePos.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
projCoords.y < 0.0 || projCoords.y > 1.0 ||
projCoords.z > 1.0)
{
return 0.0;
}
float currentDepth = projCoords.z;
float bias = 0.0004;
if (dot(normal, normal) > 0.001)
{
float cosTheta = dot(normalize(normal), -normalize(uLightDir));
bias = max(0.002 * (1.0 - cosTheta), 0.0002);
}
float shadow = 0.0;
float texelSize = 1.0 / 2048.0;
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
float pcfDepth = texture2D(uShadowMap, projCoords.xy + vec2(float(x), float(y)) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
return shadow;
}
void main()
{
vec4 color = texture2D(Texture, texCoord);
if (color.a < 0.1)
discard;
float ambient = 0.37;
vec3 lighting = vec3(ambient);
bool hasNormal = dot(fragNormal, fragNormal) > 0.001;
vec3 N = hasNormal ? normalize(fragNormal) : vec3(0.0);
float shadow = computeShadow(fragPosLightSpace, fragNormal);
for (int i = 0; i < 4; i++)
{
if (i >= uPointLightCount) break;
vec3 lightVec = uPointLightPos[i] - fragViewPos;
float dist = length(lightVec);
vec3 lightDir = normalize(lightVec);
float cosTheta = dot(-lightDir, normalize(uPointLightDir[i]));
float spotAtten = smoothstep(SPOT_COS_OUTER, SPOT_COS_INNER, cosTheta);
if (spotAtten <= 0.0) continue;
float atten = 1.0 / (1.0 + 0.35 * dist + 0.15 * dist * dist);
float diff = hasNormal ? max(dot(N, lightDir), 0.0) : 1.0;
lighting += uPointLightColor[i] * diff * atten * spotAtten * (1.0 - shadow);
}
color.rgb *= lighting;
vec3 fogColor = vec3(0.01, 0.01, 0.05);
float fogFactor = clamp((fogDistance - 15.0) / 8.0, 0.0, 1.0);
color.rgb = mix(color.rgb, fogColor, fogFactor);
gl_FragColor = vec4(color.rgb, color.a * uAlpha);
}

View File

@ -0,0 +1,97 @@
precision mediump float;
uniform sampler2D Texture;
uniform sampler2D uShadowMap;
uniform vec3 uLightDir;
uniform float uAlpha;
uniform int uPointLightCount;
uniform vec3 uPointLightPos[4];
uniform vec3 uPointLightDir[4];
uniform vec3 uPointLightColor[4];
varying vec2 texCoord;
varying vec4 fragPosLightSpace;
varying vec3 fragNormal;
varying float fogDistance;
varying vec3 fragViewPos;
const float SPOT_COS_OUTER = 0.7071;
const float SPOT_COS_INNER = 0.82;
float computeShadow(vec4 lightSpacePos, vec3 normal)
{
vec3 projCoords = lightSpacePos.xyz / lightSpacePos.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
projCoords.y < 0.0 || projCoords.y > 1.0 ||
projCoords.z > 1.0)
{
return 0.0;
}
float currentDepth = projCoords.z;
float bias = 0.0004;
if (dot(normal, normal) > 0.001)
{
float cosTheta = dot(normalize(normal), -normalize(uLightDir));
bias = max(0.002 * (1.0 - cosTheta), 0.0002);
}
float shadow = 0.0;
float texelSize = 1.0 / 2048.0;
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
float pcfDepth = texture2D(uShadowMap, projCoords.xy + vec2(float(x), float(y)) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
return shadow;
}
void main()
{
vec4 color = texture2D(Texture, texCoord);
if (color.a < 0.1)
discard;
float ambient = 0.37;
vec3 lighting = vec3(ambient);
bool hasNormal = dot(fragNormal, fragNormal) > 0.001;
vec3 N = hasNormal ? normalize(fragNormal) : vec3(0.0);
float shadow = computeShadow(fragPosLightSpace, fragNormal);
for (int i = 0; i < 4; i++)
{
if (i >= uPointLightCount) break;
vec3 lightVec = uPointLightPos[i] - fragViewPos;
float dist = length(lightVec);
vec3 lightDir = normalize(lightVec);
float cosTheta = dot(-lightDir, normalize(uPointLightDir[i]));
float spotAtten = smoothstep(SPOT_COS_OUTER, SPOT_COS_INNER, cosTheta);
if (spotAtten <= 0.0) continue;
float atten = 1.0 / (1.0 + 0.35 * dist + 0.15 * dist * dist);
float diff = hasNormal ? max(dot(N, lightDir), 0.0) : 1.0;
lighting += uPointLightColor[i] * diff * atten * spotAtten * (1.0 - shadow);
}
color.rgb *= lighting;
vec3 fogColor = vec3(0.01, 0.01, 0.05);
float fogFactor = clamp((fogDistance - 15.0) / 8.0, 0.0, 1.0);
color.rgb = mix(color.rgb, fogColor, fogFactor);
gl_FragColor = vec4(color.rgb, color.a * uAlpha);
}

View File

@ -0,0 +1,68 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
attribute vec4 aBoneIndices0;
attribute vec2 aBoneIndices1;
attribute vec4 aBoneWeights0;
attribute vec2 aBoneWeights1;
varying vec2 texCoord;
varying float fogDistance;
varying vec3 fragViewPos;
varying vec3 fragNormal;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uBoneMatrices[64];
uniform vec3 uPlayerEyePos;
void main()
{
vec4 skinnedPos = vec4(0.0, 0.0, 0.0, 0.0);
vec3 skinnedNormal = vec3(0.0, 0.0, 0.0);
vec4 originalPos = vec4(vPosition, 1.0);
float totalWeight = 0.0;
if (aBoneWeights0.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.x)] * originalPos * aBoneWeights0.x;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.x)]) * vNormal * aBoneWeights0.x;
totalWeight += aBoneWeights0.x;
}
if (aBoneWeights0.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.y)] * originalPos * aBoneWeights0.y;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.y)]) * vNormal * aBoneWeights0.y;
totalWeight += aBoneWeights0.y;
}
if (aBoneWeights0.z > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.z)] * originalPos * aBoneWeights0.z;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.z)]) * vNormal * aBoneWeights0.z;
totalWeight += aBoneWeights0.z;
}
if (aBoneWeights0.w > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.w)] * originalPos * aBoneWeights0.w;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.w)]) * vNormal * aBoneWeights0.w;
totalWeight += aBoneWeights0.w;
}
if (aBoneWeights1.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.x)] * originalPos * aBoneWeights1.x;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.x)]) * vNormal * aBoneWeights1.x;
totalWeight += aBoneWeights1.x;
}
if (aBoneWeights1.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.y)] * originalPos * aBoneWeights1.y;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.y)]) * vNormal * aBoneWeights1.y;
totalWeight += aBoneWeights1.y;
}
if (totalWeight < 0.001) {
skinnedPos = originalPos;
skinnedNormal = vNormal;
}
vec4 eyePos = ModelViewMatrix * skinnedPos;
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * skinnedPos;
texCoord = vTexCoord;
fragViewPos = eyePos.xyz;
fragNormal = mat3(ModelViewMatrix) * skinnedNormal;
}

View File

@ -0,0 +1,71 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
attribute vec4 aBoneIndices0;
attribute vec2 aBoneIndices1;
attribute vec4 aBoneWeights0;
attribute vec2 aBoneWeights1;
varying vec2 texCoord;
varying vec4 fragPosLightSpace;
varying vec3 fragNormal;
varying float fogDistance;
varying vec3 fragViewPos;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uLightFromCamera;
uniform mat4 uBoneMatrices[64];
uniform vec3 uPlayerEyePos;
void main()
{
vec4 skinnedPos = vec4(0.0, 0.0, 0.0, 0.0);
vec3 skinnedNormal = vec3(0.0, 0.0, 0.0);
vec4 originalPos = vec4(vPosition, 1.0);
float totalWeight = 0.0;
if (aBoneWeights0.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.x)] * originalPos * aBoneWeights0.x;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.x)]) * vNormal * aBoneWeights0.x;
totalWeight += aBoneWeights0.x;
}
if (aBoneWeights0.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.y)] * originalPos * aBoneWeights0.y;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.y)]) * vNormal * aBoneWeights0.y;
totalWeight += aBoneWeights0.y;
}
if (aBoneWeights0.z > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.z)] * originalPos * aBoneWeights0.z;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.z)]) * vNormal * aBoneWeights0.z;
totalWeight += aBoneWeights0.z;
}
if (aBoneWeights0.w > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.w)] * originalPos * aBoneWeights0.w;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.w)]) * vNormal * aBoneWeights0.w;
totalWeight += aBoneWeights0.w;
}
if (aBoneWeights1.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.x)] * originalPos * aBoneWeights1.x;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.x)]) * vNormal * aBoneWeights1.x;
totalWeight += aBoneWeights1.x;
}
if (aBoneWeights1.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.y)] * originalPos * aBoneWeights1.y;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.y)]) * vNormal * aBoneWeights1.y;
totalWeight += aBoneWeights1.y;
}
if (totalWeight < 0.001) {
skinnedPos = originalPos;
skinnedNormal = vNormal;
}
vec4 eyePos = ModelViewMatrix * skinnedPos;
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * skinnedPos;
texCoord = vTexCoord;
fragViewPos = eyePos.xyz;
fragNormal = mat3(ModelViewMatrix) * skinnedNormal;
fragPosLightSpace = uLightFromCamera * eyePos;
}

View File

@ -0,0 +1,58 @@
precision mediump float;
uniform sampler2D Texture;
uniform float uAlpha;
uniform int uPointLightCount;
uniform vec3 uPointLightPos[4];
uniform vec3 uPointLightDir[4];
uniform vec3 uPointLightColor[4];
varying vec2 texCoord;
varying float fogDistance;
varying vec3 fragViewPos;
varying vec3 fragNormal;
// cos(45 deg) = half-angle for a 90-degree full-angle spotlight
const float SPOT_COS_OUTER = 0.7071;
const float SPOT_COS_INNER = 0.82; // slightly tighter inner cone for smooth edge
void main()
{
vec4 color = texture2D(Texture, texCoord);
if (color.a < 0.1)
discard;
float ambient = 0.37;
vec3 lighting = vec3(ambient);
bool hasNormal = dot(fragNormal, fragNormal) > 0.001;
vec3 N = hasNormal ? normalize(fragNormal) : vec3(0.0);
for (int i = 0; i < 4; i++)
{
if (i >= uPointLightCount) break;
vec3 lightVec = uPointLightPos[i] - fragViewPos;
float dist = length(lightVec);
vec3 lightDir = normalize(lightVec);
// Spotlight cone: angle between the cone axis and the ray from light to fragment
float cosTheta = dot(-lightDir, normalize(uPointLightDir[i]));
float spotAtten = smoothstep(SPOT_COS_OUTER, SPOT_COS_INNER, cosTheta);
if (spotAtten <= 0.0) continue;
float atten = 1.0 / (1.0 + 0.35 * dist + 0.15 * dist * dist);
float diff = hasNormal ? max(dot(N, lightDir), 0.0) : 1.0;
lighting += uPointLightColor[i] * diff * atten * spotAtten;
}
color.rgb *= lighting;
vec3 fogColor = vec3(0.01, 0.01, 0.05);
float fogFactor = clamp((fogDistance - 15.0) / 8.0, 0.0, 1.0);
color.rgb = mix(color.rgb, fogColor, fogFactor);
gl_FragColor = vec4(color.rgb, color.a * uAlpha);
}

View File

@ -901,6 +901,295 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri
CheckGlError(__FILE__, __LINE__);
}
// ==================== Night pass with point lights ====================
void Character::drawNight(Renderer& renderer, const float* pointLightPosEye, const float* pointLightDirEye, const float* pointLightColors, int pointLightCount) {
if (!enabled) return;
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
}
renderer.RenderUniform1f("uAlpha", 1.0f);
glDisable(GL_BLEND);
if (!isPlayer && hp <= 0) return;
if (useGpuSkinning) {
drawNightGpuSkinning(renderer, pointLightPosEye, pointLightDirEye, pointLightColors, pointLightCount);
} else {
drawNightCpu(renderer, pointLightPosEye, pointLightDirEye, pointLightColors, pointLightCount);
}
}
void Character::drawNightGpuSkinning(Renderer& renderer, const float* plPosEye, const float* plDirEye, const float* plColors, int plCount) {
AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState);
if (it == animations.end()) return;
if (!prepareGpuSkinning()) return;
static const std::string nightSkinningShader = "night_fog_skinning";
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
renderer.shaderManager.PushShader(nightSkinningShader);
renderer.RenderUniform1i(textureUniformName, 0);
const float playerEyePosNight[3] = { 0.0f, 0.0f, -Environment::zoom };
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNight);
renderer.RenderUniform1i("uPointLightCount", plCount);
if (plCount > 0) {
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
}
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
it->second.gpuSkinningShaderData.skinningMatrices[0].data());
for (const auto& name : it->second.model.meshNamesOrdered) {
auto pit = it->second.gpuSkinningShaderData.perMesh.find(name);
if (pit == it->second.gpuSkinningShaderData.perMesh.end()) continue;
auto tit = meshTextures.find(name);
if (tit == meshTextures.end() || !tit->second) continue;
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
pit->second.RenderVBO(renderer);
}
renderer.PopMatrix();
renderer.shaderManager.PushShader("night_fog");
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNight);
renderer.RenderUniform1i("uPointLightCount", plCount);
if (plCount > 0) {
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
}
drawAttachedWeapon(renderer);
renderer.shaderManager.PopShader();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
void Character::drawNightCpu(Renderer& renderer, const float* plPosEye, const float* plDirEye, const float* plColors, int plCount) {
AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState);
if (it == animations.end()) return;
static const std::string nightShader = "night_fog";
renderer.shaderManager.PushShader(nightShader);
renderer.RenderUniform1i(textureUniformName, 0);
const float playerEyePosNight[3] = { 0.0f, 0.0f, -Environment::zoom };
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNight);
renderer.RenderUniform1i("uPointLightCount", plCount);
if (plCount > 0) {
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
}
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
auto& anim = it->second;
for (const auto& name : anim.model.meshNamesOrdered) {
auto mit = anim.model.meshes.find(name);
if (mit == anim.model.meshes.end()) continue;
auto tit = meshTextures.find(name);
if (tit == meshTextures.end() || !tit->second) continue;
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
modelMutable.AssignFrom(mit->second.mesh);
modelMutable.RefreshVBO();
renderer.DrawVertexRenderStruct(modelMutable);
}
renderer.PopMatrix();
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNight);
drawAttachedWeapon(renderer);
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
// ==================== Night pass with spot-light + shadow ====================
void Character::drawNightWithShadow(Renderer& renderer,
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex)
{
if (!enabled) return;
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
}
renderer.RenderUniform1f("uAlpha", 1.0f);
glDisable(GL_BLEND);
if (!isPlayer && hp <= 0) return;
if (useGpuSkinning) {
drawNightGpuSkinningWithShadow(renderer, plPosEye, plDirEye, plColors, plCount, lightFromCamera, shadowMapTex);
} else {
drawNightCpuWithShadow(renderer, plPosEye, plDirEye, plColors, plCount, lightFromCamera, shadowMapTex);
}
}
void Character::drawNightGpuSkinningWithShadow(Renderer& renderer,
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex)
{
AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState);
if (it == animations.end()) return;
if (!prepareGpuSkinning()) return;
static const std::string nightShadowSkinningShader = "night_fog_skinning_shadow";
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
renderer.shaderManager.PushShader(nightShadowSkinningShader);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1i("uShadowMap", 1);
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
// First light's eye-space direction used for shadow bias
if (plCount > 0) renderer.RenderUniform3fv("uLightDir", plDirEye);
const float playerEyePosNightShadow[3] = { 0.0f, 0.0f, -Environment::zoom };
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNightShadow);
renderer.RenderUniform1i("uPointLightCount", plCount);
if (plCount > 0) {
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
}
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, shadowMapTex);
glActiveTexture(GL_TEXTURE0);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
it->second.gpuSkinningShaderData.skinningMatrices[0].data());
for (const auto& name : it->second.model.meshNamesOrdered) {
auto pit = it->second.gpuSkinningShaderData.perMesh.find(name);
if (pit == it->second.gpuSkinningShaderData.perMesh.end()) continue;
auto tit = meshTextures.find(name);
if (tit == meshTextures.end() || !tit->second) continue;
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
pit->second.RenderVBO(renderer);
}
renderer.PopMatrix();
// Weapon uses the non-skinning night shadow shader
renderer.shaderManager.PushShader("night_fog_shadow");
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1i("uShadowMap", 1);
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
if (plCount > 0) renderer.RenderUniform3fv("uLightDir", plDirEye);
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNightShadow);
renderer.RenderUniform1i("uPointLightCount", plCount);
if (plCount > 0) {
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
}
drawAttachedWeapon(renderer);
renderer.shaderManager.PopShader();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
void Character::drawNightCpuWithShadow(Renderer& renderer,
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex)
{
AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState);
if (it == animations.end()) return;
static const std::string nightShadowShader = "night_fog_shadow";
renderer.shaderManager.PushShader(nightShadowShader);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1i("uShadowMap", 1);
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
if (plCount > 0) renderer.RenderUniform3fv("uLightDir", plDirEye);
const float playerEyePosNightShadow[3] = { 0.0f, 0.0f, -Environment::zoom };
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNightShadow);
renderer.RenderUniform1i("uPointLightCount", plCount);
if (plCount > 0) {
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
}
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, shadowMapTex);
glActiveTexture(GL_TEXTURE0);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
auto& anim = it->second;
for (const auto& name : anim.model.meshNamesOrdered) {
auto mit = anim.model.meshes.find(name);
if (mit == anim.model.meshes.end()) continue;
auto tit = meshTextures.find(name);
if (tit == meshTextures.end() || !tit->second) continue;
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
modelMutable.AssignFrom(mit->second.mesh);
modelMutable.RefreshVBO();
renderer.DrawVertexRenderStruct(modelMutable);
}
renderer.PopMatrix();
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNightShadow);
drawAttachedWeapon(renderer);
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
void Character::setupHitSparks(std::shared_ptr<Texture> sparkTexture)
{
hitSparkEmitter.setTexture(sparkTexture);

View File

@ -51,6 +51,10 @@ public:
void draw(Renderer& renderer);
void drawShadowDepth(Renderer& renderer);
void drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
void drawNight(Renderer& renderer, const float* pointLightPosEye, const float* pointLightDirEye, const float* pointLightColors, int pointLightCount);
void drawNightWithShadow(Renderer& renderer,
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex);
// Character-to-character collision (XZ-plane). Used by Location to keep
// player/NPCs from walking through each other.
@ -201,6 +205,16 @@ private:
// Shadow: draw with shadow-aware shaders
void drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
void drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
// Night: draw with spot-light shaders
void drawNightGpuSkinning(Renderer& renderer, const float* plPosEye, const float* plDirEye, const float* plColors, int plCount);
void drawNightCpu(Renderer& renderer, const float* plPosEye, const float* plDirEye, const float* plColors, int plCount);
// Night: draw with spot-light + shadow shaders
void drawNightGpuSkinningWithShadow(Renderer& renderer,
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex);
void drawNightCpuWithShadow(Renderer& renderer,
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex);
};
} // namespace ZL

View File

@ -159,6 +159,10 @@ namespace ZL
renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE);
std::cout << "Load resurces step 12x5" << std::endl;
renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog", "resources/shaders/night_fog.vertex", "resources/shaders/night_fog_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning", "resources/shaders/night_fog_skinning.vertex", "resources/shaders/night_fog_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog_shadow", "resources/shaders/night_fog_shadow.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning_shadow", "resources/shaders/night_fog_skinning_shadow.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE);
#else
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_desktop.fragment", CONST_ZIP_FILE);
@ -181,6 +185,10 @@ namespace ZL
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog", "resources/shaders/night_fog.vertex", "resources/shaders/night_fog_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning", "resources/shaders/night_fog_skinning.vertex", "resources/shaders/night_fog_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog_shadow", "resources/shaders/night_fog_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning_shadow", "resources/shaders/night_fog_skinning_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE);
#endif
@ -414,6 +422,18 @@ namespace ZL
});
}
// Add test spotlight to every location for night-mode testing.
{
PointLight testLight;
//testLight.position = Eigen::Vector3f(-3.5951f, 3.0f, 0.280929f);
testLight.position = Eigen::Vector3f(-3.5951f, 6.0f, 0.280929f);
testLight.direction = Eigen::Vector3f(0.0f, -1.0f, 0.0f); // straight down
//testLight.color = Eigen::Vector3f(2.0f, 1.6f, 1.2f);
testLight.color = Eigen::Vector3f(4.0f, 3.2f, 2.4f);
for (auto& [name, loc] : locations)
loc->pointLights.push_back(testLight);
}
currentLocation = locations["location_dorm"];
currentLocation->scriptEngine.callLocationEnterCallback();
@ -520,13 +540,18 @@ namespace ZL
{
if (currentLocation)
{
// Sync global darklands flag so Location's raycast and draw functions see it.
// Sync global flags so Location's draw functions see them.
currentLocation->isDarklands = isDarklands;
currentLocation->isNight = isNight;
if (isDarklands) {
currentLocation->drawGameDarklands();
CheckGlError(__FILE__, __LINE__);
}
else if (isNight) {
currentLocation->drawGameNight();
CheckGlError(__FILE__, __LINE__);
}
else if (currentLocation->shadowMap) {
CheckGlError(__FILE__, __LINE__);
currentLocation->drawShadowDepthPass();
@ -727,6 +752,13 @@ namespace ZL
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
switch (event.key.keysym.sym) {
case SDLK_9:
if (editorMode == EditorMode::InteractiveObjects && currentLocation) {
currentLocation->editor.selectInteractiveObject(9);
} else {
isNight = !isNight;
}
break;
case SDLK_0:
case SDLK_1:
case SDLK_2:
@ -736,7 +768,6 @@ namespace ZL
case SDLK_6:
case SDLK_7:
case SDLK_8:
case SDLK_9:
if (editorMode == EditorMode::InteractiveObjects && currentLocation) {
currentLocation->editor.selectInteractiveObject(event.key.keysym.sym - SDLK_0);
} else if (event.key.keysym.sym == SDLK_0) {

View File

@ -52,6 +52,9 @@ namespace ZL {
// Returns false if a transition is already in progress.
bool startDarklandsTransition();
// Global night mode state — persists across location transitions.
bool isNight = false;
Inventory inventory;
InteractiveObject* pickedUpObject = nullptr;

View File

@ -9,6 +9,7 @@
#include <functional>
#include <memory>
#include <cfloat>
#include <limits>
#include "GameConstants.h"
#include "Character.h"
#include "external/nlohmann/json.hpp"
@ -878,6 +879,185 @@ namespace ZL
}
}
void Location::drawNightShadowDepthPass(const PointLight& light)
{
if (!shadowMap) return;
static constexpr float kSpotFovY = static_cast<float>(M_PI) * 0.5f; // 90°
shadowMap->updateSpotlightMatrix(light.position, light.direction, kSpotFovY, 0.1f, 20.0f);
shadowMap->bind();
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glEnable(GL_CULL_FACE);
renderer.shaderManager.PushShader("shadow_depth");
renderer.PushPerspectiveProjectionMatrix(kSpotFovY, 1.0f, 0.1f, 20.0f);
renderer.PushSpecialMatrix(shadowMap->getLightViewMatrix());
for (auto& [name, gameObj] : gameObjects) {
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
for (auto& intObj : interactiveObjects) {
if (intObj.isActive && intObj.loadedObject.texture) {
renderer.PushMatrix();
renderer.TranslateMatrix(intObj.position);
renderer.DrawVertexRenderStruct(intObj.loadedObject.mesh);
renderer.PopMatrix();
}
}
if (player) player->drawShadowDepth(renderer);
for (auto& npc : npcs) npc->drawShadowDepth(renderer);
renderer.PopMatrix(); // light view
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
glDisable(GL_CULL_FACE);
shadowMap->unbind();
}
void Location::drawGameNight()
{
// --- Find nearest spotlight for shadow casting ---
const PointLight* shadowLight = nullptr;
if (player && !pointLights.empty()) {
float minDist = FLT_MAX;
for (const auto& pl : pointLights) {
const float d = (pl.position - player->position).norm();
if (d < minDist) { minDist = d; shadowLight = &pl; }
}
}
const bool hasShadows = (shadowMap != nullptr && shadowLight != nullptr);
if (hasShadows) {
drawNightShadowDepthPass(*shadowLight);
}
// --- Main render pass ---
glClearColor(0.01f, 0.01f, 0.05f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
const std::string mainShader = hasShadows ? "night_fog_shadow" : "night_fog";
renderer.shaderManager.PushShader(mainShader);
renderer.RenderUniform1i(textureUniformName, 0);
if (hasShadows) {
renderer.RenderUniform1i("uShadowMap", 1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthTexture());
glActiveTexture(GL_TEXTURE0);
}
const float playerEyePos[3] = { 0.0f, 0.0f, -Environment::zoom };
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePos);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0, 0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
cameraViewMatrix = renderer.GetCurrentModelViewMatrix();
// Transform spot lights to eye space and build uniform arrays (max 4)
static constexpr int MAX_POINT_LIGHTS = 4;
float plPosEye[MAX_POINT_LIGHTS * 3] = {};
float plDirEye[MAX_POINT_LIGHTS * 3] = {};
float plColors[MAX_POINT_LIGHTS * 3] = {};
const Eigen::Matrix3f camRot = cameraViewMatrix.block<3, 3>(0, 0);
const int lightCount = min(static_cast<int>(pointLights.size()), MAX_POINT_LIGHTS);
for (int i = 0; i < lightCount; ++i) {
const Eigen::Vector4f worldPos(pointLights[i].position.x(), pointLights[i].position.y(), pointLights[i].position.z(), 1.0f);
const Eigen::Vector4f eyePos4 = cameraViewMatrix * worldPos;
plPosEye[i * 3 + 0] = eyePos4.x();
plPosEye[i * 3 + 1] = eyePos4.y();
plPosEye[i * 3 + 2] = eyePos4.z();
const Eigen::Vector3f eyeDir = camRot * pointLights[i].direction;
plDirEye[i * 3 + 0] = eyeDir.x();
plDirEye[i * 3 + 1] = eyeDir.y();
plDirEye[i * 3 + 2] = eyeDir.z();
plColors[i * 3 + 0] = pointLights[i].color.x();
plColors[i * 3 + 1] = pointLights[i].color.y();
plColors[i * 3 + 2] = pointLights[i].color.z();
}
// Compute light-from-camera matrix for shadow lookup
Eigen::Matrix4f lightFromCamera = Eigen::Matrix4f::Identity();
if (hasShadows) {
lightFromCamera = shadowMap->getLightSpaceMatrix() * cameraViewMatrix.inverse();
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
// Pass first light's eye-space direction as uLightDir for shadow bias
renderer.RenderUniform3fv("uLightDir", plDirEye);
}
renderer.RenderUniform1i("uPointLightCount", lightCount);
if (lightCount > 0) {
renderer.RenderUniform3fvArray("uPointLightPos[0]", lightCount, plPosEye);
renderer.RenderUniform3fvArray("uPointLightDir[0]", lightCount, plDirEye);
renderer.RenderUniform3fvArray("uPointLightColor[0]", lightCount, plColors);
}
renderer.RenderUniform1f("uAlpha", 1.0f);
for (auto& [name, gameObj] : gameObjects) {
if (!gameObj.texture) continue;
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
for (auto& intObj : interactiveObjects) {
if (intObj.isActive) {
intObj.draw(renderer);
}
}
renderer.RenderUniform1f("uAlpha", 1.0f);
if (player) player->prepareHitSparksForDraw(cameraViewMatrix);
for (auto& npc : npcs) npc->prepareHitSparksForDraw(cameraViewMatrix);
for (auto& tz : teleportZones) tz.prepareForDraw(cameraViewMatrix);
if (hasShadows) {
if (player) player->drawNightWithShadow(renderer, plPosEye, plDirEye, plColors, lightCount, lightFromCamera, shadowMap->getDepthTexture());
for (auto& npc : npcs) npc->drawNightWithShadow(renderer, plPosEye, plDirEye, plColors, lightCount, lightFromCamera, shadowMap->getDepthTexture());
} else {
if (player) player->drawNight(renderer, plPosEye, plDirEye, plColors, lightCount);
for (auto& npc : npcs) npc->drawNight(renderer, plPosEye, plDirEye, plColors, lightCount);
}
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
if (editorMode == EditorMode::Navigation) {
editor.drawNavigation();
editor.drawPoints();
}
if (editorMode == EditorMode::InteractiveObjects) {
editor.drawInteractiveObjectBounds();
}
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
if (npcNameText) {
Eigen::Matrix4f proj = MakePerspectiveMatrix(1.0f / 1.5f,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
for (auto& npc : npcs) {
if (npc) npc->drawName(*npcNameText, cameraViewMatrix, proj);
}
if (player) player->drawHealthBar(renderer, cameraViewMatrix, proj);
for (auto& npc : npcs) {
if (npc) npc->drawHealthBar(renderer, cameraViewMatrix, proj);
}
}
}
bool Location::setNavigationAreaAvailable(const std::string& areaName, bool available)
{
if (!navigation) return false;

View File

@ -20,6 +20,13 @@
namespace ZL
{
struct PointLight
{
Eigen::Vector3f position; // world space
Eigen::Vector3f direction; // world space, unit vector the cone points toward
Eigen::Vector3f color; // RGB, values > 1.0 increase intensity
};
struct TriggerZone
{
std::string id;
@ -83,9 +90,12 @@ namespace ZL
std::vector<TriggerZone> triggerZones;
std::vector<PointLight> pointLights;
// Set by Game and kept in sync across location transitions.
// Read by draw functions and raycast — do not write from Location code.
bool isDarklands = false;
bool isNight = false;
// Called when the player successfully taps the ground and a floor walk target is set.
// Used by the tutorial system to detect the "tap to walk" gesture.
@ -122,6 +132,8 @@ namespace ZL
void drawShadowDepthPass();
void drawGameWithShadows();
void drawGameDarklands();
void drawGameNight();
void drawNightShadowDepthPass(const PointLight& light);
bool setNavigationAreaAvailable(const std::string& areaName, bool available);

View File

@ -825,6 +825,16 @@ namespace ZL {
}
}
void Renderer::RenderUniform3fvArray(const std::string& uniformName, int count, const float* value)
{
auto shader = shaderManager.GetCurrentShader();
auto uniform = shader->uniformList.find(uniformName);
if (uniform != shader->uniformList.end())
{
glUniform3fv(uniform->second, count, value);
}
}
void Renderer::RenderUniform4fv(const std::string& uniformName, const float* value)
{
auto shader = shaderManager.GetCurrentShader();

View File

@ -135,6 +135,7 @@ namespace ZL {
void RenderUniformMatrix4fvArray(const std::string& uniformName, int count, bool transpose, const float* value);
void RenderUniform1i(const std::string& uniformName, const int value);
void RenderUniform3fv(const std::string& uniformName, const float* value);
void RenderUniform3fvArray(const std::string& uniformName, int count, const float* value);
void RenderUniform4fv(const std::string& uniformName, const float* value);
void RenderUniform1f(const std::string& uniformName, float value);

View File

@ -133,6 +133,29 @@ namespace ZL {
lightSpaceMatrix = lightProjectionMatrix * lightViewMatrix;
}
static Eigen::Matrix4f perspective(float fovY, float aspect, float zNear, float zFar) {
float t = std::tan(fovY * 0.5f);
Eigen::Matrix4f m = Eigen::Matrix4f::Zero();
m(0, 0) = 1.0f / (aspect * t);
m(1, 1) = 1.0f / t;
m(2, 2) = -(zFar + zNear) / (zFar - zNear);
m(2, 3) = -(2.0f * zFar * zNear) / (zFar - zNear);
m(3, 2) = -1.0f;
return m;
}
void ShadowMap::updateSpotlightMatrix(const Eigen::Vector3f& lightPos,
const Eigen::Vector3f& lightDir,
float fovY, float nearZ, float farZ) {
Eigen::Vector3f d = lightDir.normalized();
Eigen::Vector3f up(0.f, 1.f, 0.f);
if (std::abs(d.dot(up)) > 0.99f)
up = Eigen::Vector3f(1.f, 0.f, 0.f);
lightViewMatrix = lookAt(lightPos, lightPos + d, up);
lightProjectionMatrix = perspective(fovY, 1.0f, nearZ, farZ);
lightSpaceMatrix = lightProjectionMatrix * lightViewMatrix;
}
void ShadowMap::bind() {
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, resolution, resolution);

View File

@ -29,6 +29,9 @@ namespace ZL {
void setLightDirection(const Eigen::Vector3f& dir);
void updateLightSpaceMatrix(const Eigen::Vector3f& sceneCenter);
void updateSpotlightMatrix(const Eigen::Vector3f& lightPos,
const Eigen::Vector3f& lightDir,
float fovY, float nearZ, float farZ);
void bind();
void unbind();