Create working lighting system
This commit is contained in:
@@ -13,6 +13,7 @@ import com.bartlomiejpluta.base.api.gui.GUI;
|
||||
import com.bartlomiejpluta.base.api.icon.Icon;
|
||||
import com.bartlomiejpluta.base.api.image.Image;
|
||||
import com.bartlomiejpluta.base.api.input.Input;
|
||||
import com.bartlomiejpluta.base.api.light.Light;
|
||||
import com.bartlomiejpluta.base.api.map.handler.MapHandler;
|
||||
import com.bartlomiejpluta.base.api.runner.GameRunner;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
@@ -29,6 +30,7 @@ import com.bartlomiejpluta.base.engine.world.entity.AbstractEntity;
|
||||
import com.bartlomiejpluta.base.engine.world.icon.manager.IconManager;
|
||||
import com.bartlomiejpluta.base.engine.world.icon.manager.IconSetManager;
|
||||
import com.bartlomiejpluta.base.engine.world.image.manager.ImageManager;
|
||||
import com.bartlomiejpluta.base.engine.world.light.DefaultLight;
|
||||
import com.bartlomiejpluta.base.engine.world.map.manager.MapManager;
|
||||
import com.bartlomiejpluta.base.engine.world.map.model.DefaultGameMap;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
@@ -171,6 +173,11 @@ public class DefaultContext implements Context {
|
||||
return new AbstractEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Light createLight() {
|
||||
return new DefaultLight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon createIcon(@NonNull String iconSetUid, int row, int column) {
|
||||
return iconManager.createIcon(iconSetUid, row, column);
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.bartlomiejpluta.base.engine.core.gl.render;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.CounterName;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.RenderConstants;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
@@ -22,15 +24,27 @@ public class DefaultRenderer implements Renderer {
|
||||
public void init() {
|
||||
log.info("Initializing renderer");
|
||||
shaderManager
|
||||
.createShader("default", "/shaders/default.vs", "/shaders/default.fs")
|
||||
.selectShader("default")
|
||||
.createUniform(UniformName.UNI_VIEW_MODEL_MATRIX)
|
||||
.createUniform(UniformName.UNI_PROJECTION_MATRIX)
|
||||
.createUniform(UniformName.UNI_OBJECT_COLOR)
|
||||
.createUniform(UniformName.UNI_HAS_OBJECT_TEXTURE)
|
||||
.createUniform(UniformName.UNI_TEXTURE_SAMPLER)
|
||||
.createUniform(UniformName.UNI_SPRITE_SIZE)
|
||||
.createUniform(UniformName.UNI_SPRITE_POSITION);
|
||||
.createShader("default", "/shaders/default.vs", "/shaders/default.fs")
|
||||
.selectShader("default")
|
||||
.createUniform(UniformName.UNI_VIEW_MODEL_MATRIX)
|
||||
.createUniform(UniformName.UNI_MODEL_MATRIX)
|
||||
.createUniform(UniformName.UNI_PROJECTION_MATRIX)
|
||||
.createUniform(UniformName.UNI_OBJECT_COLOR)
|
||||
.createUniform(UniformName.UNI_HAS_OBJECT_TEXTURE)
|
||||
.createUniform(UniformName.UNI_TEXTURE_SAMPLER)
|
||||
.createUniform(UniformName.UNI_SPRITE_SIZE)
|
||||
.createUniform(UniformName.UNI_SPRITE_POSITION)
|
||||
.createUniform(UniformName.UNI_AMBIENT)
|
||||
.createUniform(UniformName.UNI_ACTIVE_LIGHTS)
|
||||
.createCounter(CounterName.LIGHT);
|
||||
|
||||
for(int i=0; i<RenderConstants.MAX_LIGHTS; ++i) {
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].position");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].intensity");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].constantAttenuation");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].linearAttenuation");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].quadraticAttenuation");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,6 +53,7 @@ public class DefaultRenderer implements Renderer {
|
||||
updateViewport(screen);
|
||||
|
||||
shaderManager.selectShader("default").useSelectedShader();
|
||||
shaderManager.resetCounters();
|
||||
|
||||
// Important note:
|
||||
// The camera render method must be invoked **before** each consecutive item renders
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.shader.constant;
|
||||
|
||||
public interface CounterName {
|
||||
String LIGHT = "LIGHT";
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.shader.constant;
|
||||
|
||||
public interface RenderConstants {
|
||||
int MAX_LIGHTS = 100;
|
||||
}
|
||||
@@ -2,10 +2,14 @@ package com.bartlomiejpluta.base.engine.core.gl.shader.constant;
|
||||
|
||||
public interface UniformName {
|
||||
String UNI_VIEW_MODEL_MATRIX = "viewModelMatrix";
|
||||
String UNI_MODEL_MATRIX = "modelMatrix";
|
||||
String UNI_PROJECTION_MATRIX = "projectionMatrix";
|
||||
String UNI_OBJECT_COLOR = "objectColor";
|
||||
String UNI_HAS_OBJECT_TEXTURE = "hasTexture";
|
||||
String UNI_TEXTURE_SAMPLER = "sampler";
|
||||
String UNI_SPRITE_SIZE = "spriteSize";
|
||||
String UNI_SPRITE_POSITION = "spritePosition";
|
||||
String UNI_LIGHTS = "lights";
|
||||
String UNI_ACTIVE_LIGHTS = "activeLights";
|
||||
String UNI_AMBIENT = "ambient";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.shader.manager;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.program.GLShaderProgram;
|
||||
import com.bartlomiejpluta.base.engine.error.AppException;
|
||||
import com.bartlomiejpluta.base.engine.util.res.ResourcesManager;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderProgram;
|
||||
@@ -13,6 +14,9 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -20,6 +24,7 @@ import java.util.Map;
|
||||
public class DefaultShaderManager implements ShaderManager {
|
||||
private final ResourcesManager resourcesManager;
|
||||
private final Map<String, ShaderProgram> shaders = new HashMap<>();
|
||||
private final Map<String, AtomicInteger> counters = new HashMap<>();
|
||||
private ShaderProgram current;
|
||||
|
||||
@Override
|
||||
@@ -142,6 +147,42 @@ public class DefaultShaderManager implements ShaderManager {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShaderManager createCounter(String counterName) {
|
||||
if (counters.containsKey(counterName)) {
|
||||
throw new AppException(format("The [%s] counter already exists", counterName));
|
||||
}
|
||||
|
||||
log.info("Creating {} uniform counter", counterName);
|
||||
counters.put(counterName, new AtomicInteger(0));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextNumber(String counterName) {
|
||||
return counters.get(counterName).getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int topNumber(String counterName) {
|
||||
return counters.get(counterName).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShaderManager setUniformCounter(String uniformName, String counterName) {
|
||||
setUniform(uniformName, counters.get(counterName).get());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShaderManager resetCounters() {
|
||||
for(var counter : counters.values()) {
|
||||
counter.set(0);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
log.info("Disposing shaders");
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.bartlomiejpluta.base.engine.world.light;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.light.Light;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.CounterName;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.world.location.LocationableModel;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector3fc;
|
||||
|
||||
public class DefaultLight extends LocationableModel implements Light {
|
||||
private final Vector3f intensity = new Vector3f(1f, 1f, 1f);
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean luminescent;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private float constantAttenuation = 1f;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private float linearAttenuation = 0f;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private float quadraticAttenuation = 1f;
|
||||
|
||||
@Override
|
||||
public Vector3fc getIntensity() {
|
||||
return intensity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIntensity(float red, float green, float blue) {
|
||||
this.intensity.x = red;
|
||||
this.intensity.y = green;
|
||||
this.intensity.z = blue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float dt) {
|
||||
// noop
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
var lightNumber = shaderManager.nextNumber(CounterName.LIGHT);
|
||||
shaderManager.setUniform(UniformName.UNI_LIGHTS + "[" + lightNumber + "].position", position);
|
||||
shaderManager.setUniform(UniformName.UNI_LIGHTS + "[" + lightNumber + "].intensity", intensity);
|
||||
shaderManager.setUniform(UniformName.UNI_LIGHTS + "[" + lightNumber + "].constantAttenuation", constantAttenuation);
|
||||
shaderManager.setUniform(UniformName.UNI_LIGHTS + "[" + lightNumber + "].linearAttenuation", linearAttenuation);
|
||||
shaderManager.setUniform(UniformName.UNI_LIGHTS + "[" + lightNumber + "].quadraticAttenuation", quadraticAttenuation);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,15 @@ package com.bartlomiejpluta.base.engine.world.map.layer.base;
|
||||
import com.bartlomiejpluta.base.api.animation.Animation;
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.event.Event;
|
||||
import com.bartlomiejpluta.base.api.light.Light;
|
||||
import com.bartlomiejpluta.base.api.map.layer.base.Layer;
|
||||
import com.bartlomiejpluta.base.api.map.model.GameMap;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.CounterName;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.internal.logic.Updatable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.joml.Vector2fc;
|
||||
|
||||
@@ -23,6 +27,9 @@ public abstract class BaseLayer implements Layer, Updatable {
|
||||
|
||||
protected final ArrayList<Animation> animations = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
protected final ArrayList<Light> lights = new ArrayList<>();
|
||||
|
||||
public BaseLayer(@NonNull GameMap map) {
|
||||
this.map = map;
|
||||
this.stepSize = map.getStepSize();
|
||||
@@ -40,6 +47,26 @@ public abstract class BaseLayer implements Layer, Updatable {
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLight(Light light) {
|
||||
lights.add(light);
|
||||
light.setStepSize(stepSize.x(), stepSize.y());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLight(Light light) {
|
||||
// Disclaimer
|
||||
// This is a workaround for concurrent modification exception
|
||||
// which is thrown when entity is tried to be removed
|
||||
// in the body of for-each-entity loop
|
||||
lights.remove(lights.indexOf(light));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearLights() {
|
||||
lights.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float dt) {
|
||||
|
||||
@@ -58,6 +85,11 @@ public abstract class BaseLayer implements Layer, Updatable {
|
||||
animation.onFinish(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Disclaimer as above for lights
|
||||
for (int i = 0; i < lights.size(); ++i) {
|
||||
lights.get(i).update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,6 +97,12 @@ public abstract class BaseLayer implements Layer, Updatable {
|
||||
for (var animation : animations) {
|
||||
animation.render(screen, camera, shaderManager);
|
||||
}
|
||||
|
||||
for (var light : lights) {
|
||||
light.render(screen, camera, shaderManager);
|
||||
}
|
||||
|
||||
shaderManager.setUniformCounter(UniformName.UNI_ACTIVE_LIGHTS, CounterName.LIGHT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.bartlomiejpluta.base.api.map.layer.object.PassageAbility;
|
||||
import com.bartlomiejpluta.base.api.map.layer.tile.TileLayer;
|
||||
import com.bartlomiejpluta.base.api.map.model.GameMap;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.util.mesh.MeshManager;
|
||||
import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.autotile.DefaultAutoTileLayer;
|
||||
@@ -27,6 +28,7 @@ import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector2fc;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -58,6 +60,9 @@ public class DefaultGameMap implements Renderable, Updatable, GameMap {
|
||||
@Getter
|
||||
private final String handler;
|
||||
|
||||
@Getter
|
||||
private final Vector3f ambientColor = new Vector3f(1, 1, 1);
|
||||
|
||||
public DefaultGameMap(int tileWidth, int tileHeight, int rows, int columns, String handler) {
|
||||
this.rows = rows;
|
||||
this.columns = columns;
|
||||
@@ -77,11 +82,18 @@ public class DefaultGameMap implements Renderable, Updatable, GameMap {
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
shaderManager.setUniform(UniformName.UNI_AMBIENT, ambientColor);
|
||||
|
||||
for (var layer : layers) {
|
||||
layer.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layer getLayer(int layerIndex) {
|
||||
return layers.get(layerIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileLayer getTileLayer(int layerIndex) {
|
||||
return (TileLayer) layers.get(layerIndex);
|
||||
@@ -113,6 +125,13 @@ public class DefaultGameMap implements Renderable, Updatable, GameMap {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAmbientColor(float red, float green, float blue) {
|
||||
this.ambientColor.x = red;
|
||||
this.ambientColor.y = green;
|
||||
this.ambientColor.z = blue;
|
||||
}
|
||||
|
||||
public TileLayer createTileLayer(@NonNull TileSet tileSet) {
|
||||
var layer = new DefaultTileLayer(this, tileSet, rows, columns);
|
||||
layers.add(layer);
|
||||
|
||||
@@ -38,6 +38,7 @@ public abstract class Sprite extends LocationableModel implements Renderable {
|
||||
}
|
||||
|
||||
shaderManager.setUniform(UniformName.UNI_VIEW_MODEL_MATRIX, camera.computeViewModelMatrix(getModelMatrix()));
|
||||
shaderManager.setUniform(UniformName.UNI_MODEL_MATRIX, getModelMatrix());
|
||||
material.render(screen, camera, shaderManager);
|
||||
mesh.render(screen, camera, shaderManager);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,39 @@
|
||||
#version 330
|
||||
|
||||
struct Light
|
||||
{
|
||||
vec2 position;
|
||||
vec3 intensity;
|
||||
float constantAttenuation;
|
||||
float linearAttenuation;
|
||||
float quadraticAttenuation;
|
||||
};
|
||||
|
||||
uniform vec4 objectColor;
|
||||
uniform int hasTexture;
|
||||
uniform sampler2D sampler;
|
||||
uniform vec2 spriteSize;
|
||||
uniform vec2 spritePosition;
|
||||
uniform vec3 ambient;
|
||||
uniform Light lights[100];
|
||||
uniform int activeLights;
|
||||
|
||||
in vec2 objectPosition;
|
||||
in vec2 fragmentTexCoord;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
if(hasTexture == 1)
|
||||
vec4 color = hasTexture == 1 ? objectColor * texture(sampler, fragmentTexCoord * spriteSize + spritePosition) : objectColor;
|
||||
vec4 total = vec4(color.rgb * ambient, color.a);
|
||||
|
||||
for(int i=0; i<activeLights; ++i)
|
||||
{
|
||||
fragColor = objectColor * texture(sampler, fragmentTexCoord * spriteSize + spritePosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
fragColor = objectColor;
|
||||
Light light = lights[i];
|
||||
float dist = distance(light.position, objectPosition);
|
||||
total.rgb += color.rgb * light.intensity.rgb / (light.constantAttenuation + light.linearAttenuation * dist + light.quadraticAttenuation * dist * dist);
|
||||
}
|
||||
|
||||
fragColor = total;
|
||||
}
|
||||
@@ -1,15 +1,18 @@
|
||||
#version 330
|
||||
|
||||
uniform mat4 viewModelMatrix;
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
layout(location=0) in vec2 position;
|
||||
layout(location=1) in vec2 texCoord;
|
||||
|
||||
out vec2 objectPosition;
|
||||
out vec2 fragmentTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = projectionMatrix * viewModelMatrix * vec4(position, 0.0, 1.0);
|
||||
objectPosition = (modelMatrix * vec4(position, 0.0, 1.0)).xy;
|
||||
fragmentTexCoord = texCoord;
|
||||
}
|
||||
Reference in New Issue
Block a user