org.jge.render.RenderEngine.java Source code

Java tutorial

Introduction

Here is the source code for org.jge.render.RenderEngine.java

Source

package org.jge.render;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL30.*;

import java.util.ArrayList;
import java.util.Stack;

import org.jge.AbstractResource;
import org.jge.EngineException;
import org.jge.JGEngine;
import org.jge.Profiler.ProfileTimer;
import org.jge.ResourceLocation;
import org.jge.Window;
import org.jge.components.BaseLight;
import org.jge.components.BaseLight.LightCamTrans;
import org.jge.components.Camera;
import org.jge.components.SceneObject;
import org.jge.components.ShadowMapSize;
import org.jge.components.ShadowingInfo;
import org.jge.events.EventBus;
import org.jge.events.WindowResizeEvent;
import org.jge.game.DummySceneObject;
import org.jge.game.HUDObject;
import org.jge.gpuresources.MappedValues;
import org.jge.gpuresources.TextureResource;
import org.jge.maths.Maths;
import org.jge.maths.Matrix4;
import org.jge.maths.Quaternion;
import org.jge.maths.Transform;
import org.jge.maths.Vector3;
import org.jge.render.mesh.Mesh;
import org.jge.render.shaders.Shader;
import org.jge.util.HashMapWithDefault;
import org.jge.util.Log;

import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.util.glu.GLU;

/**
 * The MIT License (MIT)
 * 
 * Copyright (c) 2014 jglrxavpok
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 * @author jglrxavpok
 * 
 */
public class RenderEngine extends MappedValues {
    private static final Matrix4 bias = new Matrix4().initScale(0.5f, 0.5f, 0.5f)
            .mul(new Matrix4().initTranslation(1, 1, 1));

    private static boolean TEST_DRAW_SHADOW_MAP = false;

    private ProfileTimer renderProfileTimer = new ProfileTimer();
    private ProfileTimer windowSyncProfileTimer = new ProfileTimer();
    private Texture renderToTextTarget;
    private Mesh planeMesh;
    private Transform altTransform;
    private Material planeMaterial;
    private Camera altCamera;
    private SceneObject renderToTextCameraObject;

    private Camera renderCamera;
    private Shader ambientShader;

    private ArrayList<Shader> postFilters;
    private ArrayList<BaseLight> lights;
    private BaseLight activeLight;

    private HashMapWithDefault<String, Integer> samplers;
    public Shader defaultShader;
    private Shader renderToTextShader;
    private Shader shadowMapShader;
    public Shader nullFilterShader;
    public Shader gausBlurFilterShader;

    private Matrix4 initMatrix;

    private Matrix4 lightMatrix;

    private Texture[] shadowMaps;

    private Texture[] shadowMapTempTargets;

    private Texture renderToTextTargetTemp;

    private boolean renderingShadowMap;

    private Quaternion remplacingColor;

    private HUDObject hudObject;

    private TextureResource renderTarget;

    private RenderState renderState;

    private Stack<RenderState> renderStatesStack;

    private Stack<Runnable> beforeRenders;

    private EventBus eventBus;

    private Texture boundTexture;

    public RenderEngine() {
        eventBus = new EventBus();
        renderState = new RenderState();
        enableGLCap(GL_TEXTURE_2D);
        renderStatesStack = new Stack<RenderState>();
        defaultShader = new Shader(new ResourceLocation("shaders", "basic"));
        samplers = new HashMapWithDefault<String, Integer>();
        samplers.setDefault(-1);

        samplers.put("diffuse", 0);
        samplers.put("normalMap", 1);
        samplers.put("dispMap", 2);
        samplers.put("shadowMap", 3);
        samplers.put("filterTexture", 4);
        samplers.put("lightMap", 5);
    }

    public void setHUDObject(HUDObject hud) {
        this.hudObject = hud;
    }

    public HUDObject getHUDObject() {
        return hudObject;
    }

    public double displayRenderTime() {
        return displayRenderTime(0);
    }

    public double displayRenderTime(double divisor) {
        return renderProfileTimer.displayAndReset("Render time", divisor);
    }

    public double displayWindowSyncTime() {
        return displayWindowSyncTime(0);
    }

