Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -69,9 +69,10 @@ shadowsonwater = false shadows = true -shadowquality = 0 ; Shadow map resolution. (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High) - ; High values can crash the game when using a graphics card with low memory! -shadowpcf = true +shadowquality = 1 ; Shadow map resolution. (0 - Low, 1 - Medium, 2 - High, 3 - Ultra) + ; High values can break shadows when using a graphics card with low memory! +shadowpcf = true ; Basic shadow filtering. +shadowpcss = false ; High quality (and expensive) variable penumbra shadows. vsync = false particles = true fog = true @@ -100,7 +101,7 @@ ;;;;; EXPERIMENTAL ;;;;; ; Prefer GLSL shaders over ARB shaders. Allows fancier graphical effects. -preferglsl = false +preferglsl = true ; Experimental probably-non-working GPU skinning support; requires preferglsl; use at own risk gpuskinning = false @@ -111,6 +112,8 @@ ; Use screen-space postprocessing filters (HDR, bloom, DOF, etc). Incompatible with fixed renderpath. postproc = false +dof = false ; Use depth of field effect. + ; Quality level of shader effects (set to 10 to display all effects) materialmgr.quality = 2.0 Index: binaries/data/mods/public/gui/options/options.json =================================================================== --- binaries/data/mods/public/gui/options/options.json +++ binaries/data/mods/public/gui/options/options.json @@ -58,6 +58,13 @@ "config": "gui.session.ceasefirecounter" }, { + "type": "boolean", + "label": "Unit Silhouettes", + "tooltip": "Show outlines of units behind buildings.", + "config": "silhouettes", + "function": "Renderer_SetSilhouettesEnabled" + }, + { "type": "dropdown", "label": "Late Observer Joins", "tooltip": "Allow everybody or buddies only to join the game as observer after it started.", @@ -97,16 +104,8 @@ }, { "type": "boolean", - "label": "Prefer GLSL", - "tooltip": "Use OpenGL 2.0 shaders (recommended).", - "config": "preferglsl", - "function": "Renderer_SetPreferGLSLEnabled" - }, - { - "type": "boolean", "label": "Fog", "tooltip": "Enable Fog.", - "dependencies": ["preferglsl"], "config": "fog", "function": "Renderer_SetFogEnabled" }, @@ -118,6 +117,14 @@ "function": "Renderer_SetPostprocEnabled" }, { + "type": "boolean", + "label": "Depth of Field", + "tooltip": "Apply the depth of field postprocessing effect.", + "dependencies": ["postproc"], + "config": "dof", + "function": "Renderer_SetDOFEnabled" + }, + { "type": "slider", "label": "Shader Effects", "tooltip": "Number of shader effects. REQUIRES GAME RESTART", @@ -135,16 +142,15 @@ { "type": "dropdown", "label": "Shadow Quality", - "tooltip": "Shadow map resolution. High values can crash the game when using a graphics card with low memory!", + "tooltip": "Shadow map resolution.", "dependencies": ["shadows"], "config": "shadowquality", "function": "Renderer_RecreateShadowMap", "list": [ - { "value": -2, "label": "Very Low" }, - { "value": -1, "label": "Low" }, - { "value": 0, "label": "Medium" }, - { "value": 1, "label": "High" }, - { "value": 2, "label": "Very High" } + { "value": 0, "label": "Low" }, + { "value": 1, "label": "Medium" }, + { "value": 2, "label": "High" }, + { "value": 3, "label": "Ultra (MAY NOT WORK)" } ] }, { @@ -157,10 +163,11 @@ }, { "type": "boolean", - "label": "Unit Silhouettes", - "tooltip": "Show outlines of units behind buildings.", - "config": "silhouettes", - "function": "Renderer_SetSilhouettesEnabled" + "label": "Soft Shadows", + "tooltip": "Variable penumbra shadows. (Expensive)", + "dependencies": ["shadows", "shadowpcf"], + "config": "shadowpcss", + "function": "Renderer_SetShadowPCSSEnabled" }, { "type": "boolean", Index: binaries/data/mods/public/shaders/effects/postproc/DOF.xml =================================================================== --- binaries/data/mods/public/shaders/effects/postproc/DOF.xml +++ binaries/data/mods/public/shaders/effects/postproc/DOF.xml @@ -1,10 +0,0 @@ - - - - - - - - - - Index: binaries/data/mods/public/shaders/effects/postproc/HDR.xml =================================================================== --- binaries/data/mods/public/shaders/effects/postproc/HDR.xml +++ binaries/data/mods/public/shaders/effects/postproc/HDR.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file Index: binaries/data/mods/public/shaders/effects/postproc/hdr.xml =================================================================== --- binaries/data/mods/public/shaders/effects/postproc/hdr.xml +++ binaries/data/mods/public/shaders/effects/postproc/hdr.xml @@ -1,9 +0,0 @@ - - - - - - - - - Index: binaries/data/mods/public/shaders/glsl/bloom.fs =================================================================== --- binaries/data/mods/public/shaders/glsl/bloom.fs +++ binaries/data/mods/public/shaders/glsl/bloom.fs @@ -1,41 +1,37 @@ -#version 110 +#version 120 varying vec2 v_tex; uniform sampler2D renderedTex; uniform vec2 texSize; +#if BIG_BLUR + // 5 tap linear sampled gaussian blur, created using BlurNinja: https://github.com/manuelbua/blur-ninja + uniform float weight[2] = float[](0.375, 0.3125); + uniform float offset = 1.2; +#else + // 3 tap gaussian blur + uniform float weight[2] = float[](0.5, 0.25); + uniform float offset = 1.0; +#endif + void main() { - #if BLOOM_NOP - gl_FragColor = texture2D(renderedTex, v_tex); - gl_FragColor.a = 1.0; - #endif + #if BLOOM_NOP + gl_FragColor = texture2D(renderedTex, v_tex); + gl_FragColor.a = 1.0; + #endif + + #if BLOOM_PASS_H + gl_FragColor = texture2D(renderedTex, gl_FragCoord.xy / texSize) * weight[0]; + gl_FragColor += texture2D(renderedTex, (gl_FragCoord.xy + vec2(offset, 0.0))/texSize) * weight[1]; + gl_FragColor += texture2D(renderedTex, (gl_FragCoord.xy - vec2(offset, 0.0))/texSize) * weight[1]; + gl_FragColor.a = 1.0; + #endif - #if BLOOM_PASS_H - vec4 color = vec4(0.0); - vec2 v_tex_offs = vec2(v_tex.x - 0.01, v_tex.y); - - for (int i = 0; i < 6; ++i) - { - color += texture2D(renderedTex, v_tex_offs); - v_tex_offs += vec2(0.004, 0.0); - } - - gl_FragColor.rgb = color.rgb / 6.0; - gl_FragColor.a = 1.0; - #endif - - #if BLOOM_PASS_V - vec4 color = vec4(0.0); - vec2 v_tex_offs = vec2(v_tex.x, v_tex.y - 0.01); - - for (int i = 0; i < 6; ++i) - { - color += texture2D(renderedTex, v_tex_offs); - v_tex_offs += vec2(0.0, 0.004); - } - - gl_FragColor.rgb = color.rgb / 6.0; - gl_FragColor.a = 1.0; - #endif + #if BLOOM_PASS_V + gl_FragColor = texture2D(renderedTex, gl_FragCoord.xy / texSize) * weight[0]; + gl_FragColor += texture2D(renderedTex, ((gl_FragCoord.xy + vec2(0.0, offset))/texSize)) * weight[1]; + gl_FragColor += texture2D(renderedTex, ((gl_FragCoord.xy - vec2(0.0, offset))/texSize)) * weight[1]; + gl_FragColor.a = 1.0; + #endif } \ No newline at end of file Index: binaries/data/mods/public/shaders/glsl/bloom.xml =================================================================== --- binaries/data/mods/public/shaders/glsl/bloom.xml +++ binaries/data/mods/public/shaders/glsl/bloom.xml @@ -1,7 +1,7 @@ - + Index: binaries/data/mods/public/shaders/glsl/dof_hdr.fs =================================================================== --- binaries/data/mods/public/shaders/glsl/dof_hdr.fs +++ binaries/data/mods/public/shaders/glsl/dof_hdr.fs @@ -0,0 +1,71 @@ +#version 120 + +uniform sampler2D renderedTex; +uniform sampler2D depthTex; +uniform sampler2D downscaleTex2; +uniform sampler2D blurTex2; +uniform sampler2D blurTex4; +uniform sampler2D blurTex8; + +uniform float width; +uniform float height; + +uniform float zNear; +uniform float zFar; + +uniform float brightness; +uniform float contrast; +uniform float saturation; +uniform float bloom; + +varying vec2 v_tex; + +float linearizeDepth(float depth) +{ + return 2.0 * zNear / (zNear + zFar - depth * (zFar - zNear)); +} + +void main(void) +{ + vec3 color = texture2D(renderedTex, v_tex).rgb; + vec3 blur2 = texture2D(blurTex2, v_tex).rgb; + + #if USE_DOF + // Apply Depth of Field + vec3 down2 = texture2D(downscaleTex2, v_tex).rgb; + + float depth = linearizeDepth(texture2D(depthTex, v_tex).r); + float amount = pow(depth, 1.25) * 6.5; // Using a semi-exponential ramp to keep nearby objects from being semi-blurred. + + if (amount < 1.0){ + color = mix(color, down2, amount); + }else{ + amount = clamp(amount - 1.0, 0.0, 1.0); + color = mix(down2, blur2, amount); + } + #endif + + #if USE_HDR + // Apply bloom and color adjustments + if (bloom < 0.2){ + // Note: 0.2 is the "zero" setting for bloom for some unfathomable reason. + // If bloom is set to "zero" we skip all the texture reads and operations so as not to waste GPU cycles. + vec3 blur4 = texture2D(blurTex4, v_tex).rgb; + vec3 blur8 = texture2D(blurTex8, v_tex).rgb; + vec3 blur = (blur2 + blur4 + blur8)/3.0; + + blur = mix(color, blur, (1.0 - (bloom * 5.0))); + + color = max(blur, color); + } + + color = (color + brightness - 0.5) * contrast + 0.5; + + color = mix(vec3(dot(color, vec3(0.299, 0.587, 0.114))), color, saturation); + #endif + + gl_FragColor.rgb = color; + gl_FragColor.a = 1.0; +} + + Index: binaries/data/mods/public/shaders/glsl/dof_hdr.vs =================================================================== --- binaries/data/mods/public/shaders/glsl/dof_hdr.vs +++ binaries/data/mods/public/shaders/glsl/dof_hdr.vs @@ -0,0 +1,13 @@ +#version 110 + +varying vec2 v_tex; + +attribute vec3 a_vertex; +attribute vec2 a_uv0; + +void main() +{ + gl_Position = vec4(a_vertex, 1.0); + + v_tex = a_uv0; +} Index: binaries/data/mods/public/shaders/glsl/dof_hdr.xml =================================================================== --- binaries/data/mods/public/shaders/glsl/dof_hdr.xml +++ binaries/data/mods/public/shaders/glsl/dof_hdr.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + Index: binaries/data/mods/public/shaders/glsl/model_common.fs =================================================================== --- binaries/data/mods/public/shaders/glsl/model_common.fs +++ binaries/data/mods/public/shaders/glsl/model_common.fs @@ -10,9 +10,13 @@ varying vec4 v_shadow; #if USE_SHADOW_SAMPLER uniform sampler2DShadow shadowTex; - #if USE_SHADOW_PCF + #if USE_SHADOW_PCF || USE_SHADOW_PCSS uniform vec4 shadowScale; + uniform vec3 lightScale; #endif + #if USE_SHADOW_PCSS + uniform sampler2D blockerTex; + #endif #else uniform sampler2D shadowTex; #endif @@ -26,6 +30,20 @@ #endif #endif +#if USE_SHADOW_PCF || (USE_SHADOW_PCSS && USE_TRANSPARENT) + // This is a 'random' poisson disk with a uniform distribution created with a poisson disk generator. + uniform vec2 offsets[8] = vec2[]( + vec2(-0.353553, 0.612372), + vec2(-0.25, -0.433013), + vec2(0.663414, 0.55667), + vec2(-0.332232, 0.120922), + vec2(0.137281, -0.778559), + vec2(0.106337, 0.603069), + vec2(-0.879002, -0.319931), + vec2(0.191511, -0.160697) + ); +#endif + uniform vec3 shadingColor; uniform vec3 ambient; uniform vec3 sunColor; @@ -71,18 +89,71 @@ #if USE_SHADOW && !DISABLE_RECEIVE_SHADOWS float biasedShdwZ = v_shadow.z - shadowBias; #if USE_SHADOW_SAMPLER - #if USE_SHADOW_PCF - vec2 offset = fract(v_shadow.xy - 0.5); - vec4 size = vec4(offset + 1.0, 2.0 - offset); - vec4 weight = (vec4(1.0, 1.0, -0.5, -0.5) + (v_shadow.xy - 0.5*offset).xyxy) * shadowScale.zwzw; - return (1.0/9.0)*dot(size.zxzx*size.wwyy, - vec4(shadow2D(shadowTex, vec3(weight.zw, biasedShdwZ)).r, - shadow2D(shadowTex, vec3(weight.xw, biasedShdwZ)).r, - shadow2D(shadowTex, vec3(weight.zy, biasedShdwZ)).r, - shadow2D(shadowTex, vec3(weight.xy, biasedShdwZ)).r)); - #else - return shadow2D(shadowTex, vec3(v_shadow.xy, biasedShdwZ)).r; - #endif + #if USE_SHADOW_PCF || (USE_SHADOW_PCSS && USE_TRANSPARENT) + float blurRadius = 3.0; + const int numSamples = 8; + float lit = 0.0; + vec2 uv = v_shadow.xy - (0.5 * fract(v_shadow.xy - 0.5)); + + for (int i = 0; i < numSamples; ++i){ + vec2 offset = (uv + offsets[i] * blurRadius * lightScale.xy) * shadowScale.zw; + lit += shadow2D(shadowTex, vec3(offset, biasedShdwZ)).r; + } + + return lit/float(numSamples); + #else + #if USE_SHADOW_PCSS + //float blurscale = shadowScale.x/4096.0; + int numSamples = 5; + vec2 uv = v_shadow.xy - (0.5 * fract(v_shadow.xy - 0.5)); + + // Perform blocker search + float hits = 0.0; + float avgdist = 0.0; + vec2 off = vec2(-24.5); + for (int i = 0; i < numSamples; ++i){ + for (int j = 0; j < numSamples; ++j){ + vec2 offset = (uv + vec2(off.x + 9.6 * float(j), off.y) * lightScale.xy) * shadowScale.zw; + float dist = texture2D(blockerTex, offset).z; + + // Note: I'm converting a bool to a float here to avoid a nasty branch in the shader. + float hit = float(dist < biasedShdwZ); + hits += hit; + avgdist += dist * hit; + } + off.y = off.y + 9.6; + } + + if (hits == 0.0) return 1.0; // Early exit if no blockers are found. + + // Perform penumbra estimation and scale the number of samples with blur size for efficiency. + avgdist /= hits; + + float lightSize = 1.5 * lightScale.z; + float amount = clamp(lightSize * (biasedShdwZ - avgdist)/avgdist, 0.0, 1.0); + float sampleamount = clamp((lightSize/1.5) * (biasedShdwZ - avgdist)/avgdist, 0.0, 1.0); + + numSamples = int(floor(mix(2.0, 16.0, amount))); + float sampleScale = mix(1.0, 24.0, sampleamount); // This allows smooth transitions by varying the spacing of the samples. + float blurscale = sampleScale/float(numSamples); + + // Perform shadow sampling + float lit = 0.0; + off = vec2(-0.5 - 2.0 * float(numSamples)/2.0); + lit += shadow2D(shadowTex, vec3(uv * shadowScale.zw, biasedShdwZ)).r; + for (int i = 0; i < numSamples; ++i){ + for (int j = 0; j < numSamples; ++j){ + vec2 offset = (uv + vec2(off.x + 2.0 * float(j), off.y) * blurscale * lightScale.xy) * shadowScale.zw; + lit += shadow2D(shadowTex, vec3(offset, biasedShdwZ)).r; + } + off.y = off.y + 2.0; + } + float shad = 1.0 - lit/(float(numSamples * numSamples) + 1.0); + return 1.0 - pow(shad, 1.25); + #else + return shadow2D(shadowTex, vec3(v_shadow.xy, biasedShdwZ)).r; + #endif + #endif #else if (biasedShdwZ >= 1.0) return 1.0; Index: binaries/data/mods/public/shaders/glsl/model_common.vs =================================================================== --- binaries/data/mods/public/shaders/glsl/model_common.vs +++ binaries/data/mods/public/shaders/glsl/model_common.vs @@ -13,7 +13,7 @@ uniform mat4 shadowTransform; uniform mat4 instancingTransform; -#if USE_SHADOW_SAMPLER && USE_SHADOW_PCF +#if USE_SHADOW_SAMPLER && (USE_SHADOW_PCF || USE_SHADOW_PCSS) uniform vec4 shadowScale; #endif @@ -165,8 +165,9 @@ #endif #endif - v_lighting.xyz = max(0.0, dot(normal, -sunDir)) * sunColor; + v_lighting.xyz = max(0.0, dot(normal, -sunDir)) * sunColor; + v_tex = a_uv0; #if (USE_INSTANCING || USE_GPU_SKINNING) && USE_AO @@ -175,8 +176,8 @@ #if USE_SHADOW v_shadow = shadowTransform * position; - #if USE_SHADOW_SAMPLER && USE_SHADOW_PCF - v_shadow.xy *= shadowScale.xy; + #if USE_SHADOW_SAMPLER && (USE_SHADOW_PCF || USE_SHADOW_PCSS) + v_shadow.xy /= shadowScale.w; #endif #endif Index: binaries/data/mods/public/shaders/glsl/terrain_common.fs =================================================================== --- binaries/data/mods/public/shaders/glsl/terrain_common.fs +++ binaries/data/mods/public/shaders/glsl/terrain_common.fs @@ -7,12 +7,17 @@ uniform sampler2D specTex; #if USE_SHADOW - uniform float shadowAngle; #if USE_SHADOW_SAMPLER uniform sampler2DShadow shadowTex; - #if USE_SHADOW_PCF - uniform vec4 shadowScale; + #if USE_SHADOW_PCF || USE_SHADOW_PCSS + uniform vec4 shadowScale; + // shadowScale.xy gives the dimensionless scale factor based on the shadow map resolution and the shadow frustum x and y bounds. + // shadowScale.z gives the dimensionless relative z bounds of the shadow frustum. + // shadowScale.w gives 1.0 divided by the shadow map length in texels, for tex coord scaling. #endif + #if USE_SHADOW_PCSS + uniform sampler2D blockerTex; + #endif #else uniform sampler2D shadowTex; #endif @@ -64,6 +69,28 @@ #endif #endif +#if USE_SHADOW_PCF || USE_SHADOW_PCSS + // This is a 'random' poisson disk with a uniform distribution created with a poisson disk generator. + uniform vec2 offsets[16] = vec2[]( + vec2(-0.353553, 0.612372), + vec2(-0.25, -0.433013), + vec2(0.663414, 0.55667), + vec2(-0.332232, 0.120922), + vec2(0.137281, -0.778559), + vec2(0.106337, 0.603069), + vec2(-0.879002, -0.319931), + vec2(0.191511, -0.160697), + vec2(0.729784, 0.172962), + vec2(-0.383621, 0.406614), + vec2(-0.258521, -0.86352), + vec2(0.258577, 0.34733), + vec2(-0.82355, 0.0962588), + vec2(0.261982, -0.607343), + vec2(-0.0562987, 0.966608), + vec2(-0.147695, -0.0971404) + ); +#endif + float get_shadow() { float shadowBias = 0.0005; @@ -70,18 +97,69 @@ #if USE_SHADOW && !DISABLE_RECEIVE_SHADOWS float biasedShdwZ = v_shadow.z - shadowBias; #if USE_SHADOW_SAMPLER - #if USE_SHADOW_PCF - vec2 offset = fract(v_shadow.xy - 0.5); - vec4 size = vec4(offset + 1.0, 2.0 - offset); - vec4 weight = (vec4(1.0, 1.0, -0.5, -0.5) + (v_shadow.xy - 0.5*offset).xyxy) * shadowScale.zwzw; - return (1.0/9.0)*dot(size.zxzx*size.wwyy, - vec4(shadow2D(shadowTex, vec3(weight.zw, biasedShdwZ)).r, - shadow2D(shadowTex, vec3(weight.xw, biasedShdwZ)).r, - shadow2D(shadowTex, vec3(weight.zy, biasedShdwZ)).r, - shadow2D(shadowTex, vec3(weight.xy, biasedShdwZ)).r)); - #else - return shadow2D(shadowTex, vec3(v_shadow.xy, biasedShdwZ)).r; - #endif + #if USE_SHADOW_PCF + float blurRadius = 3.0; + const int numSamples = 16; + float lit = 0.0; + vec2 uv = v_shadow.xy - (0.5 * fract(v_shadow.xy - 0.5)); + + for (int i = 0; i < numSamples; ++i){ + vec2 offset = (uv + offsets[i] * blurRadius * shadowScale.xy) * shadowScale.w; + lit += shadow2D(shadowTex, vec3(offset, biasedShdwZ)).r; + } + + return lit/float(numSamples); + #else + #if USE_SHADOW_PCSS + int numSamples = 5; + vec2 uv = v_shadow.xy - (0.5 * fract(v_shadow.xy - 0.5)); + + // Perform blocker search + float hits = 0.0; + float avgdist = 0.0; + vec2 off = vec2(-24.5); + for (int i = 0; i < numSamples; ++i){ + for (int j = 0; j < numSamples; ++j){ + vec2 offset = (uv + vec2(off.x + 9.6 * float(j), off.y) * shadowScale.xy) * shadowScale.w; + float dist = texture2D(blockerTex, offset).z; + + // Note: I'm converting a bool to a float here to avoid a nasty branch in the shader. + float hit = float(dist < biasedShdwZ); + hits += hit; + avgdist += dist * hit; + } + off.y = off.y + 9.6; + } + + if (hits == 0.0) return 1.0; // Early exit if no blockers are found. + + // Perform penumbra estimation and scale the number of samples with blur size for efficiency. + avgdist /= hits; + + float amount = min(shadowScale.z * (biasedShdwZ - avgdist)/avgdist, 1.0); + float sampleamount = min((shadowScale.z/1.5) * (biasedShdwZ - avgdist)/avgdist, 1.0); + + numSamples = int(floor(mix(2.0, 16.0, amount))); + float sampleScale = mix(1.0, 24.0, sampleamount); // This allows smooth transitions by varying the spacing of the samples. + float blurscale = sampleScale/float(numSamples); + + // Perform shadow sampling + float lit = 0.0; + off = vec2(-0.5 - float(numSamples)); + lit += shadow2D(shadowTex, vec3(uv * shadowScale.w, biasedShdwZ)).r; + for (int i = 0; i < numSamples; ++i){ + for (int j = 0; j < numSamples; ++j){ + vec2 offset = (uv + vec2(off.x + 2.0 * float(j), off.y) * blurscale * shadowScale.xy) * shadowScale.w; + lit += shadow2D(shadowTex, vec3(offset, biasedShdwZ)).r; + } + off.y = off.y + 2.0; + } + float shad = 1.0 - lit/(float(numSamples * numSamples) + 1.0); + return 1.0 - pow(shad, 1.25); + #else + return shadow2D(shadowTex, vec3(v_shadow.xy, biasedShdwZ)).r; + #endif + #endif #else if (biasedShdwZ >= 1.0) return 1.0; Index: binaries/data/mods/public/shaders/glsl/terrain_common.vs =================================================================== --- binaries/data/mods/public/shaders/glsl/terrain_common.vs +++ binaries/data/mods/public/shaders/glsl/terrain_common.vs @@ -13,7 +13,7 @@ uniform vec2 losTransform; uniform mat4 shadowTransform; -#if USE_SHADOW_SAMPLER && USE_SHADOW_PCF +#if USE_SHADOW_SAMPLER && (USE_SHADOW_PCF || USE_SHADOW_PCSS) uniform vec4 shadowScale; #endif @@ -92,8 +92,8 @@ #if USE_SHADOW v_shadow = shadowTransform * vec4(a_vertex, 1.0); - #if USE_SHADOW_SAMPLER && USE_SHADOW_PCF - v_shadow.xy *= shadowScale.xy; + #if USE_SHADOW_SAMPLER && (USE_SHADOW_PCF || USE_SHADOW_PCSS) + v_shadow.xy /= shadowScale.w; #endif #endif Index: source/graphics/MapReader.cpp =================================================================== --- source/graphics/MapReader.cpp +++ source/graphics/MapReader.cpp @@ -1432,7 +1432,7 @@ } if (pPostproc) - pPostproc->SetPostEffect(L"default"); + pPostproc->SetPostEffect(L"None"); std::wstring skySet; GET_ENVIRONMENT_PROPERTY(envObj, SkySet, skySet) Index: source/ps/CStrInternStatic.h =================================================================== --- source/ps/CStrInternStatic.h +++ source/ps/CStrInternStatic.h @@ -31,6 +31,8 @@ X(2) X(ALPHABLEND_PASS_BLEND) X(ALPHABLEND_PASS_OPAQUE) +X(ALPHABLEND_PASS_REFLECTIONS) +X(BIG_BLUR) X(BLEND) X(BLOOM_NOP) X(BLOOM_PASS_H) @@ -50,8 +52,10 @@ X(SYS_HAS_GLSL) X(SYS_PREFER_GLSL) X(USE_FANCY_EFFECTS) +X(USE_DOF) X(USE_FP_SHADOW) X(USE_GPU_SKINNING) +X(USE_HDR) X(USE_INSTANCING) X(USE_NORMALS) X(USE_OBJECTCOLOR) @@ -60,6 +64,7 @@ X(USE_REFRACTION) X(USE_SHADOW) X(USE_SHADOW_PCF) +X(USE_SHADOW_PCSS) X(USE_SHADOW_SAMPLER) X(USE_SHADOWS_ON_WATER) X(USE_FOG) @@ -77,7 +82,9 @@ X(ambient) X(baseTex) X(blendTex) +X(blockerTex) X(bloom) +X(downscaleTex2) X(blurTex2) X(blurTex4) X(blurTex8) @@ -86,6 +93,7 @@ X(color) X(colorAdd) X(colorMul) +X(contrast) X(delta) X(depthTex) X(foamTex) @@ -98,7 +106,6 @@ X(gui_solid) X(gui_solid_mask) X(gui_text) -X(hdr) X(height) X(instancingTransform) X(losMap) Index: source/ps/GameSetup/Config.h =================================================================== --- source/ps/GameSetup/Config.h +++ source/ps/GameSetup/Config.h @@ -65,6 +65,8 @@ // flag to switch on shadow PCF extern bool g_ShadowPCF; +// flag to switch on shadow PCSS +extern bool g_ShadowPCSS; // flag to switch on particles rendering extern bool g_Particles; // flag to switch on fog @@ -78,6 +80,8 @@ extern bool g_PreferGLSL; // Use screen-space postprocessing filters (HDR, bloom, DOF, etc) extern bool g_PostProc; +// Use depth of field +extern bool g_DOF; // Use smooth LOS interpolation extern bool g_SmoothLOS; Index: source/ps/GameSetup/Config.cpp =================================================================== --- source/ps/GameSetup/Config.cpp +++ source/ps/GameSetup/Config.cpp @@ -41,6 +41,7 @@ bool g_Shadows = false; bool g_ShadowPCF = false; +bool g_ShadowPCSS = false; bool g_WaterEffects = true; bool g_WaterFancyEffects = false; @@ -56,6 +57,7 @@ bool g_PreferGLSL = false; bool g_PostProc = false; +bool g_DOF = false; bool g_SmoothLOS = false; float g_Gamma = 1.0f; @@ -96,6 +98,7 @@ CFG_GET_VAL("renderactors", g_RenderActors); CFG_GET_VAL("shadows", g_Shadows); CFG_GET_VAL("shadowpcf", g_ShadowPCF); + CFG_GET_VAL("shadowpcss", g_ShadowPCSS); CFG_GET_VAL("watereffects", g_WaterEffects); CFG_GET_VAL("waterfancyeffects", g_WaterFancyEffects); @@ -111,6 +114,7 @@ CFG_GET_VAL("showsky", g_ShowSky); CFG_GET_VAL("preferglsl", g_PreferGLSL); CFG_GET_VAL("postproc", g_PostProc); + CFG_GET_VAL("dof", g_DOF); CFG_GET_VAL("smoothlos", g_SmoothLOS); CFG_GET_VAL("gui.scale", g_GuiScale); } Index: source/ps/GameSetup/GameSetup.cpp =================================================================== --- source/ps/GameSetup/GameSetup.cpp +++ source/ps/GameSetup/GameSetup.cpp @@ -598,6 +598,8 @@ g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath)); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWPCF, g_ShadowPCF); g_ConfigDB.SetValueBool(CFG_SYSTEM, "shadowpcf", g_ShadowPCF); + g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWPCSS, g_ShadowPCSS); + g_ConfigDB.SetValueBool(CFG_SYSTEM, "shadowpcss", g_ShadowPCSS); g_Renderer.SetOptionBool(CRenderer::OPT_PARTICLES, g_Particles); g_ConfigDB.SetValueBool(CFG_SYSTEM, "particles", g_Particles); g_Renderer.SetOptionBool(CRenderer::OPT_FOG, g_Fog); @@ -610,6 +612,8 @@ g_ConfigDB.SetValueBool(CFG_SYSTEM, "preferglsl", g_PreferGLSL); g_Renderer.SetOptionBool(CRenderer::OPT_POSTPROC, g_PostProc); g_ConfigDB.SetValueBool(CFG_SYSTEM, "postproc", g_PostProc); + g_Renderer.SetOptionBool(CRenderer::OPT_DOF, g_DOF); + g_ConfigDB.SetValueBool(CFG_SYSTEM, "dof", g_DOF); g_Renderer.SetOptionBool(CRenderer::OPT_SMOOTHLOS, g_SmoothLOS); g_ConfigDB.SetValueBool(CFG_SYSTEM, "smoothlos", g_SmoothLOS); Index: source/renderer/PostprocManager.h =================================================================== --- source/renderer/PostprocManager.h +++ source/renderer/PostprocManager.h @@ -40,6 +40,9 @@ // Indicates which of the ping-pong buffers is used for reading and which for drawing. bool m_WhichBuffer; + // Indicates whether the current map has HDR enabled. + bool m_MapHDR; + // The name and shader technique we are using. "default" name means no technique is used // (i.e. while we do allocate the buffers, no effects are rendered). CStrW m_PostProcEffect; @@ -61,7 +64,7 @@ // GPU-based Gaussian blur in two passes. inOutTex contains the input image and will be filled // with the blurred image. tempTex must have the same size as inOutTex. // inWidth and inHeight are the dimensions of the images in texels. - void ApplyBlurGauss(GLuint inOutTex, GLuint tempTex, int inWidth, int inHeight); + void ApplyBlurGauss(GLuint inOutTex, GLuint tempTex, int inWidth, int inHeight, bool bigBlur); // Applies a pass of a given effect to the entire current framebuffer. The shader is // provided with a number of general-purpose variables, including the rendered screen so far, @@ -99,6 +102,9 @@ // Sets the current effect. void SetPostEffect(const CStrW& name); + // Refreshes the shaders. + void MakeShadersDirty(); + // Clears the two color buffers and depth buffer, and redirects all rendering // to our textures instead of directly to the system framebuffer. // @note CPostprocManager must be initialized first Index: source/renderer/PostprocManager.cpp =================================================================== --- source/renderer/PostprocManager.cpp +++ source/renderer/PostprocManager.cpp @@ -37,7 +37,7 @@ #if !CONFIG2_GLES CPostprocManager::CPostprocManager() - : m_IsInitialized(false), m_PingFbo(0), m_PongFbo(0), m_PostProcEffect(L"default"), m_ColorTex1(0), m_ColorTex2(0), + : m_IsInitialized(false), m_PingFbo(0), m_PongFbo(0), m_PostProcEffect(L"None"), m_ColorTex1(0), m_ColorTex2(0), m_DepthTex(0), m_BloomFbo(0), m_BlurTex2a(0), m_BlurTex2b(0), m_BlurTex4a(0), m_BlurTex4b(0), m_BlurTex8a(0), m_BlurTex8b(0), m_WhichBuffer(true) { @@ -102,15 +102,17 @@ { Cleanup(); - #define GEN_BUFFER_RGBA(name, w, h) \ - glGenTextures(1, (GLuint*)&name); \ - glBindTexture(GL_TEXTURE_2D, name); \ - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); \ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); \ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); \ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); \ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if (!g_Renderer.m_Options.m_Postproc) return; // Don't create textures if we're not going to use them. +#define GEN_BUFFER_RGBA(name, w, h) \ + glGenTextures(1, (GLuint*)&name); \ + glBindTexture(GL_TEXTURE_2D, name); \ + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); \ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); \ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); \ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); \ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + // Two fullscreen ping-pong textures. GEN_BUFFER_RGBA(m_ColorTex1, m_Width, m_Height); GEN_BUFFER_RGBA(m_ColorTex2, m_Width, m_Height); @@ -128,7 +130,7 @@ GEN_BUFFER_RGBA(m_BlurTex8a, m_Width / 8, m_Height / 8); GEN_BUFFER_RGBA(m_BlurTex8b, m_Width / 8, m_Height / 8); - #undef GEN_BUFFER_RGBA +#undef GEN_BUFFER_RGBA // Allocate the Depth/Stencil texture. glGenTextures(1, (GLuint*)&m_DepthTex); @@ -135,7 +137,7 @@ glBindTexture(GL_TEXTURE_2D, m_DepthTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8_EXT, m_Width, m_Height, - 0, GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT, 0); + 0, GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -151,10 +153,10 @@ pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_TEXTURE_2D, m_ColorTex1, 0); + GL_TEXTURE_2D, m_ColorTex1, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, m_DepthTex, 0); + GL_TEXTURE_2D, m_DepthTex, 0); GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) @@ -166,10 +168,10 @@ pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_TEXTURE_2D, m_ColorTex2, 0); + GL_TEXTURE_2D, m_ColorTex2, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, m_DepthTex, 0); + GL_TEXTURE_2D, m_DepthTex, 0); status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) @@ -182,7 +184,7 @@ /* pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_TEXTURE_2D, m_BloomTex1, 0); + GL_TEXTURE_2D, m_BloomTex1, 0); status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) @@ -254,7 +256,7 @@ tech->EndPass(); } -void CPostprocManager::ApplyBlurGauss(GLuint inOutTex, GLuint tempTex, int inWidth, int inHeight) +void CPostprocManager::ApplyBlurGauss(GLuint inOutTex, GLuint tempTex, int inWidth, int inHeight, bool bigBlur) { // Set tempTex as our rendering target. pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo); @@ -263,6 +265,7 @@ // Get bloom shader, for a horizontal Gaussian blur pass. CShaderDefines defines2; defines2.Add(str_BLOOM_PASS_H, str_1); + if (bigBlur) defines2.Add(str_BIG_BLUR, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, g_Renderer.GetSystemShaderDefines(), defines2); @@ -309,6 +312,7 @@ // Get bloom shader, for a vertical Gaussian blur pass. CShaderDefines defines3; defines3.Add(str_BLOOM_PASS_V, str_1); + if (bigBlur) defines3.Add(str_BIG_BLUR, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, g_Renderer.GetSystemShaderDefines(), defines3); @@ -340,19 +344,32 @@ int width = m_Width, height = m_Height; - #define SCALE_AND_BLUR(tex1, tex2, temptex) \ + #define SCALE_AND_BLUR_3_TAP(tex1, tex2, temptex) \ ApplyBlurDownscale2x(tex1, tex2, width, height); \ width /= 2; \ height /= 2; \ - ApplyBlurGauss(tex2, temptex, width, height); + ApplyBlurGauss(tex2, temptex, width, height, false); \ + if (g_Renderer.m_Options.m_DOF) ApplyBlurDownscale2x(tex1, temptex, width * 2, height * 2); - // We do the same thing for each scale, incrementally adding more and more blur. - SCALE_AND_BLUR(m_WhichBuffer ? m_ColorTex1 : m_ColorTex2, m_BlurTex2a, m_BlurTex2b); - SCALE_AND_BLUR(m_BlurTex2a, m_BlurTex4a, m_BlurTex4b); - SCALE_AND_BLUR(m_BlurTex4a, m_BlurTex8a, m_BlurTex8b); + #define SCALE_AND_BLUR_5_TAP(tex1, tex2, temptex) \ + ApplyBlurDownscale2x(tex1, tex2, width, height); \ + width /= 2; \ + height /= 2; \ + ApplyBlurGauss(tex2, temptex, width, height, true); - #undef SCALE_AND_BLUR + // Apply a small blur for dof and cache an extra non-blurred downscaled image for smooth interpolation. + SCALE_AND_BLUR_3_TAP(m_WhichBuffer ? m_ColorTex1 : m_ColorTex2, m_BlurTex2a, m_BlurTex2b); + + // Apply a large blur for bloom. + if (m_MapHDR && g_LightEnv.m_Bloom < 0.2f) + { + SCALE_AND_BLUR_5_TAP(m_BlurTex2a, m_BlurTex4a, m_BlurTex4b); + SCALE_AND_BLUR_5_TAP(m_BlurTex4a, m_BlurTex8a, m_BlurTex8b); + } + #undef SCALE_AND_BLUR_3_TAP + #undef SCALE_AND_BLUR_5_TAP + pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, originalFBO); } @@ -424,10 +441,10 @@ shader->BindTexture(str_depthTex, m_DepthTex); + shader->BindTexture(str_downscaleTex2, m_BlurTex2b); shader->BindTexture(str_blurTex2, m_BlurTex2a); shader->BindTexture(str_blurTex4, m_BlurTex4a); shader->BindTexture(str_blurTex8, m_BlurTex8a); - shader->Uniform(str_width, m_Width); shader->Uniform(str_height, m_Height); shader->Uniform(str_zNear, g_Game->GetView()->GetNear()); @@ -434,7 +451,7 @@ shader->Uniform(str_zFar, g_Game->GetView()->GetFar()); shader->Uniform(str_brightness, g_LightEnv.m_Brightness); - shader->Uniform(str_hdr, g_LightEnv.m_Contrast); + shader->Uniform(str_contrast, g_LightEnv.m_Contrast); shader->Uniform(str_saturation, g_LightEnv.m_Saturation); shader->Uniform(str_bloom, g_LightEnv.m_Bloom); @@ -475,10 +492,6 @@ { ENSURE(m_IsInitialized); - // Don't do anything if we are using the default effect. - if (m_PostProcEffect == L"default") - return; - pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, 0, 0); @@ -526,7 +539,7 @@ effects.push_back(path.Basename().string()); // Add the default "null" effect to the list. - effects.push_back(L"default"); + effects.push_back(L"None"); sort(effects.begin(), effects.end()); @@ -535,16 +548,32 @@ void CPostprocManager::SetPostEffect(const CStrW& name) { + if (name == L"default" || name == L"None" || name == L"DOF") + { + m_MapHDR = false; + m_PostProcEffect = L"None"; + }else if (name == L"hdr" || name == L"HDR") + { + m_MapHDR = true; + m_PostProcEffect = L"HDR"; + }else + { + // Custom effects are not actually supported without modifying PostprocManager. This is a stub! + //CStrW n = L"postproc/" + name; + } + MakeShadersDirty(); +} + +void CPostprocManager::MakeShadersDirty() +{ if (m_IsInitialized) { - if (name != L"default") - { - CStrW n = L"postproc/" + name; - m_PostProcTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(n.ToUTF8())); - } + CStrW n = L"postproc/HDR"; + CShaderDefines techdefines; + if (g_Renderer.m_Options.m_DOF) techdefines.Add(str_USE_DOF, str_1); + if (m_MapHDR) techdefines.Add(str_USE_HDR, str_1); + m_PostProcTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(n.ToUTF8()), g_Renderer.GetSystemShaderDefines(), techdefines); } - - m_PostProcEffect = name; } #else Index: source/renderer/RenderModifiers.cpp =================================================================== --- source/renderer/RenderModifiers.cpp +++ source/renderer/RenderModifiers.cpp @@ -79,10 +79,10 @@ if (GetShadowMap() && shader->GetTextureBinding(str_shadowTex).Active()) { shader->BindTexture(str_shadowTex, GetShadowMap()->GetTexture()); + shader->BindTexture(str_blockerTex, GetShadowMap()->GetBlockerTexture()); shader->Uniform(str_shadowTransform, GetShadowMap()->GetTextureMatrix()); - int width = GetShadowMap()->GetWidth(); - int height = GetShadowMap()->GetHeight(); - shader->Uniform(str_shadowScale, width, height, 1.0f / width, 1.0f / height); + const CVector4D& shadowscale = GetShadowMap()->GetShadowScale(); + shader->Uniform(str_shadowScale, shadowscale.X, shadowscale.Y, shadowscale.Z, shadowscale.W); } if (GetLightEnv()) Index: source/renderer/Renderer.h =================================================================== --- source/renderer/Renderer.h +++ source/renderer/Renderer.h @@ -83,6 +83,7 @@ OPT_WATERREFRACTION, OPT_SHADOWSONWATER, OPT_SHADOWPCF, + OPT_SHADOWPCSS, OPT_PARTICLES, OPT_PREFERGLSL, OPT_FOG, @@ -90,6 +91,7 @@ OPT_SHOWSKY, OPT_SMOOTHLOS, OPT_POSTPROC, + OPT_DOF, OPT_DISPLAYFRUSTUM, }; @@ -151,6 +153,7 @@ bool m_ShadowAlphaFix; bool m_ARBProgramShadow; bool m_ShadowPCF; + bool m_ShadowPCSS; bool m_Particles; bool m_PreferGLSL; bool m_ForceAlphaTest; @@ -160,6 +163,7 @@ bool m_SmoothLOS; bool m_ShowSky; bool m_Postproc; + bool m_DOF; bool m_DisplayFrustum; } m_Options; Index: source/renderer/Renderer.cpp =================================================================== --- source/renderer/Renderer.cpp +++ source/renderer/Renderer.cpp @@ -440,6 +440,7 @@ m_Options.m_ShadowAlphaFix = true; m_Options.m_ARBProgramShadow = true; m_Options.m_ShadowPCF = false; + m_Options.m_ShadowPCSS = false; m_Options.m_Particles = false; m_Options.m_Silhouettes = false; m_Options.m_PreferGLSL = false; @@ -573,8 +574,10 @@ m->globalContext.Add(str_USE_SHADOW, str_1); if (m_Caps.m_ARBProgramShadow && m_Options.m_ARBProgramShadow) m->globalContext.Add(str_USE_FP_SHADOW, str_1); - if (m_Options.m_ShadowPCF) + if (m_Options.m_ShadowPCF && !m_Options.m_ShadowPCSS) m->globalContext.Add(str_USE_SHADOW_PCF, str_1); + if (m_Options.m_ShadowPCF && m_Options.m_ShadowPCSS) + m->globalContext.Add(str_USE_SHADOW_PCSS, str_1); #if !CONFIG2_GLES m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1); #endif @@ -663,6 +666,8 @@ if (m_Options.m_Postproc) m->postprocManager.Initialize(); + m->shadow.RecreateTexture(); + return true; } @@ -669,12 +674,12 @@ // resize renderer view void CRenderer::Resize(int width, int height) { + m_Width = width; + m_Height = height; + // need to recreate the shadow map object to resize the shadow texture m->shadow.RecreateTexture(); - m_Width = width; - m_Height = height; - m->postprocManager.Resize(); m_WaterManager->Resize(); @@ -717,7 +722,13 @@ case OPT_SHADOWPCF: m_Options.m_ShadowPCF = value; MakeShadersDirty(); + if (m_Options.m_ShadowPCSS) m->shadow.RecreateTexture(); break; + case OPT_SHADOWPCSS: + m_Options.m_ShadowPCSS = value; + MakeShadersDirty(); + m->shadow.RecreateTexture(); + break; case OPT_PARTICLES: m_Options.m_Particles = value; break; @@ -741,7 +752,12 @@ break; case OPT_POSTPROC: m_Options.m_Postproc = value; + m->postprocManager.Resize(); break; + case OPT_DOF: + m_Options.m_DOF = value; + m->postprocManager.MakeShadersDirty(); + break; case OPT_DISPLAYFRUSTUM: m_Options.m_DisplayFrustum = value; break; @@ -774,6 +790,8 @@ return m_Options.m_WaterShadows; case OPT_SHADOWPCF: return m_Options.m_ShadowPCF; + case OPT_SHADOWPCSS: + return m_Options.m_ShadowPCSS; case OPT_PARTICLES: return m_Options.m_Particles; case OPT_PREFERGLSL: @@ -788,6 +806,8 @@ return m_Options.m_SmoothLOS; case OPT_POSTPROC: return m_Options.m_Postproc; + case OPT_DOF: + return m_Options.m_DOF; case OPT_DISPLAYFRUSTUM: return m_Options.m_DisplayFrustum; default: @@ -1237,10 +1257,10 @@ else { // Render terrain and models + RenderModels(context, CULL_REFLECTIONS); + ogl_WarnIfError(); RenderPatches(context, CULL_REFLECTIONS); ogl_WarnIfError(); - RenderModels(context, CULL_REFLECTIONS); - ogl_WarnIfError(); RenderTransparentModels(context, CULL_REFLECTIONS, TRANSPARENT, true); ogl_WarnIfError(); } @@ -1311,10 +1331,10 @@ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Render terrain and models + RenderModels(context, CULL_REFRACTIONS); + ogl_WarnIfError(); RenderPatches(context, CULL_REFRACTIONS); ogl_WarnIfError(); - RenderModels(context, CULL_REFRACTIONS); - ogl_WarnIfError(); RenderTransparentModels(context, CULL_REFRACTIONS, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); @@ -1531,56 +1551,50 @@ } } + // Render submitted terrain patches and models + RenderModels(context, cullGroup); + ogl_WarnIfError(); + + RenderPatches(context, cullGroup); + ogl_WarnIfError(); + if (m_Options.m_ShowSky) { m->skyManager.RenderSky(); } - // render submitted patches and models - RenderPatches(context, cullGroup); + // Render the occluding parts of alpha-blended objects. + RenderTransparentModels(context, cullGroup, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); - // render debug-related terrain overlays + // Render debug-related terrain overlays ITerrainOverlay::RenderOverlaysBeforeWater(); ogl_WarnIfError(); - // render other debug-related overlays before water (so they can be seen when underwater) + // Render other debug-related overlays before water (so they can be seen when underwater) m->overlayRenderer.RenderOverlaysBeforeWater(); ogl_WarnIfError(); - RenderModels(context, cullGroup); - ogl_WarnIfError(); - // render water if (m_WaterManager->m_RenderWater && g_Game && waterScissor.GetVolume() > 0) { - // render transparent stuff, but only the solid parts that can occlude block water - RenderTransparentModels(context, cullGroup, TRANSPARENT_OPAQUE, false); - ogl_WarnIfError(); - m->terrainRenderer.RenderWater(context, cullGroup, &m->shadow); ogl_WarnIfError(); - - // render transparent stuff again, but only the blended parts that overlap water - RenderTransparentModels(context, cullGroup, TRANSPARENT_BLEND, false); - ogl_WarnIfError(); } - else - { - // render transparent stuff, so it can overlap models/terrain - RenderTransparentModels(context, cullGroup, TRANSPARENT, false); - ogl_WarnIfError(); - } - // render debug-related terrain overlays + // Render the transparent parts of alpha-blended objects. + RenderTransparentModels(context, cullGroup, TRANSPARENT_BLEND, false); + ogl_WarnIfError(); + + // Render debug-related terrain overlays ITerrainOverlay::RenderOverlaysAfterWater(cullGroup); ogl_WarnIfError(); - // render some other overlays after water (so they can be displayed on top of water) + // Render some other overlays after water (so they can be displayed on top of water) m->overlayRenderer.RenderOverlaysAfterWater(); ogl_WarnIfError(); - // particles are transparent so render after water + // Particles are transparent so render after water if (m_Options.m_Particles) { RenderParticles(cullGroup); Index: source/renderer/ShadowMap.h =================================================================== --- source/renderer/ShadowMap.h +++ source/renderer/ShadowMap.h @@ -136,6 +136,14 @@ GLuint GetTexture() const; /** + * GetBlockerTexture: Retrieve the OpenGL texture object name that contains the shadow map copy used + * for blocker search by PCSS. + * + * @return the texture name of the shadow map blocker search texture + */ + GLuint GetBlockerTexture() const; + + /** * GetTextureMatrix: Retrieve the world-space to shadow map texture coordinates * transformation matrix. * @@ -145,6 +153,14 @@ const CMatrix3D& GetTextureMatrix() const; /** + * GetLightspaceScale: Retrieve the 3D scale factor for scaling shadow filter kernels. + * + * @return the vector representing the relative scale of the shadow map resolution + * and the bounds of the light space frustum. + */ + const CVector4D& GetShadowScale() const; + + /** * Visualize shadow mapping calculations to help in * debugging and optimal shadow map usage. */ Index: source/renderer/ShadowMap.cpp =================================================================== --- source/renderer/ShadowMap.cpp +++ source/renderer/ShadowMap.cpp @@ -52,8 +52,10 @@ int DepthTextureBits; // the EXT_framebuffer_object framebuffer GLuint Framebuffer; + GLuint BlockerFramebuffer; // handle of shadow map GLuint Texture; + GLuint blockerTexture; // width, height of shadow map int Width, Height; // Shadow map quality (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High) @@ -78,6 +80,8 @@ CBoundingBoxAligned ShadowRenderBound; + CVector4D ShadowScale; + // Camera transformed into light space CCamera LightspaceCamera; @@ -106,7 +110,9 @@ { m = new ShadowMapInternals; m->Framebuffer = 0; + m->BlockerFramebuffer = 0; m->Texture = 0; + m->blockerTexture = 0; m->DummyTexture = 0; m->Width = 0; m->Height = 0; @@ -129,10 +135,14 @@ { if (m->Texture) glDeleteTextures(1, &m->Texture); + if (m->blockerTexture) + glDeleteTextures(1, &m->blockerTexture); if (m->DummyTexture) glDeleteTextures(1, &m->DummyTexture); if (m->Framebuffer) pglDeleteFramebuffersEXT(1, &m->Framebuffer); + if (m->BlockerFramebuffer) + pglDeleteFramebuffersEXT(1, &m->BlockerFramebuffer); delete m; } @@ -144,16 +154,22 @@ { if (m->Texture) glDeleteTextures(1, &m->Texture); + if (m->blockerTexture) + glDeleteTextures(1, &m->blockerTexture); if (m->DummyTexture) glDeleteTextures(1, &m->DummyTexture); if (m->Framebuffer) pglDeleteFramebuffersEXT(1, &m->Framebuffer); + if (m->BlockerFramebuffer) + pglDeleteFramebuffersEXT(1, &m->BlockerFramebuffer); m->Texture = 0; + m->blockerTexture = 0; m->DummyTexture = 0; m->Framebuffer = 0; + m->BlockerFramebuffer = 0; - // (Texture will be constructed in next SetupFrame) + m->CreateTexture(); } ////////////////////////////////////////////////////////////////////////////// @@ -160,9 +176,6 @@ // SetupFrame: camera and light direction for this frame void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir) { - if (!m->Texture) - m->CreateTexture(); - CVector3D z = lightdir; CVector3D y; CVector3D x = camera.m_Orientation.GetIn(); @@ -306,6 +319,7 @@ ShadowRenderBound[1].X = ceil(ShadowRenderBound[1].X / boundInc) * boundInc; ShadowRenderBound[1].Y = ceil(ShadowRenderBound[1].Y / boundInc) * boundInc; + // Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering CVector3D scale = ShadowRenderBound[1] - ShadowRenderBound[0]; CVector3D shift = (ShadowRenderBound[1] + ShadowRenderBound[0]) * -0.5; @@ -324,6 +338,7 @@ // make sure a given world position falls on a consistent shadowmap texel fractional offset float offsetX = fmod(ShadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth)); float offsetY = fmod(ShadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight)); + LightProjection.SetZero(); LightProjection._11 = scale.X; @@ -351,6 +366,12 @@ lightToTex._34 = -ShadowRenderBound[0].Z * texscalez; lightToTex._44 = 1.0; + // Set up the shadow scaling factors for constant-width PCF/PCSS filtering. + ShadowScale.X = (float)Width / (2048.f * (ShadowRenderBound[1].X - ShadowRenderBound[0].X)/100.f); + ShadowScale.Y = (float)Width / (2048.f * (ShadowRenderBound[1].Y - ShadowRenderBound[0].Y)/100.f); + ShadowScale.Z = (ShadowRenderBound[1].Z - ShadowRenderBound[0].Z) / 100.f; + ShadowScale.W = 1.f / (float)Width; + TextureMatrix = lightToTex * LightTransform; } @@ -360,23 +381,6 @@ // Create the shadow map void ShadowMapInternals::CreateTexture() { - // Cleanup - if (Texture) - { - glDeleteTextures(1, &Texture); - Texture = 0; - } - if (DummyTexture) - { - glDeleteTextures(1, &DummyTexture); - DummyTexture = 0; - } - if (Framebuffer) - { - pglDeleteFramebuffersEXT(1, &Framebuffer); - Framebuffer = 0; - } - // save the caller's FBO glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &SavedViewFBO); @@ -395,20 +399,16 @@ int shadow_map_size = (int)round_up_to_pow2((unsigned)std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())); switch (QualityLevel) { - // Very Low - case -2: - shadow_map_size /= 4; - break; // Low - case -1: + case 0: shadow_map_size /= 2; break; // High - case 1: + case 2: shadow_map_size *= 2; break; // Ultra - case 2: + case 3: shadow_map_size *= 4; break; // Medium as is @@ -426,19 +426,24 @@ EffectiveHeight = Height; const char* formatname; + GLenum format; - switch(DepthTextureBits) - { - case 16: formatname = "DEPTH_COMPONENT16"; break; - case 24: formatname = "DEPTH_COMPONENT24"; break; - case 32: formatname = "DEPTH_COMPONENT32"; break; - default: formatname = "DEPTH_COMPONENT"; break; - } + #if CONFIG2_GLES + formatname = "DEPTH_COMPONENT"; + format = GL_DEPTH_COMPONENT; + #else + switch (DepthTextureBits) + { + case 16: formatname = "DEPTH_COMPONENT16"; format = GL_DEPTH_COMPONENT16; break; + case 24: formatname = "DEPTH_COMPONENT24"; format = GL_DEPTH_COMPONENT24; break; + case 32: formatname = "DEPTH_COMPONENT32"; format = GL_DEPTH_COMPONENT32; break; + default: formatname = "DEPTH_COMPONENT16"; format = GL_DEPTH_COMPONENT16; break; + } + #endif - LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)", + LOGMESSAGE("Creating shadow textures (size %d x %d) (format = %s)", Width, Height, formatname); - if (g_Renderer.m_Options.m_ShadowAlphaFix) { glGenTextures(1, &DummyTexture); @@ -453,20 +458,8 @@ glGenTextures(1, &Texture); g_Renderer.BindTexture(0, Texture); - GLenum format; + -#if CONFIG2_GLES - format = GL_DEPTH_COMPONENT; -#else - switch (DepthTextureBits) - { - case 16: format = GL_DEPTH_COMPONENT16; break; - case 24: format = GL_DEPTH_COMPONENT24; break; - case 32: format = GL_DEPTH_COMPONENT32; break; - default: format = GL_DEPTH_COMPONENT; break; - } -#endif - glTexImage2D(GL_TEXTURE_2D, 0, format, Width, Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); // GLES requires type == UNSIGNED_SHORT or UNSIGNED_INT @@ -474,21 +467,21 @@ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#if CONFIG2_GLES - // GLES doesn't do depth comparisons, so treat it as a - // basic unfiltered depth texture - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -#else - // Enable automatic depth comparisons - glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + #if CONFIG2_GLES + // GLES doesn't do depth comparisons, so treat it as a + // basic unfiltered depth texture + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + #else + // Enable automatic depth comparisons + glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - // Use GL_LINEAR to trigger automatic PCF on some devices - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -#endif + // Use GL_LINEAR for higher quality sampling. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + #endif ogl_WarnIfError(); @@ -502,32 +495,74 @@ { pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, DummyTexture, 0); } - else - { -#if CONFIG2_GLES -#warning TODO: figure out whether the glDrawBuffer/glReadBuffer stuff is needed, since it is not supported by GLES -#else - glDrawBuffer(GL_NONE); -#endif - } -#if !CONFIG2_GLES - glReadBuffer(GL_NONE); -#endif - ogl_WarnIfError(); GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); - pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, SavedViewFBO); - if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { - LOGWARNING("Framebuffer object incomplete: 0x%04X", status); + LOGWARNING("Shadow Map Framebuffer object incomplete: 0x%04X", status); // Disable shadow rendering (but let the user try again if they want) g_Renderer.m_Options.m_Shadows = false; + g_Renderer.MakeShadersDirty(); } + + if (g_Renderer.m_Options.m_ShadowPCF && g_Renderer.m_Options.m_ShadowPCSS) + { + // generate texture for PCSS blocker search + pglGenFramebuffersEXT(1, &BlockerFramebuffer); + glGenTextures(1, &blockerTexture); + g_Renderer.BindTexture(0, blockerTexture); + + glTexImage2D(GL_TEXTURE_2D, 0, format, Width, Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); + // GLES requires type == UNSIGNED_SHORT or UNSIGNED_INT + + // set texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + #if CONFIG2_GLES + // GLES doesn't do depth comparisons, so treat it as a + // basic unfiltered depth texture + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + #else + // Disable automatic depth comparisons + glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); + + // Use GL_NEAREST for accurate blocker search. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + #endif + + ogl_WarnIfError(); + + // bind to framebuffer object + glBindTexture(GL_TEXTURE_2D, 0); + pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, BlockerFramebuffer); + + pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, blockerTexture, 0); + + ogl_WarnIfError(); + + status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + + if (status != GL_FRAMEBUFFER_COMPLETE_EXT) + { + LOGWARNING("PCSS Blocker Search Framebuffer object incomplete: 0x%04X", status); + + // Disable shadow rendering (but let the user try again if they want) + g_Renderer.m_Options.m_ShadowPCSS = false; + g_Renderer.MakeShadersDirty(); + g_ConfigDB.SetValueBool(CFG_USER, "shadowpcss", false); + + } + } + + pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, SavedViewFBO); } @@ -586,6 +621,14 @@ // Finish rendering into shadow map texture void ShadowMap::EndRender() { + if (g_Renderer.m_Options.m_ShadowPCF && g_Renderer.m_Options.m_ShadowPCSS && m->BlockerFramebuffer) + { + // Copy the shadow map for PCSS blocker search + pglBindFramebufferEXT(GL_READ_FRAMEBUFFER, m->Framebuffer); + pglBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, m->BlockerFramebuffer); + pglBlitFramebufferEXT(0, 0, m->EffectiveWidth, m->EffectiveHeight, 0, 0, m->EffectiveWidth, m->EffectiveHeight, GL_DEPTH_BUFFER_BIT, GL_NEAREST); + } + glDisable(GL_SCISSOR_TEST); g_Renderer.SetViewCamera(m->SavedViewCamera); @@ -609,12 +652,22 @@ return m->Texture; } +GLuint ShadowMap::GetBlockerTexture() const +{ + return m->blockerTexture; +} + const CMatrix3D& ShadowMap::GetTextureMatrix() const { return m->TextureMatrix; } +const CVector4D& ShadowMap::GetShadowScale() const +{ + return m->ShadowScale; +} + /////////////////////////////////////////////////////////////////////////////////////////////////// // Depth texture bits int ShadowMap::GetDepthTextureBits() const @@ -626,14 +679,8 @@ { if (bits != m->DepthTextureBits) { - if (m->Texture) - { - glDeleteTextures(1, &m->Texture); - m->Texture = 0; - } - m->Width = m->Height = 0; - m->DepthTextureBits = bits; + RecreateTexture(); } } Index: source/renderer/TerrainRenderer.cpp =================================================================== --- source/renderer/TerrainRenderer.cpp +++ source/renderer/TerrainRenderer.cpp @@ -458,10 +458,10 @@ if (shadow) { shader->BindTexture(str_shadowTex, shadow->GetTexture()); + shader->BindTexture(str_blockerTex, shadow->GetBlockerTexture()); shader->Uniform(str_shadowTransform, shadow->GetTextureMatrix()); - int width = shadow->GetWidth(); - int height = shadow->GetHeight(); - shader->Uniform(str_shadowScale, width, height, 1.0f / width, 1.0f / height); + const CVector4D& shadowscale = shadow->GetShadowScale(); + shader->Uniform(str_shadowScale, shadowscale.X, shadowscale.Y, shadowscale.Z, shadowscale.W); } CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); Index: source/renderer/scripting/JSInterface_Renderer.h =================================================================== --- source/renderer/scripting/JSInterface_Renderer.h +++ source/renderer/scripting/JSInterface_Renderer.h @@ -33,6 +33,7 @@ DECLARE_BOOLEAN_SCRIPT_SETTING(Shadows); DECLARE_BOOLEAN_SCRIPT_SETTING(ShadowPCF); + DECLARE_BOOLEAN_SCRIPT_SETTING(ShadowPCSS); DECLARE_BOOLEAN_SCRIPT_SETTING(Particles); DECLARE_BOOLEAN_SCRIPT_SETTING(PreferGLSL); DECLARE_BOOLEAN_SCRIPT_SETTING(WaterEffects); @@ -46,6 +47,7 @@ DECLARE_BOOLEAN_SCRIPT_SETTING(ShowSky); DECLARE_BOOLEAN_SCRIPT_SETTING(SmoothLOS); DECLARE_BOOLEAN_SCRIPT_SETTING(Postproc); + DECLARE_BOOLEAN_SCRIPT_SETTING(DOF); DECLARE_BOOLEAN_SCRIPT_SETTING(DisplayFrustum); void RegisterScriptFunctions(const ScriptInterface& scriptInterface); Index: source/renderer/scripting/JSInterface_Renderer.cpp =================================================================== --- source/renderer/scripting/JSInterface_Renderer.cpp +++ source/renderer/scripting/JSInterface_Renderer.cpp @@ -38,6 +38,7 @@ IMPLEMENT_BOOLEAN_SCRIPT_SETTING(WATEREFFECTS, WaterEffects); IMPLEMENT_BOOLEAN_SCRIPT_SETTING(WATERFANCYEFFECTS, WaterFancyEffects); IMPLEMENT_BOOLEAN_SCRIPT_SETTING(SHADOWPCF, ShadowPCF); +IMPLEMENT_BOOLEAN_SCRIPT_SETTING(SHADOWPCSS, ShadowPCSS); IMPLEMENT_BOOLEAN_SCRIPT_SETTING(SHADOWS, Shadows); IMPLEMENT_BOOLEAN_SCRIPT_SETTING(WATERREALDEPTH, WaterRealDepth); IMPLEMENT_BOOLEAN_SCRIPT_SETTING(WATERREFLECTION, WaterReflection); @@ -48,6 +49,7 @@ IMPLEMENT_BOOLEAN_SCRIPT_SETTING(SHOWSKY, ShowSky); IMPLEMENT_BOOLEAN_SCRIPT_SETTING(SMOOTHLOS, SmoothLOS); IMPLEMENT_BOOLEAN_SCRIPT_SETTING(POSTPROC, Postproc); +IMPLEMENT_BOOLEAN_SCRIPT_SETTING(DOF, DOF); IMPLEMENT_BOOLEAN_SCRIPT_SETTING(DISPLAYFRUSTUM, DisplayFrustum); #undef IMPLEMENT_BOOLEAN_SCRIPT_SETTING @@ -79,6 +81,7 @@ scriptInterface.RegisterFunction("Renderer_RecreateShadowMap"); REGISTER_BOOLEAN_SCRIPT_SETTING(Shadows); REGISTER_BOOLEAN_SCRIPT_SETTING(ShadowPCF); + REGISTER_BOOLEAN_SCRIPT_SETTING(ShadowPCSS); REGISTER_BOOLEAN_SCRIPT_SETTING(Particles); REGISTER_BOOLEAN_SCRIPT_SETTING(PreferGLSL); REGISTER_BOOLEAN_SCRIPT_SETTING(WaterEffects); @@ -92,6 +95,7 @@ REGISTER_BOOLEAN_SCRIPT_SETTING(ShowSky); REGISTER_BOOLEAN_SCRIPT_SETTING(SmoothLOS); REGISTER_BOOLEAN_SCRIPT_SETTING(Postproc); + REGISTER_BOOLEAN_SCRIPT_SETTING(DOF); REGISTER_BOOLEAN_SCRIPT_SETTING(DisplayFrustum); }