texture atlasing for particle system
Some checks are pending
studiorailgun/Renderer/pipeline/head Build queued...

This commit is contained in:
austin 2024-09-19 23:13:18 -04:00
parent 2ef4b13786
commit 4b41ac823c
13 changed files with 283 additions and 14 deletions

View File

@ -19,7 +19,7 @@
"z": 0.0 "z": 0.0
}, },
"acceleration": -0.01, "acceleration": -0.01,
"texture": "", "texture": "blood1.png",
"size": 0.3, "size": 0.3,
"color": { "color": {
"x": 0.5, "x": 0.5,

View File

@ -50,7 +50,8 @@ struct Cluster {
uint lightIndices[MAX_LIGHTS_PER_CLUSTER]; uint lightIndices[MAX_LIGHTS_PER_CLUSTER];
}; };
out vec4 FragColor; layout (location = 0) out vec4 accum;
layout (location = 1) out float reveal;
layout(std430, binding = CLUSTER_SSBO_BIND_POINT) restrict buffer clusterGridSSBO { layout(std430, binding = CLUSTER_SSBO_BIND_POINT) restrict buffer clusterGridSSBO {
@ -119,7 +120,7 @@ void main(){
//get color of base texture //get color of base texture
// vec3 textureColor = vec3((norm.x + 1) / 2.0, norm.y, 1.0 - (norm.x + 1) / 2.0); // vec3 textureColor = vec3((norm.x + 1) / 2.0, norm.y, 1.0 - (norm.x + 1) / 2.0);
vec3 textureColor = color.rgb; vec4 textureColor = texture(material.diffuse,TexCoord);
// vec3 textureColor = vec3(0.17647,0.4,0.09411);//texture(material.diffuse, TexCoord).rgb; // vec3 textureColor = vec3(0.17647,0.4,0.09411);//texture(material.diffuse, TexCoord).rgb;
//shadow //shadow
@ -136,19 +137,25 @@ void main(){
} }
//error checking on light clusters //error checking on light clusters
if(pointLightCount > MAX_LIGHTS_PER_CLUSTER){ if(pointLightCount > MAX_LIGHTS_PER_CLUSTER){
FragColor = vec4(1.0f,0.0f,0.0f,1); accum = vec4(1.0f,0.0f,0.0f,1);
reveal = textureColor.a;
return; return;
} }
//calculate final color //calculate final color
vec3 finalColor = textureColor * lightIntensity;// * max(shadow,0.4); vec4 finalColor = textureColor.rgba * vec4(lightIntensity,1.0);// * max(shadow,0.4);
// vec3 lightAmount = CalcDirLight(norm, viewDir); // vec3 lightAmount = CalcDirLight(norm, viewDir);
// for(int i = 0; i < NR_POINT_LIGHTS; i++){ // for(int i = 0; i < NR_POINT_LIGHTS; i++){
// lightAmount += CalcPointLight(i, norm, FragPos, viewDir); // lightAmount += CalcPointLight(i, norm, FragPos, viewDir);
// } // }
//this final calculation is for transparency //calculate weight function
FragColor = vec4(finalColor, 1.0);//texture(ourTexture, TexCoord);//vec4(result, 1.0); float weight = clamp(pow(min(1.0, finalColor.a * 10.0) + 0.01, 3.0) * 1e8 *
pow(1.0 - gl_FragCoord.z * 0.9, 3.0), 1e-2, 3e3);
//emit colors
accum = vec4(finalColor.rgb * finalColor.a, finalColor.a) * weight;
reveal = finalColor.a;
} }
// calculates the color when using a directional light. // calculates the color when using a directional light.

View File