    public double displayWindowSyncTime(double divisor) {
        return windowSyncProfileTimer.displayAndReset("Window sync time", divisor);
    }

    public RenderEngine setShadowing(boolean shadowing) {
        this.setBoolean("shadowing", shadowing);
        return this;
    }

    public RenderEngine setLighting(boolean lighting) {
        this.setBoolean("lighting", lighting);
        return this;
    }

    public RenderEngine setParallaxDispMapping(boolean parDispMapping) {
        this.setBoolean("dispMapping", parDispMapping);
        return this;
    }

    public boolean isLightingOn() {
        return getBoolean("lighting");
    }

    public boolean isShadowingOn() {
        return getBoolean("shadowing");
    }

    public boolean isParallaxDispMappingOn() {
        return getBoolean("dispMapping");
    }

    public void init() {
        try {
            beforeRenders = new Stack<Runnable>();
            this.setBoolean("normalMapping", true);
            this.setParallaxDispMapping(true);
            this.setLighting(true);
            this.setShadowing(true);
            postFilters = new ArrayList<Shader>();

            lightMatrix = new Matrix4().initScale(0, 0, 0);
            ambientShader = new Shader(new ResourceLocation("shaders", "forward-ambient"));
            renderToTextShader = new Shader(new ResourceLocation("shaders", "renderToText"));
            shadowMapShader = new Shader(new ResourceLocation("shaders", "shadowMapGen"));
            nullFilterShader = new Shader(new ResourceLocation("shaders", "filter-null"));
            gausBlurFilterShader = new Shader(new ResourceLocation("shaders", "filter-gausBlur7x1"));

            setClearColor(0, 0, 0, 0);
            glFrontFace(GL_CW);
            glCullFace(GL_BACK);
            enableGLCap(GL_CULL_FACE);
            enableGLCap(GL_DEPTH_TEST);
            enableGLCap(GL32.GL_DEPTH_CLAMP);

            glShadeModel(GL_SMOOTH);
            setVector3("ambient", Vector3.get(0.75f, 0.75f, 0.75f));
            shadowMaps = new Texture[ShadowMapSize.values().length];
            shadowMapTempTargets = new Texture[ShadowMapSize.values().length];
            for (int i = 0; i < ShadowMapSize.values().length; i++) {
                shadowMaps[i] = new Texture(ShadowMapSize.values()[i].getSize(),
                        ShadowMapSize.values()[i].getSize(), null, GL_TEXTURE_2D, GL_LINEAR, GL_COLOR_ATTACHMENT0,
                        GL_RG32F, GL_RGBA, true);
                shadowMapTempTargets[i] = new Texture(ShadowMapSize.values()[i].getSize(),
                        ShadowMapSize.values()[i].getSize(), null, GL_TEXTURE_2D, GL_LINEAR, GL_COLOR_ATTACHMENT0,
                        GL_RG32F, GL_RGBA, true);
            }

            lights = new ArrayList<BaseLight>();

            glClampColor(GL_CLAMP_FRAGMENT_COLOR, GL_FALSE);
            glClampColor(GL_CLAMP_READ_COLOR, GL_FALSE);
            glClampColor(GL_CLAMP_VERTEX_COLOR, GL_FALSE);

            double width = (double) Window.getCurrent().getWidth() / 1.0;
            double height = (double) Window.getCurrent().getHeight() / 1.0;

            renderToTextTarget = new Texture((int) width, (int) height, null, GL_TEXTURE_2D, GL_NEAREST,
                    GL30.GL_COLOR_ATTACHMENT0, GL30.GL_RGBA32F, GL_RGBA, false);
            renderToTextTargetTemp = new Texture((int) width, (int) height, null, GL_TEXTURE_2D, GL_NEAREST,
                    GL30.GL_COLOR_ATTACHMENT0, GL30.GL_RGBA32F, GL_RGBA, false);
            planeMaterial = new Material();
            planeMaterial.setFloat("specularIntensity", 1);
            planeMaterial.setFloat("specularPower", 8);
            planeMaterial.setTexture("diffuse", renderToTextTarget);
            altTransform = new Transform();
            altCamera = new Camera(initMatrix = new Matrix4().initIdentity());
            altCamera.setName("alternative camera");
            renderToTextCameraObject = new DummySceneObject(altCamera);
            renderToTextCameraObject.getTransform().rotate(Vector3.get(0, 1, 0), (float) Maths.toRadians(180));

            altTransform.rotate(Vector3.get(1, 0, 0), (float) Maths.toRadians(90));
            altTransform.rotate(Vector3.get(0, 1, 0), (float) Maths.toRadians(180));
            planeMesh = new Mesh(JGEngine.getClasspathResourceLoader()
                    .getResource(new ResourceLocation("models", "planePrimitive.obj")));

            setVector3("shadowColor", Vector3.get(0, 0, 0));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public RenderEngine addPostProcessingFilter(Shader filter) {
        postFilters.add(filter);
        return this;
    }

    public RenderEngine removeAllPostProcessingFilters() {
        postFilters.clear();
        return this;
    }

    public Camera getCamera() {
        return renderCamera;
    }

    public RenderEngine clearBuffers() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        return this;
    }

    public Texture loadTexture(AbstractResource res) throws EngineException {
        return new Texture(res);
    }

    public void blurShadowMap(int index, float blurScale) {
        Texture text = shadowMaps[index];
        setVector3("blurScale", Vector3.get(blurScale / (text.getWidth()), 0, 0));
        applyFilter(gausBlurFilterShader, text, shadowMapTempTargets[index]);

        setVector3("blurScale", Vector3.get(0, blurScale / (text.getHeight()), 0));
        applyFilter(gausBlurFilterShader, shadowMapTempTargets[index], text);
    }

    public void applyFilter(Shader filter, Texture source, Texture dest) {
        if (dest == null)
            Window.getCurrent().bindAsRenderTarget();
        else
            dest.bindAsRenderTarget();

        setTexture("filterTexture", source);
        altCamera.getParent().getTransform().setPosition(Vector3.NULL);
        altCamera.getParent().getTransform().setRotation(Quaternion.NULL);
        altCamera.setProjection(initMatrix);
        glClear(GL_DEPTH_BUFFER_BIT);

        filter.bind();
        filter.updateUniforms(altTransform, altCamera, planeMaterial, this);
        planeMesh.draw();

        setTexture("filterTexture", null);
    }

    public RenderEngine renderScene(SceneObject object, Camera renderCamera, Texture renderToTextureText,
            Texture renderToTextureTextTemp, double delta) {
        setInt("lightNumber", Maths.max(1, lights.size() + 1));

        renderToTextureText.bindAsRenderTarget();
        object.renderAll(ambientShader, renderCamera, delta, this);

        for (BaseLight light : lights) {
            if (!light.isEnabled())
                continue;
            int shadowMapIndex = 0;
            activeLight = light;
            ShadowingInfo shadowingInfo = light.getShadowingInfo();
            if (shadowingInfo != null) {
                shadowMapIndex = shadowingInfo.getShadowMapSize().ordinal();
            }
            setLightMatrix(initMatrix);
            shadowMaps[shadowMapIndex].bindAsRenderTarget();
            setTexture("shadowMap", shadowMaps[shadowMapIndex]);
            setTexture("shadowMapTempTarget", shadowMapTempTargets[shadowMapIndex]);
            setClearColor(0, 0, 0, 0);
            glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
            renderingShadowMap = true;
            if (shadowingInfo != null && isShadowingOn()) {
                setFloat("shadowLightBleedingReduction", shadowingInfo.getLightBleedingReduction());
                setFloat("shadowVarianceMin", shadowingInfo.getVarianceMin());
                setVector3("shadowTexelSize",
                        Vector3.get(1.0f / (float) ShadowMapSize.values()[shadowMapIndex].getSize(),
                                1.0f / (float) ShadowMapSize.values()[shadowMapIndex].getSize(), 0f));
                altCamera.setProjection(shadowingInfo.getProjection());
                LightCamTrans tr = activeLight.getLightCamTrans(renderCamera);
                altCamera.getParent().getTransform().setPosition(tr.pos);
                altCamera.getParent().getTransform().setRotation(tr.rot);

                setLightMatrix(bias.mul(altCamera.getViewProjection()));

                if (shadowingInfo.flipFaces())
                    glCullFace(GL_FRONT);
                object.renderAll(shadowMapShader, altCamera, delta, this);
                if (shadowingInfo.flipFaces())
                    glCullFace(GL_BACK);

                if (shadowingInfo.getShadowSoftness() != 0.0)
                    blurShadowMap(shadowMapIndex, shadowingInfo.getShadowSoftness());
            } else {
                setLightMatrix(new Matrix4().initScale(0, 0, 0));

                setFloat("shadowLightBleedingReduction", 0);
                setFloat("shadowVarianceMin", 0.0002f);
            }

            renderingShadowMap = false;
            renderToTextureText.bindAsRenderTarget();

            enableGLCap(GL_BLEND);
            setBlendFunc(GL_ONE, GL_ONE);
            glDepthMask(false);
            glDepthFunc(GL_EQUAL);
            if (light.getShader() != null)
                object.renderAll(light.getShader(), renderCamera, delta, this);
            glDepthFunc(GL_LESS);
            glDepthMask(true);
            disableGLCap(GL_BLEND);

            activeLight = null;
        }

        if (!postFilters.isEmpty() && renderToTextureText.equals(this.renderToTextTarget)) {
            int i = 0;
            for (Shader filter : postFilters) {
                if (i % 2 == 0) {
                    applyFilter(filter, renderToTextureText, renderToTextureTextTemp);
                } else {
                    applyFilter(filter, renderToTextureTextTemp, renderToTextureText);
                }
                i++;
            }

            if (i % 2 != 0) {
                applyFilter(nullFilterShader, renderToTextureTextTemp, renderToTextureText);
            }
        }
        applyFilter(renderToTextShader, renderToTextureText, renderToTextureTextTemp);
        applyFilter(nullFilterShader, renderToTextureTextTemp, renderToTextureText);
        // printIfGLError();
        return this;
    }

    public RenderEngine render(SceneObject object, double delta) {
        while (!beforeRenders.isEmpty())
            beforeRenders.pop().run();

        renderProfileTimer.startInvocation();
        renderToTextTarget.bindAsRenderTarget();
        setClearColor(0, 0, 0, 0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        renderScene(object, this.renderCamera, renderToTextTarget, renderToTextTargetTemp, delta);
        Window.getCurrent().bindAsRenderTarget();

        altCamera.getParent().getTransform().setPosition(Vector3.NULL);
        altCamera.getParent().getTransform().setRotation(Quaternion.NULL);
        altCamera.setProjection(initMatrix);
        setClearColor(0.0f, 0.0f, 1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        defaultShader.bind();
        defaultShader.updateUniforms(altTransform, altCamera, planeMaterial, this);
        planeMesh.draw();

        if (hudObject != null && object != hudObject) {
            defaultShader.bind();
            hudObject.renderAll(defaultShader, altCamera, delta, this);
        }

        if (TEST_DRAW_SHADOW_MAP) {
            glClear(GL_DEPTH_BUFFER);
            disableGLCap(GL_DEPTH_TEST);
            planeMaterial.setTexture("diffuse", getTexture("shadowMap"));
            defaultShader.bind();
            altTransform.setScale(Vector3.get(0.1f, 0.1f, 0.1f));
            altTransform.setPosition(Vector3.get(-0.9f, -0.9f, 0f));
            defaultShader.updateUniforms(altTransform, altCamera, planeMaterial, this);
            planeMesh.draw();
            altTransform.setPosition(Vector3.get(0, 0, 0));
            altTransform.setScale(Vector3.get(1, 1, 1));
            planeMaterial.setTexture("diffuse", renderToTextTarget);
            enableGLCap(GL_DEPTH_TEST);
        }

        object.onPostRenderAll(delta, this);
        if (hudObject != null) {
            hudObject.onPostRenderAll(delta, this);
        }
        renderProfileTimer.endInvocation();

        windowSyncProfileTimer.startInvocation();
        windowSyncProfileTimer.endInvocation();
        return this;
    }

    public static void printIfGLError() {
        int error = glGetError();
        if (error != GL_NO_ERROR) {
            Log.error("Error " + error + ": " + GLU.gluErrorString(error));
        }
    }

    public boolean isRenderingShadowMap() {
        return renderingShadowMap;
    }

    public Matrix4 getLightMatrix() {
        return lightMatrix;
    }

    /**
     * Returns a copy of the ambient color vector
     * 
     * @return
     */
    public Vector3 getAmbientColor() {
        return getVector3("ambient").copy();
    }

    public RenderEngine addLight(BaseLight light) {
        lights.add(light);
        return this;
    }

    public BaseLight getActiveLight() {
        return activeLight;
    }

    public RenderEngine removeLight(BaseLight light) {
        lights.remove(light);
        return this;
    }

    public int getSamplerSlot(String name) {
        return samplers.get(name);
    }

    public void setLightMatrix(Matrix4 m) {
        this.lightMatrix = m;
        renderState.setLightMatrix(lightMatrix);
    }

    public RenderEngine setRemplacingColor(Quaternion color) {
        this.remplacingColor = color;
        return this;
    }

    public Quaternion getRemplacingColor() {
        return remplacingColor;
    }

    public RenderEngine setRenderTarget(TextureResource target) {
        this.renderTarget = target;
        return this;
    }

    public TextureResource getRenderTarget() {
        return renderTarget;
    }

    public RenderEngine setCamera(Camera cam) {
        this.renderCamera = cam;
        return this;
    }

    public RenderEngine pushState() {
        renderStatesStack.push(renderState);
        renderState = renderState.clone();
        return this;
    }

    public RenderEngine popState() {
        RenderState pop = renderStatesStack.pop();
        pop.apply(this);
        renderState = pop;
        return this;
    }

    public RenderState getRenderState() {
        return renderState;
    }

    public boolean isGLCapEnabled(int cap) {
        return glIsEnabled(cap);
    }

    public RenderEngine enableGLCap(int cap) {
        glEnable(cap);
        renderState.setGLCap(cap, true);
        return this;
    }

    public RenderEngine disableGLCap(int cap) {
        glDisable(cap);
        renderState.setGLCap(cap, false);
        return this;
    }

    public RenderEngine setClearColor(float r, float g, float b, float a) {
        glClearColor(r, g, b, a);
        renderState.setClearColor(r, g, b, a);
        return this;
    }

    public void setBoolean(String name, boolean value) {
        super.setBoolean(name, value);
        renderState.setBoolean(name, value);
    }

    public void setVector3(String name, Vector3 value) {
        super.setVector3(name, value);
        renderState.setVector3(name, value.copy());
    }

    public void setFloat(String name, float value) {
        super.setFloat(name, value);
        renderState.setFloat(name, value);
    }

    public void setInt(String name, int value) {
        super.setInt(name, value);
        renderState.setInt(name, value);
    }

    public void setTexture(String name, Texture value) {
        super.setTexture(name, value);
        renderState.setTexture(name, value);
    }

    public RenderEngine setBlendFunc(int src, int dst) {
        glBlendFunc(src, dst);
        renderState.setBlendFunc(src, dst);
        return this;
    }

    public RenderEngine setAlphaFunc(int func, float ref) {
        glAlphaFunc(func, ref);
        renderState.setAlphaFunc(func, ref);
        return this;
    }

    public RenderEngine setAmbientColor(Vector3 ambient) {
        this.setVector3("ambient", ambient);
        return this;
    }

    public RenderEngine setAmbientColor(float r, float g, float b) {
        return setAmbientColor(Vector3.get(r, g, b));
    }

    public void invokeBeforeRender(Runnable r) {
        beforeRenders.push(r);
    }

    public void fireEvent(WindowResizeEvent event) {
        eventBus.fireEvent(event);
    }

    public EventBus getEventBus() {
        return eventBus;
    }

    public RenderEngine bindTexture(Texture texture) {
        bindTexture(texture, 0);
        return this;
    }

    public RenderEngine bindTexture(Texture texture, int samplerSlot) {
        assert (samplerSlot >= 0 && samplerSlot <= 31) : "Sampler slot must be >= 0 and <= 31";
        GL13.glActiveTexture(GL13.GL_TEXTURE0 + samplerSlot);
        glBindTexture(texture.getResource().getTarget(), texture.getResource().getID());
        this.boundTexture = texture;
        return this;
    }

    public Texture getBoundTexture() {
        return boundTexture;
    }
}