texture atlasing for terrain w/ triplanar mapping
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-05-04 16:11:18 -04:00
parent f7ac8d908e
commit a241d97d33
29 changed files with 749 additions and 131 deletions

View File

@ -7,7 +7,7 @@
{
"id" : 1,
"name" : "dirt",
"texture" : "/Textures/Ground/Dirt1.png"
"texture" : "/Textures/Ground/Dirt1_256.png"
},
{
"id" : 2,
@ -15,7 +15,7 @@
"ambientFoliage" : [
"Green Grass"
],
"texture" : "/Textures/Ground/GrassTileable.png"
"texture" : "/Textures/Ground/GrassTileable256.png"
}
]
}

View File

@ -1,7 +1,16 @@
#version 330 core
//texture defines
#define ATLAS_ELEMENT_DIM 256.0
#define ATLAS_DIM 8192.0
#define ATLAS_EL_PER_ROW 32
#define ATLAS_NORMALIZED_ELEMENT_WIDTH 0.031 //within the single texture within the atlas, we use this so we never go over the end of the texture
#define ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL 0.03125 //used to properly shift from texture to texture in the atlas
#define NR_POINT_LIGHTS 10
out vec4 FragColor;
@ -44,6 +53,8 @@ in vec2 texPlane1;
in vec2 texPlane2;
in vec2 texPlane3;
in vec4 FragPosLightSpace;
in vec3 samplerIndexVec; //the indices in the atlas of textures to sample
in vec3 samplerRatioVec; //the vector of HOW MUCH to pull from each texture in the atlas
uniform vec3 viewPos;
@ -67,7 +78,7 @@ uniform sampler2D shadowMap;
// vec3 CalcSpotLight(vec3 normal, vec3 fragPos, vec3 viewDir);
float calcLightIntensityTotal(vec3 normal);
float ShadowCalculation(vec4 fragPosLightSpace, vec3 lightDir, vec3 normal);
vec3 getColor(vec2 texPlane1, vec2 texPlane2, vec2 texPlane3, vec3 normal, Material material);
vec3 getColor(vec2 texPlane1, vec2 texPlane2, vec2 texPlane3, vec3 normal, vec3 samplerIndexVec, vec3 samplerRatioVec, Material material);
void main(){
vec3 norm = normalize(Normal);
@ -77,7 +88,7 @@ void main(){
float lightIntensity = calcLightIntensityTotal(norm);
//get color of base texture
vec3 textureColor = getColor(texPlane1, texPlane2, texPlane3, norm, material);
vec3 textureColor = getColor(texPlane1, texPlane2, texPlane3, norm, samplerIndexVec, samplerRatioVec, material);
//shadow
float shadow = ShadowCalculation(FragPosLightSpace, normalize(-dLDirection), norm);
@ -94,14 +105,82 @@ void main(){
}
vec3 getColor(vec2 texPlane1, vec2 texPlane2, vec2 texPlane3, vec3 normal, Material material){
/**
* The function that gets the texture color based on the triplanar texture mapping and the voxel type at each point along the vert.
* See the triplanar mapping wiki article for an explanation of math involved.
*/
vec3 getColor(vec2 texPlane1, vec2 texPlane2, vec2 texPlane3, vec3 normal, vec3 samplerIndexVec, vec3 samplerRatioVec, Material material){
vec3 weights = abs(normal);
vec3 albedoX = texture(material.diffuse, texPlane1).rgb;
vec3 albedoY = texture(material.diffuse, texPlane2).rgb;
vec3 albedoZ = texture(material.diffuse, texPlane3).rgb;
//what is the index in the atlas of the texture for a given vertex
int vert1AtlasIndex = int(samplerIndexVec.x);
int vert2AtlasIndex = int(samplerIndexVec.y);
int vert3AtlasIndex = int(samplerIndexVec.z);
//what is the weight of that texture relative to the fragment
float vert1Weight = samplerRatioVec.x;
float vert2Weight = samplerRatioVec.y;
float vert3Weight = samplerRatioVec.z;
//the x-wise uv of the texture for vert1
vec2 vert1_x_uv = vec2(
(fract(texPlane1.x) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (mod(samplerIndexVec.x,ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL),
(fract(texPlane1.y) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (round(samplerIndexVec.x / ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL)
);
//the x-wise uv of the texture for vert2
vec2 vert2_x_uv = vec2(
(fract(texPlane1.x) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (mod(samplerIndexVec.y,ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL),
(fract(texPlane1.y) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (round(samplerIndexVec.y / ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL)
);
//the x-wise uv of the texture for vert3
vec2 vert3_x_uv = vec2(
(fract(texPlane1.x) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (mod(samplerIndexVec.z,ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL),
(fract(texPlane1.y) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (round(samplerIndexVec.z / ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL)
);
//albedo for the X texture
vec3 albedoX = texture(material.diffuse, vert1_x_uv).rgb * vert1Weight + texture(material.diffuse, vert2_x_uv).rgb * vert2Weight + texture(material.diffuse, vert3_x_uv).rgb * vert3Weight;
//the y-wise uv of the texture for vert1
vec2 vert1_y_uv = vec2(
(fract(texPlane2.x) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (mod(samplerIndexVec.x,ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL),
(fract(texPlane2.y) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (round(samplerIndexVec.x / ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL)
);
//the y-wise uv of the texture for vert2
vec2 vert2_y_uv = vec2(
(fract(texPlane2.x) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (mod(samplerIndexVec.y,ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL),
(fract(texPlane2.y) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (round(samplerIndexVec.y / ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL)
);
//the y-wise uv of the texture for vert3
vec2 vert3_y_uv = vec2(
(fract(texPlane2.x) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (mod(samplerIndexVec.z,ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL),
(fract(texPlane2.y) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (round(samplerIndexVec.z / ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL)
);
//albedo for the X texture
vec3 albedoY = texture(material.diffuse, vert1_y_uv).rgb * vert1Weight + texture(material.diffuse, vert2_y_uv).rgb * vert2Weight + texture(material.diffuse, vert3_y_uv).rgb * vert3Weight;
//the z-wise uv of the texture for vert1
vec2 vert1_z_uv = vec2(
(fract(texPlane3.x) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (mod(samplerIndexVec.x,ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL),
(fract(texPlane3.y) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (round(samplerIndexVec.x / ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL)
);
//the z-wise uv of the texture for vert2
vec2 vert2_z_uv = vec2(
(fract(texPlane3.x) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (mod(samplerIndexVec.y,ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL),
(fract(texPlane3.y) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (round(samplerIndexVec.y / ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL)
);
//the z-wise uv of the texture for vert3
vec2 vert3_z_uv = vec2(
(fract(texPlane3.x) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (mod(samplerIndexVec.z,ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL),
(fract(texPlane3.y) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (round(samplerIndexVec.z / ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL)
);
//albedo for the X texture
vec3 albedoZ = texture(material.diffuse, vert1_z_uv).rgb * vert1Weight + texture(material.diffuse, vert2_z_uv).rgb * vert2Weight + texture(material.diffuse, vert3_z_uv).rgb * vert3Weight;
return (albedoX * weights.x + albedoY * weights.y + albedoZ * weights.z);
}

View File

@ -2,13 +2,16 @@
#version 330 core
//defines
#define TEXTURE_MAP_SCALE 3.0
#define TEXTURE_MAP_SCALE 1.0
#define MODEL_TOTAL_DIM 16.0
//input buffers
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 4) in vec2 aTex;
layout (location = 5) in vec3 samplerIndices; //the indices in the atlas of textures to sample
layout (location = 6) in vec3 samplerRatioVectors; //the interpolated ratio of HOW MUCH to pull from each texture in the atlas
//coordinate space transformation matrices
@ -27,6 +30,8 @@ out vec2 texPlane1;
out vec2 texPlane2;
out vec2 texPlane3;
out vec4 FragPosLightSpace;
out vec3 samplerIndexVec; //the indices in the atlas of textures to sample
out vec3 samplerRatioVec; //the vector of HOW MUCH to pull from each texture in the atlas
@ -52,6 +57,10 @@ void main() {
texPlane2.x = texPlane2.x * sign(Normal.y);
texPlane3.x = texPlane3.x * sign(Normal.z);
//pass through what atlas'd textures to sample
samplerIndexVec = samplerIndices;
samplerRatioVec = samplerRatioVectors;
//shadow map stuff
FragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0);

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -1,3 +1,3 @@
#maven.buildNumber.plugin properties file
#Sun Apr 21 23:25:24 EDT 2024
buildNumber=102
#Thu May 02 18:40:02 EDT 2024
buildNumber=119

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -254,11 +254,14 @@ Data Cleanup
Fix grass not generating for closest tiles
- There is no distance check in the ClientFoliageManager
(05/04/2024)
Ground Texture Atlas system
- Basic atlas working with marching cubes
- Make it work with triplanar mapping
# TODO
Ground Texture Atlas system
Character movement in particular feels off
- Bring back strafing
- Fix interaction with networking
@ -267,6 +270,9 @@ Character movement in particular feels off
Fix being able to walk off far side of the world (ie in level editor)
Grass System properly LOD
- Have foliage dynamically time out cells to be reconsidered based on distance from player (if close, short cooldown, if far long cooldown)
Data Cleanup
- Clean up creatures
- Remove unused ones
@ -277,6 +283,12 @@ Data Cleanup
- Move model textures into models
- Recursive model transform data
Clean up Material class
- fix storing textures in the mat class ( pain :c )
Ground Texture Atlas system
- Refactor to block main thread only when creating the actual texture object (load buffered image separately)
More Debug menus
- Screen that shows the overall status of draw cell manager
- Screen that shows the overall status of fluid cell manager

View File

@ -6,4 +6,5 @@
- @subpage DrawCell
- @subpage Fonts
- @subpage modelLoading
- @subpage instanceindex
- @subpage instanceindex
- @subpage terrain

View File

@ -0,0 +1,155 @@
@page terrain Terrain
# Triplanar Mapping with Texture Atlas
## Texture Atlas Theory
It's really easy to use a single texture for a plane of geometry. Think of a heightmap that has a dirt texture applied to it. If the edges of each quad are clamped to (0,1), it will show endlessly tiling dirt.
The basic idea of the atlas texture is to pack a whole bunch of smaller textures into a larger texture and then sample based on what you want at a particular point. The engine goes through all voxel types on startup and constructs a texture atlas based on the textures provided in the data.
Here's an example of what it looks like:
![](/docs/src/images/renderer/terrain/textureAtlasExample.png)
Notice the two small parts in the bottom left. Those are the textures for two different types of voxels.
## Triplanar Mapping Theory
The basic idea of triplanar mapping is we're going to use the vertex coordinates to take "fake" photos of the camera from the x, y, and z angles and then blend them together.
![](/docs/src/images/renderer/terrain/triplanarTheoryCameras.png)
We use the normals of each vertex to determine how much to sample of the x, y, and z photos respectively. IE, if the normal points straight up, you know to only sample from the Y-aligned photo.
![](/docs/src/images/renderer/terrain/triplanarTheoryNormals.png)
These samples aren't literally photos. We're not using framebuffers to somehow get textures.
Instead, what we're technically calculating is the UVs to sample with. The idea is that we want to blend from a horizontal view to a vertical view as the texture gently curves in a hill.
This gets complicated once you want to have multiple textures.
With a single triangle of terrain, you may be sampling from three separate textures based on the terrain values at each point.
![](/docs/src/images/renderer/terrain/triplanarTheoryMultiTexture.png)
In order to make both of these systems work, we need to sample nine separate times. We have to sample:
- For each vertex's potentially unique texture
- For each camera angle (x, y, z)
Reference: https://catlikecoding.com/unity/tutorials/advanced-rendering/triplanar-mapping/
## Using Arrays instead of Elements
An inviolable rule of working with opengl is that to get to get the textures showing on all sides of a vertex, we will need to send duplicate vertices. Unfortunately this means we don't get to use glDrawElements.
With a normal mesh, we might send data that looks like:
Vert X | Vert Y | Vert Z | Normal X | Normal Y | Normal Z | UV X | UV Y
------------- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | -------------
0 | 0 | 0 | 0 | 1 | 0 | 0 | 0
0 | 0 | 2 | 0 | 1 | 0 | 0 | 1
2 | 0 | 2 | 0 | 1 | 0 | 1 | 1
There would then be a separate table of indices into the first table where groups of three indices in the second table correspond to a single triangle
Element 1 | Element 2 | Element 3
--- | --- | ---
0 | 1 | 2
1 | 2 | 3
However, given the note at the top of this section, we can't use this element-index scheme because we want to have the textures blend seamlessly on all sides of a given vertex.
So instead, our data may look like
Vert X | Vert Y | Vert Z | Normal X | Normal Y | Normal Z | UV X | UV Y
------------- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | -------------
0 | 0 | 0 | 0 | 1 | 0 | 0 | 0
0 | 0 | 2 | 0 | 1 | 0 | 0 | 1
2 | 0 | 2 | 0 | 1 | 0 | 1 | 1
0 | 0 | 2 | 0 | 1 | 0 | 0 | 1
(Notice rows two and four are duplicate data)
## Atlas Data
### Indices
Another component required to make this scheme work is extra data to tell the mesh what texture in the atlas to sample at what point. Alongside the Vert, Normal, and UV data we also send the atlas location of the three textures to sample for the current triangle.
This would look something like the following table.
Sample Index 1 | Sample Index 2 | Sample Index 3
--- | --- | ---
0 | 0 | 0
0 | 0 | 0
0 | 0 | 0
1 | 0 | 0
1 | 0 | 0
1 | 0 | 0
The first triangle would be uniformly the same texture. The second triangle would blend from the texture at atlas location "1" to the texture at atlas location "0".
### Weights
Another piece of information the fragment shader needs to know is how close to each vertex in the triangle it is. This lets the shader determine how much of each texture to pull from.
The data for this would look like
Weight 1 | Weight 2 | Weight 3
--- | --- | ---
1.0 | 0.0 | 0.0
0.0 | 1.0 | 0.0
0.0 | 0.0 | 1.0
1.0 | 0.0 | 0.0
0.0 | 1.0 | 0.0
0.0 | 0.0 | 1.0
The idea is that the vertex shader will automatically interpolate between the groups of three vectors and give us a single vector of weights.
## Shader Math
The math that does all texture calculations is pretty complicated and has three main parts
This first block calculates the UVs to sample from for the camera view along the X axis. We need to sample a texture for each of the vertices. The idea is that vert1 could be a dirt texture, vert2 is grass, vert3 is stone, and we need to know how to blend between all three. The following code block calculates UVs for a single of the three textures.
``` glsl
vec2 vert1_x_uv = vec2(
(fract(texPlane1.x) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (mod(samplerIndexVec.x,ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL),
(fract(texPlane1.y) * ATLAS_NORMALIZED_ELEMENT_WIDTH) + (round(samplerIndexVec.x / ATLAS_EL_PER_ROW) * ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL)
);
```
The naming format is vert\<which vert \>_\<which camera (x,y,z)\>_uv.
`texPlane1` is a base UV value that is passed into the fragment shader. It's calculated based on the vertex position in the mesh itself (model matrix not applied). IE In the image below, the vertex a 0,0 and 1,0 would generate UVs from 0 to 1 along the X axis.
![](/docs/src/images/renderer/terrain/polygonOverflowExplanation.png)
!!IMPORTANT POINT!! Because we are passing these values that are greated than 1 as we go along the model (2->3, 3->4, etc), when the UVs translate from the vertex to fragment shader they are re-normalized into the range [0,1]. This is important for later.
`ATLAS_NORMALIZED_ELEMENT_WIDTH` is the width of a single texture within the atlas image. Because OpenGL images all have dimensions [0,1], this variable has the value 1/32. Except, it's slightly less than 1/32. This is to keep us from sampling just past the edge of the atlas entry and into the next one (was creating weird lines).
`samplerIndexVec` Contains the index into the atlas that we want to sample for this particular vertex. It is some single integer that is modulo wrapped up the atlas.
`ATLAS_NORMALIZED_ELEMENT_WIDTH_FULL` is precisely 1/32. This is so we properly move along the atlas. If we used `ATLAS_NORMALIZED_ELEMENT_WIDTH`, we wouldn't be sampling far enough along and would gradually fall back into previous elements.
Now that we have calculated the UVs for each of the vertices along the X axis, we actually sample for each texture and combine them to get an overall x-axis texture. The vertWeights come from data pased into the GPU as an array attrib. (this is that second table that goes 1,0,0 - 0,1,0 - 0,0,1 - 1,0,0 etc)
``` glsl
vec3 albedoX = texture(material.diffuse, vert1_x_uv).rgb * vert1Weight + texture(material.diffuse, vert2_x_uv).rgb * vert2Weight + texture(material.diffuse, vert3_x_uv).rgb * vert3Weight;
```
Finally, we sum the three different axis colors (x,y,z) and weight them based on the normals.
``` glsl
return (albedoX * weights.x + albedoY * weights.y + albedoZ * weights.z);
```

View File

@ -100,7 +100,9 @@ public class ClientSimulation {
}
Globals.profiler.endCpuSample();
//update foliage
Globals.clientFoliageManager.update();
if(Globals.clientFoliageManager != null){
Globals.clientFoliageManager.update();
}
//tally collidables and offset position accordingly
// for(Entity currentCollidable : Globals.entityManager.getEntitiesWithTag(EntityTags.COLLIDABLE)){
// CollidableTree tree = CollidableTree.getCollidableTree(currentCollidable);

View File

@ -63,14 +63,14 @@ public class DrawCell {
/**
* Generates a drawable entity based on this chunk
*/
public void generateDrawableEntity(){
public void generateDrawableEntity(VoxelTextureAtlas atlas){
if(modelEntity != null){
Globals.clientScene.deregisterEntity(modelEntity);
}
fillInData();
modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(weights, types, 0);
modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(weights, types, 0, atlas);
ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos());
}

View File

@ -42,6 +42,9 @@ public class DrawCellManager {
Set<String> drawable;
Set<String> undrawable;
Set<String> updateable;
//voxel atlas
VoxelTextureAtlas atlas;
ShaderProgram program;
@ -190,7 +193,7 @@ public class DrawCellManager {
undrawable.remove(targetKey);
drawable.add(targetKey);
//make drawable entity
keyCellMap.get(targetKey).generateDrawableEntity();
keyCellMap.get(targetKey).generateDrawableEntity(atlas);
//evaluate for foliage
Globals.clientFoliageManager.evaluateChunk(worldPos);
}
@ -222,7 +225,7 @@ public class DrawCellManager {
// stride = stride + 1;
// }
keyCellMap.get(targetKey).destroy();
keyCellMap.get(targetKey).generateDrawableEntity();
keyCellMap.get(targetKey).generateDrawableEntity(atlas);
}
drawable.add(targetKey);
}
@ -432,6 +435,13 @@ public class DrawCellManager {
return drawRadius;
}
/**
* Initializes the voxel texture atlas
*/
public void attachTextureAtlas(VoxelTextureAtlas voxelTextureAtlas){
atlas = voxelTextureAtlas;
}
// public

View File

@ -0,0 +1,102 @@
package electrosphere.client.terrain.cells;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import org.joml.Vector2i;
import electrosphere.engine.Globals;
import electrosphere.game.data.voxel.VoxelData;
import electrosphere.game.data.voxel.VoxelType;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.texture.Texture;
import electrosphere.util.FileUtils;
/**
* An atlas texture and accompanying map of all voxel textures
*/
public class VoxelTextureAtlas {
//A map of voxel id -> coordinates in the atlas texture for its texture
Map<Integer,Integer> typeCoordMap = new HashMap<Integer,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 = 256;
//the width in pixels of the whole atlas texture
public static final int ATLAS_DIM = 8192;
//number of textures per row in the atlas
public static final int ELEMENTS_PER_ROW = ATLAS_DIM / ATLAS_ELEMENT_DIM;
/**
* Creates the voxel texture atlas object
* @return the atlas object
*/
public static VoxelTextureAtlas createVoxelTextureAtlas(VoxelData data){
Globals.profiler.beginCpuSample("createVoxelTextureAtlas");
VoxelTextureAtlas rVal = new VoxelTextureAtlas();
int iterator = 0;
BufferedImage image = new BufferedImage(ATLAS_DIM, ATLAS_DIM, BufferedImage.TYPE_4BYTE_ABGR);
Graphics graphics = image.getGraphics();
for(VoxelType type : data.getTypes()){
if(type.getTexture() != null){
int offX = iterator % ELEMENTS_PER_ROW;
int offY = iterator / ELEMENTS_PER_ROW;
try {
BufferedImage newType = ImageIO.read(FileUtils.getAssetFile(type.getTexture()));
graphics.drawImage(newType, iterator * ATLAS_ELEMENT_DIM * offX, ATLAS_DIM - ATLAS_ELEMENT_DIM - iterator * ATLAS_ELEMENT_DIM * offY, null);
} catch (IOException e) {
LoggerInterface.loggerRenderer.ERROR("Texture atlas failed to find texture " + type.getTexture(), e);
}
//put coords in map
rVal.typeCoordMap.put(type.getId(),iterator);
//iterate
iterator++;
}
}
Globals.profiler.endCpuSample();
//construct texture atlas from buffered image
rVal.specular = new Texture(image);
rVal.normal = new Texture(image);
return rVal;
}
/**
* 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 voxel type (the voxel type is provided by its id)
* @param voxelTypeId The id of the voxel type
* @return the index in the atlas of the texture of the provided voxel type
*/
public int getVoxelTypeOffset(int voxelTypeId){
return typeCoordMap.get(voxelTypeId);
}
}

View File

@ -15,6 +15,7 @@ import org.joml.Vector3i;
import electrosphere.client.scene.ClientWorldData;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.client.terrain.cache.ClientTerrainCache;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.engine.Globals;
import electrosphere.entity.types.terrain.TerrainChunkData;
import electrosphere.logger.LoggerInterface;
@ -142,11 +143,11 @@ public class ClientTerrainManager {
* @param data The chunk data (triangles, normals, etc)
* @return The model path that is promised to eventually reflect the terrain model when it makes it to gpu
*/
public static String queueTerrainGridGeneration(TerrainChunkData data){
public static String queueTerrainGridGeneration(TerrainChunkData data, VoxelTextureAtlas atlas){
String promisedHash = "";
UUID newUUID = UUID.randomUUID();
promisedHash = newUUID.toString();
TerrainChunkGenQueueItem queueItem = new TerrainChunkGenQueueItem(data, promisedHash);
TerrainChunkGenQueueItem queueItem = new TerrainChunkGenQueueItem(data, promisedHash, atlas);
terrainChunkGenerationQueue.add(queueItem);
return promisedHash;
}
@ -157,7 +158,7 @@ public class ClientTerrainManager {
public static void generateTerrainChunkGeometry(){
Globals.profiler.beginCpuSample("generateTerrainChunkGeometry");
for(TerrainChunkGenQueueItem queueItem : terrainChunkGenerationQueue){
Model terrainModel = TerrainChunkModelGeneration.generateTerrainModel(queueItem.getData());
Model terrainModel = TerrainChunkModelGeneration.generateTerrainModel(queueItem.getData(), queueItem.getAtlas());
Globals.assetManager.registerModelToSpecificString(terrainModel, queueItem.getPromisedHash());
}
terrainChunkGenerationQueue.clear();

View File

@ -1,23 +1,54 @@
package electrosphere.client.terrain.manager;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.entity.types.terrain.TerrainChunkData;
/**
* Represents an item in a queue of terrain chunks to have models generated in the main thread
*/
public class TerrainChunkGenQueueItem {
//the data of the chunk (verts, normals, etc)
TerrainChunkData data;
//the hash promised to store the model under in asset manager
String promisedHash;
//the texture atlas
VoxelTextureAtlas atlas;
public TerrainChunkGenQueueItem(TerrainChunkData data, String promisedHash){
/**
* Creates a queue item
* @param data
* @param promisedHash
* @param atlas
*/
public TerrainChunkGenQueueItem(TerrainChunkData data, String promisedHash, VoxelTextureAtlas atlas){
this.data = data;
this.promisedHash = promisedHash;
this.atlas = atlas;
}
/**
* Gets the mesh data for the chunk
* @return the mesh data
*/
public TerrainChunkData getData(){
return data;
}
/**
* Gets the hash promised for the chunk
* @return the hash
*/
public String getPromisedHash(){
return this.promisedHash;
}
/**
* Gets the texture atlas assigned to the chunk
* @return the atlas
*/
public VoxelTextureAtlas getAtlas(){
return atlas;
}
}

View File

@ -20,6 +20,7 @@ import electrosphere.client.scene.ClientSceneWrapper;
import electrosphere.client.scene.ClientWorldData;
import electrosphere.client.sim.ClientSimulation;
import electrosphere.client.terrain.cells.DrawCellManager;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.collision.CollisionEngine;
import electrosphere.collision.CollisionWorldData;
@ -332,6 +333,7 @@ public class Globals {
//chunk stuff
//draw cell manager
public static DrawCellManager drawCellManager;
public static VoxelTextureAtlas voxelTextureAtlas;
//fluid cell manager
public static FluidCellManager fluidCellManager;
@ -512,7 +514,6 @@ public class Globals {
defaultMeshShader = ShaderProgram.smart_assemble_shader(false,true);
//init terrain shader program
terrainShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/terrain2/terrain2.vs", "/Shaders/terrain2/terrain2.fs");
TerrainChunkModelGeneration.terrainChunkShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/terrain2/terrain2.vs", "/Shaders/terrain2/terrain2.fs");
//init fluid shader program
FluidChunkModelGeneration.fluidChunkShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/fluid2/fluid2.vs", "/Shaders/fluid2/fluid2.fs");
//init models
@ -549,6 +550,9 @@ public class Globals {
//debug shaders
assetManager.addShaderToQueue("Shaders/ui/debug/windowBorder/windowBound.vs", null, "Shaders/ui/debug/windowBorder/windowBound.fs");
assetManager.addShaderToQueue("Shaders/ui/debug/windowContentBorder/windowContentBound.vs", null, "Shaders/ui/debug/windowContentBorder/windowContentBound.fs");
//voxel texture atlas
voxelTextureAtlas = VoxelTextureAtlas.createVoxelTextureAtlas(Globals.gameConfigCurrent.getVoxelData());
//as these assets are required for the renderer to work, we go ahead and
//load them into memory now. The loading time penalty is worth it I think.

View File

@ -240,6 +240,8 @@ public class ClientLoading {
}
//initialize draw cell manager
Globals.drawCellManager = new DrawCellManager(Globals.clientTerrainManager, 0, 0, 0);
//construct texture atlas
Globals.drawCellManager.attachTextureAtlas(Globals.voxelTextureAtlas);
//set our draw cell manager to actually generate drawable chunks
Globals.drawCellManager.setGenerateDrawables(true);
//Alerts the client simulation that it should start loading terrain

View File

@ -2,6 +2,7 @@ package electrosphere.entity.types.terrain;
import org.joml.Vector3d;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.collision.PhysicsEntityUtils;
import electrosphere.collision.PhysicsUtils;
@ -25,10 +26,10 @@ public class TerrainChunk {
* @param levelOfDetail Increasing value that increments level of detail. 0 would be full resolution, 1 would be half resolution and so on. Only generates physics if levelOfDetail is 0
* @return The terrain chunk entity
*/
public static Entity clientCreateTerrainChunkEntity(float[][][] weights, int[][][] values, int levelOfDetail){
public static Entity clientCreateTerrainChunkEntity(float[][][] weights, int[][][] values, int levelOfDetail, VoxelTextureAtlas atlas){
TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values);
String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data);
String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas);
Entity rVal = EntityCreationUtils.createClientSpatialEntity();
EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);

View File

@ -2,34 +2,87 @@ package electrosphere.entity.types.terrain;
import java.util.List;
/**
* The data required to generate a texture
*/
public class TerrainChunkData {
//the verts
List<Float> vertices;
//normals
List<Float> normals;
//faces
List<Integer> faceElements;
//UVs
List<Float> uvs;
//texture samplers
List<Float> textureSamplers; //what textures in the atlas to sample
//texture ratio vector
List<Float> textureRatioVectors; //HOW MUCH of each texture in the atlas to sample
public TerrainChunkData(List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs){
/**
* Creates an object to hold data required to generate a chunk
* @param vertices
* @param normals
* @param faceElements
* @param uvs
* @param textureSamplers
*/
public TerrainChunkData(List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs, List<Float> textureSamplers, List<Float> textureRatioVectors){
this.vertices = vertices;
this.normals = normals;
this.faceElements = faceElements;
this.uvs = uvs;
this.textureSamplers = textureSamplers;
this.textureRatioVectors = textureRatioVectors;
}
/**
* Gets the vertex data
* @return the vertex data
*/
public List<Float> getVertices(){
return vertices;
}
/**
* Gets the normal data
* @return the normal data
*/
public List<Float> getNormals(){
return normals;
}
/**
* Gets the face element data
* @return the face element data
*/
public List<Integer> getFaceElements(){
return faceElements;
}
/**
* Gets the uv data
* @return the uv data
*/
public List<Float> getUVs(){
return uvs;
}
/**
* Gets the texture sampler data
* @return the texture sampler data
*/
public List<Float> getTextureSamplers(){
return textureSamplers;
}
/**
* Gets the texture ratio vector data
* @return the texture ratio vector data
*/
public List<Float> getTextureRatioVectors(){
return textureRatioVectors;
}
}

View File

@ -71,6 +71,8 @@ public class InstanceManager {
* Draws all models that are queued in this instance manager
*/
public void draw(RenderPipelineState renderPipelineState, OpenGLState openGLState){
boolean instanced = renderPipelineState.getInstanced();
boolean useMeshShader = renderPipelineState.getUseMeshShader();
renderPipelineState.setInstanced(true);
renderPipelineState.setUseMeshShader(false);
for(String modelPath : modelsToDraw){
@ -94,7 +96,8 @@ public class InstanceManager {
//clear queue
data.clearDrawQueue();
}
renderPipelineState.setInstanced(false);
renderPipelineState.setInstanced(instanced);
renderPipelineState.setUseMeshShader(useMeshShader);
}
}

View File

@ -19,6 +19,7 @@ import org.lwjgl.BufferUtils;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.engine.Globals;
import electrosphere.entity.types.terrain.TerrainChunkData;
import electrosphere.renderer.model.Material;
@ -341,9 +342,19 @@ public class TerrainChunkModelGeneration {
(byte)0x13
};
/**
* A single generated triangle
*/
static class Triangle {
//the indices
int[] indices = new int[3]; //array of size 3
/**
* Creates a triangle object
* @param index0
* @param index1
* @param index2
*/
public Triangle(int index0, int index1, int index2){
indices[0] = index0;
indices[1] = index1;
@ -351,19 +362,30 @@ public class TerrainChunkModelGeneration {
}
}
/**
* The grid cell currently being looked at
*/
static class GridCell {
Vector3f[] points = new Vector3f[8]; //array of size 8
double[] val = new double[8]; //array of size 8
int[] atlasValues = new int[8]; //array of size 8
public void setValues(
Vector3f p1, Vector3f p2, Vector3f p3, Vector3f p4,
Vector3f p5, Vector3f p6, Vector3f p7, Vector3f p8,
double val1, double val2, double val3, double val4,
double val5, double val6, double val7, double val8
double val5, double val6, double val7, double val8,
int atl1, int atl2, int atl3, int atl4,
int atl5, int atl6, int atl7, int atl8
){
//triangle points
points[0] = p1; points[1] = p2; points[2] = p3; points[3] = p4;
points[4] = p5; points[5] = p6; points[6] = p7; points[7] = p8;
//iso values
val[0] = val1; val[1] = val2; val[2] = val3; val[3] = val4;
val[4] = val5; val[5] = val6; val[6] = val7; val[7] = val8;
//atlas values
atlasValues[0] = atl1; atlasValues[1] = atl2; atlasValues[2] = atl3; atlasValues[3] = atl4;
atlasValues[4] = atl5; atlasValues[5] = atl6; atlasValues[6] = atl7; atlasValues[7] = atl8;
}
}
@ -373,9 +395,12 @@ public class TerrainChunkModelGeneration {
public static ShaderProgram terrainChunkShaderProgram = null;
//location of the sampler index data in the shader
public static final int SAMPLER_INDEX_ATTRIB_LOC = 5;
//the ratio vectors of how much to pull from each texture
public static final int SAMPLER_RATIO_ATTRIB_LOC = 6;
@ -391,6 +416,7 @@ public class TerrainChunkModelGeneration {
GridCell grid,
double isolevel,
List<Triangle> triangles,
List<Vector3f> samplerIndices,
Map<String,Integer> vertMap,
List<Vector3f> verts,
List<Vector3f> normals,
@ -400,6 +426,7 @@ public class TerrainChunkModelGeneration {
int ntriang;
int cubeIndex = 0;
Vector3f[] vertList = new Vector3f[12];
int[] samplerIndex = new int[12];
//get lookup key (index) for edge table
//edge table tells us which vertices are inside of the surface
@ -419,42 +446,55 @@ public class TerrainChunkModelGeneration {
//instead of having all intersections be perfectly at the midpoint,
//for each edge this code calculates where along the edge to place the vertex
//this should dramatically smooth the surface
if ((edgeTable[cubeIndex] & 1) > 0)
vertList[0] =
VertexInterp(isolevel,grid.points[0],grid.points[1],grid.val[0],grid.val[1]);
if ((edgeTable[cubeIndex] & 2) > 0)
vertList[1] =
VertexInterp(isolevel,grid.points[1],grid.points[2],grid.val[1],grid.val[2]);
if ((edgeTable[cubeIndex] & 4) > 0)
vertList[2] =
VertexInterp(isolevel,grid.points[2],grid.points[3],grid.val[2],grid.val[3]);
if ((edgeTable[cubeIndex] & 8) > 0)
vertList[3] =
VertexInterp(isolevel,grid.points[3],grid.points[0],grid.val[3],grid.val[0]);
if ((edgeTable[cubeIndex] & 16) > 0)
vertList[4] =
VertexInterp(isolevel,grid.points[4],grid.points[5],grid.val[4],grid.val[5]);
if ((edgeTable[cubeIndex] & 32) > 0)
vertList[5] =
VertexInterp(isolevel,grid.points[5],grid.points[6],grid.val[5],grid.val[6]);
if ((edgeTable[cubeIndex] & 64) > 0)
vertList[6] =
VertexInterp(isolevel,grid.points[6],grid.points[7],grid.val[6],grid.val[7]);
if ((edgeTable[cubeIndex] & 128) > 0)
vertList[7] =
VertexInterp(isolevel,grid.points[7],grid.points[4],grid.val[7],grid.val[4]);
if ((edgeTable[cubeIndex] & 256) > 0)
vertList[8] =
VertexInterp(isolevel,grid.points[0],grid.points[4],grid.val[0],grid.val[4]);
if ((edgeTable[cubeIndex] & 512) > 0)
vertList[9] =
VertexInterp(isolevel,grid.points[1],grid.points[5],grid.val[1],grid.val[5]);
if ((edgeTable[cubeIndex] & 1024) > 0)
vertList[10] =
VertexInterp(isolevel,grid.points[2],grid.points[6],grid.val[2],grid.val[6]);
if ((edgeTable[cubeIndex] & 2048) > 0)
vertList[11] =
VertexInterp(isolevel,grid.points[3],grid.points[7],grid.val[3],grid.val[7]);
if((edgeTable[cubeIndex] & 1) > 0){
vertList[0] = VertexInterp(isolevel,grid.points[0],grid.points[1],grid.val[0],grid.val[1]);
}
if((edgeTable[cubeIndex] & 2) > 0){
vertList[1] = VertexInterp(isolevel,grid.points[1],grid.points[2],grid.val[1],grid.val[2]);
}
if((edgeTable[cubeIndex] & 4) > 0){
vertList[2] = VertexInterp(isolevel,grid.points[2],grid.points[3],grid.val[2],grid.val[3]);
}
if((edgeTable[cubeIndex] & 8) > 0){
vertList[3] = VertexInterp(isolevel,grid.points[3],grid.points[0],grid.val[3],grid.val[0]);
}
if((edgeTable[cubeIndex] & 16) > 0){
vertList[4] = VertexInterp(isolevel,grid.points[4],grid.points[5],grid.val[4],grid.val[5]);
}
if((edgeTable[cubeIndex] & 32) > 0){
vertList[5] = VertexInterp(isolevel,grid.points[5],grid.points[6],grid.val[5],grid.val[6]);
}
if((edgeTable[cubeIndex] & 64) > 0){
vertList[6] = VertexInterp(isolevel,grid.points[6],grid.points[7],grid.val[6],grid.val[7]);
}
if((edgeTable[cubeIndex] & 128) > 0){
vertList[7] = VertexInterp(isolevel,grid.points[7],grid.points[4],grid.val[7],grid.val[4]);
}
if((edgeTable[cubeIndex] & 256) > 0){
vertList[8] = VertexInterp(isolevel,grid.points[0],grid.points[4],grid.val[0],grid.val[4]);
}
if((edgeTable[cubeIndex] & 512) > 0){
vertList[9] = VertexInterp(isolevel,grid.points[1],grid.points[5],grid.val[1],grid.val[5]);
}
if((edgeTable[cubeIndex] & 1024) > 0){
vertList[10] = VertexInterp(isolevel,grid.points[2],grid.points[6],grid.val[2],grid.val[6]);
}
if((edgeTable[cubeIndex] & 2048) > 0){
vertList[11] = VertexInterp(isolevel,grid.points[3],grid.points[7],grid.val[3],grid.val[7]);
}
if(grid.val[0] > isolevel){ samplerIndex[0] = 0; } else { samplerIndex[0] = 1; }
if(grid.val[1] > isolevel){ samplerIndex[1] = 1; } else { samplerIndex[1] = 2; }
if(grid.val[2] > isolevel){ samplerIndex[2] = 2; } else { samplerIndex[2] = 3; }
if(grid.val[3] > isolevel){ samplerIndex[3] = 3; } else { samplerIndex[3] = 0; }
if(grid.val[4] > isolevel){ samplerIndex[4] = 4; } else { samplerIndex[4] = 5; }
if(grid.val[5] > isolevel){ samplerIndex[5] = 5; } else { samplerIndex[5] = 6; }
if(grid.val[6] > isolevel){ samplerIndex[6] = 6; } else { samplerIndex[6] = 7; }
if(grid.val[7] > isolevel){ samplerIndex[7] = 7; } else { samplerIndex[7] = 4; }
if(grid.val[0] > isolevel){ samplerIndex[8] = 0; } else { samplerIndex[8] = 4; }
if(grid.val[1] > isolevel){ samplerIndex[9] = 1; } else { samplerIndex[9] = 5; }
if(grid.val[2] > isolevel){ samplerIndex[10] = 2; } else { samplerIndex[10] = 6; }
if(grid.val[3] > isolevel){ samplerIndex[11] = 3; } else { samplerIndex[11] = 7; }
//Create the triangle
ntriang = 0;
@ -484,6 +524,26 @@ public class TerrainChunkModelGeneration {
//
//Sampler triangles
//
for(int j = 0; j < 3; j++){
//we add the triangle three times so all three vertices have the same values
//that way they don't interpolate when you're in a middle point of the fragment
//this could eventually potentially be optimized to send 1/3rd the data, but
//the current approach is easier to reason about
Vector3f samplerTriangle = new Vector3f(
grid.atlasValues[samplerIndex[triTable[cubeIndex][i+0]]],
grid.atlasValues[samplerIndex[triTable[cubeIndex][i+1]]],
grid.atlasValues[samplerIndex[triTable[cubeIndex][i+2]]]
);
samplerIndices.add(samplerTriangle);
}
//
// Normals calculation
//
@ -589,8 +649,8 @@ public class TerrainChunkModelGeneration {
List<Vector3f> normals = new LinkedList<Vector3f>();
//the list of number of triangles that share a vert
List<Integer> trianglesSharingVert = new LinkedList<Integer>();
//List of elements in order
List<Integer> faceElements = new LinkedList<Integer>();
//List of texture sampler values
List<Vector3f> samplerTriangles = new LinkedList<Vector3f>();
//List of UVs
List<Float> UVs = new LinkedList<Float>();
@ -604,10 +664,12 @@ public class TerrainChunkModelGeneration {
new Vector3f(x+0,y+0,z+0), new Vector3f(x+0,y+0,z+1), new Vector3f(x+1,y+0,z+1), new Vector3f(x+1,y+0,z+0),
new Vector3f(x+0,y+1,z+0), new Vector3f(x+0,y+1,z+1), new Vector3f(x+1,y+1,z+1), new Vector3f(x+1,y+1,z+0),
terrainGrid[x+0][y+0][z+0], terrainGrid[x+0][y+0][z+1], terrainGrid[x+1][y+0][z+1], terrainGrid[x+1][y+0][z+0],
terrainGrid[x+0][y+1][z+0], terrainGrid[x+0][y+1][z+1], terrainGrid[x+1][y+1][z+1], terrainGrid[x+1][y+1][z+0]
terrainGrid[x+0][y+1][z+0], terrainGrid[x+0][y+1][z+1], terrainGrid[x+1][y+1][z+1], terrainGrid[x+1][y+1][z+0],
textureGrid[x+0][y+0][z+0], textureGrid[x+0][y+0][z+1], textureGrid[x+1][y+0][z+1], textureGrid[x+1][y+0][z+0],
textureGrid[x+0][y+1][z+0], textureGrid[x+0][y+1][z+1], textureGrid[x+1][y+1][z+1], textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
polygonize(currentCell, 0.01f, triangles, vertMap, verts, normals, trianglesSharingVert);
polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
}
@ -618,6 +680,10 @@ public class TerrainChunkModelGeneration {
List<Float> normalsFlat = new LinkedList<Float>();
//all elements of faces in order
List<Integer> elementsFlat = new LinkedList<Integer>();
//List of texture sampler values
List<Float> textureSamplers = new LinkedList<Float>();
//List of texture ratio values
List<Float> textureRatioData = new LinkedList<Float>();
//flatten verts + normals
for(Vector3f vert : verts){
@ -671,8 +737,33 @@ public class TerrainChunkModelGeneration {
UVs.add(temp[1]);
}
//flatten sampler indices
for(Vector3f samplerVec : samplerTriangles){
textureSamplers.add((float)Globals.voxelTextureAtlas.getVoxelTypeOffset((int)samplerVec.x));
textureSamplers.add((float)Globals.voxelTextureAtlas.getVoxelTypeOffset((int)samplerVec.y));
textureSamplers.add((float)Globals.voxelTextureAtlas.getVoxelTypeOffset((int)samplerVec.z));
}
//set ratio dat
for(Triangle triangle : triangles){
//first vertex
textureRatioData.add(1.0f);
textureRatioData.add(0.0f);
textureRatioData.add(0.0f);
//second vertex
textureRatioData.add(0.0f);
textureRatioData.add(1.0f);
textureRatioData.add(0.0f);
//third vertex
textureRatioData.add(0.0f);
textureRatioData.add(0.0f);
textureRatioData.add(1.0f);
}
//List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs
TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs);
TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs, textureSamplers, textureRatioData);
return rVal;
}
@ -695,84 +786,92 @@ public class TerrainChunkModelGeneration {
//
//Buffer data to GPU
//
try {
int vertexCount = data.getVertices().size() / 3;
FloatBuffer VertexArrayBufferData = BufferUtils.createFloatBuffer(vertexCount * 3);
float[] temp = new float[3];
for(float vertValue : data.getVertices()){
VertexArrayBufferData.put(vertValue);
}
VertexArrayBufferData.flip();
mesh.bufferVertices(VertexArrayBufferData, 3);
} catch (NullPointerException ex){
ex.printStackTrace();
}
//
// FACES
// Buffer data to GPU
//
int faceCount = data.getFaceElements().size() / 3;
int elementCount = data.getFaceElements().size();
try {
IntBuffer elementArrayBufferData = BufferUtils.createIntBuffer(elementCount);
int[] temp = new int[3];
FloatBuffer VertexArrayBufferData = BufferUtils.createFloatBuffer(elementCount * 3);
FloatBuffer NormalArrayBufferData = BufferUtils.createFloatBuffer(elementCount * 3);
FloatBuffer TextureArrayBufferData = BufferUtils.createFloatBuffer(elementCount * 2);
for(int element : data.getFaceElements()){
elementArrayBufferData.put(element);
//for each element, need to push vert, normal, etc
//push current vertex
VertexArrayBufferData.put(data.getVertices().get(element*3+0));
VertexArrayBufferData.put(data.getVertices().get(element*3+1));
VertexArrayBufferData.put(data.getVertices().get(element*3+2));
//push current normals
NormalArrayBufferData.put(data.getNormals().get(element*3+0));
NormalArrayBufferData.put(data.getNormals().get(element*3+1));
NormalArrayBufferData.put(data.getNormals().get(element*3+2));
//push current uvs
TextureArrayBufferData.put(data.getUVs().get(element*2+0));
TextureArrayBufferData.put(data.getUVs().get(element*2+1));
}
elementArrayBufferData.flip();
mesh.bufferFaces(elementArrayBufferData,elementCount);
//actually buffer vertices
VertexArrayBufferData.flip();
mesh.bufferVertices(VertexArrayBufferData, 3);
//actually buffer normals
NormalArrayBufferData.flip();
mesh.bufferNormals(NormalArrayBufferData, 3);
//actually buffer UVs
TextureArrayBufferData.flip();
mesh.bufferTextureCoords(TextureArrayBufferData, 2);
} catch (NullPointerException ex){
ex.printStackTrace();
}
//alert mesh to use direct array access and set the number of elements in the direct arrays
mesh.setUseElementArray(false);
mesh.setDirectArraySize(elementCount);
//
// NORMALS
// SAMPLER INDICES
//
try {
int normalCount = data.getNormals().size() / 3;
FloatBuffer NormalArrayBufferData;
if(normalCount > 0){
NormalArrayBufferData = BufferUtils.createFloatBuffer(normalCount * 3);
float[] temp = new float[3];
for(float normalValue : data.getNormals()){
NormalArrayBufferData.put(normalValue);
}
NormalArrayBufferData.flip();
mesh.bufferNormals(NormalArrayBufferData, 3);
int vertexCount = data.getTextureSamplers().size() / 3;
FloatBuffer samplerBuffer = BufferUtils.createFloatBuffer(vertexCount * 3);
for(float samplerVec : data.getTextureSamplers()){
samplerBuffer.put(samplerVec);
}
samplerBuffer.flip();
mesh.bufferCustomFloatAttribArray(samplerBuffer, 3, SAMPLER_INDEX_ATTRIB_LOC);
} catch (NullPointerException ex){
ex.printStackTrace();
}
//
// TEXTURE COORDINATES
// SAMPLER RATIO DATA
//
try {
int textureCoordCount = data.getUVs().size() / 2;
FloatBuffer TextureArrayBufferData;
if(textureCoordCount > 0){
TextureArrayBufferData = BufferUtils.createFloatBuffer(textureCoordCount * 2);
float[] temp = new float[2];
for(float uvValue : data.getUVs()){
TextureArrayBufferData.put(uvValue);
}
TextureArrayBufferData.flip();
mesh.bufferTextureCoords(TextureArrayBufferData, 2);
int vertexCount = data.getTextureRatioVectors().size() / 3;
FloatBuffer samplerBuffer = BufferUtils.createFloatBuffer(vertexCount * 3);
for(float samplerVec : data.getTextureRatioVectors()){
samplerBuffer.put(samplerVec);
}
samplerBuffer.flip();
mesh.bufferCustomFloatAttribArray(samplerBuffer, 3, SAMPLER_RATIO_ATTRIB_LOC);
} catch (NullPointerException ex){
ex.printStackTrace();
}
//bounding sphere logic
float halfChunk = ServerTerrainChunk.CHUNK_DIMENSION / 2.0f;
mesh.updateBoundingSphere(
halfChunk,
@ -791,20 +890,21 @@ public class TerrainChunkModelGeneration {
/**
* Generates a model based on a terrainchunkdata object
* @param data The terrain chunk data object
* @param atlas The atlas texture for the chunk
* @return The model
*/
public static Model generateTerrainModel(TerrainChunkData data){
public static Model generateTerrainModel(TerrainChunkData data, VoxelTextureAtlas atlas){
Model rVal = new Model();
Mesh m = generateTerrainMesh(data);
//construct the material for the chunk
Material groundMat = new Material();
groundMat.set_diffuse("/Textures/Ground/Dirt1.png");
groundMat.set_specular("/Textures/Ground/Dirt1.png");
Globals.assetManager.addTexturePathtoQueue("/Textures/Ground/Dirt1.png");
groundMat.setTexturePointer(atlas.getSpecular().getTexturePointer());
groundMat.setNormalTexturePointer(atlas.getNormal().getTexturePointer());
m.setMaterial(groundMat);
m.setShader(TerrainChunkModelGeneration.terrainChunkShaderProgram);
//shader logic
m.setShader(Globals.terrainShaderProgram);
m.setParent(rVal);
rVal.getMeshes().add(m);
@ -818,6 +918,13 @@ public class TerrainChunkModelGeneration {
return x + "_" + y + "_" + z;
}
/**
* Gets the already existing index of this point
* @param vert the vertex's raw position
* @param vertMap the map of key ->Vert index
* @param verts
* @return
*/
private static int getVertIndex(Vector3f vert, Map<String,Integer> vertMap, List<Vector3f> verts){
int rVal = -1;
String vertKey = getVertKeyFromPoints(vert.x,vert.y,vert.z);

View File

@ -25,11 +25,18 @@ public class Material {
//or whether it should have a manually set texturePointer and not look up while binding
boolean usesFetch = true;
//texture pointer for the specular
int texturePointer;
//texture pointer for the normal
int normalPointer;
/**
* A material that contains textures
*/
public Material(){
}
//basically useless because blender doesn't support exporting mats with fbx
public static Material load_material_from_aimaterial(AIMaterial input){
Material rVal = new Material();
@ -65,6 +72,15 @@ public class Material {
texturePointer = pointer;
usesFetch = false;
}
/**
* Sets the in-material pointer for the normal
* @param pointer the normal texture
*/
public void setNormalTexturePointer(int pointer){
normalPointer = pointer;
usesFetch = false;
}
/**
* Applies the material

View File

@ -50,12 +50,14 @@ public class Mesh {
private int vertexBuffer;
private int normalBuffer;
private int elementArrayBuffer;
private int elementCount;
private int elementCount; //pulls double duty as both the element array count as well as the direct array count, based on whichever is used
private int vertexArrayObject;
private int boneWeightBuffer;
private int boneIndexBuffer;
private int textureCoordBuffer;
//uses element instances
private boolean useElementArray = true;
//THIS IS NOT GUARANTEED TO BE THE PARENT MODEL THAT THIS WAS LOADED IN
@ -131,6 +133,14 @@ public class Mesh {
this.elementCount = elementCount;
}
}
/**
* Sets the number of elements in the directly referenced arrays underneath this mesh
* @param directArraySize The number of elements (ie the number of vertices, or normals, etc)
*/
public void setDirectArraySize(int directArraySize){
this.elementCount = directArraySize;
}
/**
* Buffers texture coordinates to the gpu
@ -316,6 +326,9 @@ public class Mesh {
* @param renderPipelineState The state of the render pipeline
*/
public void complexDraw(RenderPipelineState renderPipelineState, OpenGLState openGLState){
//bind vao off the rip
glBindVertexArray(vertexArrayObject);
if(renderPipelineState.getUseMeshShader()){
ShaderProgram selectedProgram = null;
@ -362,9 +375,6 @@ public class Mesh {
glBindVertexArray(vertexArrayObject);
if(textureMask != null){
int i = 0;
@ -465,7 +475,11 @@ public class Mesh {
if(renderPipelineState.getInstanced()){
GL45.glDrawElementsInstanced(GL_TRIANGLES, elementCount, GL_UNSIGNED_INT, 0, renderPipelineState.getInstanceCount());
} else {
GL11.glDrawElements(GL_TRIANGLES, elementCount, GL_UNSIGNED_INT, 0);
if(useElementArray){
GL11.glDrawElements(GL_TRIANGLES, elementCount, GL_UNSIGNED_INT, 0);
} else {
GL11.glDrawArrays(GL_TRIANGLES, 0, elementCount);
}
}
glBindVertexArray(0);
}
@ -548,4 +562,20 @@ public class Mesh {
this.boneIdList.add(boneId);
}
/**
* Sets whether to use an element array or a direct array
* @param useElementArray if true, use elements, else use direct array reference
*/
public void setUseElementArray(boolean useElementArray){
this.useElementArray = useElementArray;
}
/**
* Gets whether the mesh uses an element array or a direct array
* @return if true, use elements, else use direct array reference
*/
public boolean getUseElementArray(){
return this.useElementArray;
}
}