diff --git a/data/base/shaders/pointlights.frag b/data/base/shaders/pointlights.frag index 90b99bdb873..4f2fe748591 100644 --- a/data/base/shaders/pointlights.frag +++ b/data/base/shaders/pointlights.frag @@ -1,6 +1,7 @@ #define WZ_MAX_POINT_LIGHTS 0 #define WZ_MAX_INDEXED_POINT_LIGHTS 0 #define WZ_BUCKET_DIMENSION 0 +#define WZ_VOLUMETRIC_LIGHTING_ENABLED 0 uniform vec4 PointLightsPosition[WZ_MAX_POINT_LIGHTS]; uniform vec4 PointLightsColorAndEnergy[WZ_MAX_POINT_LIGHTS]; @@ -8,6 +9,7 @@ uniform ivec4 bucketOffsetAndSize[WZ_BUCKET_DIMENSION * WZ_BUCKET_DIMENSION]; uniform ivec4 PointLightsIndex[WZ_MAX_INDEXED_POINT_LIGHTS]; uniform int viewportWidth; uniform int viewportHeight; +uniform vec4 cameraPos; // in modelSpace // 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 @@ -21,22 +23,29 @@ float pointLightEnergyAtPosition(vec3 position, vec3 pointLightWorldPosition, fl return numerator * numerator / ( 1.f + 2.f * sqNormDist); } -vec4 processPointLight(vec3 WorldFragPos, vec3 fragNormal, vec3 viewVector, vec4 albedo, float gloss, vec3 pointLightWorldPosition, float pointLightEnergy, vec3 pointLightColor, mat3 normalWorldSpaceToLocalSpace) +struct MaterialInfo +{ + vec4 albedo; + float gloss; +}; + +vec4 processPointLight(vec3 WorldFragPos, vec3 fragNormal, vec3 viewVector, MaterialInfo material, 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.f); + vec4 lightColor = vec4(pointLightColor * energy, 1); - float pointLightLambert = max(dot(fragNormal, pointLightDir), 0.f); + 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)); + return lightColor * pointLightLambert * (material.albedo + pointLightBlinn * (material.gloss * material.gloss)); } + // This function expects that we have : // - a uniforms named bucketOffsetAndSize of ivec4[] // - a uniforms named PointLightsPosition of vec4[] @@ -48,8 +57,7 @@ vec4 iterateOverAllPointLights( vec3 WorldFragPos, vec3 fragNormal, vec3 viewVector, - vec4 albedo, - float gloss, + MaterialInfo material, mat3 normalWorldSpaceToLocalSpace ) { vec4 light = vec4(0.f); @@ -62,8 +70,49 @@ vec4 iterateOverAllPointLights( int lightIndex = PointLightsIndex[entryInLightList / 4][entryInLightList % 4]; vec4 position = PointLightsPosition[lightIndex]; vec4 colorAndEnergy = PointLightsColorAndEnergy[lightIndex]; - vec3 tmp = position.xyz * vec3(1.f, 1.f, -1.f); - light += processPointLight(WorldFragPos, fragNormal, viewVector, albedo, gloss, tmp, colorAndEnergy.w, colorAndEnergy.xyz, normalWorldSpaceToLocalSpace); + light += processPointLight(WorldFragPos, fragNormal, viewVector, material, position.xyz, colorAndEnergy.w, colorAndEnergy.xyz, normalWorldSpaceToLocalSpace); } return light; } + + +// based on equations found here : https://www.shadertoy.com/view/lstfR7 +vec4 volumetricLights( + vec2 clipSpaceCoord, + vec3 cameraPosition, + vec3 WorldFragPos, + vec3 sunLightColor +) { + vec3 result = vec3(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); + + + vec3 viewLine = cameraPosition.xyz - WorldFragPos; + vec3 currentTransmittence = vec3(1); + vec3 transMittance = vec3(1); + + +#define STEPS (WZ_VOLUMETRIC_LIGHTING_ENABLED * 4) + for (int i = 0; i < STEPS; i++) + { + + vec3 posOnViewLine = WorldFragPos + viewLine * i / STEPS; + // fog is thicker near 0 + float thickness = exp(-posOnViewLine.y / 300); + + vec3 od = fogColor.xyz * thickness * length(viewLine / STEPS) / 1000; + + vec3 scatteredLight = sunLightColor * od; + result += scatteredLight * currentTransmittence; + + currentTransmittence *= exp2(od); + transMittance *= exp2(-od); + } + return vec4(result * transMittance, transMittance); +} + +vec3 toneMap(vec3 x) +{ + return x; +} \ No newline at end of file diff --git a/data/base/shaders/tcmask_instanced.frag b/data/base/shaders/tcmask_instanced.frag index 873941f5d96..c3afd05d740 100644 --- a/data/base/shaders/tcmask_instanced.frag +++ b/data/base/shaders/tcmask_instanced.frag @@ -376,9 +376,13 @@ void main() 0., 1., 0., 0., 0., 1. ); + MaterialInfo materialInfo; + materialInfo.albedo = diffuse; + materialInfo.gloss = specularMapValue; // Normals are in view space, we need to get back to world space vec3 worldSpaceNormal = -(inverse(ViewMatrix) * vec4(N, 0.f)).xyz; - light += iterateOverAllPointLights(clipSpaceCoord, fragPos, worldSpaceNormal, normalize(halfVec - lightDir), diffuse, specularMapValue, identityMat); + + light += iterateOverAllPointLights(clipSpaceCoord, fragPos, worldSpaceNormal, normalize(halfVec - lightDir), materialInfo, identityMat); #endif light.rgb *= visibility; @@ -403,7 +407,12 @@ void main() fragColour.a = 0.66 + 0.66 * graphicsCycle; } - if (fogEnabled > 0) + if (WZ_VOLUMETRIC_LIGHTING_ENABLED != 0) { + vec2 clipSpaceCoord = gl_FragCoord.xy / vec2(viewportWidth, viewportHeight); + vec4 volumetric = volumetricLights(clipSpaceCoord, cameraPos.xyz, fragPos, diffuse.xyz); + fragColour.xyz = toneMap(fragColour.xyz * volumetric.a + volumetric.xyz) * lightmap_vec4.a; + } + else if (fogEnabled > 0) { // Calculate linear fog float fogFactor = (fogEnd - vertexDistance) / (fogEnd - fogStart); diff --git a/data/base/shaders/terrain_combined_high.frag b/data/base/shaders/terrain_combined_high.frag index 4e7c0a22193..4d0bd57a591 100644 --- a/data/base/shaders/terrain_combined_high.frag +++ b/data/base/shaders/terrain_combined_high.frag @@ -60,7 +60,7 @@ uniform vec4 diffuseLight; uniform vec4 specularLight; -uniform vec4 cameraPos; // in modelSpace +//uniform vec4 cameraPos; // in modelSpace uniform vec4 sunPos; // in modelSpace, normalized // fog @@ -148,10 +148,14 @@ vec4 doBumpMapping(BumpData b, vec3 lightDir, vec3 halfVec) { vec4 res = (b.color*light) + light_spec; + MaterialInfo materialInfo; + materialInfo.albedo = b.color; + materialInfo.gloss = b.gloss; + #if WZ_POINT_LIGHT_ENABLED == 1 // point lights vec2 clipSpaceCoord = gl_FragCoord.xy / vec2(float(viewportWidth), float(viewportHeight)); - res += iterateOverAllPointLights(clipSpaceCoord, fragPos, b.N, normalize(halfVec - lightDir), b.color, b.gloss, ModelTangentMatrix); + res += iterateOverAllPointLights(clipSpaceCoord, fragPos, b.N, normalize(halfVec - lightDir), materialInfo, ModelTangentMatrix); #endif return vec4(res.rgb, b.color.a); @@ -186,7 +190,13 @@ void main() { vec4 fragColor = main_bumpMapping(); - if (fogEnabled > 0) + if (WZ_VOLUMETRIC_LIGHTING_ENABLED != 1) { + vec4 lightmap_vec4 = texture(lightmap_tex, uvLightmap, 0.f); + vec2 clipSpaceCoord = gl_FragCoord.xy / vec2(viewportWidth, viewportHeight); + vec4 volumetric = volumetricLights(clipSpaceCoord, cameraPos.xyz, fragPos, diffuseLight.xyz); + fragColor.xyz = toneMap(fragColor.xyz * volumetric.a + volumetric.xyz) * lightmap_vec4.a; + } + else if (fogEnabled > 0) { // Calculate linear fog float fogFactor = (fogEnd - vertexDistance) / (fogEnd - fogStart); diff --git a/data/base/shaders/vk/pointlights.glsl b/data/base/shaders/vk/pointlights.glsl index eb6363c90f7..2ad18be2325 100644 --- a/data/base/shaders/vk/pointlights.glsl +++ b/data/base/shaders/vk/pointlights.glsl @@ -10,7 +10,13 @@ float pointLightEnergyAtPosition(vec3 position, vec3 pointLightWorldPosition, fl 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) +struct MaterialInfo +{ + vec4 albedo; + float gloss; +}; + +vec4 processPointLight(vec3 WorldFragPos, vec3 fragNormal, vec3 viewVector, MaterialInfo material, vec3 pointLightWorldPosition, float pointLightEnergy, vec3 pointLightColor, mat3 normalWorldSpaceToLocalSpace) { vec3 pointLightVector = WorldFragPos - pointLightWorldPosition; vec3 pointLightDir = -normalize(pointLightVector * normalWorldSpaceToLocalSpace); @@ -23,9 +29,10 @@ vec4 processPointLight(vec3 WorldFragPos, vec3 fragNormal, vec3 viewVector, vec4 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)); + return lightColor * pointLightLambert * (material.albedo + pointLightBlinn * (material.gloss * material.gloss)); } + // This function expects that we have : // - a uniforms named bucketOffsetAndSize of ivec4[] // - a uniforms named PointLightsPosition of vec4[] @@ -37,8 +44,7 @@ vec4 iterateOverAllPointLights( vec3 WorldFragPos, vec3 fragNormal, vec3 viewVector, - vec4 albedo, - float gloss, + MaterialInfo material, mat3 normalWorldSpaceToLocalSpace ) { vec4 light = vec4(0); @@ -51,8 +57,48 @@ vec4 iterateOverAllPointLights( 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); + light += processPointLight(WorldFragPos, fragNormal, viewVector, material, position.xyz, colorAndEnergy.w, colorAndEnergy.xyz, normalWorldSpaceToLocalSpace); } return light; } + + +// based on equations found here : https://www.shadertoy.com/view/lstfR7 +vec4 volumetricLights( + vec2 clipSpaceCoord, + vec3 cameraPosition, + vec3 WorldFragPos, + vec3 sunLightColor +) { + vec3 result = vec3(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); + + + vec3 viewLine = cameraPosition.xyz - WorldFragPos; + vec3 currentTransmittence = vec3(1); + vec3 transMittance = vec3(1); + + +#define STEPS (WZ_VOLUMETRIC_LIGHTING_ENABLED * 4) + for (int i = 0; i < STEPS; i++) + { + + vec3 posOnViewLine = WorldFragPos + viewLine * i / STEPS; + // fog is thicker near 0 + float thickness = exp(-posOnViewLine.y / 300); + + vec3 od = fogColor.xyz * thickness * length(viewLine / STEPS) / 1000; + vec3 scatteredLight = sunLightColor * od; + result += scatteredLight * currentTransmittence; + + currentTransmittence *= exp2(od); + transMittance *= exp2(-od); + } + return vec4(result * transMittance, transMittance); +} + +vec3 toneMap(vec3 x) +{ + return x; +} \ No newline at end of file diff --git a/data/base/shaders/vk/tcmask_instanced.frag b/data/base/shaders/vk/tcmask_instanced.frag index d5258a8fe48..7b3ba507549 100644 --- a/data/base/shaders/vk/tcmask_instanced.frag +++ b/data/base/shaders/vk/tcmask_instanced.frag @@ -8,6 +8,7 @@ layout (constant_id = 1) const uint WZ_SHADOW_MODE = 1; layout (constant_id = 2) const uint WZ_SHADOW_FILTER_SIZE = 5; layout (constant_id = 3) const uint WZ_SHADOW_CASCADES_COUNT = 3; layout (constant_id = 4) const uint WZ_POINT_LIGHT_ENABLED = 0; +layout (constant_id = 5) const uint WZ_VOLUMETRIC_LIGHTING_ENABLED = 0; layout(set = 2, binding = 0) uniform sampler2D Texture; // diffuse layout(set = 2, binding = 1) uniform sampler2D TextureTcmask; // tcmask @@ -31,8 +32,6 @@ 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; @@ -264,6 +263,9 @@ float getShadowVisibility() } } + +#include "pointlights.glsl" + vec3 blendAddEffectLighting(vec3 a, vec3 b) { return min(a + b, vec3(1.0)); } @@ -346,7 +348,10 @@ void main() ); // Normals are in view space, we need to get back to world space vec3 worldSpaceNormal = -(inverse(ViewMatrix) * vec4(N, 0.f)).xyz; - light += iterateOverAllPointLights(clipSpaceCoord, fragPos, worldSpaceNormal, normalize(halfVec - lightDir), diffuse, specularMapValue, identityMat); + MaterialInfo materialInfo; + materialInfo.albedo = diffuse; + materialInfo.gloss = specularMapValue; + light += iterateOverAllPointLights(clipSpaceCoord, fragPos, worldSpaceNormal, normalize(halfVec - lightDir), materialInfo, identityMat); } light.rgb *= visibility; @@ -370,8 +375,13 @@ void main() { fragColour.a = 0.66 + 0.66 * graphicsCycle; } - - if (fogEnabled > 0) + + if (WZ_VOLUMETRIC_LIGHTING_ENABLED != 0) { + vec2 clipSpaceCoord = gl_FragCoord.xy / vec2(viewportWidth, viewportHeight); + vec4 volumetric = volumetricLights(clipSpaceCoord, cameraPos.xyz, fragPos, diffuse.xyz); + fragColour.xyz = toneMap(fragColour.xyz * volumetric.a + volumetric.xyz) * lightmap_vec4.a; + } + else if (fogEnabled > 0) { // Calculate linear fog float fogFactor = (fogEnd - vertexDistance) / (fogEnd - fogStart); diff --git a/data/base/shaders/vk/tcmask_instanced.glsl b/data/base/shaders/vk/tcmask_instanced.glsl index aa40eb921e5..4973d87b040 100644 --- a/data/base/shaders/vk/tcmask_instanced.glsl +++ b/data/base/shaders/vk/tcmask_instanced.glsl @@ -31,6 +31,7 @@ layout(std140, set = 0, binding = 0) uniform globaluniforms vec4 PointLightsColorAndEnergy[WZ_MAX_POINT_LIGHTS]; ivec4 bucketOffsetAndSize[WZ_BUCKET_DIMENSION * WZ_BUCKET_DIMENSION]; ivec4 PointLightsIndex[WZ_MAX_INDEXED_POINT_LIGHTS]; + vec4 cameraPos; // in modelSpace }; layout(std140, set = 1, binding = 0) uniform meshuniforms diff --git a/data/base/shaders/vk/terrain_combined_high.frag b/data/base/shaders/vk/terrain_combined_high.frag index 1649d4148b5..218eaf1da61 100644 --- a/data/base/shaders/vk/terrain_combined_high.frag +++ b/data/base/shaders/vk/terrain_combined_high.frag @@ -7,6 +7,7 @@ layout (constant_id = 1) const uint WZ_SHADOW_MODE = 1; layout (constant_id = 2) const uint WZ_SHADOW_FILTER_SIZE = 5; layout (constant_id = 3) const uint WZ_SHADOW_CASCADES_COUNT = 3; layout (constant_id = 4) const uint WZ_POINT_LIGHT_ENABLED = 0; +layout (constant_id = 5) const uint WZ_VOLUMETRIC_LIGHTING_ENABLED = 0; layout(set = 1, binding = 0) uniform sampler2D lightmap_tex; @@ -64,6 +65,10 @@ vec3 blendAddEffectLighting(vec3 a, vec3 b) { } vec4 doBumpMapping(BumpData b, vec3 lightDir, vec3 halfVec) { + MaterialInfo materialInfo; + materialInfo.albedo = b.color; + materialInfo.gloss = b.gloss; + vec3 L = normalize(lightDir); float lambertTerm = max(dot(b.N, L), 0.0); // diffuse lighting // Gaussian specular term computation @@ -90,7 +95,7 @@ vec4 doBumpMapping(BumpData b, vec3 lightDir, vec3 halfVec) { { // 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); + res += iterateOverAllPointLights(clipSpaceCoord, frag.fragPos, b.N, normalize(halfVec - lightDir), materialInfo, ModelTangentMatrix); } return vec4(res.rgb, b.color.a); @@ -125,7 +130,13 @@ void main() { vec4 fragColor = main_bumpMapping(); - if (fogEnabled > 0) + if (WZ_VOLUMETRIC_LIGHTING_ENABLED != 1) { + vec4 lightmap_vec4 = texture(lightmap_tex, frag.uvLightmap, 0.f); + vec2 clipSpaceCoord = gl_FragCoord.xy / vec2(viewportWidth, viewportHeight); + vec4 volumetric = volumetricLights(clipSpaceCoord, cameraPos.xyz, frag.fragPos, diffuseLight.xyz); + fragColor.xyz = toneMap(fragColor.xyz * volumetric.a + volumetric.xyz) * lightmap_vec4.a; + } + else if (fogEnabled > 0) { // Calculate linear fog float fogFactor = (fogEnd - frag.vertexDistance) / (fogEnd - fogStart); diff --git a/lib/ivis_opengl/gfx_api.h b/lib/ivis_opengl/gfx_api.h index 9bc49b356a5..a6a6149f18f 100644 --- a/lib/ivis_opengl/gfx_api.h +++ b/lib/ivis_opengl/gfx_api.h @@ -43,6 +43,15 @@ using nonstd::optional; using nonstd::nullopt; + +enum class VOLUMETRIC_LIGHT_LEVEL +{ + disabled, + low, + medium, + high, +}; + namespace gfx_api { // Must be implemented by backend (ex. SDL) @@ -326,13 +335,15 @@ namespace gfx_api uint32_t shadowFilterSize = 5; uint32_t shadowCascadesCount = WZ_MAX_SHADOW_CASCADES; bool isPointLightPerPixelEnabled = false; + VOLUMETRIC_LIGHT_LEVEL isVolumetricLightingEnabled = VOLUMETRIC_LIGHT_LEVEL::disabled; bool operator==(const lighting_constants& rhs) const { return shadowMode == rhs.shadowMode && shadowFilterSize == rhs.shadowFilterSize && shadowCascadesCount == rhs.shadowCascadesCount - && isPointLightPerPixelEnabled == rhs.isPointLightPerPixelEnabled; + && isPointLightPerPixelEnabled == rhs.isPointLightPerPixelEnabled + && isVolumetricLightingEnabled == rhs.isVolumetricLightingEnabled; } }; @@ -764,6 +775,7 @@ namespace gfx_api std::array PointLightsColorAndEnergy; std::array bucketOffsetAndSize; std::array indexed_lights; + glm::vec4 cameraPos; }; // Only change per mesh diff --git a/lib/ivis_opengl/gfx_api_gl.cpp b/lib/ivis_opengl/gfx_api_gl.cpp index 1d88ea09e84..b1dde35e852 100644 --- a/lib/ivis_opengl/gfx_api_gl.cpp +++ b/lib/ivis_opengl/gfx_api_gl.cpp @@ -819,7 +819,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", "PointLightsPosition", "PointLightsColorAndEnergy", "bucketOffsetAndSize", "PointLightsIndex", "viewportWidth", "viewportHeight", + "ProjectionMatrix", "ViewMatrix", "ModelUVLightmapMatrix", "ShadowMapMVPMatrix", "lightPosition", "sceneColor", "ambient", "diffuse", "specular", "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnd", "fogStart", "graphicsCycle", "fogEnabled", "PointLightsPosition", "PointLightsColorAndEnergy", "bucketOffsetAndSize", "PointLightsIndex", "viewportWidth", "viewportHeight", "cameraPos", // per-mesh uniforms "tcmask", "normalmap", "specularmap", "hasTangents" }, @@ -844,7 +844,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", "PointLightsPosition", "PointLightsColorAndEnergy", "bucketOffsetAndSize", "PointLightsIndex", "viewportWidth", "viewportHeight", + "ProjectionMatrix", "ViewMatrix", "ModelUVLightmapMatrix", "ShadowMapMVPMatrix", "lightPosition", "sceneColor", "ambient", "diffuse", "specular", "fogColor", "ShadowMapCascadeSplits", "ShadowMapSize", "fogEnd", "fogStart", "graphicsCycle", "fogEnabled", "PointLightsPosition", "PointLightsColorAndEnergy", "bucketOffsetAndSize", "PointLightsIndex", "viewportWidth", "viewportHeight", "cameraPos", // per-mesh uniforms "tcmask", "normalmap", "specularmap", "hasTangents", }, @@ -1657,6 +1657,7 @@ static bool patchFragmentShaderPointLightsDefines(std::string& fragmentShaderStr 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)), + std::make_pair("WZ_VOLUMETRIC_LIGHTING_ENABLED", static_cast(lightingConstants.isVolumetricLightingEnabled)), }; const auto& replacer = [&fragmentShaderStr](const std::string& define, const auto& value) -> bool { @@ -2100,14 +2101,15 @@ void gl_pipeline_state_object::set_constants(const gfx_api::Draw3DShapeInstanced setUniforms(19, cbuf.indexed_lights); setUniforms(20, cbuf.viewportWidth); setUniforms(21, cbuf.viewportheight); + setUniforms(22, cbuf.cameraPos); } void gl_pipeline_state_object::set_constants(const gfx_api::Draw3DShapeInstancedPerMeshUniforms& cbuf) { - setUniforms(22, cbuf.tcmask); - setUniforms(23, cbuf.normalMap); - setUniforms(24, cbuf.specularMap); - setUniforms(25, cbuf.hasTangents); + setUniforms(23, cbuf.tcmask); + setUniforms(24, cbuf.normalMap); + setUniforms(25, cbuf.specularMap); + setUniforms(26, cbuf.hasTangents); } void gl_pipeline_state_object::set_constants(const gfx_api::Draw3DShapeInstancedDepthOnlyGlobalUniforms& cbuf) diff --git a/lib/ivis_opengl/gfx_api_vk.cpp b/lib/ivis_opengl/gfx_api_vk.cpp index bdcaadb16b9..8db87691aaa 100644 --- a/lib/ivis_opengl/gfx_api_vk.cpp +++ b/lib/ivis_opengl/gfx_api_vk.cpp @@ -1147,12 +1147,13 @@ struct shader_infos bool specializationConstant_2_shadowFilterSize = false; bool specializationConstant_3_shadowCascadesCount = false; bool specializationConstant_4_pointLightEnabled = false; + bool specializationConstant_5_volumetricEnabled = false; }; static const std::map spv_files { std::make_pair(SHADER_COMPONENT, shader_infos{ "shaders/vk/tcmask.vert.spv", "shaders/vk/tcmask.frag.spv", true }), - std::make_pair(SHADER_COMPONENT_INSTANCED, shader_infos{ "shaders/vk/tcmask_instanced.vert.spv", "shaders/vk/tcmask_instanced.frag.spv", true, true, true, true, true }), + std::make_pair(SHADER_COMPONENT_INSTANCED, shader_infos{ "shaders/vk/tcmask_instanced.vert.spv", "shaders/vk/tcmask_instanced.frag.spv", true, true, true, true, true, true }), std::make_pair(SHADER_COMPONENT_DEPTH_INSTANCED, shader_infos{ "shaders/vk/tcmask_depth_instanced.vert.spv", "shaders/vk/tcmask_depth_instanced.frag.spv" }), std::make_pair(SHADER_NOLIGHT, shader_infos{ "shaders/vk/nolight.vert.spv", "shaders/vk/nolight.frag.spv", true }), std::make_pair(SHADER_NOLIGHT_INSTANCED, shader_infos{ "shaders/vk/nolight_instanced.vert.spv", "shaders/vk/nolight_instanced.frag.spv", true }), @@ -1162,7 +1163,7 @@ static const std::map spv_files std::make_pair(SHADER_DECALS, shader_infos{ "shaders/vk/decals.vert.spv", "shaders/vk/decals.frag.spv", true }), std::make_pair(SHADER_TERRAIN_COMBINED_CLASSIC, shader_infos{ "shaders/vk/terrain_combined.vert.spv", "shaders/vk/terrain_combined_classic.frag.spv", true, true, true, true }), std::make_pair(SHADER_TERRAIN_COMBINED_MEDIUM, shader_infos{ "shaders/vk/terrain_combined.vert.spv", "shaders/vk/terrain_combined_medium.frag.spv", true, true, true, true }), - std::make_pair(SHADER_TERRAIN_COMBINED_HIGH, shader_infos{ "shaders/vk/terrain_combined.vert.spv", "shaders/vk/terrain_combined_high.frag.spv", true, true, true, true, true }), + std::make_pair(SHADER_TERRAIN_COMBINED_HIGH, shader_infos{ "shaders/vk/terrain_combined.vert.spv", "shaders/vk/terrain_combined_high.frag.spv", true, true, true, true, true, true }), std::make_pair(SHADER_WATER, shader_infos{ "shaders/vk/terrain_water.vert.spv", "shaders/vk/water.frag.spv", true }), std::make_pair(SHADER_WATER_HIGH, shader_infos{ "shaders/vk/terrain_water_high.vert.spv", "shaders/vk/terrain_water_high.frag.spv", true }), std::make_pair(SHADER_WATER_CLASSIC, shader_infos{ "shaders/vk/terrain_water_classic.vert.spv", "shaders/vk/terrain_water_classic.frag.spv", true }), @@ -1773,6 +1774,11 @@ VkPSO::VkPSO(vk::Device _dev, appendSpecializationConstant_uint32(4, static_cast(root->shadowConstants.isPointLightPerPixelEnabled)); hasSpecializationConstant_PointLightConstants = true; } + if (shaderInfo.specializationConstant_5_volumetricEnabled) + { + appendSpecializationConstant_uint32(5, static_cast(root->shadowConstants.isVolumetricLightingEnabled)); + hasSpecializationConstant_VolumetricLightingConstants = true; + } if (!specializationEntries.empty()) { ASSERT(pipelineStages[1].pSpecializationInfo == nullptr, "get_stages unexpectedly set pSpecializationInfo - this will overwrite!"); @@ -6174,7 +6180,7 @@ bool VkRoot::setShadowConstants(gfx_api::lighting_constants newValues) auto& renderPass = renderPasses[renderPassId]; ASSERT(pipeline->renderpass_compat, "Pipeline has no associated renderpass compat structure"); - if (pipeline->hasSpecializationConstant_ShadowConstants || pipeline->hasSpecializationConstant_PointLightConstants) + if (pipeline->hasSpecializationConstant_ShadowConstants || pipeline->hasSpecializationConstant_PointLightConstants || pipeline->hasSpecializationConstant_VolumetricLightingConstants) { buffering_mechanism::get_current_resources().pso_to_delete.emplace_back(pipeline); pipelineInfo.renderPassPSO[renderPassId] = new VkPSO(dev, physDeviceProps.limits, pipelineInfo.createInfo, renderPass.rp, renderPass.rp_compat_info, renderPass.msaaSamples, vkDynLoader, *this); diff --git a/lib/ivis_opengl/gfx_api_vk.h b/lib/ivis_opengl/gfx_api_vk.h index 323990caae0..d174cb91c49 100644 --- a/lib/ivis_opengl/gfx_api_vk.h +++ b/lib/ivis_opengl/gfx_api_vk.h @@ -375,6 +375,7 @@ struct VkPSO final std::shared_ptr renderpass_compat; bool hasSpecializationConstant_ShadowConstants = false; bool hasSpecializationConstant_PointLightConstants = false; + bool hasSpecializationConstant_VolumetricLightingConstants = false; private: // Read shader into text buffer diff --git a/lib/ivis_opengl/piedraw.cpp b/lib/ivis_opengl/piedraw.cpp index 69813ae572f..d1e2401d28c 100644 --- a/lib/ivis_opengl/piedraw.cpp +++ b/lib/ivis_opengl/piedraw.cpp @@ -1414,29 +1414,35 @@ bool InstancedMeshRenderer::DrawAll(uint64_t currentGameFrame, const glm::mat4& glm::vec4 specular(lighting0[LIGHT_SPECULAR][0], lighting0[LIGHT_SPECULAR][1], lighting0[LIGHT_SPECULAR][2], lighting0[LIGHT_SPECULAR][3]); const auto &renderState = getCurrentRenderState(); - const glm::vec4 fogColor = renderState.fogEnabled ? glm::vec4( - renderState.fogColour.vector[0] / 255.f, - renderState.fogColour.vector[1] / 255.f, - renderState.fogColour.vector[2] / 255.f, - renderState.fogColour.vector[3] / 255.f - ) : glm::vec4(0.f); - if (useInstancedRendering) { - + const glm::vec3 cameraPos = (glm::inverse(viewMatrix) * glm::vec4(0, 0, 0, 1)).xyz(); + const glm::vec4 fogColor = glm::vec4( + renderState.fogColour.vector[0] / 255.f, + renderState.fogColour.vector[1] / 255.f, + renderState.fogColour.vector[2] / 255.f, + renderState.fogColour.vector[3] / 255.f + ); 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, static_cast(dimension.first), static_cast(dimension.second), 0.f, bucketLight.positions, bucketLight.colorAndEnergy, bucketLight.bucketOffsetAndSize, bucketLight.light_index + 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, + glm::vec4(cameraPos, 0.f) }; Draw3DShapes_Instanced(currentGameFrame, perFrameUniformsShaderOnce, globalUniforms, drawParts, depthPass); } else { + const glm::vec4 fogColor = renderState.fogEnabled ? glm::vec4( + renderState.fogColour.vector[0] / 255.f, + renderState.fogColour.vector[1] / 255.f, + renderState.fogColour.vector[2] / 255.f, + renderState.fogColour.vector[3] / 255.f + ) : glm::vec4(0.f); gfx_api::Draw3DShapeGlobalUniforms globalUniforms { projectionMatrix, viewMatrix, shadowCascades.shadowMVPMatrix[0], glm::vec4(currentSunPosition, 0.f), sceneColor, ambient, diffuse, specular, fogColor, diff --git a/lib/ivis_opengl/pielighting.cpp b/lib/ivis_opengl/pielighting.cpp index 92daa19e08f..bb7f2363a5f 100644 --- a/lib/ivis_opengl/pielighting.cpp +++ b/lib/ivis_opengl/pielighting.cpp @@ -151,7 +151,8 @@ void renderingNew::LightingManager::ComputeFrameData(const LightingData& data, L 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; + // z coordinate system require the -1 factor before entering shaders + 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; diff --git a/lib/ivis_opengl/piestate.cpp b/lib/ivis_opengl/piestate.cpp index a0b21c034e6..4f4ba747c42 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 pointLightEnabled) +bool pie_LoadShaders(uint32_t shadowFilterSize, bool pointLightEnabled, VOLUMETRIC_LIGHT_LEVEL volumetricEnabled) { // note: actual loading of shaders now occurs in gfx_api @@ -128,6 +128,7 @@ bool pie_LoadShaders(uint32_t shadowFilterSize, bool pointLightEnabled) auto shadowConstants = gfx_api::context::get().getShadowConstants(); shadowConstants.shadowFilterSize = shadowFilterSize; shadowConstants.isPointLightPerPixelEnabled = pointLightEnabled; + shadowConstants.isVolumetricLightingEnabled = volumetricEnabled; 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 8072477ec6d..0352cf325f7 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 pointLightEnabled); +bool pie_LoadShaders(uint32_t shadowFilterSize, bool pointLightEnabled, VOLUMETRIC_LIGHT_LEVEL volumetricEnabled); void pie_FreeShaders(); namespace pie_internal diff --git a/src/configuration.cpp b/src/configuration.cpp index cbd7e3d9dc0..e7a3174af62 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -618,6 +618,11 @@ bool loadConfig() war_setPointLightPerPixelLighting(value.value_or(false)); } + { + auto value = iniGetIntegerOpt("volumetricLighting"); + war_setVolumetricLighting(static_cast(value.value_or(0))); + } + ActivityManager::instance().endLoadingSettings(); return true; } @@ -779,6 +784,7 @@ bool saveConfig() iniSetInteger("shadowFilterSize", (int)war_getShadowFilterSize()); iniSetInteger("shadowMapResolution", (int)war_getShadowMapResolution()); iniSetBool("pointLightsPerpixel", war_getPointLightPerPixelLighting()); + iniSetInteger("volumetricLighting", static_cast(war_getVolumetricLighting())); iniSetInteger("configVersion", CURRCONFVERSION); // write out ini file changes diff --git a/src/frontend.cpp b/src/frontend.cpp index a0dda42d935..4100348f976 100644 --- a/src/frontend.cpp +++ b/src/frontend.cpp @@ -867,6 +867,20 @@ char const* graphicsOptionsLightingString() return war_getPointLightPerPixelLighting() ? _("Per Pixel") : _("Lightmap"); } +char const* graphicsOptionsVolumetricLightingString() +{ + switch (war_getVolumetricLighting()) + { + case VOLUMETRIC_LIGHT_LEVEL::low: return _("Low"); + case VOLUMETRIC_LIGHT_LEVEL::medium: return _("Medium"); + case VOLUMETRIC_LIGHT_LEVEL::high: return _("High"); + default: + break; + } + + return _("Disabled"); +} + char const *graphicsOptionsFogString() { return pie_GetFogEnabled() ? _("On") : _("Off"); @@ -1212,6 +1226,12 @@ void startGraphicsOptionsMenu() grid->place({ 1, 1, false }, row, addMargin(makeTextButton(FRONTEND_LIGHTS_R, graphicsOptionsLightingString(), WBUT_SECONDARY))); row.start++; + /////////// + // Volumetric lighting + grid->place({ 0 }, row, addMargin(makeTextButton(FRONTEND_VOLUMETRIC_LIGHTING, _("Volumetric Lighting"), WBUT_SECONDARY))); + grid->place({ 1, 1, false }, row, addMargin(makeTextButton(FRONTEND_VOLUMETRIC_LIGHTING_R, graphicsOptionsVolumetricLightingString(), 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"); @@ -1300,8 +1320,41 @@ bool runGraphicsOptionsMenu() case FRONTEND_LIGHTS: case FRONTEND_LIGHTS_R: { - war_setPointLightPerPixelLighting(!war_getPointLightPerPixelLighting()); - widgSetString(psWScreen, FRONTEND_LIGHTS_R, graphicsOptionsLightingString()); + bool newValue = !war_getPointLightPerPixelLighting(); + if (getTerrainShaderQuality() != TerrainShaderQuality::NORMAL_MAPPING) + { + newValue = false; // point light per pixel lighting is only supported in normal_mapping mode + } + auto shadowConstants = gfx_api::context::get().getShadowConstants(); + shadowConstants.isPointLightPerPixelEnabled = newValue; + if (gfx_api::context::get().setShadowConstants(shadowConstants)) + { + war_setPointLightPerPixelLighting(newValue); + widgSetString(psWScreen, FRONTEND_LIGHTS_R, graphicsOptionsLightingString()); + } + else + { + debug(LOG_ERROR, "Failed to set per pixel point lighting value: %d", (int)newValue); + } + break; + } + case FRONTEND_VOLUMETRIC_LIGHTING: + case FRONTEND_VOLUMETRIC_LIGHTING_R: + { + auto previousValue = static_cast(war_getVolumetricLighting()); + auto newValue = (previousValue + 1) % 4; + + auto shadowConstants = gfx_api::context::get().getShadowConstants(); + shadowConstants.isVolumetricLightingEnabled = static_cast(newValue); + if (gfx_api::context::get().setShadowConstants(shadowConstants)) + { + war_setVolumetricLighting(static_cast(newValue)); + widgSetString(psWScreen, FRONTEND_VOLUMETRIC_LIGHTING_R, graphicsOptionsVolumetricLightingString()); + } + else + { + debug(LOG_ERROR, "Failed to set volumetric lighting value: %d", (int)newValue); + } break; } case FRONTEND_FOG: diff --git a/src/frontend.h b/src/frontend.h index ee33ff5dc93..aefc177d2ba 100644 --- a/src/frontend.h +++ b/src/frontend.h @@ -293,6 +293,8 @@ enum FRONTEND_SHADOW_FILTER_SIZE_DROPDOWN, FRONTEND_LIGHTS, FRONTEND_LIGHTS_R, + FRONTEND_VOLUMETRIC_LIGHTING, + FRONTEND_VOLUMETRIC_LIGHTING_R, FRONTEND_FOG, FRONTEND_FOG_R, FRONTEND_RADAR, diff --git a/src/main.cpp b/src/main.cpp index 295eac1640e..6d8c25e91d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2142,7 +2142,7 @@ int realmain(int argc, char *argv[]) { return EXIT_FAILURE; } - if (!pie_LoadShaders(war_getShadowFilterSize(), war_getPointLightPerPixelLighting() && getTerrainShaderQuality() == TerrainShaderQuality::NORMAL_MAPPING)) + if (!pie_LoadShaders(war_getShadowFilterSize(), war_getPointLightPerPixelLighting() && getTerrainShaderQuality() == TerrainShaderQuality::NORMAL_MAPPING, war_getVolumetricLighting())) { return EXIT_FAILURE; } diff --git a/src/warzoneconfig.cpp b/src/warzoneconfig.cpp index 779cae71373..bf2262b171e 100644 --- a/src/warzoneconfig.cpp +++ b/src/warzoneconfig.cpp @@ -96,6 +96,7 @@ struct WARZONE_GLOBALS 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; + VOLUMETRIC_LIGHT_LEVEL volumetricLighting = VOLUMETRIC_LIGHT_LEVEL::disabled; // groups UI bool groupsMenuEnabled = true; @@ -662,11 +663,21 @@ bool war_getPointLightPerPixelLighting() return warGlobs.pointLightLighting; } +VOLUMETRIC_LIGHT_LEVEL war_getVolumetricLighting() +{ + return warGlobs.volumetricLighting; +} + void war_setPointLightPerPixelLighting(bool perPixelEnabled) { warGlobs.pointLightLighting = perPixelEnabled; } +void war_setVolumetricLighting(VOLUMETRIC_LIGHT_LEVEL volumetricEnabled) +{ + warGlobs.volumetricLighting = volumetricEnabled; +} + bool war_getGroupsMenuEnabled() { return warGlobs.groupsMenuEnabled; diff --git a/src/warzoneconfig.h b/src/warzoneconfig.h index 91b00d5cff7..f0df056f045 100644 --- a/src/warzoneconfig.h +++ b/src/warzoneconfig.h @@ -59,6 +59,8 @@ enum class JS_BACKEND num_backends // Must be last! }; +enum class VOLUMETRIC_LIGHT_LEVEL; + bool js_backend_from_str(const char *str, JS_BACKEND &output_backend); std::string to_string(JS_BACKEND backend); @@ -165,6 +167,9 @@ void war_setShadowMapResolution(uint32_t resolution); bool war_getPointLightPerPixelLighting(); void war_setPointLightPerPixelLighting(bool perPixelEnabled); +VOLUMETRIC_LIGHT_LEVEL war_getVolumetricLighting(); +void war_setVolumetricLighting(VOLUMETRIC_LIGHT_LEVEL enabled); + bool war_getGroupsMenuEnabled(); void war_setGroupsMenuEnabled(bool enabled);