Soft Shadows with ShadowHelper or another framebuffer

Started by nCore, March 18, 2012, 08:50:05 PM

Previous topic - Next topic

nCore

Hi Egon, hi everyone ;)

I'm just wondering is there any possible way to get the shadowhelper buffer with generated shadows from casters, get them into another framebuffer and make them blurry as soft shadows?

I've searched the forum up and down and without a result.

Sorry for newbie, dumb question.

Thanks in advance.

Chris.

EgonOlsen

I'm not sure what you want to blur here...i could make the method to access the depth map public, but i don't see the point. Bluring a depth map won't help you in your task to create softer shadows.

nCore

Hi again,

just want do something similar like in this case: http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/soft-edged-shadows-r2193,

just want to make texture that can hold the scene depth, next render it to shadow map and render the shadowed scene into a buffer that can be rendered to the main frame buffer... is there a simple way to do this? Above example is made in DX but I can "port" it to OpenGL.

EgonOlsen

No, not an easy way...you have to rewrite what the ShadowHelper does, because it uses the fixed function pipeline. You need shaders for what you want to do....i'll post the code for the ShadowHelper. Maybe to helps to extract what you need...


package com.threed.jpct.util;

import com.threed.jpct.*;
import java.util.*;
import java.awt.*;

/**
* The ShadowHelper-class eases shadow mapping. While shadow mapping in jPCT is
* possible without this helper class, you have to have a good knowledge of how
* shadow mapping works in practice to implement it yourself using the methods
* that jPCT is offering for it.<br>
* To simplify shadow mapping, this helper exists and it should be sufficient
* for most shadow applications.<br>
* jPCT uses a fixed function approach with depth textures for shadow mapping.
* It is advised to initialize this class after enabling the OpenGL renderer
* when render-to-texture should be done by using FBOs because otherwise, it's
* not known if the hardware actually supports it and the helper has to rely on
* the setting in Config alone. The software renderer doesn't support shadow
* mapping. It will only cost you performance for no visual benefit when using
* this class on a software rendered scene.
*
* @see com.threed.jpct.Config#glShadowZBias
* @see com.threed.jpct.Config#glUseFBO
*/
public class ShadowHelper implements java.io.Serializable {

private static final long serialVersionUID = 1L;

private static int cnt = 0;

private Texture map = null;

private boolean[] inverts = null;

private int[] lightsOff = null;

private boolean[] visible = null;

private Color[] addColor = null;

private Hashtable recMap = new Hashtable();

private Hashtable casMap = new Hashtable();

private World world = null;

private FrameBuffer buffer = null;

private int tid = 0;

private Projector proj = null;

private boolean disableLights = true;

private Color col = new Color(60, 60, 60);

private String name = "";

private boolean revert = true;

private boolean invertCulling = true;

private boolean disableAddColor = false;

private int border = 1;

private boolean rendersShadowMap = false;

private boolean rendersShadowPass = false;

/**
* Create a new shadow helper.
*
* @param world
*            the world that includes the objects to be shadowed
* @param buffer
*            the framebuffer into which the world will be rendered
* @param lightSource
*            the directional light source that casts the shadows
* @param maxSize
*            the maximum size of the shadow map. This isn't necessarily the
*            used size but an upper bound for it.
*/
public ShadowHelper(World world, FrameBuffer buffer, Projector lightSource, int maxSize) {

if (!buffer.supports(FrameBuffer.SUPPORT_FOR_SHADOW_MAPPING)) {
Logger.log("Shadow mapping is not supported by the current hardware!", Logger.ERROR);
return;
}

if (maxSize != 8192 && maxSize != 4096 && maxSize != 2048 && maxSize != 1024 && maxSize != 512 && maxSize != 256 && maxSize != 128 && maxSize != 64 && maxSize != 32 && maxSize != 16) {
maxSize = 256;
Logger.log("Consider using a power of two lower than 16384 as the shadow map's size. Adjusted to 256*256!", Logger.WARNING);
}

int hwSize = buffer.getMaxTextureSize();
Logger.log("Hardware supports textures up to " + hwSize + "*" + hwSize + " in size!", Logger.MESSAGE);
maxSize = Math.min(maxSize, hwSize);

this.world = world;
this.buffer = buffer;
this.proj = lightSource;
int height = buffer.getOutputHeight();
int width = buffer.getOutputWidth();

int x = maxSize;
int y = maxSize;

if (!Config.glUseFBO) {
while (x > width) {
x /= 2;
}

while (y > height) {
y /= 2;
}
}

maxSize=x;
if (y<maxSize) {
maxSize=y;
}
x=maxSize;
y=maxSize;

map = new Texture(x, y, null);
name = "--shadowMap-" + cnt + "-";
TextureManager tm = TextureManager.getInstance();
if (tm.containsTexture(name)) {
tm.replaceTexture(name, map);
} else {
tm.addTexture(name, map);
}
tid = tm.getTextureID(name);
cnt++;
map.setGLFiltering(false);
map.setMipmap(false);
map.setProjector(proj, true);
map.setAsShadowMap(true);
}

/**
* Resets the texture counter. The ShadowHelper creates additional textures
* for the depth maps. If no ShadowHelpers are referenced anymore by an
* application, this method can be used to reset the counter, so that the
* next texture will get the initial id. This may help to save some memory
* in the TextureManager.
*/
public static void resetTextureCounter() {
cnt = 0;
}

/**
* Returns the directional light source.
*
* @return the light source
*/
public Projector getLightSource() {
return this.proj;
}

/**
* Sets the directional light source to another one.
*
* @param lightSource
*            the new source
*/
public void setLightSource(Projector lightSource) {
if (map == null) {
return;
}
this.proj = lightSource;
map.setProjector(proj, true);
}

/**
* Sets a border around the actual shadow map to prevent the shadows from
* bleeding into parts of the scene that are not covered by the projector's
* view. Default is 1.
*
* @param width
*/
public void setBorder(int width) {
border = width;
}

/**
* Sets a new FrameBuffer. There should be no need to call this method in a
* normal application.
*
* @param buffer
*            the new FrameBuffer
*/
public void setFrameBuffer(FrameBuffer buffer) {
this.buffer = buffer;
}

/**
* If enabled, every MODE_ADD texture operation will be reverted to a
* MODE_MODULATE when doing the shadow pass. Default is true.
*
* @param mode
*            the mode
*/
public void setRevertMode(boolean mode) {
revert = mode;
}

/**
* Sets the culling mode. If set to true (which is default), the culling
* will be inverted when rendering the depth map. If set to false, it won't.
*
* @param inverted
*            should it be inverted?
*/
public void setCullingMode(boolean inverted) {
invertCulling = inverted;
}

/**
* Enabled percentage closer filtering on the shadow edges. Default is
* false. This can lead to small artifacts on some hardware but usually
* looks better if supported.
*
* @param pcf
*            should we use it?
*/
public void setFiltering(boolean pcf) {
if (map == null) {
return;
}
map.setGLFiltering(pcf);
}

/**
* Adds a receiver. A receiver is an object that is affected by the shadows.
* It doesn't necessarily casts them. Making an object a receiver makes it
* use an additional texture stage. If the number of stages that an objects
* uses exceeds the number that the underlying hardware supports, shadows
* won't work on that object.<br>
* Once an object has been added, it can't be removed, i.e. you can't
* "deshadow" it.
*
* @param rec
*            the receiver
*/
public void addReceiver(Object3D rec) {
if (map == null) {
return;
}
if (!recMap.containsKey(rec)) {
recMap.put(rec, rec);
PolygonManager pm = rec.getPolygonManager();
int max = pm.getMaxPolygonID();
for (int i = 0; i < max; i++) {
pm.addTexture(i, tid, TextureInfo.MODE_MODULATE);
}
}
}

/**
* Returns true, if the helper has been correctly initialized.
*
* @return has it?
*/
public boolean isInitialized() {
return world != null;
}

/**
* Adds a caster. A caster is an object that casts shadows. It doesn't
* necessarily receives them.
*
* @param caster
*            the new caster
*/
public void addCaster(Object3D caster) {
if (map == null) {
return;
}
if (!casMap.containsKey(caster)) {
casMap.put(caster, caster);
}
}

/**
* Removes a caster. It won't be removed from the world or something, it
* doesn't cast shadows any longer.
*
* @param caster
*            the caster to remove
*/
public void removeCaster(Object3D caster) {
if (map == null) {
return;
}
casMap.remove(caster);
}

/**
* Sets the lighting mode. If set to true (i.e. disabled), all lights but
* the ambient lights will be disabled when doing the shadow pass of the
* calculation.
*
* @param disabled
*            should the light be disabled during shadow pass? Default is
*            true.
*/
public void setLightMode(boolean disabled) {
disableLights = disabled;
}

/**
* Sets how an objects additional color will be treated. If set to true, it
* will be set back to black when rendering the shadows. Otherwise, it won't
* be touched.
*
* @param disabled
*            should the additional color be disabled during shadow pass?
*            Default is false.
*/
public void setAdditionalColorMode(boolean disabled) {
disableAddColor = disabled;
}

/**
* Sets the ambient lighting used during the shadow pass.
*
* @param col
*            the color of the light source. Default is 60,60,60
*/
public void setAmbientLight(Color col) {
this.col = col;
}

/**
* Draws the scene using the most current shadow map. This is equivalent to
* a call to the renderScene()/draw() combination in the normal main loop.
*/
public void drawScene() {
drawScene(true);
}

/**
* Draws the scene using the most current shadow map. This is equivalent to
* a call to the renderScene()/draw() combination in the normal main loop.
*
* @param complete
*            if true, everything is rendered. If its false, the last call
*            to draw() will be omitted (in case you want to do something
*            else before making it).
*/
public synchronized void drawScene(boolean complete) {
if (world == null) {
return;
}

rendersShadowPass = true;

buffer.setPaintListenerState(false);
checkArrays();

int i = 0;

for (Enumeration e = world.getObjects(); e.hasMoreElements();) {
Object3D obj = ((Object3D) e.nextElement());

lightsOff[i] = obj.getLighting();
visible[i] = obj.getVisibility();
addColor[i] = obj.getAdditionalColor();
if (disableLights) {
obj.setLighting(Object3D.LIGHTING_NO_LIGHTS);
}
if (disableAddColor) {
obj.setAdditionalColor(Color.black);
}

if (!recMap.containsKey(obj)) {
obj.setVisibility(false);
}
i++;
}

map.setEnabled(false);
int[] lights = world.getAmbientLight();
world.setAmbientLight(col.getRed(), col.getRed(), col.getBlue());
boolean store = Config.glRevertADDtoMODULATE;
Config.glRevertADDtoMODULATE = revert;
world.renderScene(buffer);
world.draw(buffer);
Config.glRevertADDtoMODULATE = store;
map.setEnabled(true);

i = 0;
for (Enumeration e = world.getObjects(); e.hasMoreElements();) {
Object3D obj = ((Object3D) e.nextElement());
obj.setLighting(lightsOff[i]);
obj.setVisibility(visible[i]);
obj.setAdditionalColor(addColor[i]);
i++;
}
buffer.setPaintListenerState(true);
rendersShadowPass = false;

world.setAmbientLight(lights[0], lights[1], lights[2]);
world.renderScene(buffer);
if (complete) {
world.draw(buffer);
}
}

/**
* Returns true if, at the time of the call, the helper is working on the
* shadow map itself. Beware that this value refers to the method-call, not
* to the actual rendering, which may happen at another time when using
* a multi-threaded renderer.
*
* @return does it render into the map right now?
*/
public boolean isRenderingShadowMap() {
//@todo
// Actually, this is pointless. It would be better if the actual rendering would be considered here...
// In that way, even the synchronized-stuff could go away.
return rendersShadowMap;
}

/**
* Returns true if, at the time of the call, the helper is doing the shadow
* pass. Beware that this value refers to the method-call, not
* to the actual rendering, which may happen at another time when using
* a multi-threaded renderer.
*
* @return does it render the dark parts right now?
*/
public boolean isRenderingShadowPass() {
// Actually, this is pointless. It would be better if the actual rendering would be considered here...
// In that way, even the synchronized-stuff could go away.
return rendersShadowPass;
}

/**
* Gets the number of receivers in the helper.
*
* @return the number
*/
public int getReceiverCount() {
return recMap.size();
}

/**
* Gets the number of casters in the helper.
*
* @return the number
*/
public int getCasterCount() {
return casMap.size();
}

/**
* Updates the shadow map as seen from the directional light source.
* Depending on the application and the use of shadows (and the
* performance), it may be necessary to call this every frame or just once.
*/
public synchronized void updateShadowMap() {

if (world == null) {
return;
}

rendersShadowMap = true;
buffer.setPaintListenerState(false);

Camera camSave = world.getCamera();
synchronized(camSave) {
world.setCameraTo(proj);

checkArrays();

int i = 0;

for (Enumeration e = world.getObjects(); e.hasMoreElements();) {
Object3D obj = ((Object3D) e.nextElement());
inverts[i] = obj.cullingIsInverted();
lightsOff[i] = obj.getLighting();
visible[i] = obj.getVisibility();
if (invertCulling) {
obj.invertCulling(true);
}

if (!casMap.containsKey(obj)) {
obj.setVisibility(false);
}
obj.setLighting(Object3D.LIGHTING_NO_LIGHTS);
i++;
}

buffer.setRenderTarget(map, border, border, border, border, true);
buffer.clearZBufferOnly();
map.setEnabled(false);
world.renderScene(buffer);
world.draw(buffer);
map.setEnabled(true);
buffer.update();
buffer.displayGLOnly();
world.setCameraTo(camSave);
}
if (!Config.glUseFBO) {
// No FBOs used? Clear zbuffer again...
buffer.clearZBufferOnly();
}
buffer.removeRenderTarget();

int i = 0;
for (Enumeration e = world.getObjects(); e.hasMoreElements();) {
Object3D obj = ((Object3D) e.nextElement());
obj.invertCulling(inverts[i]);
obj.setLighting(lightsOff[i]);
obj.setVisibility(visible[i]);
i++;
}

buffer.setPaintListenerState(true);
rendersShadowMap = false;
}

private void checkArrays() {
if (inverts == null || world.getSize() + 10 > inverts.length) {
int size = world.getSize() + 8;
inverts = new boolean[size];
lightsOff = new int[size];
visible = new boolean[size];
addColor = new Color[size];
}
}

/**
* Disposes the helper. The used resources will be freed, the helper isn't
* usable after this.
*/
public void dispose() {
synchronized (map) {
if (map == null) {
return;
}
TextureManager tm = TextureManager.getInstance();
tm.removeAndUnload(name, buffer);
map = null;
}
}

public void finalize() {
dispose();
}

}


nCore

Really thank you, I appreciate. I'll try this in the evening! ;D

PS. This is what I needed! Especially the syncronized method drawScene(), awww yeah ;) thanks!

9kaywun

Not trying to grave dig old threads.......  :-\

@EgonOlsen, is there a difference between the current jPCT ShadowHelper and the one you shared open source on this thread? If not I'd like to use it because I recently started implementing shadow effects into my MMO client, and I am trying to get the smoothest possible effect just as OP was doing
Developing a 3D multiplayer engine using jPCT:
Deleted, will return soon..