SpotLight and shadows

Anyone made spotlight which creates shadow too (could be used ie lamps, fireplace …)?

I think modifying BasicShadowRenderer it can be done, well, I tried and didnt succeed.

Light and shadow have few to do with each other in computer graphics:

https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:light_and_shadow

Well, I meant this (light have position and direction, which are used to render shadow):



http://imageshack.us/photo/my-images/217/spoti.jpg/



http://imageshack.us/photo/my-images/199/spotlightxp.jpg/



Small test:

Little modified TestSpotLight.java (from demos) (it adds SpotLightShadowRenderer to viewport):



package jme3test.light;



import com.jme3.app.SimpleApplication;

import com.jme3.light.AmbientLight;

import com.jme3.light.SpotLight;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.FastMath;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector2f;

import com.jme3.math.Vector3f;

import com.jme3.renderer.queue.RenderQueue.ShadowMode;

import com.jme3.scene.Geometry;

import com.jme3.scene.Spatial;

import com.jme3.scene.shape.Box;

import com.jme3.scene.shape.Sphere;

import com.jme3.texture.Texture.WrapMode;

import com.jme3.util.TangentBinormalGenerator;



public class TestSpotLight extends SimpleApplication

{

private Vector3f lightTarget = new Vector3f(12, 3.5f, 30);

SpotLightShadowRenderer slsr;



public static void main(String[] args)

{

TestSpotLight app = new TestSpotLight();

app.start();

}

SpotLight spot;

Geometry lightMdl;



public void setupLighting()

{

AmbientLight al = new AmbientLight();

al.setColor(ColorRGBA.White.mult(0.8f));

rootNode.addLight(al);



spot = new SpotLight();



spot.setSpotRange(1000);

spot.setSpotInnerAngle(5 * FastMath.DEG_TO_RAD);

spot.setSpotOuterAngle(10 * FastMath.DEG_TO_RAD);

spot.setPosition(new Vector3f(77.70334f, 34.013165f, 27.1017f));

spot.setDirection(lightTarget.subtract(spot.getPosition()));

spot.setColor(ColorRGBA.White.mult(2));

rootNode.addLight(spot);



lightMdl = new Geometry(“Light”, new Sphere(10, 10, 0.1f));

lightMdl.setMaterial(assetManager.loadMaterial(“Common/Materials/RedColor.j3m”));

lightMdl.setLocalTranslation(new Vector3f(77.70334f, 34.013165f, 27.1017f));

lightMdl.setLocalScale(5);

rootNode.attachChild(lightMdl);



slsr = new SpotLightShadowRenderer(assetManager, 1024);

viewPort.addProcessor(slsr);



}



public void setupFloor()

{

Material mat = assetManager.loadMaterial(“Textures/Terrain/Pond/Pond.j3m”);

mat.getTextureParam(“DiffuseMap”).getTextureValue().setWrap(WrapMode.Repeat);

mat.getTextureParam(“NormalMap”).getTextureValue().setWrap(WrapMode.Repeat);

// mat.getTextureParam(“ParallaxMap”).getTextureValue().setWrap(WrapMode.Repeat);

mat.setFloat(“Shininess”, 3);

// mat.setBoolean(“VertexLighting”, true);





Box floor = new Box(Vector3f.ZERO, 50, 1f, 50);

TangentBinormalGenerator.generate(floor);

floor.scaleTextureCoordinates(new Vector2f(5, 5));

Geometry floorGeom = new Geometry(“Floor”, floor);

floorGeom.setMaterial(mat);

floorGeom.setShadowMode(ShadowMode.Receive);

rootNode.attachChild(floorGeom);

}



public void setupSignpost()

{

Spatial signpost = assetManager.loadModel(“Models/Sign Post/Sign Post.mesh.xml”);

Material mat = assetManager.loadMaterial(“Models/Sign Post/Sign Post.j3m”);

// mat.setBoolean(“VertexLighting”, true);

signpost.setMaterial(mat);

signpost.rotate(0, FastMath.HALF_PI, 0);

signpost.setLocalTranslation(12, 3.5f, 30);

signpost.setLocalScale(4);

signpost.setShadowMode(ShadowMode.CastAndReceive);

TangentBinormalGenerator.generate(signpost);

rootNode.attachChild(signpost);

}



@Override

public void simpleInitApp()

{

cam.setLocation(new Vector3f(27.492603f, 29.138166f, -13.232513f));

cam.setRotation(new Quaternion(0.25168246f, -0.10547892f, 0.02760565f, 0.96164864f));

flyCam.setMoveSpeed(30);



setupLighting();

setupFloor();

setupSignpost();

}

float angle;



@Override

public void simpleUpdate(float tpf)

{

super.simpleUpdate(tpf);

angle += tpf;

angle %= FastMath.TWO_PI;



spot.setPosition(new Vector3f(FastMath.cos(angle) * 30f, 24.013165f, FastMath.sin(angle) * 20f));

lightMdl.setLocalTranslation(spot.getPosition());

spot.setDirection(lightTarget.subtract(spot.getPosition()));



slsr.setDirection(spot.getDirection());

slsr.setLocation(spot.getPosition());

}

}







