diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index ebf42f971..936ce74d1 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -610,14 +610,15 @@ enable_auto_exposure (Enable Automatic Exposure) bool false # Requires: shaders, enable_post_processing, enable_auto_exposure exposure_compensation (Exposure compensation) float 0.0 -1.0 1.0 -# Set the gamma value. +# Set the post processing gamma value. # Higher values give lower contrast and vice versa. # Range: from 1.0 to 5.0 +# Default: 1.6 # # Requires: shaders, enable_post_processing, tone_mapping gamma (Gamma) float 1.6 1.0 5.0 -# Apply color grading to make brighter colors warmer and darker colors cooler. +# Apply ASL CDL color grading to make brighter colors warmer and darker colors cooler. # # Requires: shaders, enable_post_processing enable_color_grading (Color grading) bool false @@ -674,10 +675,28 @@ bloom_strength_factor (Bloom Strength Factor) float 1.0 0.1 10.0 # Requires: shaders, enable_post_processing, enable_bloom bloom_radius (Bloom Radius) float 1 0.1 8 +[**Volumetric Effects] + # Set to true to enable volumetric lighting effect (a.k.a. "Godrays"). # # Requires: shaders, enable_post_processing, enable_bloom -enable_volumetric_lighting (Volumetric lighting) bool false +enable_volumetric_lighting (Volumetric Lighting) bool false + +# Set to true to render clouds as scatter volumes. +# +# Requires: shaders, enable_post_processing, enable_clouds +enable_volumetric_clouds (Volumetric Clouds) bool false + +# Higher values reduce visual quality and may introduce artifacts, but improve performance of volumetric cloud/aurora effects. +# Range: from 1 to 4, default: 2 +# +# Requires: shaders, enable_post_processing +volumetrics_undersampling (Volumetric Undersampling) int 2 1 4 + +# Set to true to render volumetric auroras ("Northern Lights"). +# +# Requires: shaders, enable_post_processing +enable_volumetric_auroras (Volumetric Auroras) bool false [**Other Effects] diff --git a/client/shaders/clouds_merge/opengl_fragment.glsl b/client/shaders/clouds_merge/opengl_fragment.glsl index c88f0d95a..a3c597a66 100644 --- a/client/shaders/clouds_merge/opengl_fragment.glsl +++ b/client/shaders/clouds_merge/opengl_fragment.glsl @@ -1,24 +1,49 @@ #define cloudsTexture texture0 #define sceneTexture texture1 +#define depthmap texture2 uniform sampler2D cloudsTexture; uniform sampler2D sceneTexture; +uniform sampler2D depthmap; uniform vec2 texelSize0; uniform vec3 dayLight; +uniform vec3 cloudColor; varying vec2 screenspaceCoordinate; +varying vec3 relativePosition; +varying vec3 viewDirection; +varying vec3 sunTint; +varying float auroraFactor; + +uniform vec3 cameraOffset; +uniform vec3 cameraPosition; + +uniform float cameraNear; +uniform float cameraFar; + +uniform float cloudHeight; +uniform float cloudThickness; + +float getDepth(vec2 screenspacePosition) { + float depth = texture2D(depthmap, screenspacePosition * 0.5 + 0.5).r; + return cameraNear * cameraFar / (cameraFar + depth * (cameraNear - cameraFar)); +} vec4 sampleClouds(vec2 uv) { vec4 cloudsKey = texture2D(cloudsTexture, uv); - const vec3 darkColor = vec3(0.05, 0.1, 0.2); + //const vec3 darkColor = vec3(0.05, 0.1, 0.2); + vec3 darkColor = vec3(0.2, 0.4, 0.8) * dayLight; const vec3 auroraDark = vec3(0., 0.5, 0.5); const vec3 auroraBright = vec3(0., 0.5, .0); + //const vec3 auroraDark = vec3(0.); + //const vec3 auroraBright = vec3(0.); + return vec4( - mix(auroraDark, auroraBright, cloudsKey.b) * cloudsKey.b * max(0., 1. - cloudsKey.r) + - cloudsKey.r * (darkColor * max(0., 1. - cloudsKey.g) + dayLight * cloudsKey.g), + mix(auroraDark, auroraBright, cloudsKey.b) * cloudsKey.b * auroraFactor + + cloudsKey.r * cloudColor * (darkColor * max(0., 1. - cloudsKey.g) + dayLight * sunTint * cloudsKey.g), cloudsKey.r); } @@ -37,8 +62,22 @@ vec4 getClouds(vec2 uv) { void main(void) { - vec4 cloudsColor = getClouds(screenspaceCoordinate * 0.5 + 0.5); + vec3 viewVec = normalize(relativePosition); + + vec3 position = cameraOffset + cameraPosition; + + float depth = getDepth(screenspaceCoordinate) / normalize(viewDirection).z; + float bottomPlaneIntersect = max((cloudHeight - cameraPosition.y) / viewVec.y, 0.); + float topPlaneIntersect = max((cloudHeight + cloudThickness - cameraPosition.y) / viewVec.y, 0.); + float minPlane = min(bottomPlaneIntersect, topPlaneIntersect); + vec4 sceneColor = texture2D(sceneTexture, screenspaceCoordinate * 0.5 + 0.5); - gl_FragColor = vec4(sceneColor.rgb * (1. - cloudsColor.a) + cloudsColor.rgb, 1.); + if (depth > minPlane) { + vec4 finalColor = getClouds(screenspaceCoordinate * 0.5 + 0.5); + + gl_FragColor = vec4(sceneColor.rgb * (1. - finalColor.a) + finalColor.rgb, 1.); + } else { + gl_FragColor = sceneColor; + } } diff --git a/client/shaders/clouds_merge/opengl_vertex.glsl b/client/shaders/clouds_merge/opengl_vertex.glsl index 5487edeb6..dc0516dfb 100644 --- a/client/shaders/clouds_merge/opengl_vertex.glsl +++ b/client/shaders/clouds_merge/opengl_vertex.glsl @@ -1,7 +1,48 @@ +uniform mat4 mCameraProjInv; +uniform mat4 mCameraView; +uniform vec3 v_LightDirection; +uniform float f_timeofday; + +varying vec3 relativePosition; +varying vec3 viewDirection; varying vec2 screenspaceCoordinate; +varying vec3 sunTint; +varying float auroraFactor; + +vec3 getDirectLightScatteringAtGround(vec3 v_LightDirection) +{ + // Based on talk at 2002 Game Developers Conference by Naty Hoffman and Arcot J. Preetham + const float beta_r0 = 1e-5; // Rayleigh scattering beta + + // These factors are calculated based on expected value of scattering factor of 1e-5 + // for Nitrogen at 532nm (green), 2e25 molecules/m3 in atmosphere + const vec3 beta_r0_l = vec3(3.3362176e-01, 8.75378289198826e-01, 1.95342379700656) * beta_r0; // wavelength-dependent scattering + + const float atmosphere_height = 15000.; // height of the atmosphere in meters + // sun/moon light at the ground level, after going through the atmosphere + return exp(-beta_r0_l * atmosphere_height / (1e-5 - dot(v_LightDirection, vec3(0., 1., 0.)))); +} + +// custom smoothstep implementation because it's not defined in glsl1.2 +// https://docs.gl/sl4/smoothstep +float mtsmoothstep(in float edge0, in float edge1, in float x) +{ + float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); + return t * t * (3.0 - 2.0 * t); +} void main(void) { + vec4 p = mCameraProjInv * inVertexPosition; + viewDirection = p.xyz / p.w; + relativePosition = (p.xyz / p.w) * mat3(mCameraView); screenspaceCoordinate = inVertexPosition.xy; + + auroraFactor = 1. - mtsmoothstep(0.13, 0.15, f_timeofday) * mtsmoothstep(0.87, 0.85, f_timeofday); + + float tintFactor = mtsmoothstep(0.21, 0.24, f_timeofday) * mtsmoothstep(0.793, 0.753, f_timeofday); + + sunTint = mix(vec3(1.0), getDirectLightScatteringAtGround(v_LightDirection), tintFactor); + gl_Position = inVertexPosition; } diff --git a/client/shaders/coarse_noise_shader/opengl_fragment.glsl b/client/shaders/coarse_noise_shader/opengl_fragment.glsl deleted file mode 100644 index c0cfecc9a..000000000 --- a/client/shaders/coarse_noise_shader/opengl_fragment.glsl +++ /dev/null @@ -1,17 +0,0 @@ -uniform float cloudDensity; - -// Pseudorandom number generator -float rand(vec2 n) { - return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); -} - -// More random pseudorandom number generator; -float noise(vec2 p){ - vec2 p2 = p + vec2(rand(p), rand(p.yx)); - return rand(p2); -} - -void main(void) -{ - gl_FragColor = vec4(vec3(step(noise(floor(gl_FragCoord.xy * 0.25)), cloudDensity)), 1.); -} diff --git a/client/shaders/coarse_noise_shader/opengl_vertex.glsl b/client/shaders/coarse_noise_shader/opengl_vertex.glsl deleted file mode 100644 index 9182a208a..000000000 --- a/client/shaders/coarse_noise_shader/opengl_vertex.glsl +++ /dev/null @@ -1,4 +0,0 @@ -void main(void) -{ - gl_Position = inVertexPosition; -} diff --git a/client/shaders/noise_shader/opengl_fragment.glsl b/client/shaders/noise_shader/opengl_fragment.glsl deleted file mode 100644 index 3a8081b8f..000000000 --- a/client/shaders/noise_shader/opengl_fragment.glsl +++ /dev/null @@ -1,15 +0,0 @@ -// Pseudorandom number generator -float rand(vec2 n) { - return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); -} - -// More random pseudorandom number generator; -float noise(vec2 p){ - vec2 p2 = p + vec2(rand(p), rand(p.yx)); - return rand(p2); -} - -void main(void) -{ - gl_FragColor = vec4(vec3(noise(gl_FragCoord.xy)), 1.); -} diff --git a/client/shaders/noise_shader/opengl_vertex.glsl b/client/shaders/noise_shader/opengl_vertex.glsl deleted file mode 100644 index 9182a208a..000000000 --- a/client/shaders/noise_shader/opengl_vertex.glsl +++ /dev/null @@ -1,4 +0,0 @@ -void main(void) -{ - gl_Position = inVertexPosition; -} diff --git a/client/shaders/volumetric_clouds/opengl_fragment.glsl b/client/shaders/volumetric_clouds/opengl_fragment.glsl index af24765be..397a7fbc3 100644 --- a/client/shaders/volumetric_clouds/opengl_fragment.glsl +++ b/client/shaders/volumetric_clouds/opengl_fragment.glsl @@ -2,10 +2,13 @@ #define noiseTexture texture1 #define noiseTextureCoarse texture2 +#define PROBING_ITERATIONS 30 #define ITERATIONS 50 -#define LIGHT_ITERATIONS 10 -#define LIGHT_DISTANCE 100. -#define AURORA_ITERATIONS 100 +#define LIGHT_ITERATIONS 3 +#define AURORA_ITERATIONS 80 + +// See clouds.cpp +#define CLOUD_SIZE 640.0 uniform sampler2D depthmap; uniform sampler2D noiseTexture; @@ -19,17 +22,17 @@ uniform float cloudDensity; varying vec3 relativePosition; varying vec3 viewDirection; -uniform vec3 eyePosition; uniform vec3 cameraOffset; uniform vec3 cameraPosition; -uniform mat4 mCameraView; -uniform mat4 mCameraProjInv; - uniform float cameraNear; uniform float cameraFar; +uniform vec2 cloudOffset; +uniform float cloudRadius; + varying vec2 screenspaceCoordinate; +varying float sunStrength; uniform float fogDistance; uniform float fogShadingParameter; @@ -41,7 +44,8 @@ uniform float animationTimer; // Derived From http://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf // and https://www.shadertoy.com/view/MslGR8 (5th one starting from the bottom) // NOTE: `frag_coord` is in pixels (i.e. not normalized UV). -float screenSpaceDither(highp vec2 frag_coord) { +float screenSpaceDither(highp vec2 frag_coord) +{ // Iestyn's RGB dither (7 asm instructions) from Portal 2 X360, slightly modified for VR. highp float dither = dot(vec2(171.0, 231.0), frag_coord); dither = fract(dither / 103.0); @@ -57,38 +61,47 @@ float mtsmoothstep(in float edge0, in float edge1, in float x) return t * t * (3.0 - 2.0 * t); } -float getDepth(vec2 screenspacePosition) { - float depth = texture2D(depthmap, screenspacePosition * 0.5 + 0.5).r; +float toLinearDepth(float depth) +{ return cameraNear * cameraFar / (cameraFar + depth * (cameraNear - cameraFar)); } -float getRawDepth(vec2 screenspacePosition) { +float getDepth(vec2 screenspacePosition) +{ return texture2D(depthmap, screenspacePosition * 0.5 + 0.5).r; } -float noise(vec3 p){ - //p.y *= 1.; +float noise(vec3 p) +{ float y = floor(p.y); float f1 = texture2D(noiseTexture, p.xz / 256. + y * 0.2).r; float f2 = texture2D(noiseTexture, p.xz / 256. + y * 0.2 + 0.2).r; return mix(f1, f2, fract(p.y)); } -float fnoise(vec3 p) { +float fnoise(vec3 p) +{ return noise(p * 4.) * 0.5 + noise(p * 8.) * 0.25; } -float fnoise3(vec3 p) { +float fnoise3(vec3 p) +{ return noise(p * 4.) * 0.5 + noise(p * 8.) * 0.25 + noise(p * 16.) * 0.125; } -float getAuroraDensity(vec3 position) { +float getAuroraDensity(vec3 position) +{ float density = pow(max(0., 1. - 10. * abs(fnoise3(vec3(position.x * 0.25, animationTimer, position.z * 0.25)) - 0.5)), 4.); - return 1.0 * density * mtsmoothstep(0.0, 0.05, position.y - 1.) * pow(1. - mtsmoothstep(0.05, 2.0, position.y - 1.), 4.); + return 0.7 * density * mtsmoothstep(0.0, 0.05, position.y - 1.) * pow(1. - mtsmoothstep(0.05, 2.0, position.y - 1.), 4.); } -float getDensity(vec3 position) { - float density = texture2D(noiseTextureCoarse, position.xz / 2560. / 16.).r * +float getCoarse(vec3 position) { + return texture2D(noiseTextureCoarse, (position.xz - cloudOffset) * 0.5 / CLOUD_SIZE / cloudRadius).r; +} + +float getDensity(vec3 position) +{ + float density = texture2D(noiseTextureCoarse, (position.xz - cloudOffset) * 0.5 / CLOUD_SIZE / cloudRadius).r * mtsmoothstep(0.0, cloudThickness * 0.2, position.y - cloudHeight) * (1.0 - mtsmoothstep(cloudThickness * 0.5, cloudThickness, position.y - cloudHeight)); @@ -97,17 +110,19 @@ float getDensity(vec3 position) { return 0.04 * density; } -float getBrightness(vec3 position, float bias) { +float getBrightness(vec3 position, float lightDistance) +{ float density = 0.; - for (int i = 0; i < LIGHT_ITERATIONS; i++) { - vec3 rayPosition = position - v_LightDirection * LIGHT_DISTANCE * (float(i) + bias) / float(LIGHT_ITERATIONS); + for (int i = 1; i <= LIGHT_ITERATIONS; i++) { + vec3 rayPosition = position - v_LightDirection * lightDistance * float(i) / float(LIGHT_ITERATIONS); - density += getDensity(rayPosition) * float(LIGHT_DISTANCE) / float(LIGHT_ITERATIONS); + density += getDensity(rayPosition) * float(lightDistance) / float(LIGHT_ITERATIONS); } return exp(-density); } -float blend(float A, float B, float alphaA, float alphaB) { +float blend(float A, float B, float alphaA, float alphaB) +{ float alphaC = alphaA + (1. - alphaA) * alphaB; return (alphaA * A + (1. - alphaA) * alphaB * B) / alphaC; } @@ -116,11 +131,16 @@ void main(void) { vec3 viewVec = normalize(relativePosition); - vec3 position = cameraOffset + eyePosition; + vec3 position = cameraOffset + cameraPosition; - float depth = getDepth(screenspaceCoordinate) / normalize(viewDirection).z; - float bottomPlaneIntersect = clamp(min((cloudHeight - eyePosition.y) / viewVec.y, depth), 0., 4. * fogDistance); - float topPlaneIntersect = clamp(min((cloudHeight + cloudThickness - eyePosition.y) / viewVec.y, depth), 0., 4. * fogDistance); + float depth = toLinearDepth(getDepth(screenspaceCoordinate)) / normalize(viewDirection).z; + float bottomPlaneIntersect = clamp((cloudHeight - cameraPosition.y) / viewVec.y, 0., 4. * fogDistance); + float topPlaneIntersect = clamp((cloudHeight + cloudThickness - cameraPosition.y) / viewVec.y, 0., 4. * fogDistance); + + if ((bottomPlaneIntersect > depth + 5.0) != (topPlaneIntersect > depth + 5.0)) { + bottomPlaneIntersect = min(depth, bottomPlaneIntersect); + topPlaneIntersect = min(depth, topPlaneIntersect); + } float startDepth = min(bottomPlaneIntersect, topPlaneIntersect); float endDepth = max(bottomPlaneIntersect, topPlaneIntersect); @@ -129,17 +149,15 @@ void main(void) vec3 color = vec3(0.); - float dx = (endDepth - startDepth) / float(ITERATIONS); - float density = 0.; float auroraStartDepth = min(max(0., 1.0 / viewVec.y), 8.); float auroraEndDepth = min(max(0., 3.0 / viewVec.y), 8.); - float rawDepth = getRawDepth(screenspaceCoordinate); + float rawDepth = getDepth(screenspaceCoordinate); if (auroraEndDepth - auroraStartDepth > 0.1 && rawDepth >= 1.0) { - for (int i = 0; i < ITERATIONS; i++) { - vec3 rayPosition = viewVec * (auroraStartDepth + (auroraEndDepth - auroraStartDepth) * (float(i) + bias) / float(ITERATIONS)); + for (int i = 0; i < AURORA_ITERATIONS; i++) { + vec3 rayPosition = viewVec * (auroraStartDepth + (auroraEndDepth - auroraStartDepth) * (float(i) + bias) / float(AURORA_ITERATIONS)); float localDensity = getAuroraDensity(rayPosition); @@ -150,33 +168,42 @@ void main(void) } color.b = density * (auroraEndDepth - auroraStartDepth) / float(AURORA_ITERATIONS); - + float sunlightContribution = 0.; float alpha = 0.; float outScatter = 2. * (dot(v_LightDirection, viewVec) * 0.5 + 0.5); + float forwardScatter = 1. + 2. * pow(min(dot(v_LightDirection, viewVec), 0.), 4.); density = 0.; - for (int i = 0; i < ITERATIONS; i++) { - vec3 rayPosition = eyePosition + viewVec * (startDepth + (endDepth - startDepth) * (float(i) + bias) / float(ITERATIONS)); + float fogDepth = min(4. * fogDistance, startDepth + 2000.); + endDepth = min(endDepth, fogDepth); - float localDensity = getDensity(rayPosition) * dx; + float dx = (endDepth - startDepth) / float(ITERATIONS); + float lightDistance = cloudThickness * 0.5; - if (localDensity < 0.0001) continue; + if (endDepth - startDepth > 0.1) { + for (int i = 0; i < ITERATIONS; i++) { + vec3 rayPosition = cameraPosition + viewVec * (startDepth + (endDepth - startDepth) * (float(i) + bias) / float(ITERATIONS)); - float clarity = clamp(fogShadingParameter - fogShadingParameter * length(rayPosition - eyePosition) / (4. * fogDistance), 0.0, 1.0); - float brightness = getBrightness(rayPosition, bias) * exp(-outScatter * localDensity); - sunlightContribution = blend(sunlightContribution, brightness, 1. - exp(-density), 1. - exp(-localDensity)); - alpha = blend(alpha, clarity, 1. - exp(-density), 1. - exp(-localDensity)); + float localDensity = getDensity(rayPosition) * dx; - density += localDensity; + if (localDensity < 0.0001) continue; - if (density > 10.0) break; + float clarity = clamp(fogShadingParameter - fogShadingParameter * length(rayPosition - cameraPosition) / (fogDepth), 0.0, 1.0); + float outScatterContribution = exp(-0.5 * outScatter * localDensity); + float brightness = getBrightness(rayPosition, lightDistance) * forwardScatter * outScatterContribution * sunStrength + (1. - outScatterContribution); + sunlightContribution = blend(sunlightContribution, brightness, 1. - exp(-density), 1. - exp(-localDensity)); + alpha = blend(alpha, clarity, 1. - exp(-density), 1. - exp(-localDensity)); + + density += localDensity; + + if (density > 10.0) break; + } } - float forwardScatter = 1. + 4. * pow(min(dot(v_LightDirection, viewVec), 0.), 4.); - color.r = (1. - exp(-density)) * alpha; - color.g = sunlightContribution * forwardScatter; + color.g = sunlightContribution; + color.b *= exp(-density); gl_FragColor = vec4(color, 1.0); } diff --git a/client/shaders/volumetric_clouds/opengl_vertex.glsl b/client/shaders/volumetric_clouds/opengl_vertex.glsl index 33ee9edf6..3b1d247a9 100644 --- a/client/shaders/volumetric_clouds/opengl_vertex.glsl +++ b/client/shaders/volumetric_clouds/opengl_vertex.glsl @@ -1,11 +1,18 @@ uniform mat4 mCameraProjInv; uniform mat4 mCameraView; -uniform vec3 eyePosition; +uniform float f_timeofday; varying vec3 relativePosition; varying vec3 viewDirection; varying vec2 screenspaceCoordinate; +varying float sunStrength; + +float mtsmoothstep(in float edge0, in float edge1, in float x) +{ + float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); + return t * t * (3.0 - 2.0 * t); +} void main(void) { @@ -13,5 +20,18 @@ void main(void) vec4 p = mCameraProjInv * inVertexPosition; viewDirection = p.xyz / p.w; relativePosition = (p.xyz / p.w) * mat3(mCameraView); + + if (f_timeofday < 0.21) { + sunStrength = + (1.0 - mtsmoothstep(0.18, 0.21, f_timeofday)); + } else if (f_timeofday >= 0.793) { + sunStrength = + mtsmoothstep(0.793, 0.823, f_timeofday); + } else { + sunStrength = + mtsmoothstep(0.21, 0.26, f_timeofday) * + (1.0 - mtsmoothstep(0.743, 0.793, f_timeofday)); + } + gl_Position = inVertexPosition; } diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 81b4086c4..5d9f6c1da 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -45,7 +45,7 @@ Clouds::Clouds(scene::ISceneManager* mgr, IShaderSource *ssrc, s32 id, u32 seed ): - scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), + scene::ISceneNode(g_settings->getBool("enable_volumetric_clouds") ? nullptr : mgr->getRootSceneNode(), mgr, id), m_seed(seed) { m_enable_shaders = g_settings->getBool("enable_shaders"); @@ -106,6 +106,14 @@ void Clouds::updateMesh() std::floor(center_of_drawing_in_noise_f.Y / cloud_size) ); + // The world position of the integer center point of drawing in the noise + v2f world_center_of_drawing_in_noise_f = v2f( + center_of_drawing_in_noise_i.X * cloud_size, + center_of_drawing_in_noise_i.Y * cloud_size + ) + m_origin; + + m_noise_position = world_center_of_drawing_in_noise_f - ((float)m_cloud_radius_i + 0.5f) * cloud_size; + // Only update mesh if it has moved enough, this saves lots of GPU buffer uploads. constexpr float max_d = 5 * BS; @@ -128,12 +136,6 @@ void Clouds::updateMesh() const u32 num_faces_to_draw = m_enable_3d ? 6 : 1; - // The world position of the integer center point of drawing in the noise - v2f world_center_of_drawing_in_noise_f = v2f( - center_of_drawing_in_noise_i.X * cloud_size, - center_of_drawing_in_noise_i.Y * cloud_size - ) + m_origin; - // Colors with primitive shading video::SColorf c_top_f(m_color); @@ -160,7 +162,7 @@ void Clouds::updateMesh() // Read noise - std::vector grid(m_cloud_radius_i * 2 * m_cloud_radius_i * 2); + m_grid.resize(m_cloud_radius_i * 2 * m_cloud_radius_i * 2); for(s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) { u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + m_cloud_radius_i; @@ -168,7 +170,7 @@ void Clouds::updateMesh() for (s16 xi = -m_cloud_radius_i; xi < m_cloud_radius_i; xi++) { u32 i = si + xi; - grid[i] = gridFilled( + m_grid[i] = gridFilled( xi + center_of_drawing_in_noise_i.X, zi + center_of_drawing_in_noise_i.Y ); @@ -205,7 +207,7 @@ void Clouds::updateMesh() u32 i = GETINDEX(xi, zi, m_cloud_radius_i); - if (!grid[i]) + if (!m_grid[i]) continue; v2f p0 = v2f(xi,zi)*cloud_size + world_center_of_drawing_in_noise_f; @@ -238,7 +240,7 @@ void Clouds::updateMesh() case 1: // back if (INAREA(xi, zi - 1, m_cloud_radius_i)) { u32 j = GETINDEX(xi, zi - 1, m_cloud_radius_i); - if(grid[j]) + if(m_grid[j]) continue; } for (video::S3DVertex &vertex : v) { @@ -253,7 +255,7 @@ void Clouds::updateMesh() case 2: //right if (INAREA(xi + 1, zi, m_cloud_radius_i)) { u32 j = GETINDEX(xi+1, zi, m_cloud_radius_i); - if(grid[j]) + if(m_grid[j]) continue; } for (video::S3DVertex &vertex : v) { @@ -268,7 +270,7 @@ void Clouds::updateMesh() case 3: // front if (INAREA(xi, zi + 1, m_cloud_radius_i)) { u32 j = GETINDEX(xi, zi + 1, m_cloud_radius_i); - if(grid[j]) + if(m_grid[j]) continue; } for (video::S3DVertex &vertex : v) { @@ -283,7 +285,7 @@ void Clouds::updateMesh() case 4: // left if (INAREA(xi-1, zi, m_cloud_radius_i)) { u32 j = GETINDEX(xi-1, zi, m_cloud_radius_i); - if(grid[j]) + if(m_grid[j]) continue; } for (video::S3DVertex &vertex : v) { @@ -343,7 +345,6 @@ void Clouds::updateMesh() void Clouds::render() { -#if 0 if (m_params.density <= 0.0f) return; // no need to do anything @@ -389,12 +390,38 @@ void Clouds::render() cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog); } +//#if 0 driver->drawMeshBuffer(m_meshbuffer.get()); - +//#endif // Restore fog settings driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density, fog_pixelfog, fog_rangefog); -#endif +} + +void Clouds::renderDepth() { + if (m_params.density <= 0.0f) + return; // no need to do anything + + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + + updateMesh(); + + // Update position + { + v2f off_origin = m_origin - m_mesh_origin; + v3f rel(off_origin.X, 0, off_origin.Y); + rel -= intToFloat(m_camera_offset, BS); + setPosition(rel); + updateAbsolutePosition(); + } + + video::SMaterial material = m_material; + material.MaterialType = video::EMT_SOLID; + material.ZWriteEnable = video::EZW_ON; + + driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); + driver->setMaterial(material); + driver->drawMeshBuffer(m_meshbuffer.get()); } void Clouds::step(float dtime) diff --git a/src/client/clouds.h b/src/client/clouds.h index 100f3fcbe..75b0f2a36 100644 --- a/src/client/clouds.h +++ b/src/client/clouds.h @@ -52,6 +52,8 @@ public: virtual void render(); + void renderDepth(); + virtual const aabb3f &getBoundingBox() const { return m_box; @@ -140,6 +142,16 @@ public: const video::SColor getColor() const { return m_color.toSColor(); } + bool getGrid(int x, int y) + { + int index = x + y * m_cloud_radius_i * 2; + if (index < 0 || index >= m_grid.size()) + return false; + return m_grid[index]; + } + + v2f getCloudOffset() const { return m_noise_position; } + private: void updateBox() { @@ -173,6 +185,7 @@ private: u16 m_cloud_radius_i; u32 m_seed; v3f m_camera_pos; + v2f m_noise_position; v3s16 m_camera_offset; bool m_camera_inside_cloud = false; @@ -180,4 +193,7 @@ private: bool m_enable_shaders, m_enable_3d; video::SColorf m_color = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); CloudParams m_params; + + std::vector m_grid; + }; diff --git a/src/client/game.cpp b/src/client/game.cpp index 252a59a0e..8a8fc29a4 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -383,11 +383,10 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter m_animation_timer_delta_pixel{"animationTimerDelta"}; CachedPixelShaderSetting m_artificial_light{ "artificialLight" }; CachedPixelShaderSetting m_day_light{"dayLight"}; - CachedPixelShaderSetting m_eye_position_pixel{ "eyePosition" }; - CachedVertexShaderSetting m_eye_position_vertex{ "eyePosition" }; CachedPixelShaderSetting m_minimap_yaw{"yawVec"}; - CachedPixelShaderSetting m_camera_offset_pixel{"cameraOffset"}; CachedVertexShaderSetting m_camera_offset_vertex{"cameraOffset"}; + CachedPixelShaderSetting m_camera_offset_pixel{ "cameraOffset" }; + CachedVertexShaderSetting m_camera_position_vertex{"cameraPosition"}; CachedPixelShaderSetting m_camera_position_pixel{"cameraPosition"}; CachedVertexShaderSetting m_camera_projinv_vertex{"mCameraProjInv"}; CachedPixelShaderSetting m_camera_projinv_pixel{"mCameraProjInv"}; @@ -420,6 +419,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_cloud_height_pixel{"cloudHeight"}; CachedPixelShaderSetting m_cloud_thickness_pixel{"cloudThickness"}; CachedPixelShaderSetting m_cloud_density_pixel{"cloudDensity"}; + CachedPixelShaderSetting m_cloud_offset_pixel{"cloudOffset"}; + CachedPixelShaderSetting m_cloud_radius_pixel{"cloudRadius"}; CachedPixelShaderSetting m_saturation_pixel{"saturation"}; float m_gamma; CachedPixelShaderSetting m_gamma_pixel{"gamma"}; @@ -432,6 +433,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_moon_brightness_pixel{"moonBrightness"}; CachedPixelShaderSetting m_volumetric_light_strength_pixel{"volumetricLightStrength"}; + CachedPixelShaderSetting + m_volumetric_cloud_color{"cloudColor"}; static constexpr std::array SETTING_CALLBACKS = { "exposure_compensation", @@ -501,20 +504,17 @@ public: m_animation_timer_delta_vertex.set(&animation_timer_delta_f, services); m_animation_timer_delta_pixel.set(&animation_timer_delta_f, services); - v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition(); - m_eye_position_pixel.set(epos, services); - m_eye_position_vertex.set(epos, services); - if (m_client->getMinimap()) { v3f minimap_yaw = m_client->getMinimap()->getYawVec(); m_minimap_yaw.set(minimap_yaw, services); } v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS); - m_camera_offset_pixel.set(offset, services); m_camera_offset_vertex.set(offset, services); + m_camera_offset_pixel.set(offset, services); v3f camera_position = m_client->getCamera()->getPosition(); + m_camera_position_vertex.set(camera_position, services); m_camera_position_pixel.set(camera_position, services); core::matrix4 camera_proj = m_client->getCamera()->getCameraNode()->getProjectionMatrix(); @@ -573,13 +573,19 @@ public: // TODO: settings Clouds* clouds = m_client->getClouds(); - if (m_client->getClouds()) { + if (clouds && g_settings->getBool("enable_volumetric_clouds")) { float cloud_height = clouds->getHeight() * 10.0f; m_cloud_height_pixel.set(&cloud_height, services); float cloud_thickness = clouds->getThickness() * 10.0f; m_cloud_thickness_pixel.set(&cloud_thickness, services); float cloud_density = clouds->getDensity(); m_cloud_density_pixel.set(&cloud_density, services); + v2f cloud_offset = clouds->getCloudOffset(); + m_cloud_offset_pixel.set(cloud_offset, services); + float cloud_radius = g_settings->getU16("cloud_radius"); + m_cloud_radius_pixel.set(&cloud_radius, services); + video::SColor cloud_color = clouds->getColor(); + m_volumetric_cloud_color.set(cloud_color, services); } if (m_volumetric_light_enabled) { @@ -4294,7 +4300,7 @@ void Game::updateClouds(float dtime) camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS; camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS; this->clouds->update(camera_node_position, this->sky->getCloudColor()); - if (this->clouds->isCameraInsideCloud() && this->fogEnabled()) { + if (this->clouds->isCameraInsideCloud() && this->fogEnabled() && !g_settings->getBool("enable_volumetric_clouds")) { // If camera is inside cloud and fog is enabled, use cloud's colors as sky colors. video::SColor clouds_dark = this->clouds->getColor().getInterpolated( video::SColor(255, 0, 0, 0), 0.9); diff --git a/src/client/render/pipeline.cpp b/src/client/render/pipeline.cpp index 8fccc7808..7700e8aeb 100644 --- a/src/client/render/pipeline.cpp +++ b/src/client/render/pipeline.cpp @@ -113,6 +113,18 @@ void TextureBuffer::swapTextures(u8 texture_a, u8 texture_b) m_textures[texture_b] = temp; } +void TextureBuffer::setTextureImage(u8 id, video::IImage* image) { + assert(m_definitions[id].valid); + + auto &definition = m_definitions[id]; + + if (m_textures[id]) m_driver->removeTexture(m_textures[id]); + + m_textures[id] = m_driver->addTexture(definition.name.c_str(), image); + definition.fixed_size = true; + definition.size = image->getDimension(); + definition.dirty = false; +} bool TextureBuffer::ensureTexture(video::ITexture **texture, const TextureDefinition& definition, PipelineContext &context) { @@ -183,6 +195,8 @@ void TextureBufferOutput::activate(PipelineContext &context) if (!render_target) render_target = driver->addRenderTarget(); + if (disable_clear) m_clear = false; + core::array textures; core::dimension2du size(0, 0); for (size_t i = 0; i < texture_map.size(); i++) { diff --git a/src/client/render/pipeline.h b/src/client/render/pipeline.h index abb108652..836b6c9b9 100644 --- a/src/client/render/pipeline.h +++ b/src/client/render/pipeline.h @@ -141,6 +141,7 @@ public: virtual video::ITexture *getTexture(u8 index) override; virtual void reset(PipelineContext &context) override; void swapTextures(u8 texture_a, u8 texture_b); + void setTextureImage(u8 id, video::IImage* image); private: static const u8 NO_DEPTH_TEXTURE = 255; @@ -182,6 +183,7 @@ public: TextureBufferOutput(TextureBuffer *buffer, const std::vector &texture_map, u8 depth_stencil); virtual ~TextureBufferOutput() override; void activate(PipelineContext &context) override; + void disableClearing() { disable_clear = true; } private: static const u8 NO_DEPTH_TEXTURE = 255; @@ -190,6 +192,7 @@ private: u8 depth_stencil { NO_DEPTH_TEXTURE }; video::IRenderTarget* render_target { nullptr }; video::IVideoDriver* driver { nullptr }; + bool disable_clear = false; }; /** diff --git a/src/client/render/secondstage.cpp b/src/client/render/secondstage.cpp index 0a03011c8..0e8b4f15b 100644 --- a/src/client/render/secondstage.cpp +++ b/src/client/render/secondstage.cpp @@ -24,64 +24,114 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/shader.h" #include "client/tile.h" #include "settings.h" +#include "noise.h" class NoiseStep : public RenderStep { public: - NoiseStep(u32 shader_id, u8 texture_id) : - shader_id(shader_id), texture_id(texture_id) + NoiseStep(TextureBuffer* buffer, u8 id, u32 size) : + buffer(buffer), id(id), size(size) { - material.UseMipMaps = false; - material.ZBuffer = true; - material.ZWriteEnable = video::EZW_ON; } - void setRenderSource(RenderSource* _source) override { - source = _source; + void setRenderSource(RenderSource* _source) override {} + + void setRenderTarget(RenderTarget* _target) override {} + + void reset(PipelineContext& context) override {} + + void run(PipelineContext& context) override + { + if (!needs_run) return; + + needs_run = false; + + video::IImage* noise_image = context.device->getVideoDriver()->createImage(video::ECF_A8R8G8B8, core::dimension2du(256, 256)); + PseudoRandom random; + for (u32 i = 0; i < size * size; ++i) { + noise_image->setPixel(i % size, i / size, video::SColor(0, random.next() % 256, 0, 0)); + } + buffer->setTextureImage(id, noise_image); + noise_image->drop(); } - void setRenderTarget(RenderTarget* _target) override { +private: + u32 size; + u8 id; + TextureBuffer* buffer = nullptr; + bool needs_run = true; +}; + +class CloudDensityStep : public RenderStep { +public: + CloudDensityStep(TextureBuffer* buffer, u8 id, Clouds* clouds) : + buffer(buffer), id(id), clouds(clouds) + { + } + + void setRenderSource(RenderSource* _source) override {} + + void setRenderTarget(RenderTarget* _target) override {} + + void reset(PipelineContext& context) override {} + + void run(PipelineContext& context) override + { + u16 cloud_radius = g_settings->getU16("cloud_radius"); + if (cloud_radius < 1) cloud_radius = 1; + + video::IImage* image = context.device->getVideoDriver()->createImage(video::ECF_A8R8G8B8, core::dimension2du(8 * cloud_radius, 8 * cloud_radius)); + + for (int x = 0; x < 2 * cloud_radius; ++x) { + for (int y = 0; y < 2 * cloud_radius; ++y) { + bool isFilled = clouds->getGrid(x, y); + + for (int i = 0; i < 16; ++i) { + int xp = x * 4 + i % 4; + int yp = y * 4 + i / 4; + + image->setPixel(xp, yp, video::SColor(255, isFilled * 255, 0, 0)); + } + } + } + + buffer->setTextureImage(id, image); + + image->drop(); + } + +private: + Clouds* clouds = nullptr; + u8 id = 0; + TextureBuffer* buffer = nullptr; +}; + +class CloudDepthStep : public RenderStep { +public: + CloudDepthStep(Clouds* clouds) : + clouds(clouds) + { + } + + void setRenderSource(RenderSource* _source) override {} + + void setRenderTarget(RenderTarget* _target) override + { target = _target; } void reset(PipelineContext& context) override {} - void run(PipelineContext& context) override { - video::ITexture* texture = source->getTexture(texture_id); - if (texture != last_texture) { - last_texture = texture; + void run(PipelineContext& context) override + { + if (target) + target->activate(context); - if (target) - target->activate(context); - - // attach the shader - material.MaterialType = context.client->getShaderSource()->getShaderInfo(shader_id).material; - - auto driver = context.device->getVideoDriver(); - - static const video::SColor color = video::SColor(0, 0, 0, 255); - static const video::S3DVertex vertices[4] = { - video::S3DVertex(1.0, -1.0, 0.0, 0.0, 0.0, -1.0, - color, 1.0, 0.0), - video::S3DVertex(-1.0, -1.0, 0.0, 0.0, 0.0, -1.0, - color, 0.0, 0.0), - video::S3DVertex(-1.0, 1.0, 0.0, 0.0, 0.0, -1.0, - color, 0.0, 1.0), - video::S3DVertex(1.0, 1.0, 0.0, 0.0, 0.0, -1.0, - color, 1.0, 1.0), - }; - static const u16 indices[6] = { 0, 1, 2, 2, 3, 0 }; - driver->setMaterial(material); - driver->drawVertexPrimitiveList(&vertices, 4, &indices, 2); - } + clouds->renderDepth(); } private: - u32 shader_id; - u8 texture_id; - video::SMaterial material; - video::ITexture* last_texture = nullptr; - RenderSource* source{ nullptr }; - RenderTarget* target{ nullptr }; + Clouds* clouds = nullptr; + RenderTarget* target = nullptr; }; PostProcessingStep::PostProcessingStep(u32 _shader_id, const std::vector &_texture_map) : @@ -160,6 +210,11 @@ void PostProcessingStep::setWrapRepeat(u8 index, bool value) { material.TextureLayers[index].TextureWrapV = value ? video::ETC_REPEAT : video::ETC_CLAMP_TO_EDGE; } +void PostProcessingStep::disableDepthTest() { + material.ZBuffer = video::ECFN_DISABLED; + material.ZWriteEnable = video::EZW_OFF; +} + RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep, v2f scale, Client *client) { auto buffer = pipeline->createOwned(); @@ -201,8 +256,8 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep const bool enable_ssaa = antialiasing == "ssaa"; const bool enable_fxaa = antialiasing == "fxaa"; const bool enable_volumetric_light = g_settings->getBool("enable_volumetric_lighting") && enable_bloom; - const bool enable_volumetric_clouds = true; - // TODO: Add clouds setting + // TODO: Proper constraints + const bool enable_volumetric_clouds = g_settings->getBool("enable_volumetric_clouds") && client->getClouds(); if (enable_ssaa) { u16 ssaa_scale = MYMAX(2, g_settings->getU16("fsaa")); @@ -226,22 +281,21 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep // post-processing stage u8 source = TEXTURE_COLOR; + + u8 final_color_source = TEXTURE_COLOR; if (enable_volumetric_clouds) { + const u16 cloud_radius = g_settings->getU16("cloud_radius"); + buffer->setTexture(TEXTURE_NOISE, core::dimension2du(256, 256), "noise", color_format); + pipeline->addStep(buffer, TEXTURE_NOISE, 256); - shader_id = client->getShaderSource()->getShader("noise_shader", TILE_MATERIAL_PLAIN, NDT_MESH); - RenderStep *noise_step = pipeline->addStep(shader_id, TEXTURE_NOISE); - noise_step->setRenderSource(buffer); - noise_step->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_NOISE)); + buffer->setTexture(TEXTURE_NOISE_COARSE, core::dimension2du(cloud_radius * 8, cloud_radius * 8), "noise_coarse", color_format); + pipeline->addStep(buffer, TEXTURE_NOISE_COARSE, client->getClouds()); - buffer->setTexture(TEXTURE_NOISE_COARSE, core::dimension2du(256, 256), "noise", color_format); + u32 undersampling = core::clamp(g_settings->getU32("volumetrics_undersampling"), (u32)1, (u32)4); - shader_id = client->getShaderSource()->getShader("coarse_noise_shader", TILE_MATERIAL_PLAIN, NDT_MESH); - noise_step = pipeline->addStep(shader_id, std::vector()); - noise_step->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_NOISE_COARSE)); - - buffer->setTexture(TEXTURE_CLOUDS_1, scale * 0.25f, "clouds_1", color_format, /*clear:*/ true); + buffer->setTexture(TEXTURE_CLOUDS_1, scale / (float)undersampling, "clouds_1", color_format, /*clear:*/ true); buffer->setTexture(TEXTURE_CLOUDS_2, scale, "clouds_2", color_format); buffer->setTexture(TEXTURE_CLOUD_DENSITY, scale, "cloud_density", color_format); @@ -253,16 +307,24 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep volumetric_clouds->setBilinearFilter(2, true); volumetric_clouds->setWrapRepeat(1, true); volumetric_clouds->setWrapRepeat(2, true); + volumetric_clouds->disableDepthTest(); source = TEXTURE_CLOUDS_1; shader_id = client->getShaderSource()->getShader("clouds_merge", TILE_MATERIAL_PLAIN, NDT_MESH); - PostProcessingStep* blend_clouds = pipeline->addStep(shader_id, std::vector { TEXTURE_CLOUDS_1, TEXTURE_COLOR }); + PostProcessingStep* blend_clouds = pipeline->addStep(shader_id, std::vector { TEXTURE_CLOUDS_1, TEXTURE_COLOR, TEXTURE_DEPTH }); blend_clouds->setRenderSource(buffer); blend_clouds->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_CLOUDS_2)); blend_clouds->setBilinearFilter(0, true); + blend_clouds->disableDepthTest(); + + CloudDepthStep* cloud_depth = pipeline->addStep(client->getClouds()); + TextureBufferOutput* cloud_depth_output = pipeline->createOwned(buffer, std::vector{ TEXTURE_COLOR }, TEXTURE_DEPTH); + cloud_depth_output->disableClearing(); + cloud_depth->setRenderTarget(cloud_depth_output); source = TEXTURE_CLOUDS_2; + final_color_source = TEXTURE_CLOUDS_2; } // common downsampling step for bloom or autoexposure @@ -281,7 +343,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep // get bright spots u32 shader_id = client->getShaderSource()->getShader("extract_bloom", TILE_MATERIAL_PLAIN, NDT_MESH); - auto extract_bloom = pipeline->addStep(shader_id, std::vector { source, TEXTURE_EXPOSURE_1 }); + RenderStep* extract_bloom = pipeline->addStep(shader_id, std::vector { source, TEXTURE_EXPOSURE_1 }); extract_bloom->setRenderSource(buffer); extract_bloom->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_BLOOM)); source = TEXTURE_BLOOM; @@ -332,14 +394,14 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep } // FXAA - u8 final_stage_source = TEXTURE_CLOUDS_2; + u8 final_stage_source = final_color_source; if (enable_fxaa) { final_stage_source = TEXTURE_FXAA; buffer->setTexture(TEXTURE_FXAA, scale, "fxaa", color_format); shader_id = client->getShaderSource()->getShader("fxaa", TILE_MATERIAL_PLAIN); - PostProcessingStep *effect = pipeline->createOwned(shader_id, std::vector { TEXTURE_COLOR }); + PostProcessingStep* effect = pipeline->createOwned(shader_id, std::vector { final_color_source }); pipeline->addStep(effect); effect->setBilinearFilter(0, true); effect->setRenderSource(buffer); @@ -348,7 +410,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep // final merge shader_id = client->getShaderSource()->getShader("second_stage", TILE_MATERIAL_PLAIN, NDT_MESH); - PostProcessingStep *effect = pipeline->createOwned(shader_id, std::vector { final_stage_source, TEXTURE_SCALE_UP, TEXTURE_EXPOSURE_2 }); + PostProcessingStep* effect = pipeline->createOwned(shader_id, std::vector { final_stage_source, TEXTURE_SCALE_UP, TEXTURE_EXPOSURE_2 }); pipeline->addStep(effect); if (enable_ssaa) effect->setBilinearFilter(0, true); diff --git a/src/client/render/secondstage.h b/src/client/render/secondstage.h index 04705f3ae..224b27e33 100644 --- a/src/client/render/secondstage.h +++ b/src/client/render/secondstage.h @@ -52,6 +52,10 @@ public: void setWrapRepeat(u8 index, bool value); + void setColor(video::SColor color); + + void disableDepthTest(); + private: u32 shader_id; std::vector texture_map; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index b0f651d85..baf1b1417 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -342,6 +342,7 @@ void set_default_settings() settings->setDefault("bloom_intensity", "0.05"); settings->setDefault("bloom_radius", "1"); settings->setDefault("enable_volumetric_lighting", "false"); + settings->setDefault("enable_volumetric_clouds", "false"); settings->setDefault("enable_bumpmaps", "false"); // Effects Shadows