Shadow for buildings (#668)

Co-authored-by: Izumi Kawashima <schedul.xor@gmail.com>
This commit is contained in:
Gustl22 2019-02-20 15:14:00 +01:00 committed by Emux
parent ba123910a4
commit b4a98ef1a9
No known key found for this signature in database
GPG Key ID: 64ED9980896038C3
9 changed files with 883 additions and 19 deletions

View File

@ -13,15 +13,31 @@ varying vec4 color;
//varying float depth; //varying float depth;
const float ff = 255.0; 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. * The diffuse of surface dependent on the light position.
* *
* @param r_norm the normal vector of vertex's face * @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)); float l = dot(normalize(r_norm), normalize(u_light));
hasLight = l > 0.0;
l = clamp((1.0 + l) / 2.0, 0.0, 1.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() { void main() {
@ -31,6 +47,8 @@ void main() {
height = u_zlimit; height = u_zlimit;
} }
gl_Position = u_mvp * vec4(a_pos.xy, height, 1.0); gl_Position = u_mvp * vec4(a_pos.xy, height, 1.0);
bool hasLight = false;
//depth = gl_Position.z; //depth = gl_Position.z;
if (u_mode == -1) { if (u_mode == -1) {
; ;
@ -40,7 +58,7 @@ void main() {
// roof / depth pass // roof / depth pass
r_norm = vec3(0.0, 0.0, 1.0); r_norm = vec3(0.0, 0.0, 1.0);
color = u_color[0] * u_alpha; color = u_color[0] * u_alpha;
color.rgb *= diffuse(r_norm); color.rgb *= diffuse(r_norm, hasLight);
} else { } else {
float lightX = u_mode == 1 ? a_normal.y : a_normal.x; float lightX = u_mode == 1 ? a_normal.y : a_normal.x;
r_norm.x = (lightX / ff) * 2.0 - 1.0; r_norm.x = (lightX / ff) * 2.0 - 1.0;
@ -59,7 +77,7 @@ void main() {
// sides 2 - use 0x00ff // sides 2 - use 0x00ff
color = u_color[2]; color = u_color[2];
} }
color.rgb *= diffuse(r_norm) * z * h; color.rgb *= diffuse(r_norm, hasLight) * z * h;
} }
color *= u_alpha; color *= u_alpha;
} else if (u_mode == 3) { } else if (u_mode == 3) {
@ -67,6 +85,15 @@ void main() {
float z = (0.98 - gl_Position.z * 0.02); float z = (0.98 - gl_Position.z * 0.02);
color = u_color[3] * z; 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 #endif
varying vec4 color; varying vec4 color;
void main() { #ifdef SHADOW
gl_FragColor = color; 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
} }

View File

@ -9,6 +9,11 @@ attribute vec4 a_pos;
attribute vec2 a_normal; attribute vec2 a_normal;
varying vec4 color; varying vec4 color;
#ifdef SHADOW
uniform mat4 u_light_mvp;
varying vec4 v_shadow_coords;
#endif
void main() { void main() {
// change height by u_alpha // change height by u_alpha
vec4 pos = a_pos; vec4 pos = a_pos;
@ -28,18 +33,40 @@ void main() {
float l = dot(r_norm, normalize(u_light)); 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)); //vec3 opp_light_dir = normalize(vec3(-u_light.xy, u_light.z));
//l += dot(r_norm, opp_light_dir) * 0.2; //l += dot(r_norm, opp_light_dir) * 0.2;
// [-1,1] to range [0,1] // [-1,1] to range [0,1]
l = (1.0 + l) / 2.0; 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; l = 0.75 + l * 0.25;
#endif
// extreme fake-ssao by height // extreme fake-ssao by height
l += (clamp(a_pos.z / 2048.0, 0.0, 0.1) - 0.05); 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; 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 #endif
varying vec4 color; varying vec4 color;
void main() { #ifdef SHADOW
gl_FragColor = color; 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
} }

View File

@ -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);
}
}
}