Modified BasicShadowRenderer.java:



package jme3test.light;

//package mygame;

//package com.jme3.shadow;



import com.jme3.material.Material;

import com.jme3.math.Vector3f;

import com.jme3.renderer.Camera;

import com.jme3.renderer.Renderer;

import com.jme3.renderer.queue.RenderQueue;

import com.jme3.renderer.queue.RenderQueue.ShadowMode;

import com.jme3.renderer.queue.GeometryList;

import com.jme3.asset.AssetManager;

import com.jme3.post.SceneProcessor;

import com.jme3.renderer.RenderManager;

import com.jme3.renderer.ViewPort;

import com.jme3.texture.FrameBuffer;

import com.jme3.texture.Image.Format;

import com.jme3.texture.Texture2D;

import com.jme3.ui.Picture;



/**

  • SpotLightShadowRenderer uses standard shadow mapping with one map

    *
  • BasicShadowRenderer @author Kirill Vainer
  • @modified mjt

    */

    public class SpotLightShadowRenderer implements SceneProcessor

    {

    private RenderManager renderManager;

    private ViewPort viewPort;

    private FrameBuffer shadowFB;

    private Texture2D shadowMap;

    private Camera shadowCam;

    private Material preshadowMat;

    private Material postshadowMat;

    private Picture dispPic = new Picture(“Picture”);

    private boolean noOccluders = false;

    private Vector3f[] points = new Vector3f[8];

    private Vector3f direction = new Vector3f();

    private Vector3f location = new Vector3f();

    public boolean debug = false;



    /**
  • Creates a SpotLightShadowRenderer
  • @param manager the asset manager
  • @param size the size of the shadow map (the map is square)

    */

    public SpotLightShadowRenderer(AssetManager manager, int size)

    {

    shadowFB = new FrameBuffer(size, size, 1);

    shadowMap = new Texture2D(size, size, Format.Depth);

    shadowFB.setDepthTexture(shadowMap);

    shadowCam = new Camera(size, size);



    preshadowMat = new Material(manager, “Common/MatDefs/Shadow/PreShadow.j3md”);

    postshadowMat = new Material(manager, “Common/MatDefs/Shadow/PostShadow.j3md”);

    postshadowMat.setTexture(“ShadowMap”, shadowMap);



    dispPic.setTexture(manager, shadowMap, false);



    for (int i = 0; i < points.length; i++)

    {

    points = new Vector3f();

    }

    }



    public void initialize(RenderManager rm, ViewPort vp)

    {

    renderManager = rm;

    viewPort = vp;



    reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());

    }



    public boolean isInitialized()

    {

    return viewPort != null;

    }



    /**
  • returns the light direction used for this processor
  • @return

    */

    public Vector3f getDirection()

    {

    return direction;

    }



    /**
  • sets the light direction to use to computs shadows
  • @param direction

    */

    public void setDirection(Vector3f direction)

    {

    this.direction.set(direction).normalizeLocal();

    }



    /**
  • returns the light location used for this processor
  • @return

    */

    public Vector3f getLocation()

    {

    return location;

    }



    /**
  • sets the light location to use to computs shadows
  • @param location

    */

    public void setLocation(Vector3f location)

    {

    this.location.set(location);

    }



    /**
  • debug only
  • @return

    */

    public Vector3f[] getPoints()

    {

    return points;

    }



    /**
  • debug only
  • returns the shadow camera
  • @return

    */

    public Camera getShadowCamera()

    {

    return shadowCam;

    }



    public void postQueue(RenderQueue rq)

    {

    GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);

    if (occluders.size() == 0)

    {

    noOccluders = true;

    return;

    } else

    {

    noOccluders = false;

    }



    GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive);



    Camera viewCam = viewPort.getCamera();

    shadowCam.setProjectionMatrix(null);

    shadowCam.setFrustumPerspective(45, 1, 1, 200); // TODO: I think these should be variables that user can change on his/her code

    shadowCam.lookAtDirection(direction, Vector3f.UNIT_Y);

    shadowCam.update();

    shadowCam.setLocation(location);

    shadowCam.update();

    shadowCam.updateViewProjection();



    com.jme3.shadow.ShadowUtil.updateFrustumPoints(shadowCam,

    shadowCam.getFrustumNear(),

    shadowCam.getFrustumFar(),

    1.0f,

    points);



    // render shadow casters to shadow map

    com.jme3.shadow.ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points);



    Renderer r = renderManager.getRenderer();

    renderManager.setCamera(shadowCam, false);

    renderManager.setForcedMaterial(preshadowMat);



    r.setFrameBuffer(shadowFB);

    r.clearBuffers(false, true, false);

    viewPort.getQueue().renderShadowQueue(ShadowMode.Cast, renderManager, shadowCam, true);

    r.setFrameBuffer(viewPort.getOutputFrameBuffer());



    renderManager.setForcedMaterial(null);

    renderManager.setCamera(viewCam, false);

    }



    /**
  • debug only
  • @return

    */

    public Picture getDisplayPicture()

    {

    return dispPic;

    }



    public void postFrame(FrameBuffer out)

    {

    if (!noOccluders)

    {

    postshadowMat.setMatrix4(“LightViewProjectionMatrix”, shadowCam.getViewProjectionMatrix());

    renderManager.setForcedMaterial(postshadowMat);

    viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, viewPort.getCamera(), true);

    renderManager.setForcedMaterial(null);

    }



    if (debug) {

    displayShadowMap(renderManager.getRenderer());

    }



    }



    public void preFrame(float tpf)

    {

    }



    public void cleanup()

    {

    }



    public void reshape(ViewPort vp, int w, int h)

    {

    dispPic.setPosition(w / 20f, h / 20f);

    dispPic.setWidth(w / 5f);

    dispPic.setHeight(h / 5f);

    }



    //debug only : displays depth shadow maps

    private void displayShadowMap(Renderer r)

    {

    Camera cam = viewPort.getCamera();

    renderManager.setCamera(cam, true);

    int h = cam.getHeight();

    dispPic.setPosition(64 * 1 + 128, h / 20f);

    dispPic.setWidth(128);

    dispPic.setHeight(128);

    dispPic.updateGeometricState();

    renderManager.renderGeometry(dispPic);



    renderManager.setCamera(cam, false);

    }

    }

1 Like

Yeah, thats basically how you’d have to do it.

And using spotlight texture when rendering ‘light’s view’, checking pixels only when on light, so no need to use that other spotlight method

(at least this is the way I used when programmed shadow mapping to csat engine). Requires small modification in shader and code (binding that texture)

hey thanks i’m gonna look into this to add spotlight support to basic and pssm shadows.

@nehon: I don’t think the way PSSM is designed would allow spot light shadows… Probably best to support it as basic shadows but allow shadows from multiple lights to be combined (multiple spot lights)

true.