Renderer/src/main/java/electrosphere/renderer/light/LightManager.java
austin d29a4f81f0
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
fix projection matrix being sent to light manager
2025-06-04 22:44:47 -04:00

394 lines
13 KiB
Java

package electrosphere.renderer.light;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import org.joml.Matrix4d;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.joml.Vector3i;
import electrosphere.client.entity.camera.CameraEntityUtils;
import electrosphere.data.entity.common.light.PointLightDescription;
import electrosphere.engine.Globals;
import electrosphere.engine.assetmanager.AssetDataStrings;
import electrosphere.entity.Entity;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderPipelineState;
import electrosphere.renderer.buffer.ShaderStorageBuffer;
import electrosphere.renderer.buffer.BufferEnums.BufferAccess;
import electrosphere.renderer.buffer.BufferEnums.BufferUsage;
import electrosphere.renderer.shader.ComputeShader;
import electrosphere.renderer.shader.MemoryBarrier;
import electrosphere.renderer.shader.MemoryBarrier.Barrier;
/**
* Manages the light sources in the engine
*/
public class LightManager {
/**
* Maximum number of lights per cluster
*/
static final int MAX_LIGHTS_PER_CLUSTER = 100;
/**
* Size of the light cluster structure
*/
static final int CLUSTER_STRUCT_SIZE = 16 + 16 + 4 + (4 * MAX_LIGHTS_PER_CLUSTER);
/**
* The width of the light cluster grid's x dimension
*/
public static final int LIGHT_CLUSTER_WIDTH_X = 12;
/**
* The width of the light cluster grid's y dimension
*/
public static final int LIGHT_CLUSTER_WIDTH_Y = 12;
/**
* The width of the light cluster grid's z dimension
*/
public static final int LIGHT_CLUSTER_WIDTH_Z = 24;
/**
* The size of a single point light struct
*/
static final int POINT_LIGHT_STRUCT_SIZE = 16 + 16 + 4 + 4 + 4 + 4;
/**
* The maximum number of point lights
*/
static final int MAX_POINT_LIGHTS = 2048;
/**
* The size of the point light buffer
*/
static final int POINT_LIGHT_BUFFER_SIZE = POINT_LIGHT_STRUCT_SIZE * MAX_POINT_LIGHTS;
/**
* The number of "threads" to run for light culling
*/
static final int CULL_LOCAL_SIZE = 128;
/**
* The number of work groups to dispatch for the light culling phase
* !!note!! DISPATCH_LOCAL_COUNT * CULL_LOCAL_SIZE must equal (LIGHT_CLUSTER_WIDTH_X * LIGHT_CLUSTER_WIDTH_Y * LIGHT_CLUSTER_WIDTH_Z)
*/
static final int DISPATCH_LOCAL_COUNT = 27;
/**
* Size of the direct light buffer
*/
static final int DIRECT_LIGHT_BUFFER_SIZE = 16 + 16 + 16;
/**
* Bind point for the cluster ssbo
*/
public static final int CLUSTER_SSBO_BIND_POINT = 1;
/**
* Bind point for the point light ssbo
*/
public static final int POINT_LIGHT_SSBO_BIND_POINT = 2;
/**
* Bind point for the direct light ssbo
*/
public static final int DIRECT_LIGHT_SSBO_BIND_POINT = 3;
/**
* The cluster grid ssbo
Light cluster struct:
{
vec4 minPoint; //16 bytes
vec4 maxPoint; //16 bytes
unsigned int count; //4 bytes
unsigned int lightIndices[MAX_LIGHTS_PER_CLUSTER]; //assuming MAX_LIGHTS_PER_CLUSTER is 100, 400 bytes
}
Totals to 436 bytes
*/
private ShaderStorageBuffer clusterGridSSBO;
/**
* The map of all entities to point lights
*/
private Map<Entity,PointLight> entityPointLightMap;
/**
* The buffer of all point light data
Point light struct:
{
vec4 position; //16 bytes
vec4 color; //16 bytes
float constant; //4 bytes
float linear; // 4 bytes
float quadratic; // 4 bytes
float radius; //4 bytes
}
*/
private ShaderStorageBuffer pointLightSSBO;
/**
* The direct light ssbo
*
struct DirectLight {
vec4 direction; //16 bytes
vec4 color; //16 bytes
vec4 ambientColor; //16 bytes
};
*/
private ShaderStorageBuffer dirLightSSBO;
/**
* The directional light
*/
private DirectionalLight directionalLight;
/**
* Color of the ambient light
*/
private Vector3f ambientLightColor = new Vector3f(0.5f);
/**
* Constructor
*/
private LightManager(){
}
/**
* Creates a light manager
* @return The light manager
*/
public static LightManager create(){
LightManager rVal = new LightManager();
//
//create the cluster ssbo
rVal.clusterGridSSBO = new ShaderStorageBuffer(
CLUSTER_STRUCT_SIZE * LIGHT_CLUSTER_WIDTH_X * LIGHT_CLUSTER_WIDTH_Y * LIGHT_CLUSTER_WIDTH_Z,
BufferUsage.STATIC,
BufferAccess.COPY
);
//
//create pointlight structures
rVal.entityPointLightMap = new HashMap<Entity,PointLight>();
rVal.pointLightSSBO = new ShaderStorageBuffer(POINT_LIGHT_BUFFER_SIZE, BufferUsage.DYNAMIC, BufferAccess.DRAW);
//
//create direct light ssbo
rVal.dirLightSSBO = new ShaderStorageBuffer(DIRECT_LIGHT_BUFFER_SIZE, BufferUsage.DYNAMIC, BufferAccess.DRAW);
rVal.directionalLight = new DirectionalLight(new Vector3f(0,-1,0));
rVal.directionalLight.setColor(new Vector3f(0.5f));
return rVal;
}
/**
* Computes the light culling clusters based on a camera
* @param renderPipelineState The render pipeline state
* @param openGLState The opengl state
* @param camera The camera
*/
private void computeLightCulling(RenderPipelineState renderPipelineState, OpenGLState openGLState, Entity camera){
openGLState.glViewport(Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT);
//
//Build AABBs
ComputeShader clusterComp = Globals.assetManager.fetchComputeShader(AssetDataStrings.COMPUTE_LIGHT_CLUSTER);
if(clusterComp != null){
openGLState.setActiveShader(renderPipelineState, clusterComp);
clusterComp.setUniform(openGLState, "zNear", CameraEntityUtils.getNearClip(camera));
clusterComp.setUniform(openGLState, "zFar", CameraEntityUtils.getFarClip(camera));
clusterComp.setUniform(openGLState, "inverseProjection", new Matrix4d(Globals.renderingEngine.getProjectionMatrix()).invert());
clusterComp.setUniform(openGLState, "gridSize", new Vector3i(LIGHT_CLUSTER_WIDTH_X,LIGHT_CLUSTER_WIDTH_Y,LIGHT_CLUSTER_WIDTH_Z));
clusterComp.setUniform(openGLState, "screenDimensions", openGLState.getViewport());
clusterComp.dispatch(LIGHT_CLUSTER_WIDTH_X,LIGHT_CLUSTER_WIDTH_Y,LIGHT_CLUSTER_WIDTH_Z);
MemoryBarrier.glMemoryBarrier(Barrier.GL_SHADER_STORAGE_BARRIER_BIT);
}
//
//cull lights
ComputeShader lightCull = Globals.assetManager.fetchComputeShader(AssetDataStrings.COMPUTE_LIGHT_CULL);
openGLState.setActiveShader(renderPipelineState, lightCull);
lightCull.setUniform(openGLState, "viewMatrix", Globals.renderingEngine.getViewMatrix());
lightCull.setUniform(openGLState, "lightCount", this.entityPointLightMap.values().size());
int dispatchLocal = (LIGHT_CLUSTER_WIDTH_X * LIGHT_CLUSTER_WIDTH_Y * LIGHT_CLUSTER_WIDTH_Z) / CULL_LOCAL_SIZE;
lightCull.dispatch(dispatchLocal, ComputeShader.DEFAULT_LOCAL_SIZE, ComputeShader.DEFAULT_LOCAL_SIZE);
MemoryBarrier.glMemoryBarrier(Barrier.GL_SHADER_STORAGE_BARRIER_BIT);
}
/**
* Pushes latest data to buffers
*/
private void pushToBuffers(RenderPipelineState renderPipelineState, OpenGLState openGLState, Entity camera){
int i = 0;
//
//push point lights
ByteBuffer pointLightBuffer = pointLightSSBO.getBuffer();
pointLightBuffer.limit(pointLightBuffer.capacity());
for(PointLight light : this.entityPointLightMap.values()){
if(i >= MAX_POINT_LIGHTS){
LoggerInterface.loggerRenderer.WARNING("CLOSE TO MAXIMUM NUMBER OF POINT LIGHTS!");
break;
}
//
//position
Vector3d modifiedCameraPos = new Vector3d(light.getPosition()).sub(CameraEntityUtils.getCameraCenter(camera));
pointLightBuffer.putFloat((float)modifiedCameraPos.x);
pointLightBuffer.putFloat((float)modifiedCameraPos.y);
pointLightBuffer.putFloat((float)modifiedCameraPos.z);
pointLightBuffer.putFloat(1);
//
//color
pointLightBuffer.putFloat(light.getColor().x);
pointLightBuffer.putFloat(light.getColor().y);
pointLightBuffer.putFloat(light.getColor().z);
pointLightBuffer.putFloat(1);
//
//const
pointLightBuffer.putFloat(light.getConstant());
pointLightBuffer.putFloat(light.getLinear());
pointLightBuffer.putFloat(light.getQuadratic());
pointLightBuffer.putFloat(light.getRadius());
}
if(pointLightBuffer.position() > 0){
pointLightBuffer.flip();
pointLightSSBO.upload(openGLState);
}
//
//push direct light
ByteBuffer directLightBuffer = dirLightSSBO.getBuffer();
{
//
//direction
directLightBuffer.putFloat(directionalLight.getDirection().x);
directLightBuffer.putFloat(directionalLight.getDirection().y);
directLightBuffer.putFloat(directionalLight.getDirection().z);
directLightBuffer.putFloat(0);
//
//color
directLightBuffer.putFloat(directionalLight.getColor().x);
directLightBuffer.putFloat(directionalLight.getColor().y);
directLightBuffer.putFloat(directionalLight.getColor().z);
directLightBuffer.putFloat(0);
//
//ambient light color
directLightBuffer.putFloat(ambientLightColor.x);
directLightBuffer.putFloat(ambientLightColor.y);
directLightBuffer.putFloat(ambientLightColor.z);
directLightBuffer.putFloat(0);
}
directLightBuffer.flip();
dirLightSSBO.upload(openGLState);
}
/**
* Updates the light manager
* @param renderPipelineState The rendering state
* @param openGLState The opengl state
* @param camera The camera
*/
public void update(RenderPipelineState renderPipelineState, OpenGLState openGLState, Entity camera){
if(camera != null){
this.pushToBuffers(renderPipelineState, openGLState, camera);
this.computeLightCulling(renderPipelineState, openGLState, camera);
}
}
/**
* Gets the cluster grid ssbo
* @return The cluster grid ssbo
*/
public ShaderStorageBuffer getClusterGridSSBO(){
return this.clusterGridSSBO;
}
/**
* Gets the point light ssbo
* @return The point light ssbo
*/
public ShaderStorageBuffer getPointLightSSBO(){
return this.pointLightSSBO;
}
/**
* Gets the direct light ssbo
* @return The direct light ssbo
*/
public ShaderStorageBuffer getDirectLightSSBO(){
return this.dirLightSSBO;
}
/**
* Gets the directional light
* @return The directional light
*/
public DirectionalLight getDirectionalLight(){
return directionalLight;
}
/**
* Checks if the light manager has a light attached to the provided entity
* @param entity The entity
* @return true if there is an attached light, false otherwise
*/
public boolean hasAttachedLight(Entity entity){
return this.entityPointLightMap.containsKey(entity);
}
/**
* Gets the point light associated with a given entity
* @param entity The entity
* @return The point light if it exists, null otherwise
*/
public PointLight getPointLight(Entity entity){
return entityPointLightMap.get(entity);
}
/**
* Destroys the light attached to an entity if one is attached
* @param entity The entity
*/
public void destroyPointLight(Entity entity){
if(entityPointLightMap.containsKey(entity)){
entityPointLightMap.remove(entity);
}
}
/**
* Creates a point light from a description.
* <p>
* Note: If there is a light already assigned to the entity, it will be overwritten by the new light
* </p>
* @param entity The entity to assign the light to
* @param description The description
* @return The light
*/
public PointLight createPointLight(Entity entity, PointLightDescription description){
PointLight light = new PointLight(description);
this.entityPointLightMap.put(entity, light);
return light;
}
}