diff --git a/data/base/shaders/pointlights.frag b/data/base/shaders/pointlights.frag new file mode 100644 index 00000000000..09759cdcb65 --- /dev/null +++ b/data/base/shaders/pointlights.frag @@ -0,0 +1,69 @@ +#define WZ_MAX_POINT_LIGHTS 0 +#define WZ_MAX_INDEXED_POINT_LIGHTS 0 +#define WZ_BUCKET_DIMENSION 0 + +uniform vec4 PointLightsPosition[WZ_MAX_POINT_LIGHTS]; +uniform vec4 PointLightsColorAndEnergy[WZ_MAX_POINT_LIGHTS]; +uniform ivec4 bucketOffsetAndSize[WZ_BUCKET_DIMENSION * WZ_BUCKET_DIMENSION]; +uniform ivec4 PointLightsIndex[WZ_MAX_INDEXED_POINT_LIGHTS]; +uniform int viewportWidth; +uniform int viewportHeight; + +// See https://lisyarus.github.io/blog/graphics/2022/07/30/point-light-attenuation.html for explanation +// we want something that looks somewhat physically correct, but must absolutely be 0 past range +float pointLightEnergyAtPosition(vec3 position, vec3 pointLightWorldPosition, float range) +{ + vec3 pointLightVector = position - pointLightWorldPosition; + float normalizedDistance = length(pointLightVector) / range; + + float sqNormDist = normalizedDistance * normalizedDistance; + float numerator = max(1 - sqNormDist, 0); + return numerator * numerator / ( 1 + 2 * sqNormDist); +} + +vec4 processPointLight(vec3 WorldFragPos, vec3 fragNormal, vec3 viewVector, vec4 albedo, float gloss, vec3 pointLightWorldPosition, float pointLightEnergy, vec3 pointLightColor, mat3 normalWorldSpaceToLocalSpace) +{ + vec3 pointLightVector = WorldFragPos - pointLightWorldPosition; + vec3 pointLightDir = -normalize(pointLightVector * normalWorldSpaceToLocalSpace); + + float energy = pointLightEnergyAtPosition(WorldFragPos, pointLightWorldPosition, pointLightEnergy); + vec4 lightColor = vec4(pointLightColor * energy, 1); + + float pointLightLambert = max(dot(fragNormal, pointLightDir), 0.0); + + vec3 pointLightHalfVec = normalize(viewVector + pointLightDir); + + float pointLightBlinn = pow(clamp(dot(fragNormal, pointLightHalfVec), 0.f, 1.f), 16.f); + return lightColor * pointLightLambert * (albedo + pointLightBlinn * (gloss * gloss)); +} + +// This function expects that we have : +// - a uniforms named bucketOffsetAndSize of ivec4[] +// - a uniforms named PointLightsPosition of vec4[] +// - a uniforms named colorAndEnergy of vec4[] +// fragNormal and view vector are expected to be in the same local space +// normalWorldSpaceToLocalSpace is used to move from world space to local space +vec4 iterateOverAllPointLights( + vec2 clipSpaceCoord, + vec3 WorldFragPos, + vec3 fragNormal, + vec3 viewVector, + vec4 albedo, + float gloss, + mat3 normalWorldSpaceToLocalSpace +) { + vec4 light = vec4(0); + ivec2 bucket = ivec2(WZ_BUCKET_DIMENSION * clipSpaceCoord); + int bucketId = min(bucket.y + bucket.x * WZ_BUCKET_DIMENSION, WZ_BUCKET_DIMENSION * WZ_BUCKET_DIMENSION - 1); + + for (int i = 0; i < bucketOffsetAndSize[bucketId].y; i++) + { + int entryInLightList = bucketOffsetAndSize[bucketId].x + i; + int lightIndex = PointLightsIndex[entryInLightList / 4][entryInLightList % 4]; + vec4 position = PointLightsPosition[lightIndex]; + vec4 colorAndEnergy = PointLightsColorAndEnergy[lightIndex]; + vec3 tmp = position.xyz * vec3(1., 1., -1.); + light += processPointLight(WorldFragPos, fragNormal, viewVector, albedo, gloss, tmp, colorAndEnergy.w, colorAndEnergy.xyz, normalWorldSpaceToLocalSpace); + } + return light; +} diff --git a/data/base/shaders/tcmask_instanced.frag b/data/base/shaders/tcmask_instanced.frag index aeaea7cf261..e977e1bdccb 100644 --- a/data/base/shaders/tcmask_instanced.frag +++ b/data/base/shaders/tcmask_instanced.frag @@ -8,6 +8,7 @@ #define WZ_SHADOW_MODE 1 #define WZ_SHADOW_FILTER_SIZE 3 #define WZ_SHADOW_CASCADES_COUNT 3 +#define WZ_POINT_LIGHT_ENABLED 0 // #define WZ_MAX_SHADOW_CASCADES 3 @@ -69,6 +70,10 @@ out vec4 FragColor; // Uses gl_FragColor #endif +#if WZ_POINT_LIGHT_ENABLED == 1 +#include "pointlights.frag" +#endif + float getShadowMapDepthComp(vec2 base_uv, float u, float v, vec2 shadowMapSizeInv, int cascadeIndex, float z) { vec2 uv = base_uv + vec2(u, v) * shadowMapSizeInv; @@ -337,13 +342,15 @@ void main() float distanceAboveTerrain = uvLightmap.z; float lightmapFactor = 1.0f - (clamp(distanceAboveTerrain, 0.f, 300.f) / 300.f); + float specularMapValue = 0; + if (lambertTerm > 0.0) { float vanillaFactor = 0.0; // Classic models shouldn't use diffuse light if (specularmap != 0) { - float specularMapValue = texture(TextureSpecular, texCoord, WZ_MIP_LOAD_BIAS).r; + specularMapValue = texture(TextureSpecular, texCoord, WZ_MIP_LOAD_BIAS).r; vec4 specularFromMap = vec4(specularMapValue, specularMapValue, specularMapValue, 1.0); // Gaussian specular term computation @@ -361,6 +368,17 @@ void main() // ambient light maxed for classic models to keep results similar to original light += vec4(blendAddEffectLighting(ambient.rgb, ((lightmap_vec4.rgb * lightmapFactor) / 3.f)), ambient.a) * diffuseMap * (1.0 + (1.0 - float(specularmap))); +#if WZ_POINT_LIGHT_ENABLED == 1 + vec2 clipSpaceCoord = gl_FragCoord.xy / vec2(viewportWidth, viewportHeight); + + mat3 identityMat = mat3( + 1., 0., 0., + 0., 1., 0., + 0., 0., 1. + ); + light += iterateOverAllPointLights(clipSpaceCoord, fragPos, -N, normalize(halfVec - lightDir), diffuse, specularMapValue, identityMat); +#endif + light.rgb *= visibility; light.a = 1.0f; diff --git a/data/base/shaders/terrain_combined.vert b/data/base/shaders/terrain_combined.vert index aff59c49685..46764401748 100644 --- a/data/base/shaders/terrain_combined.vert +++ b/data/base/shaders/terrain_combined.vert @@ -32,6 +32,8 @@ out vec4 fgroundWeights; out vec3 groundLightDir; out vec3 groundHalfVec; out mat2 decal2groundMat2; + +out mat3 ModelTangentMatrix; // for Shadows out vec3 fragPos; out vec3 fragNormal; @@ -55,7 +57,7 @@ void main() vec3 vaxis = vec3(1,0,0); // v ~ vertex.x, see uv_ground vec3 tangent = normalize(cross(vertexNormal, vaxis)); vec3 bitangent = cross(vertexNormal, tangent); - mat3 ModelTangentMatrix = mat3(tangent, bitangent, vertexNormal); // aka TBN-matrix + ModelTangentMatrix = mat3(tangent, bitangent, vertexNormal); // aka TBN-matrix // transform light to TangentSpace: vec3 eyeVec = normalize((cameraPos.xyz - vertex.xyz) * ModelTangentMatrix); groundLightDir = sunPos.xyz * ModelTangentMatrix; // already normalized diff --git a/data/base/shaders/terrain_combined_high.frag b/data/base/shaders/terrain_combined_high.frag index ec2231376ac..00bdfc53839 100644 --- a/data/base/shaders/terrain_combined_high.frag +++ b/data/base/shaders/terrain_combined_high.frag @@ -58,6 +58,8 @@ uniform vec4 ambientLight; uniform vec4 diffuseLight; uniform vec4 specularLight; + +uniform vec4 cameraPos; // in modelSpace uniform vec4 sunPos; // in modelSpace, normalized // fog @@ -77,6 +79,9 @@ in vec4 fgroundWeights; in vec3 groundLightDir; in vec3 groundHalfVec; in mat2 decal2groundMat2; + + +in mat3 ModelTangentMatrix; // For Shadows in vec3 fragPos; in vec3 fragNormal; @@ -88,6 +93,7 @@ out vec4 FragColor; #endif #include "terrain_combined_frag.glsl" +#include "pointlights.frag" vec3 getGroundUv(int i) { uint groundNo = fgrounds[i]; @@ -141,6 +147,10 @@ vec4 doBumpMapping(BumpData b, vec3 lightDir, vec3 halfVec) { vec4 res = (b.color*light) + light_spec; + // point lights + vec2 clipSpaceCoord = gl_FragCoord.xy / vec2(viewportWidth, viewportHeight); + res += iterateOverAllPointLights(clipSpaceCoord, fragPos, b.N, normalize(halfVec - lightDir), b.color, b.gloss, ModelTangentMatrix); + return vec4(res.rgb, b.color.a); } diff --git a/data/base/shaders/vk/pointlights.glsl b/data/base/shaders/vk/pointlights.glsl new file mode 100644 index 00000000000..eb6363c90f7 --- /dev/null +++ b/data/base/shaders/vk/pointlights.glsl @@ -0,0 +1,58 @@ +// See https://lisyarus.github.io/blog/graphics/2022/07/30/point-light-attenuation.html for explanation +// we want something that looks somewhat physically correct, but must absolutely be 0 past range +float pointLightEnergyAtPosition(vec3 position, vec3 pointLightWorldPosition, float range) +{ + vec3 pointLightVector = position - pointLightWorldPosition; + float normalizedDistance = length(pointLightVector) / range; + + float sqNormDist = normalizedDistance * normalizedDistance; + float numerator = max(1 - sqNormDist, 0); + return numerator * numerator / ( 1 + 2 * sqNormDist); +} + +vec4 processPointLight(vec3 WorldFragPos, vec3 fragNormal, vec3 viewVector, vec4 albedo, float gloss, vec3 pointLightWorldPosition, float pointLightEnergy, vec3 pointLightColor, mat3 normalWorldSpaceToLocalSpace) +{ + vec3 pointLightVector = WorldFragPos - pointLightWorldPosition; + vec3 pointLightDir = -normalize(pointLightVector * normalWorldSpaceToLocalSpace); + + float energy = pointLightEnergyAtPosition(WorldFragPos, pointLightWorldPosition, pointLightEnergy); + vec4 lightColor = vec4(pointLightColor * energy, 1); + + float pointLightLambert = max(dot(fragNormal, pointLightDir), 0.0); + + vec3 pointLightHalfVec = normalize(viewVector + pointLightDir); + + float pointLightBlinn = pow(clamp(dot(fragNormal, pointLightHalfVec), 0.f, 1.f), 16.f); + return lightColor * pointLightLambert * (albedo + pointLightBlinn * (gloss * gloss)); +} + +// This function expects that we have : +// - a uniforms named bucketOffsetAndSize of ivec4[] +// - a uniforms named PointLightsPosition of vec4[] +// - a uniforms named colorAndEnergy of vec4[] +// fragNormal and view vector are expected to be in the same local space +// normalWorldSpaceToLocalSpace is used to move from world space to local space +vec4 iterateOverAllPointLights( + vec2 clipSpaceCoord, + vec3 WorldFragPos, + vec3 fragNormal, + vec3 viewVector, + vec4 albedo, + float gloss, + mat3 normalWorldSpaceToLocalSpace +) { + vec4 light = vec4(0); + ivec2 bucket = ivec2(WZ_BUCKET_DIMENSION * clipSpaceCoord); + int bucketId = min(bucket.y + bucket.x * WZ_BUCKET_DIMENSION, WZ_BUCKET_DIMENSION * WZ_BUCKET_DIMENSION - 1); + + for (int i = 0; i < bucketOffsetAndSize[bucketId].y; i++) + { + int entryInLightList = bucketOffsetAndSize[bucketId].x + i; + int lightIndex = PointLightsIndex[entryInLightList / 4][entryInLightList % 4]; + vec4 position = PointLightsPosition[lightIndex]; + vec4 colorAndEnergy = PointLightsColorAndEnergy[lightIndex]; + vec3 tmp = position.xyz * vec3(1., 1., -1.); + light += processPointLight(WorldFragPos, fragNormal, viewVector, albedo, gloss, tmp, colorAndEnergy.w, colorAndEnergy.xyz, normalWorldSpaceToLocalSpace); + } + return light; +} diff --git a/data/base/shaders/vk/tcmask_instanced.frag b/data/base/shaders/vk/tcmask_instanced.frag index 39d5921590d..0cdee40a193 100644 --- a/data/base/shaders/vk/tcmask_instanced.frag +++ b/data/base/shaders/vk/tcmask_instanced.frag @@ -30,6 +30,8 @@ layout(location = 12) in vec3 fragPos; layout(location = 0) out vec4 FragColor; +#include "pointlights.glsl" + float getShadowMapDepthComp(vec2 base_uv, float u, float v, vec2 shadowMapSizeInv, int cascadeIndex, float z) { vec2 uv = base_uv + vec2(u, v) * shadowMapSizeInv; @@ -307,13 +309,14 @@ void main() float distanceAboveTerrain = uvLightmap.z; float lightmapFactor = 1.0f - (clamp(distanceAboveTerrain, 0.f, 300.f) / 300.f); + float specularMapValue = 0; if (lambertTerm > 0.0) { float vanillaFactor = 0.0; // Classic models shouldn't use diffuse light if (specularmap != 0) { - float specularMapValue = texture(TextureSpecular, texCoord, WZ_MIP_LOAD_BIAS).r; + specularMapValue = texture(TextureSpecular, texCoord, WZ_MIP_LOAD_BIAS).r; vec4 specularFromMap = vec4(specularMapValue, specularMapValue, specularMapValue, 1.0); // Gaussian specular term computation @@ -331,6 +334,15 @@ void main() // ambient light maxed for classic models to keep results similar to original light += vec4(blendAddEffectLighting(ambient.rgb, ((lightmap_vec4.rgb * lightmapFactor) / 3.f)), ambient.a) * diffuseMap * (1.0 + (1.0 - float(specularmap))); + vec2 clipSpaceCoord = gl_FragCoord.xy / vec2(viewportWidth, viewportHeight); + + mat3 identityMat = mat3( + 1., 0., 0., + 0., 1., 0., + 0., 0., 1. + ); + light += iterateOverAllPointLights(clipSpaceCoord, fragPos, -N, normalize(halfVec - lightDir), diffuse, specularMapValue, identityMat); + light.rgb *= visibility; light.a = 1.0f; diff --git a/data/base/shaders/vk/tcmask_instanced.glsl b/data/base/shaders/vk/tcmask_instanced.glsl index caf6db92347..aa40eb921e5 100644 --- a/data/base/shaders/vk/tcmask_instanced.glsl +++ b/data/base/shaders/vk/tcmask_instanced.glsl @@ -2,6 +2,10 @@ #define WZ_MAX_SHADOW_CASCADES 3 +#define WZ_MAX_POINT_LIGHTS 128 +#define WZ_MAX_INDEXED_POINT_LIGHTS 512 +#define WZ_BUCKET_DIMENSION 8 + layout(std140, set = 0, binding = 0) uniform globaluniforms { mat4 ProjectionMatrix; @@ -20,6 +24,13 @@ layout(std140, set = 0, binding = 0) uniform globaluniforms float fogStart; float graphicsCycle; int fogEnabled; + int viewportWidth; + int viewportHeight; + + vec4 PointLightsPosition[WZ_MAX_POINT_LIGHTS]; + vec4 PointLightsColorAndEnergy[WZ_MAX_POINT_LIGHTS]; + ivec4 bucketOffsetAndSize[WZ_BUCKET_DIMENSION * WZ_BUCKET_DIMENSION]; + ivec4 PointLightsIndex[WZ_MAX_INDEXED_POINT_LIGHTS]; }; layout(std140, set = 1, binding = 0) uniform meshuniforms diff --git a/data/base/shaders/vk/terrain_combined.glsl b/data/base/shaders/vk/terrain_combined.glsl index 2ade267438e..ec5b4c8eadf 100644 --- a/data/base/shaders/vk/terrain_combined.glsl +++ b/data/base/shaders/vk/terrain_combined.glsl @@ -2,6 +2,11 @@ #define WZ_MAX_SHADOW_CASCADES 3 +#define WZ_MAX_POINT_LIGHTS 128 +#define WZ_MAX_INDEXED_POINT_LIGHTS 512 +#define WZ_BUCKET_DIMENSION 8 + + layout(std140, set = 0, binding = 0) uniform cbuffer { mat4 ModelViewProjectionMatrix; mat4 ViewMatrix; @@ -21,6 +26,12 @@ layout(std140, set = 0, binding = 0) uniform cbuffer { float fogEnd; float fogStart; int quality; + int viewportWidth; + int viewportHeight; + vec4 PointLightsPosition[WZ_MAX_POINT_LIGHTS]; + vec4 PointLightsColorAndEnergy[WZ_MAX_POINT_LIGHTS]; + ivec4 bucketOffsetAndSize[WZ_BUCKET_DIMENSION * WZ_BUCKET_DIMENSION]; + ivec4 PointLightsIndex[WZ_MAX_INDEXED_POINT_LIGHTS]; }; // interpolated data. location count = 11 diff --git a/data/base/shaders/vk/terrain_combined.vert b/data/base/shaders/vk/terrain_combined.vert index d3c35897e97..fe846ebe5ed 100644 --- a/data/base/shaders/vk/terrain_combined.vert +++ b/data/base/shaders/vk/terrain_combined.vert @@ -12,6 +12,7 @@ layout(location = 7) in vec4 groundWeights; // ground weights for splatting layout(location = 0) out FragData frag; layout(location = 11) out flat FragFlatData fragf; +layout(location = 14) out mat3 ModelTangentMatrix; void main() { @@ -33,7 +34,7 @@ void main() vec3 vaxis = vec3(1,0,0); // v ~ vertex.x, see uv_ground vec3 tangent = normalize(cross(vertexNormal, vaxis)); vec3 bitangent = cross(vertexNormal, tangent); - mat3 ModelTangentMatrix = mat3(tangent, bitangent, vertexNormal); // aka TBN-matrix + ModelTangentMatrix = mat3(tangent, bitangent, vertexNormal); // aka TBN-matrix // transform light to TangentSpace: vec3 eyeVec = normalize((cameraPos.xyz - vertex.xyz) * ModelTangentMatrix); frag.groundLightDir = sunPos.xyz * ModelTangentMatrix; // already normalized diff --git a/data/base/shaders/vk/terrain_combined_high.frag b/data/base/shaders/vk/terrain_combined_high.frag index a029b92db19..717c224954a 100644 --- a/data/base/shaders/vk/terrain_combined_high.frag +++ b/data/base/shaders/vk/terrain_combined_high.frag @@ -26,10 +26,12 @@ layout(set = 1, binding = 9) uniform sampler2DArrayShadow shadowMap; layout(location = 0) in FragData frag; layout(location = 11) flat in FragFlatData fragf; +layout(location = 14) in mat3 ModelTangentMatrix; layout(location = 0) out vec4 FragColor; #include "terrain_combined_frag.glsl" +#include "pointlights.glsl" vec3 getGroundUv(int i) { uint groundNo = fragf.grounds[i]; @@ -83,6 +85,10 @@ vec4 doBumpMapping(BumpData b, vec3 lightDir, vec3 halfVec) { vec4 res = (b.color*light) + light_spec; + // point lights + vec2 clipSpaceCoord = gl_FragCoord.xy / vec2(viewportWidth, viewportHeight); + res += iterateOverAllPointLights(clipSpaceCoord, frag.fragPos, b.N, normalize(halfVec - lightDir), b.color, b.gloss, ModelTangentMatrix); + return vec4(res.rgb, b.color.a); } diff --git a/lib/framework/string_ext.h b/lib/framework/string_ext.h index 3f06d8ddf80..85d8b090824 100644 --- a/lib/framework/string_ext.h +++ b/lib/framework/string_ext.h @@ -46,8 +46,8 @@ # endif # if defined(DEBUG) # define strdup(s) \ - strdup2(s,__FILE__,__LINE__) -static inline char *strdup2(const char *s, char *fileName, int line) + strdup2(static_cast(s),__LINE__) +static inline char *strdup2(const char *s, int line) { char *result; diff --git a/lib/ivis_opengl/CMakeLists.txt b/lib/ivis_opengl/CMakeLists.txt index aa88418dc60..7c9a3bd82f6 100644 --- a/lib/ivis_opengl/CMakeLists.txt +++ b/lib/ivis_opengl/CMakeLists.txt @@ -1,5 +1,6 @@ file(GLOB HEADERS "bitimage.h" + "culling.h" "gfx_api.h" "gfx_api_formats_def.h" "gfx_api_gl.h" @@ -14,6 +15,7 @@ file(GLOB HEADERS "pieclip.h" "piedef.h" "piefunc.h" + "pielighting.h" "piematrix.h" "piemode.h" "pienormalize.h" @@ -29,6 +31,7 @@ file(GLOB HEADERS file(GLOB SRC "bitimage.cpp" + "culling.cpp" "gfx_api.cpp" "gfx_api_gl.cpp" "gfx_api_image_basis_priv.cpp" @@ -42,6 +45,7 @@ file(GLOB SRC "piedraw.cpp" "piefunc.cpp" "pieimage.cpp" + "pielighting.cpp" "piematrix.cpp" "piemode.cpp" "piepalette.cpp" diff --git a/lib/ivis_opengl/culling.cpp b/lib/ivis_opengl/culling.cpp new file mode 100644 index 00000000000..1c75831584e --- /dev/null +++ b/lib/ivis_opengl/culling.cpp @@ -0,0 +1,56 @@ + + +/* + This file is part of Warzone 2100. + Copyright (C) 1999-2004 Eidos Interactive + Copyright (C) 2005-2020 Warzone 2100 Project + + Warzone 2100 is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Warzone 2100 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Warzone 2100; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "culling.h" +#include +#include +#include +#include + +BoundingBox transformBoundingBox(const glm::mat4& worldViewProjectionMatrix, const BoundingBox& worldSpaceBoundingBox) +{ + BoundingBox bboxInClipSpace; + for (int i = 0; i < 8; i++) + { + glm::vec4 tmp = worldViewProjectionMatrix * glm::vec4(worldSpaceBoundingBox[i], 1.0); + tmp = (tmp / tmp.w); + bboxInClipSpace[i] = glm::vec3(tmp.x, tmp.y, tmp.z); + } + return bboxInClipSpace; +} + + +bool isBBoxInClipSpace(const IntersectionOfHalfSpace& intersectionOfHalfSpace, const BoundingBox& points) +{ + // We test against the complement of the half space + // If all points lies in the complement a half space, it can't be part of the intersection + auto CheckAllPointsInSpace = [&points](const HalfSpaceCheck& predicate) + { + return std::all_of(points.begin(), points.end(), [&predicate](const auto& v) { return !predicate(v); }); + }; + + for (const auto& predicate : intersectionOfHalfSpace) + { + if (CheckAllPointsInSpace(predicate)) + return false; + } + return true; +} diff --git a/lib/ivis_opengl/culling.h b/lib/ivis_opengl/culling.h new file mode 100644 index 00000000000..bf1c466e57c --- /dev/null +++ b/lib/ivis_opengl/culling.h @@ -0,0 +1,38 @@ +/* + This file is part of Warzone 2100. + Copyright (C) 1999-2004 Eidos Interactive + Copyright (C) 2005-2020 Warzone 2100 Project + + Warzone 2100 is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Warzone 2100 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Warzone 2100; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include +#include +#include +#include + +using BoundingBox = std::array; + +/// Project a bounding box in clip space +BoundingBox transformBoundingBox(const glm::mat4& worldViewProjectionMatrix, const BoundingBox& worldSpaceBoundingBox); + +/// Define a half space +using HalfSpaceCheck = std::function; +/// Define a view frustrum (as an intersection of half space +using IntersectionOfHalfSpace = std::array< HalfSpaceCheck, 6>; + +bool isBBoxInClipSpace(const IntersectionOfHalfSpace& intersectionOfHalfSpace, const BoundingBox& points); diff --git a/lib/ivis_opengl/gfx_api.h b/lib/ivis_opengl/gfx_api.h index a199c377247..f823facaaef 100644 --- a/lib/ivis_opengl/gfx_api.h +++ b/lib/ivis_opengl/gfx_api.h @@ -27,6 +27,7 @@ #include #include #include +#include #include "lib/framework/frame.h" #include "lib/framework/wzstring.h" @@ -319,17 +320,19 @@ namespace gfx_api {} }; - struct shadow_constants + struct lighting_constants { uint32_t shadowMode = 1; uint32_t shadowFilterSize = 5; uint32_t shadowCascadesCount = WZ_MAX_SHADOW_CASCADES; + bool isPointLightPerPixelEnabled = false; - bool operator==(const shadow_constants& rhs) const + bool operator==(const lighting_constants& rhs) const { return shadowMode == rhs.shadowMode && shadowFilterSize == rhs.shadowFilterSize - && shadowCascadesCount == rhs.shadowCascadesCount; + && shadowCascadesCount == rhs.shadowCascadesCount + && isPointLightPerPixelEnabled == rhs.isPointLightPerPixelEnabled; } }; @@ -412,6 +415,7 @@ namespace gfx_api virtual bool getScreenshot(std::function)> callback) = 0; virtual void handleWindowSizeChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight) = 0; virtual std::pair getDrawableDimensions() = 0; + virtual bool isYAxisInverted() const = 0; virtual bool shouldDraw() = 0; virtual void shutdown() = 0; virtual const size_t& current_FrameNum() const = 0; @@ -422,8 +426,8 @@ namespace gfx_api virtual bool supports2DTextureArrays() const = 0; virtual bool supportsIntVertexAttributes() const = 0; virtual size_t maxFramesInFlight() const = 0; - virtual shadow_constants getShadowConstants() = 0; - virtual bool setShadowConstants(shadow_constants values) = 0; + virtual lighting_constants getShadowConstants() = 0; + virtual bool setShadowConstants(lighting_constants values) = 0; // instanced rendering APIs virtual bool supportsInstancedRendering() = 0; virtual void draw_instanced(const std::size_t& offset, const std::size_t &count, const primitive_type &primitive, std::size_t instance_count) = 0; @@ -728,6 +732,10 @@ namespace gfx_api using Draw3DShapeAlphaNoDepthWRT = Draw3DShape; using Draw3DShapeNoLightAlphaNoDepthWRT = Draw3DShape; + constexpr size_t max_lights = 128; + constexpr size_t max_indexed_lights = 512; + constexpr size_t bucket_dimension = 8; + // Only change once per frame struct Draw3DShapeInstancedGlobalUniforms { @@ -747,6 +755,13 @@ namespace gfx_api float fogBegin; float timeState; // graphicsCycle int fogEnabled; + int viewportWidth; + int viewportheight; + float unused2; + std::array PointLightsPosition; + std::array PointLightsColorAndEnergy; + std::array bucketOffsetAndSize; + std::array indexed_lights; }; // Only change per mesh @@ -978,6 +993,8 @@ namespace gfx_api }; static_assert(WZ_MAX_SHADOW_CASCADES <= 4, "Packing the ShadowMapCascadeSplits into a vec4 won't work..."); + + struct TerrainCombinedUniforms { glm::mat4 ModelViewProjectionMatrix; @@ -998,6 +1015,13 @@ namespace gfx_api float fog_begin; float fog_end; int quality; + int viewportWidth; + int viewportheight; + float unused2; + std::array PointLightsPosition; + std::array PointLightsColorAndEnergy; + std::array bucketOffsetAndSize; + std::array indexed_lights; }; template diff --git a/lib/ivis_opengl/gfx_api_gl.cpp b/lib/ivis_opengl/gfx_api_gl.cpp index 513c09bce3a..a78ebaf3401 100644 --- a/lib/ivis_opengl/gfx_api_gl.cpp +++ b/lib/ivis_opengl/gfx_api_gl.cpp @@ -35,9 +35,14 @@ #include #include #include +#include "3rdparty/fmt/include/fmt/format.h" #include +#ifdef WZ_CC_MSVC +#include +#endif + #ifndef GL_GENERATE_MIPMAP #define GL_GENERATE_MIPMAP 0x8191 #endif @@ -733,7 +738,7 @@ static const std::map shader_to_file_table = std::make_pair(SHADER_COMPONENT_INSTANCED, program_data{ "Component program", "shaders/tcmask_instanced.vert", "shaders/tcmask_instanced.frag", { // per-frame global uniforms - "ProjectionMatrix", "ViewMatrix", "ModelUVLightmapMatrix", "ShadowMapMVPMatrix", "lightPosition", "sceneColor", "ambient", "diffuse", "specular", "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnd", "fogStart", "graphicsCycle", "fogEnabled", + "ProjectionMatrix", "ViewMatrix", "ModelUVLightmapMatrix", "ShadowMapMVPMatrix", "lightPosition", "sceneColor", "ambient", "diffuse", "specular", "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnd", "fogStart", "graphicsCycle", "fogEnabled", "PointLightsPosition", "PointLightsColorAndEnergy", "bucketOffsetAndSize", "PointLightsIndex", "viewportWidth", "viewportHeight", // per-mesh uniforms "tcmask", "normalmap", "specularmap", "hasTangents" }, @@ -758,7 +763,7 @@ static const std::map shader_to_file_table = std::make_pair(SHADER_NOLIGHT_INSTANCED, program_data{ "Plain program", "shaders/nolight_instanced.vert", "shaders/nolight_instanced.frag", { // per-frame global uniforms - "ProjectionMatrix", "ViewMatrix", "ModelUVLightmapMatrix", "ShadowMapMVPMatrix", "lightPosition", "sceneColor", "ambient", "diffuse", "specular", "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnd", "fogStart", "graphicsCycle", "fogEnabled", + "ProjectionMatrix", "ViewMatrix", "ModelUVLightmapMatrix", "ShadowMapMVPMatrix", "lightPosition", "sceneColor", "ambient", "diffuse", "specular", "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnd", "fogStart", "graphicsCycle", "fogEnabled", "PointLightsPosition", "PointLightsColorAndEnergy", "bucketOffsetAndSize", "PointLightsIndex", "viewportWidth", "viewportHeight", // per-mesh uniforms "tcmask", "normalmap", "specularmap", "hasTangents", }, @@ -778,21 +783,21 @@ static const std::map shader_to_file_table = std::make_pair(SHADER_TERRAIN_COMBINED_CLASSIC, program_data{ "terrain decals program", "shaders/terrain_combined.vert", "shaders/terrain_combined_classic.frag", { "ModelViewProjectionMatrix", "ViewMatrix", "ModelUVLightmapMatrix", "ShadowMapMVPMatrix", "groundScale", "cameraPos", "sunPos", "emissiveLight", "ambientLight", "diffuseLight", "specularLight", - "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnabled", "fogEnd", "fogStart", "quality", + "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnabled", "fogEnd", "fogStart", "quality", "PointLightsPosition", "PointLightsColorAndEnergy", "bucketOffsetAndSize", "PointLightsIndex", "viewportWidth", "viewportHeight", "lightmap_tex", "groundTex", "groundNormal", "groundSpecular", "groundHeight", "decalTex", "decalNormal", "decalSpecular", "decalHeight", "shadowMap" } }), std::make_pair(SHADER_TERRAIN_COMBINED_MEDIUM, program_data{ "terrain decals program", "shaders/terrain_combined.vert", "shaders/terrain_combined_medium.frag", { "ModelViewProjectionMatrix", "ViewMatrix", "ModelUVLightmapMatrix", "ShadowMapMVPMatrix", "groundScale", "cameraPos", "sunPos", "emissiveLight", "ambientLight", "diffuseLight", "specularLight", - "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnabled", "fogEnd", "fogStart", "quality", + "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnabled", "fogEnd", "fogStart", "quality", "PointLightsPosition", "PointLightsColorAndEnergy", "bucketOffsetAndSize", "PointLightsIndex", "viewportWidth", "viewportHeight", "lightmap_tex", "groundTex", "groundNormal", "groundSpecular", "groundHeight", "decalTex", "decalNormal", "decalSpecular", "decalHeight", "shadowMap" } }), std::make_pair(SHADER_TERRAIN_COMBINED_HIGH, program_data{ "terrain decals program", "shaders/terrain_combined.vert", "shaders/terrain_combined_high.frag", { "ModelViewProjectionMatrix", "ViewMatrix", "ModelUVLightmapMatrix", "ShadowMapMVPMatrix", "groundScale", "cameraPos", "sunPos", "emissiveLight", "ambientLight", "diffuseLight", "specularLight", - "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnabled", "fogEnd", "fogStart", "quality", + "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnabled", "fogEnd", "fogStart", "quality", "PointLightsPosition", "PointLightsColorAndEnergy", "bucketOffsetAndSize", "PointLightsIndex", "viewportWidth", "viewportHeight", "lightmap_tex", "groundTex", "groundNormal", "groundSpecular", "groundHeight", "decalTex", "decalNormal", "decalSpecular", "decalHeight", "shadowMap" } }), @@ -1045,7 +1050,7 @@ typename std::pair>gl_ }); } -gl_pipeline_state_object::gl_pipeline_state_object(bool gles, bool fragmentHighpFloatAvailable, bool fragmentHighpIntAvailable, bool patchFragmentShaderMipLodBias, const gfx_api::pipeline_create_info& createInfo, optional mipLodBias, const gfx_api::shadow_constants& shadowConstants) : +gl_pipeline_state_object::gl_pipeline_state_object(bool gles, bool fragmentHighpFloatAvailable, bool fragmentHighpIntAvailable, bool patchFragmentShaderMipLodBias, const gfx_api::pipeline_create_info& createInfo, optional mipLodBias, const gfx_api::lighting_constants& shadowConstants) : desc(createInfo.state_desc), vertex_buffer_desc(createInfo.attribute_descriptions) { std::string vertexShaderHeader; @@ -1391,6 +1396,12 @@ void gl_pipeline_state_object::printShaderInfoLog(code_part part, GLuint shader) GLchar *infoLog = (GLchar *)malloc(infologLen); glGetShaderInfoLog(shader, infologLen, &charsWritten, infoLog); + + // Display log in VS output log +#ifdef WZ_CC_MSVC + OutputDebugStringA(infoLog); +#endif + debug(part, "Shader info log: %s", infoLog); free(infoLog); } @@ -1569,7 +1580,26 @@ static void patchFragmentShaderTextureLodBias(std::string& fragmentShaderStr, fl fragmentShaderStr = std::regex_replace(fragmentShaderStr, re, astringf("#define WZ_MIP_LOAD_BIAS %s", floatAsString.c_str())); } -static bool patchFragmentShaderShadowConstants(std::string& fragmentShaderStr, const gfx_api::shadow_constants& shadowConstants) +static void patchDefines(std::string& fragmentShaderStr, const gfx_api::lighting_constants& lightingConstants) +{ + const auto defines = { + std::make_pair("WZ_MAX_POINT_LIGHTS", gfx_api::max_lights), + std::make_pair("WZ_MAX_INDEXED_POINT_LIGHTS", gfx_api::max_indexed_lights), + std::make_pair("WZ_BUCKET_DIMENSION", gfx_api::bucket_dimension), + std::make_pair("WZ_POINT_LIGHT_ENABLED", static_cast(lightingConstants.isPointLightPerPixelEnabled)), + }; + + const auto& replacer = [&fragmentShaderStr](const std::string& define, const auto& value) { + const auto re_1 = std::regex(fmt::format("#define {} .*", define), std::regex_constants::ECMAScript); + regex_replace_wrapper(fragmentShaderStr, re_1, fmt::format("#define {} {}", define, value)); + }; + for (const auto& p : defines) + { + replacer(p.first, p.second); + } +} + +static bool patchFragmentShaderShadowConstants(std::string& fragmentShaderStr, const gfx_api::lighting_constants& shadowConstants) { // #define WZ_SHADOW_MODE const auto re_1 = std::regex("#define WZ_SHADOW_MODE .*", std::regex_constants::ECMAScript); @@ -1593,7 +1623,7 @@ void gl_pipeline_state_object::build_program(bool fragmentHighpFloatAvailable, b const char * fragment_header, const std::string& fragmentPath, const std::vector &uniformNames, const std::vector> &samplersToBind, - optional mipLodBias, const gfx_api::shadow_constants& shadowConstants) + optional mipLodBias, const gfx_api::lighting_constants& lightingConstants) { GLint status; bool success = true; // Assume overall success @@ -1712,7 +1742,8 @@ void gl_pipeline_state_object::build_program(bool fragmentHighpFloatAvailable, b { patchFragmentShaderTextureLodBias(fragmentShaderStr, mipLodBias.value()); } - hasSpecializationConstant_ShadowConstants = patchFragmentShaderShadowConstants(fragmentShaderStr, shadowConstants); + hasSpecializationConstant_ShadowConstants = patchFragmentShaderShadowConstants(fragmentShaderStr, lightingConstants); + patchDefines(fragmentShaderStr, lightingConstants); const char* ShaderStrings[2] = { fragment_header, fragmentShaderStr.c_str() }; @@ -1823,6 +1854,24 @@ void gl_pipeline_state_object::setUniforms(size_t uniformIdx, const ::glm::mat4 } } +void gl_pipeline_state_object::setUniforms(size_t uniformIdx, const ::glm::vec4 *m, size_t count) +{ + glUniform4fv(locations[uniformIdx], static_cast(count), glm::value_ptr(*m)); + if (duplicateFragmentUniformLocations[uniformIdx] != -1) + { + glUniform4fv(duplicateFragmentUniformLocations[uniformIdx], static_cast(count), glm::value_ptr(*m)); + } +} + +void gl_pipeline_state_object::setUniforms(size_t uniformIdx, const ::glm::ivec4 *m, size_t count) +{ + glUniform4iv(locations[uniformIdx], static_cast(count), glm::value_ptr(*m)); + if (duplicateFragmentUniformLocations[uniformIdx] != -1) + { + glUniform4iv(duplicateFragmentUniformLocations[uniformIdx], static_cast(count), glm::value_ptr(*m)); + } +} + void gl_pipeline_state_object::setUniforms(size_t uniformIdx, const float *v, size_t count) { glUniform1fv(locations[uniformIdx], static_cast(count), v); @@ -1967,14 +2016,20 @@ void gl_pipeline_state_object::set_constants(const gfx_api::Draw3DShapeInstanced setUniforms(13, cbuf.fogBegin); setUniforms(14, cbuf.timeState); setUniforms(15, cbuf.fogEnabled); + setUniforms(16, cbuf.PointLightsPosition); + setUniforms(17, cbuf.PointLightsColorAndEnergy); + setUniforms(18, cbuf.bucketOffsetAndSize); + setUniforms(19, cbuf.indexed_lights); + setUniforms(20, cbuf.viewportWidth); + setUniforms(21, cbuf.viewportheight); } void gl_pipeline_state_object::set_constants(const gfx_api::Draw3DShapeInstancedPerMeshUniforms& cbuf) { - setUniforms(16, cbuf.tcmask); - setUniforms(17, cbuf.normalMap); - setUniforms(18, cbuf.specularMap); - setUniforms(19, cbuf.hasTangents); + setUniforms(22, cbuf.tcmask); + setUniforms(23, cbuf.normalMap); + setUniforms(24, cbuf.specularMap); + setUniforms(25, cbuf.hasTangents); } void gl_pipeline_state_object::set_constants(const gfx_api::Draw3DShapeInstancedDepthOnlyGlobalUniforms& cbuf) @@ -2061,6 +2116,12 @@ void gl_pipeline_state_object::set_constants(const gfx_api::TerrainCombinedUnifo setUniforms(i++, cbuf.fog_begin); setUniforms(i++, cbuf.fog_end); setUniforms(i++, cbuf.quality); + setUniforms(i++, cbuf.PointLightsPosition); + setUniforms(i++, cbuf.PointLightsColorAndEnergy); + setUniforms(i++, cbuf.bucketOffsetAndSize); + setUniforms(i++, cbuf.indexed_lights); + setUniforms(i++, cbuf.viewportWidth); + setUniforms(i++, cbuf.viewportheight); setUniforms(i++, 0); // lightmap_tex setUniforms(i++, 1); // ground setUniforms(i++, 2); // groundNormal @@ -4269,12 +4330,12 @@ size_t gl_context::maxFramesInFlight() const return 2; } -gfx_api::shadow_constants gl_context::getShadowConstants() +gfx_api::lighting_constants gl_context::getShadowConstants() { return shadowConstants; } -bool gl_context::setShadowConstants(gfx_api::shadow_constants newValues) +bool gl_context::setShadowConstants(gfx_api::lighting_constants newValues) { if (shadowConstants == newValues) { diff --git a/lib/ivis_opengl/gfx_api_gl.h b/lib/ivis_opengl/gfx_api_gl.h index 663380c1997..427df94e161 100644 --- a/lib/ivis_opengl/gfx_api_gl.h +++ b/lib/ivis_opengl/gfx_api_gl.h @@ -181,7 +181,7 @@ struct gl_pipeline_state_object final : public gfx_api::pipeline_state_object template typename std::pair> uniform_setting_func(); - gl_pipeline_state_object(bool gles, bool fragmentHighpFloatAvailable, bool fragmentHighpIntAvailable, bool patchFragmentShaderMipLodBias, const gfx_api::pipeline_create_info& createInfo, optional mipLodBias, const gfx_api::shadow_constants& shadowConstants); + gl_pipeline_state_object(bool gles, bool fragmentHighpFloatAvailable, bool fragmentHighpIntAvailable, bool patchFragmentShaderMipLodBias, const gfx_api::pipeline_create_info& createInfo, optional mipLodBias, const gfx_api::lighting_constants& shadowConstants); ~gl_pipeline_state_object(); void set_constants(const void* buffer, const size_t& size); void set_uniforms(const size_t& first, const std::vector>& uniform_blocks); @@ -208,7 +208,7 @@ struct gl_pipeline_state_object final : public gfx_api::pipeline_state_object const char * fragment_header, const std::string& fragmentPath, const std::vector &uniformNames, const std::vector> &samplersToBind, - optional mipLodBias, const gfx_api::shadow_constants& shadowConstants); + optional mipLodBias, const gfx_api::lighting_constants& shadowConstants); void fetch_uniforms(const std::vector& uniformNames, const std::vector& duplicateFragmentUniforms, const std::string& programName); @@ -226,6 +226,13 @@ struct gl_pipeline_state_object final : public gfx_api::pipeline_state_object void setUniforms(size_t uniformIdx, const float &v); void setUniforms(size_t uniformIdx, const ::glm::mat4 *m, size_t count); + void setUniforms(size_t uniformIdx, const ::glm::vec4* m, size_t count); + template + void setUniforms(size_t uniformIdx, const std::array& m) + { + setUniforms(uniformIdx, m.data(), count); + } + void setUniforms(size_t uniformIdx, const ::glm::ivec4* m, size_t count); void setUniforms(size_t uniformIdx, const float *v, size_t count); // Wish there was static reflection in C++... @@ -267,7 +274,7 @@ struct gl_context final : public gfx_api::context size_t scratchbuffer_size = 0; bool khr_debug = false; optional mipLodBias; - gfx_api::shadow_constants shadowConstants; + gfx_api::lighting_constants shadowConstants; bool gles = false; bool fragmentHighpFloatAvailable = true; @@ -323,6 +330,7 @@ struct gl_context final : public gfx_api::context virtual bool getScreenshot(std::function)> callback) override; virtual void handleWindowSizeChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight) override; virtual std::pair getDrawableDimensions() override; + bool isYAxisInverted() const override { return false; } virtual bool shouldDraw() override; virtual void shutdown() override; virtual const size_t& current_FrameNum() const override; @@ -333,8 +341,8 @@ struct gl_context final : public gfx_api::context virtual bool supports2DTextureArrays() const override; virtual bool supportsIntVertexAttributes() const override; virtual size_t maxFramesInFlight() const override; - virtual gfx_api::shadow_constants getShadowConstants() override; - virtual bool setShadowConstants(gfx_api::shadow_constants values) override; + virtual gfx_api::lighting_constants getShadowConstants() override; + virtual bool setShadowConstants(gfx_api::lighting_constants values) override; // instanced rendering APIs virtual bool supportsInstancedRendering() override; virtual void draw_instanced(const std::size_t& offset, const std::size_t &count, const gfx_api::primitive_type &primitive, std::size_t instance_count) override; diff --git a/lib/ivis_opengl/gfx_api_null.cpp b/lib/ivis_opengl/gfx_api_null.cpp index 1ebcd73417d..0e7de7e99e6 100644 --- a/lib/ivis_opengl/gfx_api_null.cpp +++ b/lib/ivis_opengl/gfx_api_null.cpp @@ -496,12 +496,12 @@ size_t null_context::maxFramesInFlight() const return 1; } -gfx_api::shadow_constants null_context::getShadowConstants() +gfx_api::lighting_constants null_context::getShadowConstants() { - return gfx_api::shadow_constants(); + return gfx_api::lighting_constants(); } -bool null_context::setShadowConstants(gfx_api::shadow_constants newValues) +bool null_context::setShadowConstants(gfx_api::lighting_constants newValues) { return true; } diff --git a/lib/ivis_opengl/gfx_api_null.h b/lib/ivis_opengl/gfx_api_null.h index 36521e10521..2360d5c8024 100644 --- a/lib/ivis_opengl/gfx_api_null.h +++ b/lib/ivis_opengl/gfx_api_null.h @@ -142,6 +142,7 @@ struct null_context final : public gfx_api::context virtual bool getScreenshot(std::function)> callback) override; virtual void handleWindowSizeChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight) override; virtual std::pair getDrawableDimensions() override; + bool isYAxisInverted() const override { return false; } virtual bool shouldDraw() override; virtual void shutdown() override; virtual const size_t& current_FrameNum() const override; @@ -152,8 +153,8 @@ struct null_context final : public gfx_api::context virtual bool supports2DTextureArrays() const override; virtual bool supportsIntVertexAttributes() const override; virtual size_t maxFramesInFlight() const override; - virtual gfx_api::shadow_constants getShadowConstants() override; - virtual bool setShadowConstants(gfx_api::shadow_constants values) override; + virtual gfx_api::lighting_constants getShadowConstants() override; + virtual bool setShadowConstants(gfx_api::lighting_constants values) override; // instanced rendering APIs virtual bool supportsInstancedRendering() override; virtual void draw_instanced(const std::size_t& offset, const std::size_t &count, const gfx_api::primitive_type &primitive, std::size_t instance_count) override; diff --git a/lib/ivis_opengl/gfx_api_vk.cpp b/lib/ivis_opengl/gfx_api_vk.cpp index 5fcf0cca480..5a8b47258f6 100644 --- a/lib/ivis_opengl/gfx_api_vk.cpp +++ b/lib/ivis_opengl/gfx_api_vk.cpp @@ -3322,7 +3322,7 @@ bool VkRoot::setupDebugReportCallbacks(const std::vector& extension VkDebugReportCallbackCreateInfoEXT dbgCreateInfo = {}; dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; dbgCreateInfo.pfnCallback = WZDebugReportCallback; - dbgCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | + dbgCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT | VK_DEBUG_REPORT_FLAG_BITS_MAX_ENUM_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; // | //VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT; @@ -6140,12 +6140,12 @@ size_t VkRoot::maxFramesInFlight() const return MAX_FRAMES_IN_FLIGHT; } -gfx_api::shadow_constants VkRoot::getShadowConstants() +gfx_api::lighting_constants VkRoot::getShadowConstants() { return shadowConstants; } -bool VkRoot::setShadowConstants(gfx_api::shadow_constants newValues) +bool VkRoot::setShadowConstants(gfx_api::lighting_constants newValues) { if (shadowConstants == newValues) { diff --git a/lib/ivis_opengl/gfx_api_vk.h b/lib/ivis_opengl/gfx_api_vk.h index 39e9cc606fe..1e493fb54a7 100644 --- a/lib/ivis_opengl/gfx_api_vk.h +++ b/lib/ivis_opengl/gfx_api_vk.h @@ -605,7 +605,7 @@ struct VkRoot final : gfx_api::context VkhInfo debugInfo; gfx_api::context::swap_interval_mode swapMode = gfx_api::context::swap_interval_mode::vsync; optional mipLodBias; - gfx_api::shadow_constants shadowConstants; + gfx_api::lighting_constants shadowConstants; std::vector supportedInstanceExtensionProperties; std::vector instanceExtensions; @@ -857,6 +857,7 @@ struct VkRoot final : gfx_api::context virtual bool getScreenshot(std::function)> callback) override; virtual void handleWindowSizeChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight) override; virtual std::pair getDrawableDimensions() override; + bool isYAxisInverted() const override { return true; } virtual bool shouldDraw() override; virtual void shutdown() override; virtual const size_t& current_FrameNum() const override; @@ -867,8 +868,8 @@ struct VkRoot final : gfx_api::context virtual bool supports2DTextureArrays() const override; virtual bool supportsIntVertexAttributes() const override; virtual size_t maxFramesInFlight() const override; - virtual gfx_api::shadow_constants getShadowConstants() override; - virtual bool setShadowConstants(gfx_api::shadow_constants values) override; + virtual gfx_api::lighting_constants getShadowConstants() override; + virtual bool setShadowConstants(gfx_api::lighting_constants values) override; // instanced rendering APIs virtual bool supportsInstancedRendering() override; virtual void draw_instanced(const std::size_t& offset, const std::size_t &count, const gfx_api::primitive_type &primitive, std::size_t instance_count) override; diff --git a/lib/ivis_opengl/piedraw.cpp b/lib/ivis_opengl/piedraw.cpp index da88968e573..aa33a00bf77 100644 --- a/lib/ivis_opengl/piedraw.cpp +++ b/lib/ivis_opengl/piedraw.cpp @@ -36,6 +36,7 @@ #include "lib/ivis_opengl/pieclip.h" #include "lib/ivis_opengl/pieblitfunc.h" #include "piematrix.h" +#include "pielighting.h" #include "screen.h" #include @@ -46,6 +47,7 @@ #include #include + #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" @@ -1391,11 +1393,14 @@ bool InstancedMeshRenderer::DrawAll(uint64_t currentGameFrame, const glm::mat4& if (useInstancedRendering) { + + auto bucketLight = getCurrentLightingManager().getPointLightBuckets(); + auto dimension = gfx_api::context::get().getDrawableDimensions(); gfx_api::Draw3DShapeInstancedGlobalUniforms globalUniforms { projectionMatrix, viewMatrix, modelUVLightmapMatrix, {shadowCascades.shadowMVPMatrix[0], shadowCascades.shadowMVPMatrix[1], shadowCascades.shadowMVPMatrix[2]}, glm::vec4(currentSunPosition, 0.f), sceneColor, ambient, diffuse, specular, fogColor, {shadowCascades.shadowCascadeSplit[0], shadowCascades.shadowCascadeSplit[1], shadowCascades.shadowCascadeSplit[2], pie_getPerspectiveZFar()}, shadowCascades.shadowMapSize, - renderState.fogBegin, renderState.fogEnd, pie_GetShaderTime(), renderState.fogEnabled + renderState.fogBegin, renderState.fogEnd, pie_GetShaderTime(), renderState.fogEnabled, static_cast(dimension.first), static_cast(dimension.second), 0.f, bucketLight.positions, bucketLight.colorAndEnergy, bucketLight.bucketOffsetAndSize, bucketLight.light_index }; Draw3DShapes_Instanced(currentGameFrame, perFrameUniformsShaderOnce, globalUniforms, drawParts, depthPass); } diff --git a/lib/ivis_opengl/pielighting.cpp b/lib/ivis_opengl/pielighting.cpp new file mode 100644 index 00000000000..39247857eb4 --- /dev/null +++ b/lib/ivis_opengl/pielighting.cpp @@ -0,0 +1,225 @@ +/* + This file is part of Warzone 2100. + Copyright (C) 1999-2004 Eidos Interactive + Copyright (C) 2005-2020 Warzone 2100 Project + + Warzone 2100 is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Warzone 2100 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Warzone 2100; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "pielighting.h" +#include "gfx_api.h" +#include +#include +#include +#include +#include "culling.h" +#include "src/profiling.h" + + +PIELIGHT& LightMap::operator()(int32_t x, int32_t y) +{ + // Clamp x and y values to actual ones + // Give one tile worth of leeway before asserting, for units/transporters coming in from off-map. + ASSERT(x >= -1, "mapTile: x value is too small (%d,%d) in %dx%d", x, y, mapWidth, mapHeight); + ASSERT(y >= -1, "mapTile: y value is too small (%d,%d) in %dx%d", x, y, mapWidth, mapHeight); + x = std::max(x, 0); + y = std::max(y, 0); + ASSERT(x < mapWidth + 1, "mapTile: x value is too big (%d,%d) in %dx%d", x, y, mapWidth, mapHeight); + ASSERT(y < mapHeight + 1, "mapTile: y value is too big (%d,%d) in %dx%d", x, y, mapWidth, mapHeight); + x = std::min(x, mapWidth - 1); + y = std::min(y, mapHeight - 1); + + return data[x + (y * mapWidth)]; +} + +const PIELIGHT& LightMap::operator()(int32_t x, int32_t y) const +{ + // Clamp x and y values to actual ones + // Give one tile worth of leeway before asserting, for units/transporters coming in from off-map. + ASSERT(x >= -1, "mapTile: x value is too small (%d,%d) in %dx%d", x, y, mapWidth, mapHeight); + ASSERT(y >= -1, "mapTile: y value is too small (%d,%d) in %dx%d", x, y, mapWidth, mapHeight); + x = std::max(x, 0); + y = std::max(y, 0); + ASSERT(x < mapWidth + 1, "mapTile: x value is too big (%d,%d) in %dx%d", x, y, mapWidth, mapHeight); + ASSERT(y < mapHeight + 1, "mapTile: y value is too big (%d,%d) in %dx%d", x, y, mapWidth, mapHeight); + x = std::min(x, mapWidth - 1); + y = std::min(y, mapHeight - 1); + + return data[x + (y * mapWidth)]; +} + +void LightMap::reset(size_t width, size_t height) +{ + mapWidth = static_cast(width); + mapHeight = static_cast(height); + data = std::make_unique(width * height); + ASSERT(data != nullptr, "Out of memory"); +} + + +LightingData& getCurrentLightingData() +{ + static LightingData scene; + return scene; +} + +LightMap& getCurrentLighmapData() +{ + static LightMap lightmap; + return lightmap; +} + +namespace { + BoundingBox getLightBoundingBox(const LIGHT& light) + { + glm::vec3 center = light.position; + center.z *= -1.; + float range = light.range; + glm::vec3 horizontal(1.0, 0., 0.); + glm::vec3 vertical(0.0, 1.0, 0.); + glm::vec3 forward(0.0, 0., 1.0f); + + return BoundingBox{ + center - horizontal * range - vertical * range - forward * range, + center - horizontal * range - vertical * range + forward * range, + center - horizontal * range + vertical * range - forward * range, + center - horizontal * range + vertical * range + forward * range, + center + horizontal * range - vertical * range - forward * range, + center + horizontal * range - vertical * range + forward * range, + center + horizontal * range + vertical * range - forward * range, + center + horizontal * range + vertical * range + forward * range + }; + } + +} + +void renderingNew::LightingManager::ComputeFrameData(const LightingData& data, LightMap&, const glm::mat4& worldViewProjectionMatrix) +{ + PointLightBuckets result; + + // Pick the first lights inside the view frustrum + auto viewFrustrum = IntersectionOfHalfSpace{ + [](glm::vec3 in) { return in.x >= -1.f; }, + [](glm::vec3 in) { return in.x <= 1.f; }, + [](glm::vec3 in) { + if (gfx_api::context::get().isYAxisInverted()) + return -in.y >= -1.f ; + return in.y >= -1.f ; + }, + [](glm::vec3 in) { + if (gfx_api::context::get().isYAxisInverted()) + return -in.y <= 1.f; + return in.y <= 1.f; + }, + [](glm::vec3 in) { return in.z >= 0; }, + [](glm::vec3 in) { return in.z <= 1; } + }; + + std::vector culledLights; + for (const auto& light : data.lights) + { + if (culledLights.size() >= gfx_api::max_lights) + { + break; + } + auto clipSpaceBoundingBox = transformBoundingBox(worldViewProjectionMatrix, getLightBoundingBox(light)); + if (!isBBoxInClipSpace(viewFrustrum, clipSpaceBoundingBox)) + { + continue; + } + culledLights.push_back(light); + } + + + for (size_t lightIndex = 0; lightIndex < culledLights.size(); lightIndex++) + { + const auto& light = culledLights[lightIndex]; + result.positions[lightIndex].x = light.position.x; + result.positions[lightIndex].y = light.position.y; + result.positions[lightIndex].z = light.position.z; + result.colorAndEnergy[lightIndex].x = light.colour.byte.r / 255.f; + result.colorAndEnergy[lightIndex].y = light.colour.byte.g / 255.f; + result.colorAndEnergy[lightIndex].z = light.colour.byte.b / 255.f; + result.colorAndEnergy[lightIndex].w = light.range; + } + + // Iterate over all buckets + size_t overallId = 0; + size_t bucketId = 0; + std::array lightList; + for (int i = 0; i < gfx_api::bucket_dimension; i++) + { + for (int j = 0; j < gfx_api::bucket_dimension; j++) + { + auto frustrum = IntersectionOfHalfSpace{ + [i](glm::vec3 in) { return in.x >= -1.f + 2 * static_cast(i) / gfx_api::bucket_dimension; }, + [i](glm::vec3 in) { return in.x <= -1.f + 2 * static_cast(i + 1) / gfx_api::bucket_dimension; }, + [j](glm::vec3 in) { + if (gfx_api::context::get().isYAxisInverted()) + return -in.y >= -1.f + 2 * static_cast(j) / gfx_api::bucket_dimension; + return in.y >= -1.f + 2 * static_cast(j) / gfx_api::bucket_dimension; + }, + [j](glm::vec3 in) { + if (gfx_api::context::get().isYAxisInverted()) + return -in.y <= -1.f + 2 * static_cast(j + 1) / gfx_api::bucket_dimension; + return in.y <= -1.f + 2 * static_cast(j + 1) / gfx_api::bucket_dimension; + }, + [](glm::vec3 in) { return in.z >= 0; }, + [](glm::vec3 in) { return in.z <= 1; } + }; + + size_t bucketSize = 0; + for (size_t lightIndex = 0; lightIndex < culledLights.size(); lightIndex++) + { + if (overallId + bucketSize >= lightList.size()) + continue; + const LIGHT& light = culledLights[lightIndex]; + BoundingBox clipSpaceBoundingBox = transformBoundingBox(worldViewProjectionMatrix, getLightBoundingBox(light)); + + if (isBBoxInClipSpace(frustrum, clipSpaceBoundingBox)) + { + lightList[overallId + bucketSize] = lightIndex; + + bucketSize++; + } + } + + result.bucketOffsetAndSize[bucketId] = glm::ivec4(overallId, bucketSize, 0, 0); + overallId += bucketSize; + bucketId++; + } + } + + // pack the index + for (size_t i = 0; i < lightList.size(); i++) + { + result.light_index[i / 4][i % 4] = static_cast(lightList[i]); + } + + currentPointLightBuckets = result; +} + +static std::unique_ptr lightingManager; + +void setLightingManager(std::unique_ptr manager) +{ + lightingManager = std::move(manager); +} + +ILightingManager& getCurrentLightingManager() +{ + return *lightingManager; +} + diff --git a/lib/ivis_opengl/pielighting.h b/lib/ivis_opengl/pielighting.h new file mode 100644 index 00000000000..094048b98b8 --- /dev/null +++ b/lib/ivis_opengl/pielighting.h @@ -0,0 +1,103 @@ +/* + This file is part of Warzone 2100. + Copyright (C) 1999-2004 Eidos Interactive + Copyright (C) 2005-2020 Warzone 2100 Project + + Warzone 2100 is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Warzone 2100 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Warzone 2100; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include "lib/ivis_opengl/pietypes.h" +#include "gfx_api.h" +#include +#include +#include + +struct LIGHT +{ + Vector3i position = Vector3i(0, 0, 0); + UDWORD range; + PIELIGHT colour; +}; + + +struct LightMap +{ + PIELIGHT& operator()(int32_t x, int32_t y); + const PIELIGHT& operator()(int32_t x, int32_t y) const; + + void reset(size_t width, size_t height); +private: + std::unique_ptr data = nullptr; + int32_t mapWidth; + int32_t mapHeight; +}; + +struct LightingData +{ + std::vector lights; +}; + +LightingData& getCurrentLightingData(); +LightMap& getCurrentLighmapData(); + + + + +struct ILightingManager +{ + struct PointLightBuckets + { + std::array positions = {}; + std::array colorAndEnergy = {}; + + // z and y components are used for padding, keep ivec4 ! + std::array bucketOffsetAndSize = {}; + // Unfortunately due to std140 constraint, we pack indexes in glm::ivec4 and unpack them in shader later + std::array light_index = {}; + }; + + virtual ~ILightingManager() = default; + + void SetFrameStart() + { + currentPointLightBuckets = {}; + } + + virtual void ComputeFrameData(const LightingData& data, LightMap& lightmap, const glm::mat4& worldViewProjectionMatrix) = 0; + + PointLightBuckets getPointLightBuckets() const + { + return currentPointLightBuckets; + } + + protected: + PointLightBuckets currentPointLightBuckets; +}; + + +namespace renderingNew +{ + //! This lighting manager generate a proper PointLightBuckets for per pixel point lights + struct LightingManager final : ILightingManager + { + void ComputeFrameData(const LightingData& data, LightMap& lightmap, const glm::mat4& worldViewProjectionMatrix) override; + }; +} + +void setLightingManager(std::unique_ptr manager); + +ILightingManager& getCurrentLightingManager(); diff --git a/lib/ivis_opengl/piestate.cpp b/lib/ivis_opengl/piestate.cpp index 6c1655173f3..a0b21c034e6 100644 --- a/lib/ivis_opengl/piestate.cpp +++ b/lib/ivis_opengl/piestate.cpp @@ -119,7 +119,7 @@ void pie_FreeShaders() //static float fogEnd; // Run from screen.c on init. -bool pie_LoadShaders(uint32_t shadowFilterSize) +bool pie_LoadShaders(uint32_t shadowFilterSize, bool pointLightEnabled) { // note: actual loading of shaders now occurs in gfx_api @@ -127,6 +127,7 @@ bool pie_LoadShaders(uint32_t shadowFilterSize) ASSERT(gfx_api::context::isInitialized(), "gfx context isn't initialized?"); auto shadowConstants = gfx_api::context::get().getShadowConstants(); shadowConstants.shadowFilterSize = shadowFilterSize; + shadowConstants.isPointLightPerPixelEnabled = pointLightEnabled; gfx_api::context::get().setShadowConstants(shadowConstants); if (!pie_supportsShadowMapping().value_or(false)) diff --git a/lib/ivis_opengl/piestate.h b/lib/ivis_opengl/piestate.h index e7f959461c6..8072477ec6d 100644 --- a/lib/ivis_opengl/piestate.h +++ b/lib/ivis_opengl/piestate.h @@ -82,7 +82,7 @@ const Vector3f& getDefaultSunPosition(); int pie_GetMaxAntialiasing(); -bool pie_LoadShaders(uint32_t shadowFilterSize); +bool pie_LoadShaders(uint32_t shadowFilterSize, bool pointLightEnabled); void pie_FreeShaders(); namespace pie_internal diff --git a/lib/wzmaplib/src/map_script.cpp b/lib/wzmaplib/src/map_script.cpp index 4eee1acddce..615e8e6aa5e 100644 --- a/lib/wzmaplib/src/map_script.cpp +++ b/lib/wzmaplib/src/map_script.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #define MAX_PLAYERS 11 ///< Maximum number of players in the game. diff --git a/src/configuration.cpp b/src/configuration.cpp index 48ebcdcc863..8955a289004 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -612,6 +612,12 @@ bool loadConfig() { war_setShadowMapResolution(value.value()); } + + { + auto value = iniGetBoolOpt("pointLightsPerpixel"); + war_setPointLightPerPixelLighting(value.value_or(false)); + } + ActivityManager::instance().endLoadingSettings(); return true; } @@ -772,6 +778,7 @@ bool saveConfig() iniSetInteger("terrainShadingQuality", getTerrainMappingTexturesMaxSize()); iniSetInteger("shadowFilterSize", (int)war_getShadowFilterSize()); iniSetInteger("shadowMapResolution", (int)war_getShadowMapResolution()); + iniSetBool("pointLightsPerpixel", war_getPointLightPerPixelLighting()); iniSetInteger("configVersion", CURRCONFVERSION); // write out ini file changes diff --git a/src/display.cpp b/src/display.cpp index 8c0fbfbd48f..9ec5023a3c6 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -2084,7 +2084,7 @@ void dealWithLMB() tileIsExplored(psTile) ? "Explored" : "Unexplored", mouseTileX, mouseTileY, world_coord(mouseTileX), world_coord(mouseTileY), (int)psTile->limitedContinent, (int)psTile->hoverContinent, psTile->level, (int)psTile->illumination, - (int)psTile->ambientOcclusion, psTile->colour.rgba, + (int)psTile->ambientOcclusion, getCurrentLighmapData()(mouseTileX, mouseTileY).rgba, aux & AUXBITS_DANGER ? "danger" : "", aux & AUXBITS_THREAT ? "threat" : "", (int)psTile->watchers[selectedPlayer], (int)psTile->sensors[selectedPlayer], (int)psTile->jammers[selectedPlayer], TileNumber_tile(psTile->texture), (TILE_HAS_DECAL(psTile)) ? "y" : "n", diff --git a/src/display3d.cpp b/src/display3d.cpp index 29e89e5bee7..ec61f63d40e 100644 --- a/src/display3d.cpp +++ b/src/display3d.cpp @@ -89,6 +89,8 @@ #include "faction.h" #include "wzcrashhandlingproviders.h" #include "shadowcascades.h" +#include "profiling.h" + /******************** Prototypes ********************/ @@ -105,7 +107,7 @@ static void locateMouse(); static bool renderWallSection(STRUCTURE *psStructure, const glm::mat4 &viewMatrix, const glm::mat4 &perspectiveViewMatrix); static void drawDragBox(); static void calcFlagPosScreenCoords(SDWORD *pX, SDWORD *pY, SDWORD *pR, const glm::mat4 &perspectiveViewModelMatrix); -static void drawTiles(iView *player); +static void drawTiles(iView *player, LightingData& lightData, LightMap& lightmap, ILightingManager& lightManager); static void display3DProjectiles(const glm::mat4 &viewMatrix, const glm::mat4 &perspectiveViewMatrix); static void drawDroidAndStructureSelections(); static void drawDroidSelections(); @@ -999,8 +1001,16 @@ void draw3DScene() updateFogDistance(distance); + // Set light manager + { + if (war_getPointLightPerPixelLighting()) + setLightingManager(std::make_unique()); + else + setLightingManager(std::make_unique()); + } + /* Now, draw the terrain */ - drawTiles(&playerPos); + drawTiles(&playerPos, getCurrentLightingData(), getCurrentLighmapData(), getCurrentLightingManager()); wzPerfBegin(PERF_MISC, "3D scene - misc and text"); @@ -1288,7 +1298,7 @@ glm::mat4 getBiasedShadowMapMVPMatrix(glm::mat4 lightOrthoMatrix, const glm::mat } /// Draw the terrain and all droids, missiles and other objects on it -static void drawTiles(iView *player) +static void drawTiles(iView *player, LightingData& lightData, LightMap& lightmap, ILightingManager& lightManager) { WZ_PROFILE_SCOPE(drawTiles); // draw terrain @@ -1354,6 +1364,10 @@ static void drawTiles(iView *player) const Vector3f theSun = (viewMatrix * glm::vec4(getTheSun(), 0.f)).xyz(); pie_BeginLighting(theSun); + // Reset all lighting data + lightData.lights.clear(); + lightManager.SetFrameStart(); + // update the fog of war... FIXME: Remove this const glm::mat4 tileCalcPerspectiveViewMatrix = perspectiveMatrix * baseViewMatrix; auto currTerrainShaderType = getTerrainShaderType(); @@ -1376,7 +1390,8 @@ static void drawTiles(iView *player) MAPTILE* psTile = mapTile(playerXTile + j, playerZTile + i); pos.y = map_TileHeight(playerXTile + j, playerZTile + i); - setTileColour(playerXTile + j, playerZTile + i, pal_SetBrightness((currTerrainShaderType == TerrainShaderType::SINGLE_PASS) ? 0 : static_cast(psTile->level))); + auto color = pal_SetBrightness((currTerrainShaderType == TerrainShaderType::SINGLE_PASS) ? 0 : static_cast(psTile->level)); + lightmap(playerXTile + j, playerZTile + i) = color; } tileScreenInfo[idx][jdx].z = pie_RotateProjectWithPerspective(&pos, tileCalcPerspectiveViewMatrix, &screen); tileScreenInfo[idx][jdx].x = screen.x; @@ -1418,13 +1433,19 @@ static void drawTiles(iView *player) /* This is done here as effects can light the terrain - pause mode problems though */ wzPerfBegin(PERF_EFFECTS, "3D scene - effects"); - processEffects(perspectiveViewMatrix); + processEffects(perspectiveViewMatrix, lightData); atmosUpdateSystem(); avUpdateTiles(); wzPerfEnd(PERF_EFFECTS); + // The lightmap need to be ready at this point + { + WZ_PROFILE_SCOPE(LightingManager_ComputeFrameData); + lightManager.ComputeFrameData(lightData, lightmap, perspectiveViewMatrix); + } + // prepare terrain for drawing - perFrameTerrainUpdates(); + perFrameTerrainUpdates(lightmap); // and prepare for rendering the models wzPerfBegin(PERF_MODEL_INIT, "Draw 3D scene - model init"); @@ -1459,6 +1480,7 @@ static void drawTiles(iView *player) pie_UpdateLightmap(getTerrainLightmapTexture(), getModelUVLightmapMatrix()); pie_FinalizeMeshes(currentGameFrame); + // shadow/depth-mapping passes ShadowCascadesInfo shadowCascadesInfo; shadowCascadesInfo.shadowMapSize = gfx_api::context::get().getDepthPassDimensions(0); // Note: Currently assumes that every depth pass has the same dimensions diff --git a/src/effects.cpp b/src/effects.cpp index 43a5c768304..9efe9b46db3 100644 --- a/src/effects.cpp +++ b/src/effects.cpp @@ -169,19 +169,22 @@ static uint8_t EffectForPlayer = 0; // ---------------------------------------------------------------------------------------- /* PROTOTYPES */ + +static bool rejectLandLight(LAND_LIGHT_SPEC type); + // ---------------------------------------------------------------------------------------- // ---- Update functions - every group type of effect has one of these */ static bool updateWaypoint(EFFECT *psEffect); -static bool updateExplosion(EFFECT *psEffect); +static bool updateExplosion(EFFECT *psEffect, LightingData& lightData); static bool updatePolySmoke(EFFECT *psEffect); -static bool updateGraviton(EFFECT *psEffect); +static bool updateGraviton(EFFECT *psEffect, LightingData& lightData); static bool updateConstruction(EFFECT *psEffect); static bool updateBlood(EFFECT *psEffect); -static bool updateDestruction(EFFECT *psEffect); -static bool updateFire(EFFECT *psEffect); -static bool updateSatLaser(EFFECT *psEffect); +static bool updateDestruction(EFFECT *psEffect, LightingData& lightData); +static bool updateFire(EFFECT *psEffect, LightingData& lightData); +static bool updateSatLaser(EFFECT *psEffect, LightingData& lightData); static bool updateFirework(EFFECT *psEffect); -static bool updateEffect(EFFECT *psEffect); // MASTER function +static bool updateEffect(EFFECT *psEffect, LightingData& lightData); // MASTER function // ---------------------------------------------------------------------------------------- // ---- The render functions - every group type of effect has a distinct one @@ -474,7 +477,7 @@ void addEffect(const Vector3i *pos, EFFECT_GROUP group, EFFECT_TYPE type, bool s /* Calls all the update functions for each different currently active effect */ -void processEffects(const glm::mat4 &perspectiveViewMatrix) +void processEffects(const glm::mat4 &perspectiveViewMatrix, LightingData& lightData) { WZ_PROFILE_SCOPE(processEffects); for (auto it = activeList.begin(); it != activeList.end(); ) @@ -483,7 +486,7 @@ void processEffects(const glm::mat4 &perspectiveViewMatrix) if (psEffect->birthTime <= graphicsTime) // Don't process, if it doesn't exist yet { - if (!updateEffect(psEffect)) + if (!updateEffect(psEffect, lightData)) { delete psEffect; it = activeList.erase(it); @@ -502,13 +505,13 @@ void processEffects(const glm::mat4 &perspectiveViewMatrix) } /* The general update function for all effects - calls a specific one for each. Returns false if effect should be deleted. */ -static bool updateEffect(EFFECT *psEffect) +static bool updateEffect(EFFECT *psEffect, LightingData& lightData) { /* What type of effect are we dealing with? */ switch (psEffect->group) { case EFFECT_EXPLOSION: - return updateExplosion(psEffect); + return updateExplosion(psEffect, lightData); case EFFECT_WAYPOINT: if (!gamePaused()) { @@ -530,7 +533,7 @@ static bool updateEffect(EFFECT *psEffect) case EFFECT_GRAVITON: if (!gamePaused()) { - return updateGraviton(psEffect); + return updateGraviton(psEffect, lightData); } return true; case EFFECT_BLOOD: @@ -542,19 +545,19 @@ static bool updateEffect(EFFECT *psEffect) case EFFECT_DESTRUCTION: if (!gamePaused()) { - return updateDestruction(psEffect); + return updateDestruction(psEffect, lightData); } return true; case EFFECT_FIRE: if (!gamePaused()) { - return updateFire(psEffect); + return updateFire(psEffect, lightData); } return true; case EFFECT_SAT_LASER: if (!gamePaused()) { - return updateSatLaser(psEffect); + return updateSatLaser(psEffect, lightData); } return true; case EFFECT_FIREWORK: @@ -696,7 +699,7 @@ static bool updateFirework(EFFECT *psEffect) return true; } -static bool updateSatLaser(EFFECT *psEffect) +static bool updateSatLaser(EFFECT *psEffect, LightingData& lightData) { Vector3i dv; UDWORD val; @@ -773,7 +776,7 @@ static bool updateSatLaser(EFFECT *psEffect) light.position = Vector3f(xPos, startHeight, yPos); light.range = 800; light.colour = pal_Colour(0, 0, 255); - processLight(&light); + lightData.lights.push_back(light); return true; } else @@ -783,7 +786,7 @@ static bool updateSatLaser(EFFECT *psEffect) } /** The update function for the explosions */ -static bool updateExplosion(EFFECT *psEffect) +static bool updateExplosion(EFFECT *psEffect, LightingData& lightData) { if (TEST_LIT(psEffect)) { @@ -807,11 +810,14 @@ static bool updateExplosion(EFFECT *psEffect) percent = 100; } - UDWORD range = percent; - light.position = psEffect->position; - light.range = (3 * range) / 2; - light.colour = pal_Colour(255, 0, 0); - processLight(&light); + if (psEffect->type != EXPLOSION_TYPE_LAND_LIGHT || !rejectLandLight(static_cast(psEffect->specific))) + { + UDWORD range = percent; + light.position = psEffect->position; + light.range = (3 * range) / 2; + light.colour = pal_Colour(255, 0, 0); + lightData.lights.push_back(light); + } } if (psEffect->type == EXPLOSION_TYPE_SHOCKWAVE) @@ -825,7 +831,7 @@ static bool updateExplosion(EFFECT *psEffect) light.position = psEffect->position; light.range = psEffect->size + 200; light.colour = pal_Colour(255, 255, 0); - processLight(&light); + lightData.lights.push_back(light); if (psEffect->size > MAX_SHOCKWAVE_SIZE || light.range > 600) { @@ -937,7 +943,7 @@ static bool updatePolySmoke(EFFECT *psEffect) Gravitons just fly up for a bit and then drop down and are killed off when they hit the ground */ -static bool updateGraviton(EFFECT *psEffect) +static bool updateGraviton(EFFECT *psEffect, LightingData& lightData) { float accel; Vector3i dv; @@ -949,7 +955,7 @@ static bool updateGraviton(EFFECT *psEffect) light.position = psEffect->position; light.range = 128; light.colour = pal_Colour(255, 255, 0); - processLight(&light); + lightData.lights.push_back(light); } if (gamePaused()) @@ -1069,7 +1075,7 @@ static bool updateGraviton(EFFECT *psEffect) /** This isn't really an on-screen effect itself - it just spawns other ones.... */ -static bool updateDestruction(EFFECT *psEffect) +static bool updateDestruction(EFFECT *psEffect, LightingData& lightData) { Vector3i pos; UDWORD effectType; @@ -1106,7 +1112,7 @@ static bool updateDestruction(EFFECT *psEffect) { light.colour = pal_Colour(255, 0, 0); } - processLight(&light); + lightData.lights.push_back(light); if (graphicsTime > psEffect->birthTime + psEffect->lifeSpan) { @@ -1290,7 +1296,7 @@ static bool updateConstruction(EFFECT *psEffect) } /** Update fire sequences */ -static bool updateFire(EFFECT *psEffect) +static bool updateFire(EFFECT *psEffect, LightingData& lightData) { Vector3i pos; LIGHT light; @@ -1305,7 +1311,7 @@ static bool updateFire(EFFECT *psEffect) light.position = psEffect->position; light.range = (percent * psEffect->radius * 3) / 100; light.colour = pal_Colour(255, 0, 0); - processLight(&light); + lightData.lights.push_back(light); /* Time to update the frame number on the construction sprite */ if (graphicsTime - psEffect->lastFrame > psEffect->frameDelay) diff --git a/src/effects.h b/src/effects.h index 1e9261644a9..718aad90a54 100644 --- a/src/effects.h +++ b/src/effects.h @@ -157,7 +157,7 @@ void effectGiveAuxVarSec(UDWORD var); // and so's this void initEffectsSystem(); void shutdownEffectsSystem(); -void processEffects(const glm::mat4 &perspectiveViewMatrix); +void processEffects(const glm::mat4 &perspectiveViewMatrix, struct LightingData& lightData); void addEffect(const Vector3i *pos, EFFECT_GROUP group, EFFECT_TYPE type, bool specified, iIMDShape *imd, int lit); void addEffect(const Vector3i *pos, EFFECT_GROUP group, EFFECT_TYPE type, bool specified, iIMDShape *imd, int lit, unsigned effectTime, Vector3i *rot = nullptr, Vector3f *velocity = nullptr); void addMultiEffect(const Vector3i *basePos, Vector3i *scatter, EFFECT_GROUP group, EFFECT_TYPE type, bool specified, iIMDShape *imd, unsigned int number, bool lit, unsigned int size, unsigned effectTime); diff --git a/src/frontend.cpp b/src/frontend.cpp index e6b45d0f5a4..eb90250426a 100644 --- a/src/frontend.cpp +++ b/src/frontend.cpp @@ -853,6 +853,11 @@ char const *graphicsOptionsShadowsString() return getDrawShadows() ? _("On") : _("Off"); } +char const* graphicsOptionsLightingString() +{ + return war_getPointLightPerPixelLighting() ? _("Per Pixel") : _("Lightmap"); +} + char const *graphicsOptionsFogString() { return pie_GetFogEnabled() ? _("On") : _("Off"); @@ -1192,6 +1197,12 @@ void startGraphicsOptionsMenu() row.start++; } + /////////// + // Lighting + grid->place({ 0 }, row, addMargin(makeTextButton(FRONTEND_LIGHTS, _("Per Pixel point lights"), WBUT_SECONDARY))); + grid->place({ 1, 1, false }, row, addMargin(makeTextButton(FRONTEND_LIGHTS_R, graphicsOptionsLightingString(), WBUT_SECONDARY))); + row.start++; + // LOD Distance // TRANSLATORS: "LOD" = "Level of Detail" - this setting is used to describe how level of detail (in textures) is preserved as distance increases (examples: "Default", "High", etc) std::string lodDistanceString = _("LOD Distance"); @@ -1277,7 +1288,13 @@ bool runGraphicsOptionsMenu() setDrawShadows(!getDrawShadows()); widgSetString(psWScreen, FRONTEND_SHADOWS_R, graphicsOptionsShadowsString()); break; - + case FRONTEND_LIGHTS: + case FRONTEND_LIGHTS_R: + { + war_setPointLightPerPixelLighting(!war_getPointLightPerPixelLighting()); + widgSetString(psWScreen, FRONTEND_LIGHTS_R, graphicsOptionsLightingString()); + break; + } case FRONTEND_FOG: case FRONTEND_FOG_R: if (pie_GetFogEnabled()) diff --git a/src/frontend.h b/src/frontend.h index c6d4d8e910d..868ebe5e78c 100644 --- a/src/frontend.h +++ b/src/frontend.h @@ -290,6 +290,8 @@ enum FRONTEND_SHADOWMAP_RESOLUTION_DROPDOWN, FRONTEND_SHADOW_FILTER_SIZE, FRONTEND_SHADOW_FILTER_SIZE_DROPDOWN, + FRONTEND_LIGHTS, + FRONTEND_LIGHTS_R, FRONTEND_FOG, FRONTEND_FOG_R, FRONTEND_RADAR, diff --git a/src/game.cpp b/src/game.cpp index 9f6d363ce79..07858242b2d 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1960,6 +1960,8 @@ static bool deserializeSaveGameV35Data(PHYSFS_file *fileHandle, SAVE_GAME_V35 *s struct SAVE_GAME_V38 : public SAVE_GAME_V35 { char modList[modlist_string_size]; + + SAVE_GAME_V38() : SAVE_GAME_V35() {} }; static void serializeSaveGameV38Data_json(nlohmann::json &o, const SAVE_GAME_V38 *serializeGame) diff --git a/src/input/context.h b/src/input/context.h index 2be17913ed2..6bc3a6fcb15 100644 --- a/src/input/context.h +++ b/src/input/context.h @@ -24,6 +24,7 @@ #include #include #include +#include void registerDefaultContexts(class ContextManager& contextManager, class DebugInputManager& dbgInputManager); diff --git a/src/lighting.cpp b/src/lighting.cpp index 7ef82d7dfa9..d9cede1a9c1 100644 --- a/src/lighting.cpp +++ b/src/lighting.cpp @@ -29,6 +29,7 @@ #include "lib/ivis_opengl/piestate.h" #include "lib/ivis_opengl/piematrix.h" +#include "lib/ivis_opengl/pielighting.h" #include "lib/ivis_opengl/pienormalize.h" #include "lib/ivis_opengl/piepalette.h" #include "lib/framework/fixedpoint.h" @@ -52,6 +53,8 @@ static Vector3f theSun_ForTileIllumination(0.f, 0.f, 0.f); static UDWORD calcDistToTile(UDWORD tileX, UDWORD tileY, Vector3i *pos); static void calcTileIllum(UDWORD tileX, UDWORD tileY); + + void setTheSun(Vector3f newSun) { Vector3f oldSun = theSun; @@ -237,55 +240,60 @@ static void calcTileIllum(UDWORD tileX, UDWORD tileY) tile->ambientOcclusion = static_cast(clip(254.f*ao, 60.f, 254.f)); } -static void colourTile(SDWORD xIndex, SDWORD yIndex, PIELIGHT light_colour, double fraction) +static void colourTile(LightMap& lightmap, SDWORD xIndex, SDWORD yIndex, PIELIGHT light_colour, double fraction) { - PIELIGHT colour = getTileColour(xIndex, yIndex); + PIELIGHT colour = lightmap(xIndex, yIndex); colour.byte.r = static_cast(MIN(255, colour.byte.r + light_colour.byte.r * fraction)); colour.byte.g = static_cast(MIN(255, colour.byte.g + light_colour.byte.g * fraction)); colour.byte.b = static_cast(MIN(255, colour.byte.b + light_colour.byte.b * fraction)); - setTileColour(xIndex, yIndex, colour); + lightmap(xIndex, yIndex) = colour; } -void processLight(LIGHT *psLight) +void rendering1999::LightingManager::ComputeFrameData(const LightingData& data, LightMap& lightmap, const glm::mat4& worldViewProjectionMatrix) { - /* Firstly - there's no point processing lights that are off the grid */ - if (clipXY(psLight->position.x, psLight->position.z) == false) - { - return; - } - - const int tileX = psLight->position.x / TILE_UNITS; - const int tileY = psLight->position.z / TILE_UNITS; - const int rangeSkip = static_cast(sqrtf(psLight->range * psLight->range * 2) / TILE_UNITS + 1); - - /* Rough guess? */ - int startX = tileX - rangeSkip; - int endX = tileX + rangeSkip; - int startY = tileY - rangeSkip; - int endY = tileY + rangeSkip; - - /* Clip to grid limits */ - startX = MAX(startX, 0); - endX = MAX(endX, 0); - endX = MIN(endX, mapWidth - 1); - startX = MIN(startX, endX); - startY = MAX(startY, 0); - endY = MAX(endY, 0); - endY = MIN(endY, mapHeight - 1); - startY = MIN(startY, endY); - - for (int i = startX; i <= endX; i++) + for (const auto& light : data.lights) { - for (int j = startY; j <= endY; j++) + const auto* psLight = &light; + /* Firstly - there's no point processing lights that are off the grid */ + if (clipXY(psLight->position.x, psLight->position.z) == false) { - int distToCorner = calcDistToTile(i, j, &psLight->position); + continue; + } - /* If we're inside the range of the light */ - if (distToCorner < (SDWORD)psLight->range) + const int tileX = psLight->position.x / TILE_UNITS; + const int tileY = psLight->position.z / TILE_UNITS; + const int rangeSkip = static_cast(sqrtf(psLight->range * psLight->range * 2) / TILE_UNITS + 1); + + /* Rough guess? */ + int startX = tileX - rangeSkip; + int endX = tileX + rangeSkip; + int startY = tileY - rangeSkip; + int endY = tileY + rangeSkip; + + /* Clip to grid limits */ + startX = MAX(startX, 0); + endX = MAX(endX, 0); + endX = MIN(endX, mapWidth - 1); + startX = MIN(startX, endX); + startY = MAX(startY, 0); + endY = MAX(endY, 0); + endY = MIN(endY, mapHeight - 1); + startY = MIN(startY, endY); + + for (int i = startX; i <= endX; i++) + { + for (int j = startY; j <= endY; j++) { - /* Find how close we are to it */ - double ratio = (100.0 - PERCENT(distToCorner, psLight->range)) / 100.0; - colourTile(i, j, psLight->colour, ratio); + auto position = psLight->position; + int distToCorner = calcDistToTile(i, j, &position); + + /* If we're inside the range of the light */ + if (distToCorner < (SDWORD)psLight->range) + { + /* Find how close we are to it */ + double ratio = (100.0 - PERCENT(distToCorner, psLight->range)) / 100.0; + colourTile(lightmap, i, j, psLight->colour, ratio); + } } } } @@ -380,26 +388,6 @@ void calcDroidIllumination(DROID *psDroid) psDroid->illumination = retVal; } -void doBuildingLights() -{ - UDWORD i; - LIGHT light; - - for (i = 0; i < MAX_PLAYERS; i++) - { - for (STRUCTURE* psStructure : apsStructLists[i]) - { - light.range = psStructure->pStructureType->baseWidth * TILE_UNITS; - light.position.x = psStructure->pos.x; - light.position.z = psStructure->pos.y; - light.position.y = map_Height(light.position.x, light.position.z); - light.range = psStructure->pStructureType->baseWidth * TILE_UNITS; - light.colour = pal_Colour(255, 255, 255); - processLight(&light); - } - } -} - #if 0 /* Experimental moving shadows code */ void findSunVector() diff --git a/src/lighting.h b/src/lighting.h index 2a1fd67dcfe..904ca8d8f67 100644 --- a/src/lighting.h +++ b/src/lighting.h @@ -22,20 +22,22 @@ #define __INCLUDED_SRC_LIGHTNING_H__ #include "lib/ivis_opengl/pietypes.h" +#include "lib/ivis_opengl/pielighting.h" -struct LIGHT + +namespace rendering1999 { - Vector3i position = Vector3i(0, 0, 0); - UDWORD range; - PIELIGHT colour; -}; + //! This lighting manager relies on lightmap to handle point lights + struct LightingManager final : ILightingManager { + + void ComputeFrameData(const LightingData& data, LightMap& lightmap, const glm::mat4& worldViewProjectionMatrix) override; + }; +} void setTheSun(Vector3f newSun); Vector3f getTheSun(); -void processLight(LIGHT *psLight); void initLighting(UDWORD x1, UDWORD y1, UDWORD x2, UDWORD y2); -void doBuildingLights(); void updateFogDistance(float distance); void setDefaultFogColour(); void calcDroidIllumination(DROID *psDroid); diff --git a/src/main.cpp b/src/main.cpp index efb60314245..6d9428d8386 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2128,7 +2128,7 @@ int realmain(int argc, char *argv[]) { return EXIT_FAILURE; } - if (!pie_LoadShaders(war_getShadowFilterSize())) + if (!pie_LoadShaders(war_getShadowFilterSize(), war_getPointLightPerPixelLighting())) { return EXIT_FAILURE; } diff --git a/src/map.cpp b/src/map.cpp index 5a7e210a92b..71b38ae51e2 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -52,6 +52,7 @@ #include "fpath.h" #include "levels.h" #include "lib/framework/wzapp.h" +#include "lib/ivis_opengl/pielighting.h" #define GAME_TICKS_FOR_DANGER (GAME_TICKS_PER_SEC * 2) @@ -1012,6 +1013,7 @@ bool mapLoadFromWzMapData(std::shared_ptr loadedMap) /* Allocate the memory for the map */ psMapTiles = std::make_unique(static_cast(width) * height); + getCurrentLighmapData().reset(width, height); ASSERT(psMapTiles != nullptr, "Out of memory"); mapWidth = width; diff --git a/src/map.h b/src/map.h index 4a2c71ba76f..46035fd31c4 100644 --- a/src/map.h +++ b/src/map.h @@ -76,12 +76,13 @@ struct MAPTILE uint8_t illumination; // How bright is this tile? = diffuseSunLight * ambientOcclusion uint8_t ambientOcclusion; // ambient occlusion. from 1 (max occlusion) to 254 (no occlusion), similar to illumination. float level; ///< The visibility level of the top left of the tile, for this client. for terrain lightmap - PIELIGHT colour; // color in terrain lightmap, based on tile.level and near light sources }; /* The size and contents of the map */ extern SDWORD mapWidth, mapHeight; + + extern std::unique_ptr psMapTiles; extern float waterLevel; extern char *tilesetDir; diff --git a/src/mission.cpp b/src/mission.cpp index 616ce8d7e08..4bb19d27b1f 100644 --- a/src/mission.cpp +++ b/src/mission.cpp @@ -2750,15 +2750,15 @@ static void addLandingLights(UDWORD x, UDWORD y) { addLandingLight(x, y, LL_MIDDLE, true); // middle - addLandingLight(x + 128, y + 128, LL_OUTER, false); // outer - addLandingLight(x + 128, y - 128, LL_OUTER, false); - addLandingLight(x - 128, y + 128, LL_OUTER, false); - addLandingLight(x - 128, y - 128, LL_OUTER, false); - - addLandingLight(x + 64, y + 64, LL_INNER, false); // inner - addLandingLight(x + 64, y - 64, LL_INNER, false); - addLandingLight(x - 64, y + 64, LL_INNER, false); - addLandingLight(x - 64, y - 64, LL_INNER, false); + addLandingLight(x + 128, y + 128, LL_OUTER, true); // outer + addLandingLight(x + 128, y - 128, LL_OUTER, true); + addLandingLight(x - 128, y + 128, LL_OUTER, true); + addLandingLight(x - 128, y - 128, LL_OUTER, true); + + addLandingLight(x + 64, y + 64, LL_INNER, true); // inner + addLandingLight(x + 64, y - 64, LL_INNER, true); + addLandingLight(x - 64, y + 64, LL_INNER, true); + addLandingLight(x - 64, y - 64, LL_INNER, true); } /* checks the x,y passed in are not within the boundary of any Landing Zone diff --git a/src/terrain.cpp b/src/terrain.cpp index 524673ddcf2..d0e441e5039 100644 --- a/src/terrain.cpp +++ b/src/terrain.cpp @@ -44,6 +44,7 @@ #include "lib/ivis_opengl/pieclip.h" #include "lib/ivis_opengl/piestate.h" #include "lib/ivis_opengl/screen.h" +#include "lib/ivis_opengl/pielighting.h" #include "lib/ivis_opengl/piematrix.h" #include "lib/ivis_opengl/piedraw.h" #include @@ -59,6 +60,8 @@ #include "hci.h" #include "loop.h" #include "wzcrashhandlingproviders.h" +#include "lighting.h" + #include "profiling.h" #include @@ -240,19 +243,6 @@ static void addDrawRangeElements(GLuint start, ASSERT(dreCount <= GLmaxElementsIndices, "too many indices (%i)", (int)(dreCount)); } -/// Get the colour of the terrain tile at the specified position -PIELIGHT getTileColour(int x, int y) -{ - return mapTile(x, y)->colour; -} - -/// Set the colour of the tile at the specified position -void setTileColour(int x, int y, PIELIGHT colour) -{ - MAPTILE *psTile = mapTile(x, y); - - psTile->colour = colour; -} static void flipRotateTexCoords(unsigned short texture, Vector2f &sP1, Vector2f &sP2, Vector2f &sP3, Vector2f &sP4); @@ -1595,7 +1585,7 @@ void shutdownTerrain() terrainInitialised = false; } -static void updateLightMap() +static void updateLightMap(const LightMap& lightmap) { size_t lightmapChannels = lightmapPixmap->channels(); // should always be 4 now... unsigned char* lightMapWritePtr = lightmapPixmap->bmp_w(); @@ -1604,7 +1594,7 @@ static void updateLightMap() for (int i = 0; i < mapWidth; ++i) { MAPTILE *psTile = mapTile(i, j); - PIELIGHT colour = psTile->colour; + PIELIGHT colour = lightmap(i, j); UBYTE level = static_cast(psTile->level); if (psTile->tileInfoBits & BITS_GATEWAY && showGateways) @@ -1916,12 +1906,15 @@ static void drawTerrainCombinedmpl(const glm::mat4 &ModelViewProjection, const g for (int i = 0; i < getNumGroundTypes(); i++) { groundScale[i/4][i%4] = 1.0f / (getGroundType(i).textureSize * world_coord(1)); } + + auto bucketLight = getCurrentLightingManager().getPointLightBuckets(); + auto dimension = gfx_api::context::get().getDrawableDimensions(); gfx_api::TerrainCombinedUniforms uniforms = { ModelViewProjection, ViewMatrix, ModelUVLightmap, {shadowCascades.shadowMVPMatrix[0], shadowCascades.shadowMVPMatrix[1], shadowCascades.shadowMVPMatrix[2]}, groundScale, glm::vec4(cameraPos, 0), glm::vec4(glm::normalize(sunPos), 0), pie_GetLighting0(LIGHT_EMISSIVE), pie_GetLighting0(LIGHT_AMBIENT), pie_GetLighting0(LIGHT_DIFFUSE), pie_GetLighting0(LIGHT_SPECULAR), getFogColorVec4(), {shadowCascades.shadowCascadeSplit[0], shadowCascades.shadowCascadeSplit[1], shadowCascades.shadowCascadeSplit[2], pie_getPerspectiveZFar()}, shadowCascades.shadowMapSize, - renderState.fogEnabled, renderState.fogBegin, renderState.fogEnd, terrainShaderQuality + renderState.fogEnabled, renderState.fogBegin, renderState.fogEnd, terrainShaderQuality, static_cast(dimension.first), static_cast(dimension.second), 0.f, bucketLight.positions, bucketLight.colorAndEnergy, bucketLight.bucketOffsetAndSize, bucketLight.light_index }; PSO::get().set_uniforms(uniforms); @@ -1974,7 +1967,7 @@ static void drawTerrainCombined(const glm::mat4 &ModelViewProjection, const glm: } -void perFrameTerrainUpdates() +void perFrameTerrainUpdates(const LightMap& lightMap) { WZ_PROFILE_SCOPE(perFrameTerrainUpdates); /////////////////////////////////// @@ -1984,7 +1977,7 @@ void perFrameTerrainUpdates() if (realTime - lightmapLastUpdate >= LIGHTMAP_REFRESH) { lightmapLastUpdate = realTime; - updateLightMap(); + updateLightMap(lightMap); lightmap_texture->upload(0, *(lightmapPixmap.get())); } diff --git a/src/terrain.h b/src/terrain.h index 37e7e3f0cfd..cb06cae7700 100644 --- a/src/terrain.h +++ b/src/terrain.h @@ -33,7 +33,7 @@ void loadTerrainTextures(MAP_TILESET mapTileset); bool initTerrain(); void shutdownTerrain(); -void perFrameTerrainUpdates(); +void perFrameTerrainUpdates(const struct LightMap& lightData); void drawTerrainDepthOnly(const glm::mat4 &mvp); void drawTerrain(const glm::mat4 &mvp, const glm::mat4& viewMatrix, const Vector3f &cameraPos, const Vector3f &sunPos, const ShadowCascadesInfo& shadowMVPMatrix); void drawWater(const glm::mat4 &ModelViewProjection, const Vector3f &cameraPos, const Vector3f &sunPos); @@ -46,9 +46,6 @@ namespace gfx_api gfx_api::texture* getTerrainLightmapTexture(); const glm::mat4& getModelUVLightmapMatrix(); -PIELIGHT getTileColour(int x, int y); -void setTileColour(int x, int y, PIELIGHT colour); - void markTileDirty(int i, int j); enum TerrainShaderType diff --git a/src/warzoneconfig.cpp b/src/warzoneconfig.cpp index 9e7a851f871..b1fe8074c1a 100644 --- a/src/warzoneconfig.cpp +++ b/src/warzoneconfig.cpp @@ -89,6 +89,7 @@ struct WARZONE_GLOBALS uint32_t shadowFilteringMode = 1; uint32_t shadowFilterSize = 5; uint32_t shadowMapResolution = 0; // this defaults to 0, which causes the gfx backend to figure out a recommended default based on the system properties + bool pointLightLighting = false; // groups UI bool groupsMenuEnabled = true; @@ -650,6 +651,16 @@ void war_setShadowMapResolution(uint32_t resolution) warGlobs.shadowMapResolution = resolution; } +bool war_getPointLightPerPixelLighting() +{ + return warGlobs.pointLightLighting; +} + +void war_setPointLightPerPixelLighting(bool perPixelEnabled) +{ + warGlobs.pointLightLighting = perPixelEnabled; +} + bool war_getGroupsMenuEnabled() { return warGlobs.groupsMenuEnabled; diff --git a/src/warzoneconfig.h b/src/warzoneconfig.h index 079bc528370..91b00d5cff7 100644 --- a/src/warzoneconfig.h +++ b/src/warzoneconfig.h @@ -162,6 +162,9 @@ void war_setShadowFilterSize(uint32_t filterSize); uint32_t war_getShadowMapResolution(); void war_setShadowMapResolution(uint32_t resolution); +bool war_getPointLightPerPixelLighting(); +void war_setPointLightPerPixelLighting(bool perPixelEnabled); + bool war_getGroupsMenuEnabled(); void war_setGroupsMenuEnabled(bool enabled);