grass height variance
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2025-03-28 10:05:51 -04:00
parent d87c262955
commit 7df87e4510
8 changed files with 114 additions and 177 deletions

View File

@ -14,7 +14,9 @@
"grassData": {
"baseColor": {"x": 0.25, "y": 0.6, "z": 0.43},
"tipColor": {"x": 0.17, "y": 0.71, "z": 0.12},
"maxTipCurve" : 0.27
"maxTipCurve" : 0.27,
"minHeight" : 0.7,
"maxHeight" : 5.0
},
"graphicsTemplate": {
"model": {

View File

@ -8,6 +8,11 @@
#define grassWidth 0.01 //TODO: convert to uniform
/**
* Number of variables per instance
*/
#define NUM_PER_INSTANCE_VARS 6
//input buffers
layout (location = 0) in vec3 aPos;
@ -57,6 +62,7 @@ mat4 rotation3dX(float angle);
mat4 rotation3dY(float angle);
mat4 rotation3dZ(float angle);
vec3 rotateY(vec3 vector, float angle);
mat4 scale3d(float x, float y, float z);
float easeIn(float interpolator);
float easeOut(float interpolator);
float easeInOut(float interpolator);
@ -75,15 +81,16 @@ void main() {
ivec2 texSize = textureSize(material.diffuse,0);
int sampleX = (gl_InstanceID % rowSize) * 5;
int sampleX = (gl_InstanceID % rowSize) * NUM_PER_INSTANCE_VARS;
int sampleY = (gl_InstanceID / rowSize);
//grab data out of texture
float xOffset = texelFetch(material.diffuse,ivec2(0 + sampleX,sampleY),0).r;
float yOffset = texelFetch(material.diffuse,ivec2(1 + sampleX,sampleY),0).r;
float zOffset = texelFetch(material.diffuse,ivec2(2 + sampleX,sampleY),0).r;
float rotVar = texelFetch(material.diffuse,ivec2(3 + sampleX,sampleY),0).r;
float rotVar2 = texelFetch(material.diffuse,ivec2(4 + sampleX,sampleY),0).r;
float xOffset = texelFetch(material.diffuse,ivec2(0 + sampleX,sampleY),0).r;
float yOffset = texelFetch(material.diffuse,ivec2(1 + sampleX,sampleY),0).r;
float zOffset = texelFetch(material.diffuse,ivec2(2 + sampleX,sampleY),0).r;
float rotVar = texelFetch(material.diffuse,ivec2(3 + sampleX,sampleY),0).r;
float rotVar2 = texelFetch(material.diffuse,ivec2(4 + sampleX,sampleY),0).r;
float heightScale = texelFetch(material.diffuse,ivec2(5 + sampleX,sampleY),0).r;
//
//curve float noise
@ -130,6 +137,7 @@ void main() {
//
//position transform
//
mat4 localTransform = mat4(
1.0, 0.0, 0.0, 0.0, //column 1
0.0, 1.0, 0.0, 0.0, //column 2
@ -137,8 +145,13 @@ void main() {
xOffset, yOffset, zOffset, 1.0 //column 4
);
//
// Scales the blade of grass vertically
//
mat4 localScale = scale3d(1.0,heightScale,1.0);
//normalize posiiton and normal
vec4 FinalVertex = model * localTransform * windRot * localRot * localRot2 * vec4(aPos, 1.0);
vec4 FinalVertex = model * localTransform * localScale * windRot * localRot * localRot2 * vec4(aPos, 1.0);
vec4 FinalNormal = windRot * localRot * localRot2 * vec4(aNormal, 1.0);
// vec4 FinalNormal = transpose(inverse(localRot2 * localRot * model * localTransform)) * vec4(aNormal, 1.0);
@ -225,6 +238,15 @@ mat4 rotation3dZ(float angle) {
return rVal;
}
mat4 scale3d(float x, float y, float z){
return mat4(
x, 0.0, 0.0, 0.0,
0.0, y, 0.0, 3.0,
0.0, 0.0, z, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
float easeIn(float interpolator){
return interpolator * interpolator;

View File

@ -1,3 +1,3 @@
#maven.buildNumber.plugin properties file
#Thu Mar 27 18:17:28 EDT 2025
buildNumber=608
#Fri Mar 28 09:59:20 EDT 2025
buildNumber=609

View File

@ -1351,6 +1351,9 @@ Code formatting
File-controlled foliage coloration
Fix TextureInstancedActor packing data texture incorrectly (column major instead of row major)
(03/28/2025)
Grass height variance with control from ui + file
# TODO

View File

@ -1,6 +1,5 @@
package electrosphere.client.terrain.foliage;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
@ -68,22 +67,6 @@ public class FoliageCell {
*/
static final int AIR_VOXEL_ID = 0;
/**
* <p>
* Size of a single item of foliage in the texture buffer
* </p>
* A lot of these are x 4 to account for size of float
* 3 x 4 for position
* 2 x 4 for euler rotation
*
*
* eventually:
* grass type
* color
* wind characteristics?
*/
static final int SINGLE_FOLIAGE_DATA_SIZE_BYTES = 3 * 4 + 2 * 4;
/**
* The map of all attributes for instanced foliage
*/
@ -313,154 +296,6 @@ public class FoliageCell {
this.hasGenerated = true;
}
/**
* Insert blades of grass into the entity
* @param vX the x offset of the voxel
* @param vY the y offset of the voxel
* @param vZ the z offset of the voxel
* @param floatBufferView the gpu data buffer
* @param chunkData the chunk data
* @return the number of blades of grass added
*/
protected int insertBlades(int vX, int vY, int vZ, FloatBuffer floatBufferView, ChunkData chunkData){
int rVal = 0;
//get positions offset
Vector3d voxelRealPos = new Vector3d(this.getRealPos()).add(vX,vY,vZ);
Vector3i currVoxelPos = new Vector3i(this.voxelPos).add(vX,vY,vZ);
int scale = (int)Math.pow(2,lod);
//check that the current voxel even supports foliage
boolean shouldGenerate = false;
List<String> foliageTypesSupported = null;
if(chunkData != null && currVoxelPos.y + 1 < ServerTerrainChunk.CHUNK_DIMENSION){
foliageTypesSupported = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(chunkData.getType(currVoxelPos)).getAmbientFoliage();
boolean airAbove = chunkData.getType(currVoxelPos.x,currVoxelPos.y+1,currVoxelPos.z) == AIR_VOXEL_ID;
if(foliageTypesSupported != null && airAbove){
shouldGenerate = true;
}
}
if(shouldGenerate){
//construct simple grid to place foliage on
Vector3d sample_00 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add(-0.5,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
Vector3d sample_01 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add(-0.5,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
Vector3d sample_02 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add(-0.5,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
Vector3d sample_10 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
Vector3d sample_11 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
Vector3d sample_12 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
Vector3d sample_20 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0.5,SAMPLE_START_HEIGHT,-0.5), new Vector3d(0,-1,0), RAY_LENGTH);
Vector3d sample_21 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0.5,SAMPLE_START_HEIGHT, 0), new Vector3d(0,-1,0), RAY_LENGTH);
Vector3d sample_22 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(voxelRealPos).add( 0.5,SAMPLE_START_HEIGHT, 0.5), new Vector3d(0,-1,0), RAY_LENGTH);
//get the heights of each sample
float height_11 = (float)(sample_11 != null ? sample_11.y : 0);
float height_00 = (float)(sample_00 != null ? sample_00.y : height_11);
float height_01 = (float)(sample_01 != null ? sample_01.y : height_11);
float height_02 = (float)(sample_02 != null ? sample_02.y : height_11);
float height_10 = (float)(sample_10 != null ? sample_10.y : height_11);
float height_12 = (float)(sample_12 != null ? sample_12.y : height_11);
float height_20 = (float)(sample_20 != null ? sample_20.y : height_11);
float height_21 = (float)(sample_21 != null ? sample_21.y : height_11);
float height_22 = (float)(sample_22 != null ? sample_22.y : height_11);
//each height is in real world coordinates that are absolute
//when rendering, there's already a y offset for the center of the field of grass (based on the model matrix)
//so when offseting the position of the blade of grass RELATIVE to the overall instance being drawn, need to subtract the real world coordinates of the overall instance
//in other words realPos SPECIFICALLY for the y dimension, for x and z you don't need to worry about it
//if we don't find data for the center sample, can't place grass so don't create entity
if(sample_11 != null){
//generate positions to place
for(int x = 0; x < TARGET_FOLIAGE_SPACING; x=x+scale){
for(int z = 0; z < TARGET_FOLIAGE_SPACING; z=z+scale){
//get position to place
double rand1 = placementRandomizer.nextDouble();
double rand2 = placementRandomizer.nextDouble();
double relativePositionOnGridX = x / (1.0 * TARGET_FOLIAGE_SPACING) + rand1 / TARGET_FOLIAGE_SPACING;
double relativePositionOnGridZ = z / (1.0 * TARGET_FOLIAGE_SPACING) + rand2 / TARGET_FOLIAGE_SPACING;
double offsetX = relativePositionOnGridX - 0.5;
double offsetZ = relativePositionOnGridZ - 0.5;
//determine quadrant we're placing in
double offsetY = 0;
boolean addBlade = false;
if(relativePositionOnGridX >=0.5){
if(relativePositionOnGridZ >= 0.5){
relativePositionOnGridX = relativePositionOnGridX - 0.5;
relativePositionOnGridZ = relativePositionOnGridZ - 0.5;
relativePositionOnGridX /= 0.5;
relativePositionOnGridZ /= 0.5;
// System.out.println(relativePositionOnGridX + " " + relativePositionOnGridZ);
//if we have heights for all four surrounding spots, interpolate for y value
if(sample_11 != null && sample_12 != null && sample_21 != null && sample_22 != null){
offsetY =
height_11 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
height_12 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
height_21 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
height_22 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
addBlade = true;
}
} else {
relativePositionOnGridX = relativePositionOnGridX - 0.5;
relativePositionOnGridX /= 0.5;
relativePositionOnGridZ /= 0.5;
//if we have heights for all four surrounding spots, interpolate for y value
if(sample_10 != null && sample_11 != null && sample_20 != null && sample_21 != null){
offsetY =
height_10 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
height_11 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
height_20 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
height_21 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
addBlade = true;
}
}
} else {
if(relativePositionOnGridZ >= 0.5){
relativePositionOnGridZ = relativePositionOnGridZ - 0.5;
relativePositionOnGridX /= 0.5;
relativePositionOnGridZ /= 0.5;
//if we have heights for all four surrounding spots, interpolate for y value
if(sample_01 != null && sample_02 != null && sample_11 != null && sample_12 != null){
offsetY =
height_01 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
height_02 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
height_11 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
height_12 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
addBlade = true;
}
} else {
relativePositionOnGridX /= 0.5;
relativePositionOnGridZ /= 0.5;
//if we have heights for all four surrounding spots, interpolate for y value
if(sample_00 != null && sample_01 != null && sample_10 != null && sample_11 != null){
offsetY =
height_00 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) +
height_01 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) +
height_10 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) +
height_11 * ( relativePositionOnGridX) * ( relativePositionOnGridZ);
addBlade = true;
}
}
}
if(addBlade){
//convert y to relative to chunk
offsetY = offsetY - this.getRealPos().y;
double rotVar = placementRandomizer.nextDouble() * Math.PI * 2;
double rotVar2 = placementRandomizer.nextDouble();
if(floatBufferView.limit() >= floatBufferView.position() + SINGLE_FOLIAGE_DATA_SIZE_BYTES / 4){
floatBufferView.put((float)offsetX + vX);
floatBufferView.put((float)offsetY + vY);
floatBufferView.put((float)offsetZ + vZ);
floatBufferView.put((float)rotVar);
floatBufferView.put((float)rotVar2);
rVal++;
}
}
}
}
}
}
return rVal;
}
/**
* Gets the real-space position of the foliage cell
* @return the real-space position

View File

@ -55,6 +55,14 @@ public class FoliageModel {
*/
static final int TARGET_FOLIAGE_SPACING = 200;
/**
* The number of floats we're passing per blade of grass
* 3 for position
* 2 for rotation
* 1 for vertical scale
*/
protected static final int NUM_PER_INSTANCE_VARS = 6;
/**
* <p>
* Size of a single item of foliage in the texture buffer
@ -62,6 +70,7 @@ public class FoliageModel {
* A lot of these are x 4 to account for size of float
* 3 x 4 for position
* 2 x 4 for euler rotation
* 1 x 4 for height scaling
*
*
* eventually:
@ -69,7 +78,7 @@ public class FoliageModel {
* color
* wind characteristics?
*/
static final int SINGLE_FOLIAGE_DATA_SIZE_BYTES = 3 * 4 + 2 * 4;
protected static final int SINGLE_FOLIAGE_DATA_SIZE_BYTES = NUM_PER_INSTANCE_VARS * 4;
/**
* Cutoff to place foliage at, weight-wise
@ -242,7 +251,7 @@ public class FoliageModel {
QueuedTexture queuedAsset = new QueuedTexture(QueuedTextureType.DATA_BUFF,buffer,textureWidth,textureHeight);
Globals.assetManager.queuedAsset(queuedAsset);
TextureInstancedActor actor = TextureInstancedActor.attachTextureInstancedActor(rVal, foliageType.getGraphicsTemplate().getModel().getPath(), vertexPath, fragmentPath, queuedAsset, drawCount, textureWidth / 5);
TextureInstancedActor actor = TextureInstancedActor.attachTextureInstancedActor(rVal, foliageType.getGraphicsTemplate().getModel().getPath(), vertexPath, fragmentPath, queuedAsset, drawCount, textureWidth / NUM_PER_INSTANCE_VARS);
ClientEntityUtils.initiallyPositionEntity(rVal, realPos, new Quaterniond());
EntityUtils.getScale(rVal).set(1,1,1);
//add ambient foliage behavior tree
@ -292,6 +301,8 @@ public class FoliageModel {
int rVal = 0;
float maxTipCurve = foliageType.getGrassData().getMaxTipCurve();
float minimumHeight = foliageType.getGrassData().getMinHeight();
float heightMultiplier = foliageType.getGrassData().getMaxHeight() - foliageType.getGrassData().getMinHeight();
//construct simple grid to place foliage on
// Vector3d sample_00 = Globals.clientSceneWrapper.getCollisionEngine().rayCastPosition(new Vector3d(realPos).add(-SAMPLE_OFFSET,SAMPLE_START_HEIGHT,-SAMPLE_OFFSET), new Vector3d(0,-1,0), RAY_LENGTH);
@ -415,12 +426,14 @@ public class FoliageModel {
offsetY = offsetY - realPos.y;
double rotVar = placementRandomizer.nextDouble() * Math.PI * 2;
double rotVar2 = placementRandomizer.nextDouble() * maxTipCurve;
double heightScale = placementRandomizer.nextDouble();
if(floatBufferView.limit() >= floatBufferView.position() + SINGLE_FOLIAGE_DATA_SIZE_BYTES / 4){
floatBufferView.put((float)relativePositionOnGridX + vX);
floatBufferView.put((float)offsetY + vY);
floatBufferView.put((float)relativePositionOnGridZ + vZ);
floatBufferView.put((float)rotVar);
floatBufferView.put((float)rotVar2);
floatBufferView.put((float)(heightScale * heightMultiplier + minimumHeight));
rVal++;
}
}

View File

@ -30,6 +30,16 @@ public class ImGuiEntityFoliageTab {
*/
static float[] grassMaxTipCurve = new float[1];
/**
* Grass Min Height Scale
*/
static float[] grassMinHeightScale = new float[1];
/**
* Grass Max Height Scale
*/
static float[] grassMaxHeightScale = new float[1];
/**
* Client scene entity view
*/
@ -66,6 +76,14 @@ public class ImGuiEntityFoliageTab {
grassData.setMaxTipCurve(grassMaxTipCurve[0]);
}
if(ImGui.sliderFloat("Min Height Scale", grassMinHeightScale, 0.1f, 10)){
grassData.setMinHeight(grassMinHeightScale[0]);
}
if(ImGui.sliderFloat("Max Height Scale", grassMaxHeightScale, 0.1f, 10)){
grassData.setMaxHeight(grassMaxHeightScale[0]);
}
if(ImGui.button("Regenerate All Grass")){
Globals.foliageCellManager.evictAll();
}

View File

@ -22,6 +22,16 @@ public class GrassData {
*/
float maxTipCurve;
/**
* Minimum height of the grass
*/
float minHeight;
/**
* Maximum height of the grass
*/
float maxHeight;
/**
* Gets the base color of the grass
* @return The base color
@ -53,6 +63,40 @@ public class GrassData {
public void setMaxTipCurve(float maxTipCurve) {
this.maxTipCurve = maxTipCurve;
}
/**
* Gets the minimum height of the grass
* @return The minimum height of the grass
*/
public float getMinHeight() {
return minHeight;
}
/**
* Sets the minimum height of the grass
* @param minHeight the minimum height of the grass
*/
public void setMinHeight(float minHeight) {
this.minHeight = minHeight;
}
/**
* Gets the maximum height of the grass
* @return The maximum height of the grass
*/
public float getMaxHeight() {
return maxHeight;
}
/**
* Sets the maximum height of the grass
* @param minHeight the maximum height of the grass
*/
public void setMaxHeight(float maxHeight) {
this.maxHeight = maxHeight;
}
}