So I’m working on implementing a basic 2D forward rendering system right now with lighting, but there is a slight problem that I can’t wrap my head around. All of the articles that I have read about blending point me right where I am now. In forward rendering, you need to sum up all of the fragment shading calculations to get your final scene with blending, but somehow blending is just not working for me. I apologize if this problem is extremely simple ahead of time as I haven’t really worked with blending in OpenGL until just now.
I have included what I think to be the relevant code. If you need to see any more of the code, or details, just let me know.
Core class:
package gel.engine.core;
import gel.engine.graphics.RenderingEngine;
import static org.lwjgl.opengl.GL11.*;
import gel.engine.input.Input;
import gel.engine.window.Window;
public abstract class Core
{
private static Core instance;
protected boolean running;
protected int targetFps;
protected int targetUps;
protected ProfileTimer profileTimer;
protected Window window;
protected Input input;
protected RenderingEngine renderingEngine;
public Core()
{
if (instance == null)
{
instance = this;
this.running = false;
this.targetFps = -1;
this.targetUps = 60;
window = new Window();
} else
{
throw new IllegalStateException("Can not make more than one instance of Core");
}
}
public static Core getInstance()
{
return instance;
}
public static boolean created()
{
return (instance == null);
}
public boolean isRunning()
{
return running;
}
public int getTargetFps()
{
return targetFps;
}
public int getTargetUps()
{
return targetUps;
}
public ProfileTimer getProfileTimer()
{
return profileTimer;
}
public Window getWindow()
{
return window;
}
public Input getInput()
{
return input;
}
public RenderingEngine getRenderingEngine()
{
return renderingEngine;
}
public void setTargetFps(int target)
{
if (!running)
{
this.targetFps = target;
}
}
public void setTargetUps(int target)
{
if (!running)
{
this.targetUps = target;
}
}
public void start()
{
run();
}
public void stop()
{
running = false;
}
private void run()
{
// initialize the engine
initEngine();
running = true;
long lastSecond = System.nanoTime();
long thisFrame = System.nanoTime();
long lastFrame = System.nanoTime();
double updateTime = 1e9f / targetUps;
double renderTime = 1e9f / targetFps;
double accumulativeUpdateTime = 0;
double accumulativeFrameTime = 0;
long sleepTime = 0;
int updateCount = 0;
int frameCount = 0;
while (running)
{
// Get the delta between this frame and last frame
thisFrame = System.nanoTime();
profileTimer.updateDelta(thisFrame, lastFrame);
lastFrame = thisFrame;
// Accumulate the delta in the update and render accumulate variables.
accumulativeUpdateTime += profileTimer.getDelta();
accumulativeFrameTime += profileTimer.getDelta();
// Update and take into account any missed frames.
if (targetUps != -1)
{
while (accumulativeUpdateTime >= updateTime)
{
updateGame();
updateCount++;
accumulativeUpdateTime -= updateTime;
}
} else
{
updateGame();
updateCount++;
accumulativeUpdateTime = 0;
}
// Render if possible.
if (targetFps != -1)
{
if (accumulativeFrameTime >= renderTime)
{
renderGame();
frameCount++;
accumulativeFrameTime = 0;
} else
{
// Calculate sleep time and sleep for that amount.
sleepTime = Math.round((renderTime - accumulativeFrameTime) / (1024 * 1024));
try
{
Thread.sleep(sleepTime);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
} else
{
renderGame();
frameCount++;
accumulativeFrameTime = 0;
}
// Check if a second has passed, and if so, update fps and ups.
if (System.nanoTime() - lastSecond >= 1e9)
{
profileTimer.updateFps(frameCount);
profileTimer.updateUps(updateCount);
frameCount = 0;
updateCount = 0;
lastSecond = System.nanoTime();
}
// Check if the game needs to stop.
if (window.isCloseRequested())
{
stop();
}
}
// Unload the game content.
unloadGame();
}
private void initEngine()
{
// create gl context and initialize rendering engine
window.create();
renderingEngine = new RenderingEngine();
profileTimer = new ProfileTimer();
input = new Input();
input.create();
// load game content
loadGame();
}
private void loadGame()
{
load();
}
private void updateGame()
{
input.update();
update();
}
private void renderGame()
{
glClear(GL_COLOR_BUFFER_BIT);
renderingEngine.update();
render();
window.update();
}
private void unloadGame()
{
unload();
}
public abstract void load();
public abstract void update();
public abstract void render();
public abstract void unload();
}
RenderingEngine Class:
package gel.engine.graphics;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11.glDisable;
import gel.engine.core.Core;
import gel.engine.graphics.lighting.AmbientLight;
import gel.engine.graphics.lighting.PointLight;
import gel.engine.graphics.matrix.RotationMatrix;
import gel.engine.graphics.matrix.ScaleMatrix;
import gel.engine.graphics.matrix.TransformationMatrix;
import gel.engine.graphics.matrix.TranslationMatrix;
import gel.engine.math.Vector2f;
public class RenderingEngine
{
public static final String UNIFORM_MODEL_MATRIX = "modelMatrix";
public static final String UNIFORM_VIEW_MATRIX = "viewMatrix";
public static final String UNIFORM_PROJECTION_MATRIX = "projectionMatrix";
public static final String UNIFORM_AMBIENTCOLOR = "ambientColor";
private static RenderingEngine instance;
private VertexArray vertexArray;
private OrthographicCamera camera;
private ShaderProgram ambientShader;
private ShaderProgram pointLightShader;
private AmbientLight ambient;
public RenderingEngine()
{
if (instance == null)
{
instance = this;
vertexArray = new VertexArray();
camera = new OrthographicCamera(0, Core.getInstance().getWindow().getWidth(), 0, Core.getInstance().getWindow().getHeight(), 1, -1);
ambientShader = new ShaderProgram();
ambientShader.createAndCompileVertexShader("res\\shaders\\ambientShader.vs");
ambientShader.createAndCompileFragmentShader("res\\shaders\\ambientShader.fs");
ambientShader.linkProgram();
pointLightShader = new ShaderProgram();
pointLightShader.createAndCompileVertexShader("res\\shaders\\pointLightShader.vs");
pointLightShader.createAndCompileFragmentShader("res\\shaders\\pointLightShader.fs");
pointLightShader.linkProgram();
ambient = new AmbientLight(new Color(255, 255, 255));
} else
{
throw new IllegalStateException("Can not make more than one instance of RenderingEngine");
}
}
public static RenderingEngine getInstance()
{
return instance;
}
public OrthographicCamera getCamera()
{
return camera;
}
public AmbientLight getAmbientLight()
{
return ambient;
}
public void update()
{
camera.update();
ambientShader.setMatrix4f(UNIFORM_VIEW_MATRIX, camera.getViewMatrix().getMatrix());
ambientShader.setMatrix4f(UNIFORM_PROJECTION_MATRIX, camera.getProjectionMatrix().getMatrix());
pointLightShader.setMatrix4f(UNIFORM_VIEW_MATRIX, camera.getViewMatrix().getMatrix());
pointLightShader.setMatrix4f(UNIFORM_PROJECTION_MATRIX, camera.getProjectionMatrix().getMatrix());
}
// update uniforms for rendering passes
public void renderTexture(Texture t, float x, float y, float rotation)
{
if (x + t.getWidth() >= 0 && x < Core.getInstance().getWindow().getWidth() + t.getWidth() && y + t.getHeight() >= 0 && y < Core.getInstance().getWindow().getHeight() + t.getHeight())
{
ambientShader.bind();
vertexArray.clear();
vertexArray.addVertex(new Vertex(new Vector2f(-t.getWidth() / 2, -t.getHeight() / 2), new Color(0), new Vector2f(1, 1)));
vertexArray.addVertex(new Vertex(new Vector2f(-t.getWidth() / 2, t.getHeight() / 2), new Color(0), new Vector2f(1, 0)));
vertexArray.addVertex(new Vertex(new Vector2f(t.getWidth() / 2, t.getHeight() / 2), new Color(0), new Vector2f(0, 0)));
vertexArray.addVertex(new Vertex(new Vector2f(t.getWidth() / 2, -t.getHeight() / 2), new Color(0), new Vector2f(0, 1)));
ambientShader.setMatrix4f(UNIFORM_MODEL_MATRIX, new TransformationMatrix(new TranslationMatrix(x + t.getWidth() / 2, y + t.getHeight() / 2), new RotationMatrix(rotation), new ScaleMatrix(1, 1)).getMatrix());
ambientShader.setVector3f(UNIFORM_AMBIENTCOLOR, ambient.getColor().toVector3f());
vertexArray.render();
}
}
public void renderPointLight(PointLight light)
{
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
if (light.getPosition().getX() + light.getRadius() >= 0 && light.getPosition().getX() < Core.getInstance().getWindow().getWidth() && light.getPosition().getY() + light.getRadius() >= 0 && light.getPosition().getY() < Core.getInstance().getWindow().getHeight())
{
pointLightShader.bind();
vertexArray.clear();
vertexArray.addVertex(new Vertex(new Vector2f(-light.getRadius(), -light.getRadius())));
vertexArray.addVertex(new Vertex(new Vector2f(-light.getRadius(), light.getRadius())));
vertexArray.addVertex(new Vertex(new Vector2f(light.getRadius(), light.getRadius())));
vertexArray.addVertex(new Vertex(new Vector2f(light.getRadius(), -light.getRadius())));
pointLightShader.setMatrix4f(UNIFORM_MODEL_MATRIX, new TransformationMatrix(new TranslationMatrix(light.getPosition().getX(), light.getPosition().getY()), new RotationMatrix(0), new ScaleMatrix(1, 1)).getMatrix());
pointLightShader.setVector2f("pos", light.getPosition());
pointLightShader.setVector3f("color", light.getColor().toVector3f());
pointLightShader.setFloat("radius", light.getRadius());
pointLightShader.setFloat("constant", light.getConstAtten());
pointLightShader.setFloat("linear", light.getLinearAtten());
pointLightShader.setFloat("quadratic", light.getQuadAtten());
vertexArray.render();
}
glDisable(GL_BLEND);
}
}
Game class(Subclass of Core):
package game;
import static org.lwjgl.opengl.GL11.*;
import gel.engine.core.Core;
import gel.engine.graphics.Bitmap;
import gel.engine.graphics.Color;
import gel.engine.graphics.Texture;
import gel.engine.graphics.lighting.PointLight;
import gel.engine.math.Vector2f;
public class Game extends Core
{
private Texture grass;
private PointLight light;
public static void main(String[] args)
{
Game g = new Game();
g.getWindow().setTitle("Gel Engine");
g.getWindow().setSize(16 * 60, 9 * 60);
g.getWindow().setResizable(false);
g.start();
}
@Override
public void load()
{
grass = new Texture(new Bitmap("res\\grass.png"));
light = new PointLight(new Vector2f(400, 400), new Color(255, 127, 0), 200, 0, 0, 0.01f); // pos, color, range, const, linear, quadratic attenuation
}
@Override
public void update()
{
}
@Override
public void render()
{
renderingEngine.renderTexture(grass, 400, 400, 0);
renderingEngine.renderPointLight(light);
}
@Override
public void unload()
{
}
}
Texture and Ambient Vertex Shader:
#version 110
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
void main()
{
gl_Position = projectionMatrix * viewMatrix * modelMatrix * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
}
Ambient Fragment Shader:
#version 110
uniform sampler2D textureSampler;
uniform vec3 ambientColor;
void main()
{
gl_FragColor = vec4(ambientColor, 1.0) * texture2D(textureSampler, gl_TexCoord[0].st);
}
Point light fragment shader:
#version 110
uniform vec2 pos;
uniform vec3 color;
uniform float radius;
uniform float constant;
uniform float linear;
uniform float quadratic;
void main()
{
float dist = distance(gl_FragCoord.xy, pos);
float atten = 1.0 / (quadratic * dist * dist + linear * dist + constant);
gl_FragColor = vec4(atten, atten, atten, 1.0) * vec4(color, 1.0);
}
With this code, this is the current scene that I get:
If I comment out rendering the texture in the render() method in the Game class, I get the same results. Could blending be getting ignored or something alike?
If I comment out where I render the point light in the render() method of the Game class I get the actual texture rendered, like so:
Out of those results, I would expect to get the grass texture additively blended in with the light, but that is obviously not so. If anyone who has a solution to this issue, it would be greatly appreciated.