Create working lighting system

This commit is contained in:
2023-11-07 16:51:44 +01:00
parent a3b89de71a
commit e1ea66ccb3
18 changed files with 555 additions and 15 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -0,0 +1,5 @@
package com.bartlomiejpluta.base.engine.core.gl.shader.constant;
public interface CounterName {
String LIGHT = "LIGHT";
}

View File

@@ -0,0 +1,5 @@
package com.bartlomiejpluta.base.engine.core.gl.shader.constant;
public interface RenderConstants {
int MAX_LIGHTS = 100;
}

View File

@@ -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";
}

View File

@@ -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");

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}