diff --git a/vtm/resources/assets/shaders/extrusion_layer_ext.glsl b/vtm/resources/assets/shaders/extrusion_layer_ext.glsl index c83c7be3..95a8fe51 100644 --- a/vtm/resources/assets/shaders/extrusion_layer_ext.glsl +++ b/vtm/resources/assets/shaders/extrusion_layer_ext.glsl @@ -13,15 +13,31 @@ varying vec4 color; //varying float depth; const float ff = 255.0; +#ifdef SHADOW +uniform mat4 u_light_mvp; +varying vec4 v_shadow_coords; +#endif + /** * The diffuse of surface dependent on the light position. * * @param r_norm the normal vector of vertex's face */ -float diffuse(in vec3 r_norm) { +float diffuse(in vec3 r_norm, out bool hasLight) { float l = dot(normalize(r_norm), normalize(u_light)); + hasLight = l > 0.0; l = clamp((1.0 + l) / 2.0, 0.0, 1.0); - return(0.8 + l * 0.2); + #ifdef SHADOW + if (hasLight) { + //l = (l + (1.0 - r_norm.z)) * 0.5; + l = 0.8 + l * 0.2; + } else { + l = 0.5 + l * 0.3; + } + #else + l = 0.8 + l * 0.2; + #endif + return (l); } void main() { @@ -31,6 +47,8 @@ void main() { height = u_zlimit; } gl_Position = u_mvp * vec4(a_pos.xy, height, 1.0); + bool hasLight = false; + //depth = gl_Position.z; if (u_mode == -1) { ; @@ -40,7 +58,7 @@ void main() { // roof / depth pass r_norm = vec3(0.0, 0.0, 1.0); color = u_color[0] * u_alpha; - color.rgb *= diffuse(r_norm); + color.rgb *= diffuse(r_norm, hasLight); } else { float lightX = u_mode == 1 ? a_normal.y : a_normal.x; r_norm.x = (lightX / ff) * 2.0 - 1.0; @@ -59,7 +77,7 @@ void main() { // sides 2 - use 0x00ff color = u_color[2]; } - color.rgb *= diffuse(r_norm) * z * h; + color.rgb *= diffuse(r_norm, hasLight) * z * h; } color *= u_alpha; } else if (u_mode == 3) { @@ -67,6 +85,15 @@ void main() { float z = (0.98 - gl_Position.z * 0.02); color = u_color[3] * z; } + #ifdef SHADOW + if (hasLight) { + vec4 positionFromLight = u_light_mvp * a_pos; + v_shadow_coords = (positionFromLight / positionFromLight.w); + } else { + // Discard shadow on unlighted faces + v_shadow_coords = vec4(-1.0); + } + #endif } $$ @@ -76,6 +103,76 @@ precision highp float; #endif varying vec4 color; -void main() { - gl_FragColor = color; +#ifdef SHADOW +varying vec4 v_shadow_coords; // the coords in shadow map + +uniform sampler2D u_shadowMap; +uniform vec4 u_lightColor; +uniform float u_shadowRes; + +const bool DEBUG = false; + +const float transitionDistance = 0.05; // relative transition distance at the border of shadow tex +const float minTrans = 1.0 - transitionDistance; + +const int pcfCount = 2; // the number of surrounding pixels to smooth shadow +const float biasOffset = 0.005; // offset to remove shadow acne +const float pcfTexels = float((pcfCount * 2 + 1) * (pcfCount * 2 + 1)); + +#if GLVERSION == 20 +float decodeFloat (vec4 color) { + const vec4 bitShift = vec4( + 1.0 / (256.0 * 256.0 * 256.0), + 1.0 / (256.0 * 256.0), + 1.0 / 256.0, + 1.0 + ); + return dot(color, bitShift); +} +#endif +#endif + +void main() { + #ifdef SHADOW + float shadowX = abs((v_shadow_coords.x - 0.5) * 2.0); + float shadowY = abs((v_shadow_coords.y - 0.5) * 2.0); + if (shadowX > 1.0 || shadowY > 1.0) { + // Outside the light texture set to 0.0 + gl_FragColor = vec4(color.rgb * u_lightColor.rgb, color.a); + if (DEBUG) { + gl_FragColor = vec4(0.0, 1.0, 0.0, 0.1); + } + } else { + // Inside set to 1.0; make a transition to the border + float shadowOpacity = (shadowX < minTrans && shadowY < minTrans) ? 1.0 : + (1.0 - (max(shadowX - minTrans, shadowY - minTrans) / transitionDistance)); + float distanceToLight = clamp(v_shadow_coords.z - biasOffset, 0.0, 1.0); // avoid unexpected shadow + + // Smooth shadow at borders + float shadowDiffuse = 0.0; + float texelSize = 1.0 / u_shadowRes; + for (int x = -pcfCount; x <= pcfCount; x++) { + for (int y = -pcfCount; y <= pcfCount; y++) { + #if GLVERSION == 30 + float depth = texture2D(u_shadowMap, v_shadow_coords.xy + vec2(x, y) * texelSize).r; + #else + float depth = decodeFloat(texture2D(u_shadowMap, v_shadow_coords.xy + vec2(x, y) * texelSize)); + #endif + if (distanceToLight > depth) { + shadowDiffuse += 1.0; + } + } + } + shadowDiffuse /= pcfTexels; + shadowDiffuse *= shadowOpacity; + + if (DEBUG && shadowDiffuse < 1.0) { + gl_FragColor = vec4(shadowDiffuse, color.gb, 0.1); + } else { + gl_FragColor = vec4((color.rgb * u_lightColor.rgb) * (1.0 - u_lightColor.a * shadowDiffuse), color.a); + } + } + #else + gl_FragColor = color; + #endif } diff --git a/vtm/resources/assets/shaders/extrusion_layer_mesh.glsl b/vtm/resources/assets/shaders/extrusion_layer_mesh.glsl index 8f7d5ae4..411b2583 100644 --- a/vtm/resources/assets/shaders/extrusion_layer_mesh.glsl +++ b/vtm/resources/assets/shaders/extrusion_layer_mesh.glsl @@ -9,6 +9,11 @@ attribute vec4 a_pos; attribute vec2 a_normal; varying vec4 color; +#ifdef SHADOW +uniform mat4 u_light_mvp; +varying vec4 v_shadow_coords; +#endif + void main() { // change height by u_alpha vec4 pos = a_pos; @@ -28,18 +33,40 @@ void main() { float l = dot(r_norm, normalize(u_light)); - //l *= 0.8 + #ifdef SHADOW + bool hasLight = l > 0.0; + #endif + + //l *= 0.8; //vec3 opp_light_dir = normalize(vec3(-u_light.xy, u_light.z)); //l += dot(r_norm, opp_light_dir) * 0.2; // [-1,1] to range [0,1] l = (1.0 + l) / 2.0; + #ifdef SHADOW + if (hasLight) { + l = 0.75 + l * 0.25; + } else { + l = 0.5 + l * 0.3; + } + #else l = 0.75 + l * 0.25; + #endif // extreme fake-ssao by height l += (clamp(a_pos.z / 2048.0, 0.0, 0.1) - 0.05); color = vec4(u_color.rgb * (clamp(l, 0.0, 1.0)), u_color.a) * u_alpha; + + #ifdef SHADOW + if (hasLight) { + vec4 positionFromLight = u_light_mvp * a_pos; + v_shadow_coords = (positionFromLight / positionFromLight.w); + } else { + // Discard shadow on unlighted faces + v_shadow_coords = vec4(-1.0); + } + #endif } $$ @@ -49,6 +76,77 @@ precision highp float; #endif varying vec4 color; -void main() { - gl_FragColor = color; +#ifdef SHADOW +varying vec4 v_shadow_coords; // the coords in shadow map + +uniform sampler2D u_shadowMap; +uniform vec4 u_lightColor; +uniform float u_shadowRes; +uniform int u_mode; + +const bool DEBUG = false; + +const float transitionDistance = 0.05; // relative transition distance at the border of shadow tex +const float minTrans = 1.0 - transitionDistance; + +const int pcfCount = 2; // the number of surrounding pixels to smooth shadow +const float biasOffset = 0.005; // offset to remove shadow acne +const float pcfTexels = float((pcfCount * 2 + 1) * (pcfCount * 2 + 1)); + +#if GLVERSION == 20 +float decodeFloat (vec4 color) { + const vec4 bitShift = vec4( + 1.0 / (256.0 * 256.0 * 256.0), + 1.0 / (256.0 * 256.0), + 1.0 / 256.0, + 1.0 + ); + return dot(color, bitShift); +} +#endif +#endif + +void main() { + #ifdef SHADOW + float shadowX = abs((v_shadow_coords.x - 0.5) * 2.0); + float shadowY = abs((v_shadow_coords.y - 0.5) * 2.0); + if (shadowX > 1.0 || shadowY > 1.0) { + // Outside the light texture set to 0.0 + gl_FragColor = vec4(color.rgb * u_lightColor.rgb, color.a); + if (DEBUG) { + gl_FragColor = vec4(0.0, 1.0, 0.0, 0.1); + } + } else { + // Inside set to 1.0; make a transition to the border + float shadowOpacity = (shadowX < minTrans && shadowY < minTrans) ? 1.0 : + (1.0 - (max(shadowX - minTrans, shadowY - minTrans) / transitionDistance)); + float distanceToLight = clamp(v_shadow_coords.z - biasOffset, 0.0, 1.0); // avoid unexpected shadow + + // smooth shadow at borders + float shadowDiffuse = 0.0; + float texelSize = 1.0 / u_shadowRes; + for (int x = -pcfCount; x <= pcfCount; x++) { + for (int y = -pcfCount; y <= pcfCount; y++) { + #if GLVERSION == 30 + float depth = texture2D(u_shadowMap, v_shadow_coords.xy + vec2(x, y) * texelSize).r; + #else + float depth = decodeFloat(texture2D(u_shadowMap, v_shadow_coords.xy + vec2(x, y) * texelSize)); + #endif + if (distanceToLight > depth) { + shadowDiffuse += 1.0; + } + } + } + shadowDiffuse /= pcfTexels; + shadowDiffuse *= shadowOpacity; + + if (DEBUG && shadowDiffuse < 1.0) { + gl_FragColor = vec4(shadowDiffuse, color.gb, 0.1); + } else { + gl_FragColor = vec4((color.rgb * u_lightColor.rgb) * (1.0 - u_lightColor.a * shadowDiffuse), color.a); + } + } + #else + gl_FragColor = color; + #endif } diff --git a/vtm/resources/assets/shaders/extrusion_shadow_ground.glsl b/vtm/resources/assets/shaders/extrusion_shadow_ground.glsl new file mode 100644 index 00000000..f67dc91b --- /dev/null +++ b/vtm/resources/assets/shaders/extrusion_shadow_ground.glsl @@ -0,0 +1,91 @@ +#ifdef GLES +precision highp float; +#endif +attribute vec4 a_pos; +uniform mat4 u_mvp; +uniform mat4 u_light_mvp; + +varying vec4 v_shadow_coords; + +void main(void) { + gl_Position = u_mvp * a_pos; + vec4 positionFromLight = u_light_mvp * a_pos; + v_shadow_coords = (positionFromLight / positionFromLight.w); +} + +$$ + +#ifdef GLES +precision highp float; +#endif +varying vec4 v_shadow_coords; + +uniform sampler2D u_shadowMap; +uniform vec4 u_lightColor; +uniform float u_shadowRes; + +const bool DEBUG = false; + +const float transitionDistance = 0.05; // relative transition distance at the border of shadow tex +const float minTrans = 1.0 - transitionDistance; + +const int pcfCount = 2; // the number of surrounding pixels to smooth shadow +const float biasOffset = 0.005; // offset to remove shadow acne +const float pcfTexels = float((pcfCount * 2 + 1) * (pcfCount * 2 + 1)); + +#if GLVERSION == 20 +float decodeFloat (vec4 color) { + const vec4 bitShift = vec4( + 1.0 / (256.0 * 256.0 * 256.0), + 1.0 / (256.0 * 256.0), + 1.0 / 256.0, + 1.0 + ); + return dot(color, bitShift); +} +#endif + +void main() { + float shadowX = abs((v_shadow_coords.x - 0.5) * 2.0); + float shadowY = abs((v_shadow_coords.y - 0.5) * 2.0); + if (shadowX > 1.0 || shadowY > 1.0) { + // Outside the light texture set to 0.0 + gl_FragColor = vec4(u_lightColor.rgb, 1.0); + if (DEBUG) { + gl_FragColor = vec4(0.0, 1.0, 0.0, 0.1); + } + } else { + // Inside set to 1.0; make a transition to the border + float shadowOpacity = (shadowX < minTrans && shadowY < minTrans) ? 1.0 : + (1.0 - (max(shadowX - minTrans, shadowY - minTrans) / transitionDistance)); + #if GLVERSION == 30 + float distanceToLight = v_shadow_coords.z; // remove shadow acne + #else + float distanceToLight = v_shadow_coords.z - biasOffset; // remove shadow acne + #endif + distanceToLight = clamp(distanceToLight, 0.0, 1.0); // avoid unexpected far shadow + // smooth shadow at borders + float shadowDiffuse = 0.0; + float texelSize = 1.0 / u_shadowRes; + for (int x = -pcfCount; x <= pcfCount; x++) { + for (int y = -pcfCount; y <= pcfCount; y++) { + #if GLVERSION == 30 + float depth = texture2D(u_shadowMap, v_shadow_coords.xy + vec2(x, y) * texelSize).r; + #else + float depth = decodeFloat(texture2D(u_shadowMap, v_shadow_coords.xy + vec2(x, y) * texelSize)); + #endif + if (distanceToLight > depth) { + shadowDiffuse += 1.0; + } + } + } + shadowDiffuse /= pcfTexels; + shadowDiffuse *= shadowOpacity; + + if (DEBUG && shadowDiffuse < 1.0) { + gl_FragColor = vec4(shadowDiffuse, 0.0, 0.0, 0.1); + } else { + gl_FragColor = vec4(u_lightColor.rgb * (1.0 - u_lightColor.a * shadowDiffuse), 1.0); + } + } +} diff --git a/vtm/resources/assets/shaders/extrusion_shadow_light.glsl b/vtm/resources/assets/shaders/extrusion_shadow_light.glsl new file mode 100644 index 00000000..7292e2d9 --- /dev/null +++ b/vtm/resources/assets/shaders/extrusion_shadow_light.glsl @@ -0,0 +1,53 @@ +#ifdef GLES +precision highp float; +precision highp int; +#endif +uniform mat4 u_mvp; +uniform vec4 u_color[4]; +uniform int u_mode; +uniform float u_zlimit; +attribute vec4 a_pos; + +void main(void) { + gl_Position = u_mvp * a_pos; +} + +$$ + +#ifdef GLES +precision highp float; +precision highp int; +#endif + +uniform float u_alpha; + +#if GLVERSION == 20 +vec4 encodeFloat (float depth) { + const vec4 bitShift = vec4( + 256.0 * 256.0 * 256.0, + 256.0 * 256.0, + 256.0, + 1.0 + ); + const vec4 bitMask = vec4( + 0.0, + 1.0 / 256.0, + 1.0 / 256.0, + 1.0 / 256.0 + ); + vec4 comp = fract(depth * bitShift); + comp -= comp.xxyz * bitMask; + return comp; +} +#endif + +void main(void) { + if (u_alpha < 0.8) + discard; // remove shadow when alpha is too small + + #if GLVERSION == 30 + gl_FragColor = vec4(1.0); + #else + gl_FragColor = encodeFloat(gl_FragCoord.z); + #endif +} diff --git a/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java b/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java index e3dd4b51..03f61d55 100644 --- a/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java +++ b/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java @@ -2,7 +2,7 @@ * Copyright 2013 Hannes Janetzek * Copyright 2016-2018 devemux86 * Copyright 2016 Robin Boldt - * Copyright 2017-2018 Gustl22 + * Copyright 2017-2019 Gustl22 * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -32,6 +32,7 @@ import org.oscim.renderer.OffscreenRenderer; import org.oscim.renderer.OffscreenRenderer.Mode; import org.oscim.renderer.bucket.ExtrusionBuckets; import org.oscim.renderer.bucket.RenderBuckets; +import org.oscim.renderer.light.ShadowRenderer; import org.oscim.theme.IRenderTheme; import org.oscim.theme.styles.ExtrusionStyle; import org.oscim.theme.styles.RenderStyle; @@ -59,6 +60,11 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim */ public static boolean RAW_DATA = false; + /** + * Use shadow rendering. + */ + public static boolean SHADOW = false; + /** * Let vanish extrusions / meshes which are covered by others. * {@link org.oscim.renderer.bucket.RenderBucket#EXTRUSION}: roofs are always translucent. @@ -113,7 +119,10 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim mZoomLimiter = new ZoomLimiter(tileLayer.getManager(), zoomMin, zoomMax, zoomMin); mRenderer = mExtrusionRenderer = new BuildingRenderer(tileLayer.tileRenderer(), mZoomLimiter, mesh, TRANSLUCENT); - if (POST_AA) + // TODO Allow SHADOW and POST_AA at same time + if (SHADOW) + mRenderer = new ShadowRenderer(mExtrusionRenderer); + else if (POST_AA) mRenderer = new OffscreenRenderer(Mode.SSAO_FXAA, mRenderer); mRenderTheme = tileLayer.getTheme(); diff --git a/vtm/src/org/oscim/renderer/ExtrusionRenderer.java b/vtm/src/org/oscim/renderer/ExtrusionRenderer.java index 208acf4f..630d5850 100644 --- a/vtm/src/org/oscim/renderer/ExtrusionRenderer.java +++ b/vtm/src/org/oscim/renderer/ExtrusionRenderer.java @@ -24,6 +24,7 @@ import org.oscim.core.Tile; import org.oscim.renderer.bucket.ExtrusionBucket; import org.oscim.renderer.bucket.ExtrusionBuckets; import org.oscim.renderer.bucket.RenderBuckets; +import org.oscim.renderer.light.ShadowRenderer; import org.oscim.renderer.light.Sun; import org.oscim.utils.FastMath; import org.slf4j.Logger; @@ -49,6 +50,7 @@ public abstract class ExtrusionRenderer extends LayerRenderer { private Sun mSun; private boolean mEnableCurrentSunPos; + private boolean mUseLight = true; public ExtrusionRenderer(boolean mesh, boolean translucent) { mMesh = mesh; @@ -106,7 +108,11 @@ public abstract class ExtrusionRenderer extends LayerRenderer { int uZLimit; public Shader(String shader) { - if (!create(shader)) + this(shader, null); + } + + public Shader(String shader, String directives) { + if (!createDirective(shader, directives)) return; uMVP = getUniform("u_mvp"); @@ -120,12 +126,20 @@ public abstract class ExtrusionRenderer extends LayerRenderer { } } + public void enableCurrentSunPos(boolean enableSunPos) { + mEnableCurrentSunPos = enableSunPos; + } + + public Shader getShader() { + return mShader; + } + public Sun getSun() { return mSun; } - public void enableCurrentSunPos(boolean enableSunPos) { - mEnableCurrentSunPos = enableSunPos; + public boolean isMesh() { + return mMesh; } @Override @@ -209,6 +223,8 @@ public abstract class ExtrusionRenderer extends LayerRenderer { gl.uniform1i(s.uMode, -1); for (int i = 0; i < mBucketsCnt; i++) { + if (ebs[i] == null) + return; if (ebs[i].ibo == null) return; @@ -232,7 +248,8 @@ public abstract class ExtrusionRenderer extends LayerRenderer { gl.depthFunc(GL.EQUAL); } - GLState.blend(true); + // Depth cannot be transparent (in GL20) + GLState.blend(mUseLight); GLState.enableVertexArrays(s.aPos, s.aNormal); @@ -266,8 +283,9 @@ public abstract class ExtrusionRenderer extends LayerRenderer { gl.vertexAttribPointer(s.aPos, 3, GL.SHORT, false, RenderBuckets.SHORT_BYTES * 4, eb.getVertexOffset()); - gl.vertexAttribPointer(s.aNormal, 2, GL.UNSIGNED_BYTE, - false, RenderBuckets.SHORT_BYTES * 4, eb.getVertexOffset() + RenderBuckets.SHORT_BYTES * 3); + if (mUseLight) + gl.vertexAttribPointer(s.aNormal, 2, GL.UNSIGNED_BYTE, + false, RenderBuckets.SHORT_BYTES * 4, eb.getVertexOffset() + RenderBuckets.SHORT_BYTES * 3); /* draw extruded outlines (mMesh == false) */ if (eb.idx[0] > 0) { @@ -319,7 +337,8 @@ public abstract class ExtrusionRenderer extends LayerRenderer { } /* just a temporary reference! */ - ebs[i] = null; + /* But for shadows we use them multiple times */ + //ebs[i] = null; } if (!mTranslucent) @@ -345,8 +364,16 @@ public abstract class ExtrusionRenderer extends LayerRenderer { float x = (float) ((l.x - v.pos.x) * curScale); float y = (float) ((l.y - v.pos.y) * curScale); + // Create model matrix v.mvp.setTransScale(x, y, scale / COORD_SCALE); v.mvp.setValue(10, scale / 10); + + // Create shadow map converter + // TODO may code it cleaner + if (s instanceof ShadowRenderer.Shader) + ((ShadowRenderer.Shader) s).setLightMVP(v.mvp); + + // Apply model matrix to VP-Matrix v.mvp.multiplyLhs(v.viewproj); if (mTranslucent) { @@ -359,7 +386,15 @@ public abstract class ExtrusionRenderer extends LayerRenderer { v.mvp.setAsUniform(s.uMVP); } + public void setShader(Shader shader) { + mShader = shader; + } + public void setZLimit(float zLimit) { mZLimit = zLimit; } + + public void useLight(boolean useLight) { + mUseLight = useLight; + } } diff --git a/vtm/src/org/oscim/renderer/OffscreenRenderer.java b/vtm/src/org/oscim/renderer/OffscreenRenderer.java index 72174d72..3f514004 100644 --- a/vtm/src/org/oscim/renderer/OffscreenRenderer.java +++ b/vtm/src/org/oscim/renderer/OffscreenRenderer.java @@ -195,7 +195,7 @@ public class OffscreenRenderer extends LayerRenderer { GLState.bindFramebuffer(fb); GLState.viewport(texW, texH); gl.depthMask(true); - GLState.setClearColor(mClearColor); + GLState.setClearColor(mClearColor); // FIXME SHADOW remove to use default clear color gl.clear(GL.DEPTH_BUFFER_BIT | GL.COLOR_BUFFER_BIT); mRenderer.render(viewport); @@ -223,6 +223,7 @@ public class OffscreenRenderer extends LayerRenderer { GLState.test(false, false); GLState.blend(true); + // FIXME SHADOW to work with ShadowRenderer: gl.blendFunc(GL.ZERO, GL.SRC_COLOR); gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4); GLUtils.checkGlError(getClass().getName() + ": render() end"); } diff --git a/vtm/src/org/oscim/renderer/light/ShadowFrameBuffer.java b/vtm/src/org/oscim/renderer/light/ShadowFrameBuffer.java new file mode 100644 index 00000000..17870179 --- /dev/null +++ b/vtm/src/org/oscim/renderer/light/ShadowFrameBuffer.java @@ -0,0 +1,152 @@ +/* + * Copyright 2019 Gustl22 + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package org.oscim.renderer.light; + +import org.oscim.backend.GL; +import org.oscim.backend.GLAdapter; +import org.oscim.renderer.GLState; +import org.oscim.renderer.GLUtils; + +import static org.oscim.backend.GLAdapter.gl; +import static org.oscim.backend.GLAdapter.gl30; + +/** + * The frame buffer for the shadow pass. This class sets up the depth texture + * which can be rendered to during the shadow render pass, producing a shadow + * map. + *