View File

@ -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
}

View File

@ -2,7 +2,7 @@
* Copyright 2013 Hannes Janetzek * Copyright 2013 Hannes Janetzek
* Copyright 2016-2018 devemux86 * Copyright 2016-2018 devemux86
* Copyright 2016 Robin Boldt * Copyright 2016 Robin Boldt
* Copyright 2017-2018 Gustl22 * Copyright 2017-2019 Gustl22
* *
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * 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.OffscreenRenderer.Mode;
import org.oscim.renderer.bucket.ExtrusionBuckets; import org.oscim.renderer.bucket.ExtrusionBuckets;
import org.oscim.renderer.bucket.RenderBuckets; import org.oscim.renderer.bucket.RenderBuckets;
import org.oscim.renderer.light.ShadowRenderer;
import org.oscim.theme.IRenderTheme; import org.oscim.theme.IRenderTheme;
import org.oscim.theme.styles.ExtrusionStyle; import org.oscim.theme.styles.ExtrusionStyle;
import org.oscim.theme.styles.RenderStyle; import org.oscim.theme.styles.RenderStyle;
@ -59,6 +60,11 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim
*/ */
public static boolean RAW_DATA = false; public static boolean RAW_DATA = false;
/**
* Use shadow rendering.
*/
public static boolean SHADOW = false;
/** /**
* Let vanish extrusions / meshes which are covered by others. * Let vanish extrusions / meshes which are covered by others.
* {@link org.oscim.renderer.bucket.RenderBucket#EXTRUSION}: roofs are always translucent. * {@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); mZoomLimiter = new ZoomLimiter(tileLayer.getManager(), zoomMin, zoomMax, zoomMin);
mRenderer = mExtrusionRenderer = new BuildingRenderer(tileLayer.tileRenderer(), mZoomLimiter, mesh, TRANSLUCENT); 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); mRenderer = new OffscreenRenderer(Mode.SSAO_FXAA, mRenderer);
mRenderTheme = tileLayer.getTheme(); mRenderTheme = tileLayer.getTheme();

View File

@ -24,6 +24,7 @@ import org.oscim.core.Tile;
import org.oscim.renderer.bucket.ExtrusionBucket; import org.oscim.renderer.bucket.ExtrusionBucket;
import org.oscim.renderer.bucket.ExtrusionBuckets; import org.oscim.renderer.bucket.ExtrusionBuckets;
import org.oscim.renderer.bucket.RenderBuckets; import org.oscim.renderer.bucket.RenderBuckets;
import org.oscim.renderer.light.ShadowRenderer;
import org.oscim.renderer.light.Sun; import org.oscim.renderer.light.Sun;
import org.oscim.utils.FastMath; import org.oscim.utils.FastMath;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -49,6 +50,7 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
private Sun mSun; private Sun mSun;
private boolean mEnableCurrentSunPos; private boolean mEnableCurrentSunPos;
private boolean mUseLight = true;
public ExtrusionRenderer(boolean mesh, boolean translucent) { public ExtrusionRenderer(boolean mesh, boolean translucent) {
mMesh = mesh; mMesh = mesh;
@ -106,7 +108,11 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
int uZLimit; int uZLimit;
public Shader(String shader) { public Shader(String shader) {
if (!create(shader)) this(shader, null);
}
public Shader(String shader, String directives) {
if (!createDirective(shader, directives))
return; return;
uMVP = getUniform("u_mvp"); 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() { public Sun getSun() {
return mSun; return mSun;
} }
public void enableCurrentSunPos(boolean enableSunPos) { public boolean isMesh() {
mEnableCurrentSunPos = enableSunPos; return mMesh;
} }
@Override @Override
@ -209,6 +223,8 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
gl.uniform1i(s.uMode, -1); gl.uniform1i(s.uMode, -1);
for (int i = 0; i < mBucketsCnt; i++) { for (int i = 0; i < mBucketsCnt; i++) {
if (ebs[i] == null)
return;
if (ebs[i].ibo == null) if (ebs[i].ibo == null)
return; return;
@ -232,7 +248,8 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
gl.depthFunc(GL.EQUAL); gl.depthFunc(GL.EQUAL);
} }
GLState.blend(true); // Depth cannot be transparent (in GL20)
GLState.blend(mUseLight);
GLState.enableVertexArrays(s.aPos, s.aNormal); GLState.enableVertexArrays(s.aPos, s.aNormal);
@ -266,8 +283,9 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
gl.vertexAttribPointer(s.aPos, 3, GL.SHORT, gl.vertexAttribPointer(s.aPos, 3, GL.SHORT,
false, RenderBuckets.SHORT_BYTES * 4, eb.getVertexOffset()); false, RenderBuckets.SHORT_BYTES * 4, eb.getVertexOffset());
gl.vertexAttribPointer(s.aNormal, 2, GL.UNSIGNED_BYTE, if (mUseLight)
false, RenderBuckets.SHORT_BYTES * 4, eb.getVertexOffset() + RenderBuckets.SHORT_BYTES * 3); gl.vertexAttribPointer(s.aNormal, 2, GL.UNSIGNED_BYTE,
false, RenderBuckets.SHORT_BYTES * 4, eb.getVertexOffset() + RenderBuckets.SHORT_BYTES * 3);
/* draw extruded outlines (mMesh == false) */ /* draw extruded outlines (mMesh == false) */
if (eb.idx[0] > 0) { if (eb.idx[0] > 0) {
@ -319,7 +337,8 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
} }
/* just a temporary reference! */ /* just a temporary reference! */
ebs[i] = null; /* But for shadows we use them multiple times */
//ebs[i] = null;
} }
if (!mTranslucent) if (!mTranslucent)
@ -345,8 +364,16 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
float x = (float) ((l.x - v.pos.x) * curScale); float x = (float) ((l.x - v.pos.x) * curScale);
float y = (float) ((l.y - v.pos.y) * curScale); float y = (float) ((l.y - v.pos.y) * curScale);
// Create model matrix
v.mvp.setTransScale(x, y, scale / COORD_SCALE); v.mvp.setTransScale(x, y, scale / COORD_SCALE);
v.mvp.setValue(10, scale / 10); 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); v.mvp.multiplyLhs(v.viewproj);
if (mTranslucent) { if (mTranslucent) {
@ -359,7 +386,15 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
v.mvp.setAsUniform(s.uMVP); v.mvp.setAsUniform(s.uMVP);
} }
public void setShader(Shader shader) {
mShader = shader;
}
public void setZLimit(float zLimit) { public void setZLimit(float zLimit) {
mZLimit = zLimit; mZLimit = zLimit;
} }
public void useLight(boolean useLight) {
mUseLight = useLight;
}
} }

View File

@ -195,7 +195,7 @@ public class OffscreenRenderer extends LayerRenderer {
GLState.bindFramebuffer(fb); GLState.bindFramebuffer(fb);
GLState.viewport(texW, texH); GLState.viewport(texW, texH);
gl.depthMask(true); 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); gl.clear(GL.DEPTH_BUFFER_BIT | GL.COLOR_BUFFER_BIT);
mRenderer.render(viewport); mRenderer.render(viewport);
@ -223,6 +223,7 @@ public class OffscreenRenderer extends LayerRenderer {
GLState.test(false, false); GLState.test(false, false);
GLState.blend(true); GLState.blend(true);
// FIXME SHADOW to work with ShadowRenderer: gl.blendFunc(GL.ZERO, GL.SRC_COLOR);
gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4); gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4);
GLUtils.checkGlError(getClass().getName() + ": render() end"); GLUtils.checkGlError(getClass().getName() + ": render() end");
} }

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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.
* <p>
* 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();
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}
}