Basic shadow mapping

From JPCT
Revision as of 13:46, 28 October 2016 by Admin (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

This is a basic example of shadow mapping using jPCT-AE in OpenGL ES 2.0 mode and the ShadowHelper class. This example uses PCF filtering to smooth out the edges of the shadow. Depending on the application, you might want to disable this for performance reasons.

The basic idea is simple: You create an instance of the ShadowHelper, assign a projector (i.e. the light source that should cast the shadow), some receivers and some casters and in the rendering process, you add an additional call to ShadowHelper.updateShadowMap(), if that's needed (i.e. if either the object(s) or the projector have been translated). The rest should happen automatically. If you are using your own shaders, it might happen automatically as well. That depends on how much your own shaders are based on jPCT-AE's default shaders. The shadow mapping code tries to modify existing shaders to make them support shadow mapping. Depening on the structure of the shader, this might or might not work. More information about this can be found here: GLSLShadowInjector

package com.threed.jpct.examples;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.MotionEvent;

import com.threed.jpct.Camera;
import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.GLSLShadowInjector;
import com.threed.jpct.Light;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
import com.threed.jpct.Projector;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;
import com.threed.jpct.util.ExtendedPrimitives;
import com.threed.jpct.util.MemoryHelper;
import com.threed.jpct.util.ShadowHelper;

public class ShadowExample extends Activity {

	private GLSurfaceView mGLView;
	private MyRenderer renderer = null;
	private FrameBuffer buffer = null;
	private World world = null;
	private RGBColor back = new RGBColor(50, 50, 100);

	private float touchTurn = 0;
	private float touchTurnUp = 0;

	private float xpos = -1;
	private float ypos = -1;

	private Object3D myObject = null;
	private Object3D plane = null;
	private int fps = 0;

	private Light sun = null;
	private ShadowHelper sh;

	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		mGLView = new GLSurfaceView(getApplication());
		mGLView.setEGLContextClientVersion(2);
		renderer = new MyRenderer();
		mGLView.setRenderer(renderer);
		setContentView(mGLView);
	}

	@Override
	protected void onPause() {
		super.onPause();
		mGLView.onPause();
	}

	@Override
	protected void onResume() {
		super.onResume();
		mGLView.onResume();
	}

	@Override
	protected void onStop() {
		super.onStop();
		System.exit(0);
	}

	public boolean onTouchEvent(MotionEvent me) {

		if (me.getAction() == MotionEvent.ACTION_DOWN) {
			xpos = me.getX();
			ypos = me.getY();
			return true;
		}

		if (me.getAction() == MotionEvent.ACTION_UP) {
			xpos = -1;
			ypos = -1;
			touchTurn = 0;
			touchTurnUp = 0;
			return true;
		}

		if (me.getAction() == MotionEvent.ACTION_MOVE) {
			float xd = me.getX() - xpos;
			float yd = me.getY() - ypos;

			xpos = me.getX();
			ypos = me.getY();

			touchTurn = xd / -100f;
			touchTurnUp = yd / -100f;
			return true;
		}
		return super.onTouchEvent(me);
	}

	protected boolean isFullscreenOpaque() {
		return true;
	}

	class MyRenderer implements GLSurfaceView.Renderer {

		private long time = System.currentTimeMillis();

		public MyRenderer() {
			Config.farPlane = 5000;
		}

		public void onSurfaceChanged(GL10 gl, int w, int h) {
			try {
				buffer = new FrameBuffer(w, h);

				world = new World();
				world.setAmbientLight(100, 100, 100);

				myObject = ExtendedPrimitives.createEllipsoid(new SimpleVector(20, 30, 15), 15, 1f, 1f);
				myObject.build();
				myObject.translate(0, 10, 0);
				world.addObject(myObject);

				plane = ExtendedPrimitives.createPlane(100, 2);
				plane.build();
				plane.translate(0, 30, 0);
				world.addObject(plane);

				Camera cam = world.getCamera();
				cam.moveCamera(Camera.CAMERA_MOVEOUT, 50);
				cam.lookAt(plane.getTransformedCenter());

				SimpleVector sv = new SimpleVector(myObject.getTransformedCenter());
				sv.y -= 100;
				sv.z += 30;

				sun = new Light(world);
				sun.setIntensity(250, 250, 250);
				sun.setPosition(sv);

				Projector projector = new Projector();
				projector.setClippingPlanes(1f, 300f);
				projector.setFOVLimits(0, 999);
				float fov = projector.convertDEGAngleIntoFOV(90);
				projector.setFOV(fov);
				projector.setYFOV(fov);
				projector.setPosition(sun.getPosition());
				projector.lookAt(myObject.getTransformedCenter());

				ShadowHelper.setShadowMode(GLSLShadowInjector.PCF_FILTERED_SHADOWS);

				sh = new ShadowHelper(buffer, projector, 1024);
				sh.setLightSource(projector);
				sh.setAmbientLight(new RGBColor(50, 50, 50));
				sh.addCaster(myObject);
				sh.addReceiver(plane);
				sh.setFilterSize(1);

				MemoryHelper.compact();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}

		public void onSurfaceCreated(GL10 gl, EGLConfig config) {
			//
		}

		public void onDrawFrame(GL10 gl) {
			if (touchTurn != 0) {
				myObject.rotateY(touchTurn);
				touchTurn = 0;
			}

			if (touchTurnUp != 0) {
				myObject.rotateX(touchTurnUp);
				touchTurnUp = 0;
			}

			sh.updateShadowMap(buffer, world);

			buffer.clear(back);
			world.renderScene(buffer);
			world.draw(buffer);

			buffer.display();

			if (System.currentTimeMillis() - time >= 1000) {
				Logger.log(fps + "fps");
				fps = 0;
				time = System.currentTimeMillis();
			}
			fps++;
		}
	}
}