+ * See ThinMatrix on Youtube: https://youtu.be/o6zDfDkOFIc + */ +public class ShadowFrameBuffer { + + private final int WIDTH; + private final int HEIGHT; + private int defaultWidth; + private int defaultHeight; + private int defaultFrameBuffer; + private int defaultTexture; + private int fbo; + private int shadowMap; + + /** + * Initialises the frame buffer and shadow map of a certain size. + * + * @param width - the width of the shadow map in pixels. + * @param height - the height of the shadow map in pixels. + */ + protected ShadowFrameBuffer(int width, int height) { + this.WIDTH = width; + this.HEIGHT = height; + + updateViewportDimensions(); + + fbo = createFrameBuffer(); + shadowMap = createDepthBufferAttachment(width, height); + unbindFrameBuffer(); + } + + /** + * Deletes the frame buffer and shadow map texture when the game closes. + */ + protected void cleanUp() { + GLUtils.glDeleteFrameBuffers(1, new int[]{fbo}); + GLUtils.glDeleteTextures(1, new int[]{shadowMap}); + } + + /** + * Unbinds the frame buffer, setting the default frame buffer as the current + * render target. + */ + protected void unbindFrameBuffer() { + GLState.bindFramebuffer(defaultFrameBuffer); + GLState.viewport(defaultWidth, defaultHeight); + } + + /** + * @return The ID of the shadow map texture. + */ + protected int getShadowMap() { + return shadowMap; + } + + /** + * Binds the frame buffer as the current render target. + */ + public void bindFrameBuffer() { + updateViewportDimensions(); + GLState.bindTex2D(defaultTexture); + + defaultFrameBuffer = GLState.getFramebuffer(); + GLState.bindFramebuffer(fbo); + GLState.viewport(WIDTH, HEIGHT); + } + + /** + * Creates a frame buffer and binds it so that attachments can be added to + * it. The draw buffer is set to none, indicating that there's no colour + * buffer to be rendered to. + * + * @return The newly created frame buffer's ID. + */ + private static int createFrameBuffer() { + int frameBuffer = GLUtils.glGenFrameBuffers(1)[0]; + GLState.bindFramebuffer(frameBuffer); + if (GLAdapter.isGL30()) { + GLUtils.glDrawBuffers(1, new int[]{GL.NONE}); + gl30.readBuffer(GL.NONE); + } + return frameBuffer; + } + + /** + * Creates a depth buffer texture attachment. + * + * @param width - the width of the texture. + * @param height - the height of the texture. + * @return The ID of the depth texture. + */ + private int createDepthBufferAttachment(int width, int height) { + defaultTexture = GLState.getTexture(); + int[] texture = GLUtils.glGenTextures(1); + GLState.bindTex2D(texture[0]); + if (GLAdapter.isGL30()) { + gl.texImage2D(gl.TEXTURE_2D, 0, gl30.DEPTH_COMPONENT16, + width, height, 0, gl30.DEPTH_COMPONENT, + gl.UNSIGNED_SHORT, null); + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, + width, height, 0, gl.RGBA, + gl.UNSIGNED_BYTE, null); + } + // Alternatively set to 32 bit float texture +// gl.texImage2D(gl.TEXTURE_2D, 0, gl30.DEPTH_COMPONENT32F, width, height, 0, +// gl.DEPTH_COMPONENT, gl.FLOAT, null); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); + if (GLAdapter.isGL30()) { + gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.TEXTURE_2D, texture[0], 0); + } else { + gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_2D, texture[0], 0); + } + return texture[0]; + } + + private void updateViewportDimensions() { + defaultWidth = GLState.getViewportWidth(); + defaultHeight = GLState.getViewportHeight(); + } +} \ No newline at end of file diff --git a/vtm/src/org/oscim/renderer/light/ShadowRenderer.java b/vtm/src/org/oscim/renderer/light/ShadowRenderer.java new file mode 100644 index 00000000..ae2532e0 --- /dev/null +++ b/vtm/src/org/oscim/renderer/light/ShadowRenderer.java @@ -0,0 +1,328 @@ +/* + * Copyright 2019 Gustl22 + * Copyright 2019 schedul-xor + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package org.oscim.renderer.light; + +import org.oscim.backend.GL; +import org.oscim.renderer.ExtrusionRenderer; +import org.oscim.renderer.GLMatrix; +import org.oscim.renderer.GLShader; +import org.oscim.renderer.GLState; +import org.oscim.renderer.GLUtils; +import org.oscim.renderer.GLViewport; +import org.oscim.renderer.LayerRenderer; +import org.oscim.renderer.MapRenderer; +import org.oscim.utils.math.MathUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.FloatBuffer; + +import static org.oscim.backend.GLAdapter.gl; + +public class ShadowRenderer extends LayerRenderer { + private static final Logger log = LoggerFactory.getLogger(ShadowRenderer.class); + + public static boolean DEBUG = false; + + private ExtrusionRenderer mRenderer; + + private float SHADOWMAP_RESOLUTION = 2048f; + private int mGroundQuad; + //private int mGroundShadowQuad; + private ShadowFrameBuffer mFrameBuffer; + + private float[] mOrthoMat = new float[16]; + private float[] mViewProjTmp = new float[16]; + private GLMatrix mLightMat = new GLMatrix(); + private GLMatrix mRotTmp = new GLMatrix(); + + static float[] texUnitConverterF = new float[]{ + 0.5f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.5f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 0.5f, 0.5f, 0.5f, 1.0f}; + static GLMatrix texUnitConverter = new GLMatrix(); + + static { + texUnitConverter.set(texUnitConverterF); + } + + /** + * Shader to draw the extrusions. + */ + private Shader mExtrusionShader; + + /** + * Shader to draw the ground. + */ + private GroundShader mGroundShader; + + /** + * Shader to create shadow map (of ground and extrusions) from lights view. + */ + private ExtrusionRenderer.Shader mLightShader; + + public static class GroundShader extends GLShader { + int aPos, uLightColor, uLightMvp, uMVP, uShadowMap, uShadowRes; + + public GroundShader(String shader) { + if (!createDirective(shader, "#define SHADOW 1\n")) + return; + + aPos = getAttrib("a_pos"); + uLightColor = getUniform("u_lightColor"); + uLightMvp = getUniform("u_light_mvp"); + uMVP = getUniform("u_mvp"); + uShadowMap = getUniform("u_shadowMap"); + uShadowRes = getUniform("u_shadowRes"); + } + } + + public static class Shader extends ExtrusionRenderer.Shader { + /** + * For temporary use. + */ + static final GLMatrix lightMvp = new GLMatrix(); + + /** + * The light view projection matrix. + */ + GLMatrix lightMat = null; + + /** + * The light color and shadow transparency as uniform. + */ + int uLightColor; + + /** + * The light mvp for shadow as uniform. + */ + int uLightMvp; + + /** + * The shadow map texture as uniform. + */ + int uShadowMap; + + /** + * The shadow map resolution as uniform. + */ + int uShadowRes; + + public Shader(String shader) { + super(shader, "#define SHADOW 1\n"); + uLightColor = getUniform("u_lightColor"); + uLightMvp = getUniform("u_light_mvp"); + uShadowMap = getUniform("u_shadowMap"); + uShadowRes = getUniform("u_shadowRes"); + } + + public void setLightMVP(GLMatrix model) { + if (lightMat == null) return; + synchronized (lightMvp) { + lightMvp.copy(model); + lightMvp.multiplyLhs(lightMat); + //lightMvp.addDepthOffset(delta); + lightMvp.setAsUniform(uLightMvp); + } + } + } + + public ShadowRenderer(ExtrusionRenderer renderer) { + setRenderer(renderer); + } + + public void setRenderer(ExtrusionRenderer renderer) { + mRenderer = renderer; + } + + /** + * Bind a plane to easily draw all over the ground. + */ + private static int bindPlane(float width, float height) { + int vertexBuffer; + int[] vboIds = GLUtils.glGenBuffers(1); + FloatBuffer floatBuffer = MapRenderer.getFloatBuffer(8); + // indices: 0 1 2 - 2 1 3 + float[] quad = new float[]{ + -width, height, + width, height, + -width, -height, + width, -height + }; + floatBuffer.put(quad); + floatBuffer.flip(); + vertexBuffer = vboIds[0]; + GLState.bindVertexBuffer(vertexBuffer); + gl.bufferData(GL.ARRAY_BUFFER, + quad.length * 4, floatBuffer, + GL.STATIC_DRAW); + GLState.bindVertexBuffer(GLState.UNBIND); + return vertexBuffer; + } + + @Override + public boolean setup() { + // Ground plane for shadow map + //mGroundShadowQuad = bindPlane(SHADOWMAP_RESOLUTION * 1.1f, SHADOWMAP_RESOLUTION * 1.1f); + if (!DEBUG) { + // Ground plane to draw shadows an + mGroundQuad = bindPlane(Short.MAX_VALUE, Short.MAX_VALUE); + } else { + mGroundQuad = bindPlane(SHADOWMAP_RESOLUTION * 1.1f, SHADOWMAP_RESOLUTION * 1.1f); + } + + // Shader + mGroundShader = new GroundShader("extrusion_shadow_ground"); + mLightShader = new ExtrusionRenderer.Shader("extrusion_shadow_light"); + if (mRenderer.isMesh()) + mExtrusionShader = new Shader("extrusion_layer_mesh"); + else + mExtrusionShader = new Shader("extrusion_layer_ext"); + + mFrameBuffer = new ShadowFrameBuffer((int) SHADOWMAP_RESOLUTION, (int) SHADOWMAP_RESOLUTION); + + //mRenderer.setup(); // No need to setup, as shaders are taken from here + + return super.setup(); + } + + @Override + public void update(GLViewport viewport) { + mRenderer.update(viewport); + setReady(mRenderer.isReady()); + } + + @Override + public void render(GLViewport viewport) { + /* Prepare rendering from lights view: */ + // Store vp-matrix temporarily and use vp-matrix as lightMat + viewport.viewproj.get(mViewProjTmp); + + float projWidth = SHADOWMAP_RESOLUTION; + float projHeight = SHADOWMAP_RESOLUTION; + if (DEBUG) { + projWidth *= (3. / 4); + projHeight *= (3. / 4); + } + GLMatrix.orthoM(mOrthoMat, 0, -projWidth, projWidth, projHeight, -projHeight, -SHADOWMAP_RESOLUTION, SHADOWMAP_RESOLUTION); + viewport.viewproj.set(mOrthoMat); + + // Rotate from light direction + float[] lightPos = mRenderer.getSun().getPosition(); + float rot = (float) Math.acos(lightPos[2] / 1.) * MathUtils.radiansToDegrees; + mRotTmp.setRotation(rot, 1f, 0, 0); // tilt + viewport.viewproj.multiplyRhs(mRotTmp); + + rot = MathUtils.atan2(lightPos[0], lightPos[1]) * MathUtils.radiansToDegrees; + mRotTmp.setRotation(rot, 0, 0, 1f); // bearing + viewport.viewproj.multiplyRhs(mRotTmp); + + // DRAW SHADOW MAP + { + // START DEPTH FRAMEBUFFER + mFrameBuffer.bindFrameBuffer(); + + GLState.blend(false); // depth cannot be transparent + gl.depthMask(true); + GLState.test(true, false); + + // Clear color (for gl20) and depth (for gl30) + gl.clear(gl.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT); + + // Draw GROUND shadow map (usually the ground does not cast any shadow) + /*{ + mLightShader.useProgram(); + + viewport.viewproj.setAsUniform(mLightShader.uMVP); + gl.uniform1f(mLightShader.uAlpha, 1f); + GLState.bindVertexBuffer(mGroundShadowQuad); + GLState.enableVertexArrays(mLightShader.aPos, GLState.DISABLED); + gl.vertexAttribPointer(mLightShader.aPos, 2, GL.FLOAT, false, 0, 0); + MapRenderer.bindQuadIndicesVBO(); + + gl.drawElements(GL.TRIANGLES, 6, GL.UNSIGNED_SHORT, 0); + }*/ + + // Draw EXTRUSION shadow map (in ExtrusionRenderer) + { + //ExtrusionRenderer.Shader tmpShader = mRenderer.getShader(); + mRenderer.setShader(mLightShader); + mRenderer.useLight(false); + mRenderer.render(viewport); + //mRenderer.setShader(tmpShader); + } + + // END DEPTH FRAMEBUFFER + mFrameBuffer.unbindFrameBuffer(); + } + mLightMat.copy(viewport.viewproj); // save lightMat + mLightMat.multiplyLhs(texUnitConverter); // apply shadow map converter to mLightMat + + viewport.viewproj.set(mViewProjTmp); // write back stored vp-matrix + + // DRAW SCENE + { + int lightColor = mRenderer.getSun().getColor(); + GLState.test(false, false); + gl.clear(GL.DEPTH_BUFFER_BIT); + + // Bind shadow map texture + gl.activeTexture(gl.TEXTURE2); + GLState.bindTex2D(mFrameBuffer.getShadowMap()); + + // Draw GROUND + { + mGroundShader.useProgram(); + viewport.viewproj.setAsUniform(mGroundShader.uMVP); + + gl.uniform1i(mGroundShader.uShadowMap, 2); // TEXTURE2 for shadows + GLUtils.setColor(mGroundShader.uLightColor, lightColor); + gl.uniform1f(mGroundShader.uShadowRes, SHADOWMAP_RESOLUTION); + mLightMat.setAsUniform(mGroundShader.uLightMvp); + + // Bind VBO + GLState.bindVertexBuffer(mGroundQuad); + GLState.enableVertexArrays(mGroundShader.aPos, GLState.DISABLED); + gl.vertexAttribPointer(mGroundShader.aPos, 2, GL.FLOAT, false, 0, 0); + MapRenderer.bindQuadIndicesVBO(); + GLState.blend(true); // allow transparency + gl.blendFunc(GL.ZERO, GL.SRC_COLOR); // multiply frame colors + gl.drawElements(GL.TRIANGLES, 6, GL.UNSIGNED_SHORT, 0); + GLState.blend(false); + gl.blendFunc(GL.ONE, GL.ONE_MINUS_SRC_ALPHA); // Reset to default func + } + + // Draw EXTRUSIONS (in ExtrusionRenderer) + { + //ExtrusionRenderer.Shader tmpShader = mRenderer.getShader(); + mExtrusionShader.useProgram(); + gl.uniform1i(mExtrusionShader.uShadowMap, 2); // TEXTURE2 for shadows + GLUtils.setColor(mExtrusionShader.uLightColor, lightColor); + gl.uniform1f(mExtrusionShader.uShadowRes, SHADOWMAP_RESOLUTION); + + mExtrusionShader.lightMat = mLightMat; + mRenderer.setShader(mExtrusionShader); + mRenderer.useLight(true); + mRenderer.render(viewport); + //mRenderer.setShader(tmpShader); + } + + gl.activeTexture(GL.TEXTURE0); // reset active Texture + + } + } +}