@ -12,6 +12,7 @@ A point light
struct ParticleData { struct ParticleData {
mat4 model; mat4 model;
vec4 color; vec4 color;
vec4 texture;
}; };
@ -64,7 +65,9 @@ void main() {
FragPos = vec3(model * FinalVertex); FragPos = vec3(model * FinalVertex);
ViewFragPos = vec3(view * model * FinalVertex); ViewFragPos = vec3(view * model * FinalVertex);
Normal = mat3(transpose(inverse(model))) * aNormal; Normal = mat3(transpose(inverse(model))) * aNormal;
TexCoord = aTex;
//offset based on data stored in particle data
TexCoord = (aTex * currentData.texture.xy) + currentData.texture.zw;
//shadow map stuff //shadow map stuff

View File

@ -1,3 +1,3 @@
#maven.buildNumber.plugin properties file #maven.buildNumber.plugin properties file
#Thu Sep 19 21:21:28 EDT 2024 #Thu Sep 19 23:04:32 EDT 2024
buildNumber=351 buildNumber=355

View File

@ -1018,6 +1018,8 @@ Upgrade terrain generation algorithms
Documentation Documentation
Test Creation

View File

@ -10,6 +10,7 @@ import electrosphere.client.entity.instance.InstancedEntityUtils;
import electrosphere.engine.assetmanager.AssetDataStrings; import electrosphere.engine.assetmanager.AssetDataStrings;
import electrosphere.engine.signal.Signal; import electrosphere.engine.signal.Signal;
import electrosphere.engine.signal.SignalServiceImpl; import electrosphere.engine.signal.SignalServiceImpl;
import electrosphere.entity.DrawableUtils;
import electrosphere.entity.Entity; import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityUtils; import electrosphere.entity.EntityUtils;
@ -17,6 +18,7 @@ import electrosphere.entity.state.client.particle.ClientParticleTree;
import electrosphere.game.data.particle.ParticleData; import electrosphere.game.data.particle.ParticleData;
import electrosphere.renderer.actor.instance.StridedInstanceData; import electrosphere.renderer.actor.instance.StridedInstanceData;
import electrosphere.renderer.buffer.HomogenousUniformBuffer.HomogenousBufferTypes; import electrosphere.renderer.buffer.HomogenousUniformBuffer.HomogenousBufferTypes;
import electrosphere.renderer.texture.TextureAtlas;
import electrosphere.renderer.buffer.ShaderAttribute; import electrosphere.renderer.buffer.ShaderAttribute;
/** /**
@ -44,6 +46,11 @@ public class ParticleService extends SignalServiceImpl {
*/ */
static final String FRAGMENT_SHADER_PATH = "Shaders/entities/particle/particle.fs"; static final String FRAGMENT_SHADER_PATH = "Shaders/entities/particle/particle.fs";
/**
* The particle texture atlas
*/
TextureAtlas particleTextureAtlas;
/** /**
* The instance data for the particles * The instance data for the particles
@ -60,6 +67,8 @@ public class ParticleService extends SignalServiceImpl {
ShaderAttribute colorAttrib; ShaderAttribute colorAttrib;
ShaderAttribute textureAttrib;
/** /**
* Constructor * Constructor
*/ */
@ -82,9 +91,11 @@ public class ParticleService extends SignalServiceImpl {
case RENDERING_ENGINE_READY: { case RENDERING_ENGINE_READY: {
modelAttrib = new ShaderAttribute("model", HomogenousBufferTypes.MAT4F); modelAttrib = new ShaderAttribute("model", HomogenousBufferTypes.MAT4F);
colorAttrib = new ShaderAttribute("color", HomogenousBufferTypes.VEC4F); colorAttrib = new ShaderAttribute("color", HomogenousBufferTypes.VEC4F);
textureAttrib = new ShaderAttribute("texture", HomogenousBufferTypes.VEC4F);
List<ShaderAttribute> types = Arrays.asList(new ShaderAttribute[]{ List<ShaderAttribute> types = Arrays.asList(new ShaderAttribute[]{
modelAttrib, modelAttrib,
colorAttrib, colorAttrib,
textureAttrib,
}); });
this.particleInstanceData = new StridedInstanceData(MAX_PARTICLES,PARTICLE_SSBO_BIND_POINT,types,VERTEX_SHADER_PATH,FRAGMENT_SHADER_PATH); this.particleInstanceData = new StridedInstanceData(MAX_PARTICLES,PARTICLE_SSBO_BIND_POINT,types,VERTEX_SHADER_PATH,FRAGMENT_SHADER_PATH);
this.instanceTemplate = InstanceTemplate.createInstanceTemplate(MAX_PARTICLES, AssetDataStrings.MODEL_PARTICLE, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH, particleInstanceData, PARTICLE_SSBO_BIND_POINT); this.instanceTemplate = InstanceTemplate.createInstanceTemplate(MAX_PARTICLES, AssetDataStrings.MODEL_PARTICLE, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH, particleInstanceData, PARTICLE_SSBO_BIND_POINT);
@ -104,6 +115,22 @@ public class ParticleService extends SignalServiceImpl {
return this.particleInstanceData; return this.particleInstanceData;
} }
/**
* Sets the particle texture atlas
* @param particleTextureAtlas The particle texture atlas
*/
public void setParticleTextureAtlas(TextureAtlas particleTextureAtlas){
this.particleTextureAtlas = particleTextureAtlas;
}
/**
* Sets the texture atlas for the particle texture
* @return The atlas
*/
public TextureAtlas getTextureAtlas(){
return particleTextureAtlas;
}
/** /**
* Spawns a particle * Spawns a particle
* @param data The particle data for the particle * @param data The particle data for the particle
@ -113,6 +140,7 @@ public class ParticleService extends SignalServiceImpl {
public Entity spawn(ParticleData data, Vector3d position){ public Entity spawn(ParticleData data, Vector3d position){
Entity rVal = EntityCreationUtils.createClientSpatialEntity(); Entity rVal = EntityCreationUtils.createClientSpatialEntity();
InstancedEntityUtils.makeEntityInstanced(rVal, instanceTemplate); InstancedEntityUtils.makeEntityInstanced(rVal, instanceTemplate);
DrawableUtils.makeEntityTransparent(rVal);
EntityUtils.getPosition(rVal).set(position); EntityUtils.getPosition(rVal).set(position);
ClientParticleTree.attachTree(rVal, data); ClientParticleTree.attachTree(rVal, data);
return rVal; return rVal;
@ -125,5 +153,9 @@ public class ParticleService extends SignalServiceImpl {
public ShaderAttribute getColorAttrib(){ public ShaderAttribute getColorAttrib(){
return colorAttrib; return colorAttrib;
} }
public ShaderAttribute getTextureAttrib(){
return textureAttrib;
}
} }

View File

@ -15,7 +15,8 @@ public class AssetDataStrings {
public static final String UNITSPHERE = "unitSphere"; public static final String UNITSPHERE = "unitSphere";
public static final String UNITCYLINDER = "unitCylinder"; public static final String UNITCYLINDER = "unitCylinder";
public static final String UNITCUBE = "unitCube"; public static final String UNITCUBE = "unitCube";
public static final String MODEL_PARTICLE = "particle"; public static final String MODEL_PARTICLE = "particleModel";
public static final String TEXTURE_PARTICLE = "particleTexture";
/** /**
* UI generic audio * UI generic audio

View File

@ -338,6 +338,15 @@ public class AssetManager {
return rVal; return rVal;
} }
/**
* Registers a texture to a path
* @param t The texture
* @param path The path
*/
public void registerTextureToPath(Texture t, String path){
texturesLoadedIntoMemory.put(path,t);
}
public boolean hasLoadedTexture(String path){ public boolean hasLoadedTexture(String path){
return texturesLoadedIntoMemory.containsKey(path); return texturesLoadedIntoMemory.containsKey(path);
} }

View File

@ -2,17 +2,21 @@ package electrosphere.engine.loadingthreads;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import electrosphere.client.terrain.cells.VoxelTextureAtlas; import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.engine.assetmanager.AssetDataStrings;
import electrosphere.engine.assetmanager.queue.QueuedTexture; import electrosphere.engine.assetmanager.queue.QueuedTexture;
import electrosphere.game.data.voxel.VoxelData; import electrosphere.game.data.voxel.VoxelData;
import electrosphere.game.data.voxel.VoxelType; import electrosphere.game.data.voxel.VoxelType;
import electrosphere.logger.LoggerInterface; import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.texture.TextureAtlas;
import electrosphere.util.FileUtils; import electrosphere.util.FileUtils;
/** /**
@ -33,7 +37,8 @@ public class InitialAssetLoading {
*/ */
protected static void loadData(){ protected static void loadData(){
loadTextureAtlas(); loadVoxelTextureAtlas();
loadParticleAtlas();
LoggerInterface.loggerEngine.INFO("Finished loading texture atlas"); LoggerInterface.loggerEngine.INFO("Finished loading texture atlas");
} }
@ -41,7 +46,7 @@ public class InitialAssetLoading {
/** /**
* Loads the texture atlas * Loads the texture atlas
*/ */
private static void loadTextureAtlas(){ private static void loadVoxelTextureAtlas(){
//terrain texture atlas //terrain texture atlas
Globals.profiler.beginCpuSample("createVoxelTextureAtlas"); Globals.profiler.beginCpuSample("createVoxelTextureAtlas");
VoxelData data = Globals.gameConfigCurrent.getVoxelData(); VoxelData data = Globals.gameConfigCurrent.getVoxelData();
@ -89,6 +94,88 @@ public class InitialAssetLoading {
Globals.voxelTextureAtlas.setNormal(atlasQueuedTexture.getTexture()); Globals.voxelTextureAtlas.setNormal(atlasQueuedTexture.getTexture());
} }
/**
* Loads the texture atlas
*/
private static void loadParticleAtlas(){
//terrain texture atlas
Globals.profiler.beginCpuSample("create particle texture atlas");
int iterator = 0;
BufferedImage image = null;
TextureAtlas textureAtlas = new TextureAtlas();
File particleTextureDirectory = FileUtils.getAssetFile("Textures/particles");
File cachedAtlas = FileUtils.getCacheFile("particleAtlas.png");
if(cachedAtlas.exists()){
try {
image = ImageIO.read(Files.newInputStream(cachedAtlas.toPath()));
for(File childFile : particleTextureDirectory.listFiles()){
if(!childFile.isDirectory()){
//put coords in map
textureAtlas.putPathCoord(childFile.getName(),iterator);
//iterate
iterator++;
}
}
} catch (IOException e) {
e.printStackTrace();
throw new Error("Failed to load texture atlas from cache!");
}
} else {
cachedAtlas.getParentFile().mkdirs();
image = new BufferedImage(TextureAtlas.ATLAS_DIM, TextureAtlas.ATLAS_DIM, BufferedImage.TYPE_4BYTE_ABGR);
Graphics graphics = image.getGraphics();
for(File childFile : particleTextureDirectory.listFiles()){
if(!childFile.isDirectory()){
String texturePath = "Textures/particles/" + childFile.getName();
int offX = iterator % TextureAtlas.ELEMENTS_PER_ROW;
int offY = iterator / TextureAtlas.ELEMENTS_PER_ROW;
try {
BufferedImage newType = ImageIO.read(FileUtils.getAssetFile(texturePath));
int drawX = TextureAtlas.ATLAS_ELEMENT_DIM * offX;
int drawY = TextureAtlas.ATLAS_DIM - TextureAtlas.ATLAS_ELEMENT_DIM - TextureAtlas.ATLAS_ELEMENT_DIM * offY;
graphics.drawImage(newType, drawX, drawY, TextureAtlas.ATLAS_ELEMENT_DIM, TextureAtlas.ATLAS_ELEMENT_DIM, null);
} catch (IOException e) {
LoggerInterface.loggerRenderer.ERROR("Texture atlas failed to find texture " + texturePath, e);
}
//put coords in map
textureAtlas.putPathCoord(childFile.getName(),iterator);
//iterate
iterator++;
}
}
try {
ImageIO.write(image, "png", Files.newOutputStream(cachedAtlas.toPath()));
} catch (IOException e) {
e.printStackTrace();
throw new Error("Failed to save to cache!");
}
}
Globals.profiler.endCpuSample();
//queue to asset manager
atlasQueuedTexture = new QueuedTexture(image);
Globals.assetManager.queuedAsset(atlasQueuedTexture);
//wait the texture to be loaded
while(!atlasQueuedTexture.hasLoaded()){
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
LoggerInterface.loggerEngine.ERROR("failed to sleep", e);
}
}
//construct texture atlas from buffered image
Globals.assetManager.registerTextureToPath(atlasQueuedTexture.getTexture(), AssetDataStrings.TEXTURE_PARTICLE);
textureAtlas.setSpecular(atlasQueuedTexture.getTexture());
textureAtlas.setNormal(atlasQueuedTexture.getTexture());
Globals.particleService.setParticleTextureAtlas(textureAtlas);
}
/** /**
* Gets the queued texture * Gets the queued texture
*/ */

View File

@ -18,6 +18,7 @@ import electrosphere.entity.EntityUtils;
import electrosphere.entity.btree.BehaviorTree; import electrosphere.entity.btree.BehaviorTree;
import electrosphere.game.data.particle.ParticleData; import electrosphere.game.data.particle.ParticleData;
import electrosphere.renderer.actor.instance.InstancedActor; import electrosphere.renderer.actor.instance.InstancedActor;
import electrosphere.renderer.texture.TextureAtlas;
/** /**
* Particle component for a client-side particle * Particle component for a client-side particle
@ -122,12 +123,23 @@ public class ClientParticleTree implements BehaviorTree {
//push values to buffer that eventually gets uploaded to gpu //push values to buffer that eventually gets uploaded to gpu
if(instancedActor != null){ if(instancedActor != null){
TextureAtlas particleTextureAtlas = Globals.particleService.getTextureAtlas();
int textureIndex = particleTextureAtlas.getTextureIndex(this.particleData.getTexture());
instancedActor.setAttribute(Globals.particleService.getModelAttrib(), new Matrix4f().translationRotateScale( instancedActor.setAttribute(Globals.particleService.getModelAttrib(), new Matrix4f().translationRotateScale(
new Vector3f((float)parentPosition.x,(float)parentPosition.y,(float)parentPosition.z).sub(cameraPos), new Vector3f((float)parentPosition.x,(float)parentPosition.y,(float)parentPosition.z).sub(cameraPos),
new Quaternionf(rotation), new Quaternionf(rotation),
scale scale
)); ));
instancedActor.setAttribute(Globals.particleService.getColorAttrib(), new Vector4f((float)this.color.x,(float)this.color.y,(float)this.color.z,1.0f)); instancedActor.setAttribute(Globals.particleService.getColorAttrib(), new Vector4f((float)this.color.x,(float)this.color.y,(float)this.color.z,1.0f));
//when written to buffer, will be written in order w, x, y, z
//but gpu will fetch in order x, y, z, w
instancedActor.setAttribute(Globals.particleService.getTextureAttrib(), new Vector4f(
particleTextureAtlas.getNDCDimension(),
particleTextureAtlas.getNDCCoordX(textureIndex),
particleTextureAtlas.getNDCCoordY(textureIndex),
particleTextureAtlas.getNDCDimension()
));
} }
} }

View File

@ -214,7 +214,7 @@ public class RenderUtils {
particleMesh.setMaterial(new Material(AssetDataStrings.TEXTURE_PARTICLE));

View File

@ -0,0 +1,105 @@
package electrosphere.renderer.texture;
import java.util.HashMap;
import java.util.Map;
/**
* An atlas of texture path -> coordinates on provided texture
*/
public class TextureAtlas {
//A map of texture path -> coordinates in the atlas texture for its texture
Map<String,Integer> pathCoordMap = new HashMap<String,Integer>();
//the actual texture
Texture specular;
//the normal texture
Texture normal;
//the width in pixels of a single texture in the atlas
public static final int ATLAS_ELEMENT_DIM = 64;
//the width in pixels of the whole atlas texture
public static final int ATLAS_DIM = 1024;
//number of textures per row in the atlas
public static final int ELEMENTS_PER_ROW = ATLAS_DIM / ATLAS_ELEMENT_DIM;
/**
* Puts an entry in the path-coord map to map a texture path to a position
* @param path the texture's path
* @param coord the coordinate in the map
*/
public void putPathCoord(String path, int coord){
pathCoordMap.put(path,coord);
}
/**
* Sets the specular
* @param specular the specular
*/
public void setSpecular(Texture specular){
this.specular = specular;
}
/**
* Sets the normal
* @param normal the normal
*/
public void setNormal(Texture normal){
this.normal = normal;
}
/**
* Gets the atlas specular
* @return the atlas specular
*/
public Texture getSpecular(){
return specular;
}
/**
* Gets the atlas normal
* @return the atlas normal
*/
public Texture getNormal(){
return normal;
}
/**
* Gets the index in the atlas of a provided texture path
* @param texturePath The path to the texture you want the index of
* @return the index in the atlas of the texture at the provided texture path
*/
public int getTextureIndex(String texturePath){
return pathCoordMap.containsKey(texturePath) ? pathCoordMap.get(texturePath) : -1;
}
/**
* Gets the NDC dimensions of an image in the atlas
* @return The NDC dimension
*/
public float getNDCDimension(){
return ATLAS_ELEMENT_DIM / (float)ATLAS_DIM;
}
/**
* Gets the NDC x coordinate of an index in the atlas
* @param i The index
* @return The NDC x coordinate
*/
public float getNDCCoordX(int i){
return (i % ELEMENTS_PER_ROW) * this.getNDCDimension();
}
/**
* Gets the NDC y coordinate of an index in the atlas
* @param i The index
* @return The NDC y coordinate
*/
public float getNDCCoordY(int i){
return (i / ELEMENTS_PER_ROW) * this.getNDCDimension();
}
}

View File

@ -177,6 +177,17 @@ public class FileUtils {
File targetFile = new File("./assets" + sanitizedFilePath); File targetFile = new File("./assets" + sanitizedFilePath);
return targetFile; return targetFile;
} }
/**
* Gets a cache file
* @param pathName The relative path in the cache folder
* @return The file
*/
public static File getCacheFile(String pathName){
String sanitizedFilePath = sanitizeFilePath(pathName);
File targetFile = new File("./.cache" + sanitizedFilePath);
return targetFile;
}
/** /**
* Gets a save file * Gets a save file