structures, also they're a little broken, they need to check for neighbouring YZones as well

This commit is contained in:
Halbear 2026-09-20 22:43:42 +01:00
parent a0b00b0f1c
commit d1abde919d
12 changed files with 604 additions and 134 deletions

View file

@ -53,7 +53,9 @@ layout(push_constant) uniform ChunkInfo {
uint faceOffset; uint faceOffset;
uint voxelTypeCount; uint voxelTypeCount;
uint indirectCommandIndex; uint indirectCommandIndex;
uint padding0; uint biomeCount;
uint structureCount;
uint worldSeed;
}; };
uint flatten(ivec3 pos) { uint flatten(ivec3 pos) {

View file

@ -21,9 +21,11 @@ layout(push_constant) uniform ChunkInfo {
ivec3 chunkPos; ivec3 chunkPos;
int slot; int slot;
uint faceOffset; uint faceOffset;
uint unused0; uint voxelTypeCount;
uint indirectCommandIndex; uint indirectCommandIndex;
uint padding0; uint biomeCount;
uint structureCount;
uint worldSeed;
}; };
void main() { void main() {

View file

@ -3,11 +3,23 @@ layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
const int CHUNK_SIZE = 16; const int CHUNK_SIZE = 16;
const int CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE; const int CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE;
const int STRUCTURE_CELL_SIZE = 16;
const int REPLACE_AIR_ONLY = 1;
const int REPLACE_FOLIAGE = 2;
const int REQUIRE_FLAT_GROUND = 4;
const int HAS_FOUNDATION = 8;
// A 1D storage buffer representing a flat 3D array of voxel IDs layout(push_constant) uniform ChunkInfo {
layout(std430, binding = 0) buffer VoxelData { ivec3 chunkPos;
uint voxels[]; int slot;
uint faceOffset;
uint voxelTypeCount;
uint indirectCommandIndex;
uint biomeCount;
uint structureCount;
uint worldSeed;
}; };
struct Biome { struct Biome {
uint surfaceBlock; uint surfaceBlock;
uint dirtBlock; uint dirtBlock;
@ -18,19 +30,45 @@ struct Biome {
uint[4] otherFoliage; uint[4] otherFoliage;
}; };
struct Structure {
uint sizeX;
uint sizeY;
uint sizeZ;
uint blockOffset;
uint spawnSpacing;
uint spawnChance;
uint allowedBiome;
uint groundBlock;
uint flags;
};
struct StructureVoxelResult {
uint block;
uint flags;
};
layout(std430, binding = 0) buffer VoxelData {
uint voxels[];
};
layout(std430, set = 1, binding = 0) readonly buffer BiomeUniform { layout(std430, set = 1, binding = 0) readonly buffer BiomeUniform {
Biome biomes[]; Biome biomes[];
} BiomeReg; } BiomeReg;
layout(push_constant) uniform ChunkInfo { layout(std430, set = 2, binding = 0) readonly buffer StructureRegistry {
ivec3 chunkPos; Structure structures[];
int slot; } structureReg;
uint faceOffset;
uint unused0;
uint indirectCommandIndex;
uint biomeCount;
};
layout(std430, set = 2, binding = 1) readonly buffer StructureBlocks {
uint blocks[];
} structureBlocks;
uint hash21(uvec2 p) {
p = p * 1664525u + 1013904223u;
p.x += p.y * 1664525u;
p.y += p.x * 1664525u;
return p.x ^ (p.x >> 16);
}
float random3to1(vec3 st) { float random3to1(vec3 st) {
return fract(sin(dot(st, vec3(127.1, 311.7, 74.7))) * 43758.5453123); return fract(sin(dot(st, vec3(127.1, 311.7, 74.7))) * 43758.5453123);
} }
@ -127,6 +165,331 @@ bool isCave(vec3 worldPos) {
return n > 0.5; return n > 0.5;
} }
bool shouldSpawn(uint seed, Structure s) {
return pcg_hash(seed) % 1000u < s.spawnChance;
}
bool hasFlag(uint flags, uint flag) {
return (flags & flag) != 0u;
}
int floorDiv(int value, int divisor) {
int q = value / divisor;
int r = value - q * divisor;
if (r != 0 && ((r < 0) != (divisor < 0))) {
q -= 1;
}
return q;
}
ivec2 getStructureCell(ivec2 worldXZ, int spacing) {
return ivec2(
floorDiv(worldXZ.x, spacing),
floorDiv(worldXZ.y, spacing)
);
}
float getPrimarySurfaceHeight(int worldX, int worldY, int worldZ) {
float YZone = floor(float(worldY) / 150.0) * 150.0;
return valueNoise(vec2(float(worldX) + YZone * 2.0, float(worldZ) + YZone * 2.0) * 0.005) * 100.0 +
valueNoise(vec2(float(worldX) + YZone * 2.0, float(worldZ) + YZone * 2.0) * 0.01) * 10.0 +
valueNoise(vec2(float(worldX) + YZone * 2.0, float(worldZ) + YZone * 2.0) * 0.1) * 5.0 +
valueNoise(vec2(float(worldX) + YZone * 2.0, float(worldZ) + YZone * 2.0)) * 0.5 +
YZone;
}
float getSecondarySurfaceHeight(int worldX, int worldY, int worldZ) {
float YZone2 = floor(float(worldY) / 75.0) * 75.0;
return valueNoise(vec2(float(worldX) + YZone2 * 2.0, float(worldZ) + YZone2 * 2.0) * 0.005) * 50.0 +
valueNoise(vec2(float(worldX) + YZone2 * 2.0, float(worldZ) + YZone2 * 2.0) * 0.01) * 10.0 +
valueNoise(vec2(float(worldX) + YZone2 * 2.0, float(worldZ) + YZone2 * 2.0) * 0.1) * 5.0 +
valueNoise(vec2(float(worldX) + YZone2 * 2.0, float(worldZ) + YZone2 * 2.0)) * 0.5 +
YZone2;
}
float getSurfaceHeight(int worldX, int worldZ, int referenceY) {
float primary = getPrimarySurfaceHeight(worldX, referenceY, worldZ);
float secondary = getSecondarySurfaceHeight(worldX, referenceY, worldZ);
float primaryDist = abs(primary - float(referenceY));
float secondaryDist = abs(secondary - float(referenceY));
return primaryDist <= secondaryDist ? primary : secondary;
}
bool cellSpawnsStructure(uint seed, Structure s) {
if (s.spawnChance == 0u) {
return false;
}
return pcg_hash(seed) % 1000u < s.spawnChance;
}
uint chooseStructure(uint seed, uint biome) {
if (structureCount == 0u) {
return 0u;
}
uint start = pcg_hash(seed ^ 0xA53A9D31u) % structureCount;
for (uint i = 0u; i < structureCount; i++) {
uint id = (start + i) % structureCount;
Structure s = structureReg.structures[id];
if (s.allowedBiome == 0u || s.allowedBiome == biome + 1u) {
return id;
}
}
return start;
}
ivec2 getStructureAnchorXZ(ivec2 cell, uint seed, uint spacing) {
int structureSpacing = int(max(spacing, 1u));
uint jitterX = randomRangeUint(seed ^ 0xB5297A4Du, 0u, uint(structureSpacing - 1));
uint jitterZ = randomRangeUint(seed ^ 0x68E31DA4u, 0u, uint(structureSpacing - 1));
return cell * structureSpacing + ivec2(int(jitterX), int(jitterZ));
}
uint getStructureBlock(uint structureId, ivec3 localPos) {
Structure s = structureReg.structures[structureId];
if (localPos.x < 0 || localPos.x >= int(s.sizeX)) return 0u;
if (localPos.y < 0 || localPos.y >= int(s.sizeY)) return 0u;
if (localPos.z < 0 || localPos.z >= int(s.sizeZ)) return 0u;
uint index =
uint(localPos.x) * s.sizeY * s.sizeZ +
uint(localPos.y) * s.sizeZ +
uint(localPos.z);
return structureBlocks.blocks[s.blockOffset + index];
}
bool isGroundFlatEnough(ivec2 anchorXZ, uint structureId, int referenceY) {
Structure s = structureReg.structures[structureId];
float h0 = getSurfaceHeight(anchorXZ.x, anchorXZ.y, referenceY);
float h1 = getSurfaceHeight(anchorXZ.x + int(s.sizeX) - 1, anchorXZ.y, referenceY);
float h2 = getSurfaceHeight(anchorXZ.x, anchorXZ.y + int(s.sizeZ) - 1, referenceY);
float h3 = getSurfaceHeight(anchorXZ.x + int(s.sizeX) - 1, anchorXZ.y + int(s.sizeZ) - 1, referenceY);
float minH = min(min(h0, h1), min(h2, h3));
float maxH = max(max(h0, h1), max(h2, h3));
return maxH - minH <= 2.0;
}
ivec3 getStructureCell3D(ivec3 worldPos, int spacing) {
return ivec3(
floorDiv(worldPos.x, spacing),
floorDiv(worldPos.y, 75),
floorDiv(worldPos.z, spacing)
);
}
uint hash31(ivec3 p) {
uvec3 v = uvec3(p) * uvec3(1664525u, 22695477u, 1103515245u) + uvec3(1013904223u);
v.x += v.y * v.z;
v.y += v.z * v.x;
v.z += v.x * v.y;
return v.x ^ v.y ^ v.z;
}
uint getBaseTerrainVoxelAt(ivec3 worldPos, uint biome) {
Biome currentBiome = BiomeReg.biomes[biome];
float YZone = floor(float(worldPos.y) / 150.0) * 150.0;
float SurfaceHeight =
valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.005) * 100.0 +
valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.01) * 10.0 +
valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.1) * 5.0 +
valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0)) * 0.5 +
YZone;
float IslandUnderneathHeight =
valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.005) * 100.0 +
valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.1) * 20.0 +
valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.1) * 5.0 +
valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0)) * 0.5 -
15.0 +
YZone;
float grassNoise = valueNoise(vec2(float(worldPos.z) * 0.5 - YZone * 2.0, float(worldPos.x) * 0.5 - YZone * 2.0));
uint randomBlockVal = randomRangeUint(uint(worldPos.x) * 73856093u ^ uint(worldPos.z) * 19349663u, 0u, 3u);
uint voxelType = 0u;
if (float(worldPos.y) <= SurfaceHeight && float(worldPos.y) >= IslandUnderneathHeight) {
float depthBelowSurface = SurfaceHeight - float(worldPos.y);
if (depthBelowSurface < 0.5) {
if (grassNoise < 0.65) voxelType = currentBiome.grassFoliage;
else if (grassNoise < 0.75) voxelType = currentBiome.flowers[randomBlockVal];
else if (grassNoise < 0.85) voxelType = currentBiome.otherFoliage[randomBlockVal];
else voxelType = 0u;
} else if (depthBelowSurface < 2.5) {
voxelType = currentBiome.surfaceBlock;
} else if (depthBelowSurface < 6.0) {
voxelType = currentBiome.dirtBlock;
} else {
voxelType = currentBiome.stoneBlock;
}
} else {
float YZone2 = floor(float(worldPos.y) / 75.0) * 75.0;
float IslandUnderneathHeight2 =
valueNoise(vec2(float(worldPos.x) - YZone2 * 2.0, float(worldPos.z) - YZone2 * 2.0) * 0.005) * 60.0 +
valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0) * 0.1) * 20.0 +
valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0) * 0.1) * 10.0 +
valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0)) * 0.5 -
5.0 +
YZone2;
float height2 =
valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0) * 0.005) * 50.0 +
valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0) * 0.01) * 10.0 +
valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0) * 0.1) * 5.0 +
valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0)) * 0.5 +
YZone2;
if (float(worldPos.y) <= height2 && float(worldPos.y) >= IslandUnderneathHeight2) {
float depthBelowSurface = height2 - float(worldPos.y);
if (depthBelowSurface < 0.5) {
if (grassNoise < 0.65) voxelType = currentBiome.grassFoliage;
else if (grassNoise < 0.75) voxelType = currentBiome.flowers[randomBlockVal];
else if (grassNoise < 0.85) voxelType = currentBiome.otherFoliage[randomBlockVal];
else voxelType = 0u;
} else if (depthBelowSurface < 1.5) {
voxelType = currentBiome.surfaceBlock;
} else if (depthBelowSurface < 6.0) {
voxelType = currentBiome.dirtBlock;
} else {
voxelType = currentBiome.stoneBlock;
}
}
}
if (voxelType != 0u && isCave(vec3(worldPos) * 0.05)) {
voxelType = 0u;
}
return voxelType;
}
bool isValidStructureAnchor(ivec3 origin, uint structureId, uint biome) {
Structure s = structureReg.structures[structureId];
ivec3 groundPos = origin + ivec3(0, -1, 0);
ivec3 basePos = origin;
uint groundVoxel = getBaseTerrainVoxelAt(groundPos, biome);
uint baseVoxel = getBaseTerrainVoxelAt(basePos, biome);
if (groundVoxel == 0u) {
return false;
}
if (s.groundBlock != 0u && groundVoxel != s.groundBlock) {
return false;
}
if (baseVoxel != 0u) {
return false;
}
if (isCave(vec3(groundPos) * 0.05)) {
return false;
}
return true;
}
int getStructureReferenceY(int worldY) {
return floorDiv(worldY, 75) * 75;
}
bool isStructureFootprintSupported(ivec3 origin, uint structureId, uint biome) {
Structure s = structureReg.structures[structureId];
int supportedCorners = 0;
ivec3 p0 = origin + ivec3(0, -1, 0);
ivec3 p1 = origin + ivec3(int(s.sizeX) - 1, -1, 0);
ivec3 p2 = origin + ivec3(0, -1, int(s.sizeZ) - 1);
ivec3 p3 = origin + ivec3(int(s.sizeX) - 1, -1, int(s.sizeZ) - 1);
if (getBaseTerrainVoxelAt(p0, biome) != 0u) supportedCorners++;
if (getBaseTerrainVoxelAt(p1, biome) != 0u) supportedCorners++;
if (getBaseTerrainVoxelAt(p2, biome) != 0u) supportedCorners++;
if (getBaseTerrainVoxelAt(p3, biome) != 0u) supportedCorners++;
return supportedCorners == 4;
}
StructureVoxelResult getStructureVoxelAt(ivec3 worldPos, uint biome) {
StructureVoxelResult result;
result.block = 0u;
result.flags = 0u;
if (structureCount == 0u) {
return result;
}
for (uint structureId = 0u; structureId < structureCount; structureId++) {
Structure s = structureReg.structures[structureId];
if (s.allowedBiome != 0u && s.allowedBiome != biome + 1u) {
continue;
}
int spacing = int(max(s.spawnSpacing, 1u));
ivec3 baseCell = getStructureCell3D(worldPos, spacing);
for (int ox = -1; ox <= 1; ox++) {
for (int oz = -1; oz <= 1; oz++) {
ivec3 cell = baseCell + ivec3(ox, 0, oz);
uint seed = hash31(cell + ivec3(0, int(structureId) * 97, 0)) ^ worldSeed;
if (!cellSpawnsStructure(seed, s)) {
continue;
}
ivec2 anchorXZ = getStructureAnchorXZ(cell.xz, seed, s.spawnSpacing);
int referenceY = cell.y * 75;
float anchorHeight = getSurfaceHeight(anchorXZ.x, anchorXZ.y, referenceY);
int anchorY = int(floor(anchorHeight)) + 1;
ivec3 origin = ivec3(anchorXZ.x, anchorY, anchorXZ.y);
if (!isValidStructureAnchor(origin, structureId, biome)) {
continue;
}
ivec3 local = worldPos - origin;
uint block = getStructureBlock(structureId, local);
if (block != 0u) {
result.block = block;
result.flags = s.flags;
return result;
}
}
}
}
return result;
}
void main() { void main() {
ivec3 localPos = ivec3(gl_GlobalInvocationID.xyz); ivec3 localPos = ivec3(gl_GlobalInvocationID.xyz);
ivec3 worldPos = chunkPos * CHUNK_SIZE + localPos; ivec3 worldPos = chunkPos * CHUNK_SIZE + localPos;
@ -202,6 +565,18 @@ void main() {
} }
} }
StructureVoxelResult structureVoxel = getStructureVoxelAt(worldPos, biome);
if (structureVoxel.block != 0u) {
if (hasFlag(structureVoxel.flags, uint(REPLACE_AIR_ONLY))) {
if (voxelType == 0u) {
voxelType = structureVoxel.block;
}
} else {
voxelType = structureVoxel.block;
}
}
uint index = uint((localPos.x * CHUNK_AREA) + (localPos.y * CHUNK_SIZE) + localPos.z); uint index = uint((localPos.x * CHUNK_AREA) + (localPos.y * CHUNK_SIZE) + localPos.z);
voxels[index] = voxelType; voxels[index] = voxelType;
} }

View file

@ -110,7 +110,8 @@ void main() {
faceId = min(faceId, 7u); faceId = min(faceId, 7u);
vec3 localPos = vec3(voxelX, voxelY, voxelZ) + FACE_CORNERS[faceId][cornerIndex]; vec3 localPos = vec3(voxelX, voxelY, voxelZ) + FACE_CORNERS[faceId][cornerIndex];
vec3 worldPos = localPos + push_constants.ModelOffset.xyz * 16.0; vec3 Offset = vec3(0,0,0);
vec3 worldPos = localPos + Offset + push_constants.ModelOffset.xyz * 16.0;
vec4 worldPosVec4 = vec4(worldPos, 1.0); vec4 worldPosVec4 = vec4(worldPos, 1.0);

View file

@ -1,51 +0,0 @@
#version 460 core
in vec3 FragPos;
in vec2 TexCoords;
in vec3 Normal;
out vec4 FragColor;
uniform vec3 cameraPos;
uniform vec3 lightPos;
// Material constants
const vec3 waterShallowColor = vec3(0.0, 0.6, 0.7);
const vec3 waterDeepColor = vec3(0.05, 0.15, 0.3);
const vec3 sunColor = vec3(1.0, 0.95, 0.8);
void main() {
// Normalize vectors
vec3 n = normalize(Normal);
vec3 viewDir = normalize(cameraPos - FragPos);
vec3 lightDir = normalize(lightPos - FragPos);
// 1. Ambient Lighting
vec3 ambient = 0.3 * waterShallowColor;
// 2. Diffuse Shading
float diff = max(dot(n, lightDir), 0.0);
vec3 diffuse = diff * sunColor * 0.4;
// 3. Specular Highlights (Blinn-Phong)
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(n, halfwayDir), 0.0), 64.0); // High shininess for water glaze
vec3 specular = spec * sunColor * 0.8;
// 4. Fresnel Approximation (Schlick's approximation)
// Water has a base reflectivity of roughly 0.02 at a perpendicular view angle
float F0 = 0.02;
float fresnel = F0 + (1.0 - F0) * pow(1.0 - max(dot(n, viewDir), 0.0), 5.0);
// Mix deep and shallow water colors based on the normal angle
vec3 baseWaterColor = mix(waterShallowColor, waterDeepColor, max(dot(n, vec3(0.0, 1.0, 0.0)), 0.0));
// Combine lighting results
vec3 lightingResult = ambient + diffuse + specular;
// Blend final look with Fresnel reflection dominance
vec3 finalColor = mix(baseWaterColor + specular, lightingResult + vec3(fresnel * 0.5), fresnel);
// Output final color with subtle translucency opacity
FragColor = vec4(finalColor, 0.85);
}

View file

@ -1,46 +0,0 @@
#version 450
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;
out vec3 FragPos;
out vec2 TexCoords;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform float uTime;
// Wave configuration constants
const float AMPLITUDE = 0.15;
const float FREQUENCY = 1.5;
const float SPEED = 2.0;
// Simple wave function that modifies elevation based on position and time
float calculateWave(vec3 pos, float time, vec2 direction) {
return AMPLITUDE * sin(dot(pos.xz, direction) * FREQUENCY + time * SPEED);
}
// Derivative of the wave function to calculate accurate dynamic surface normals
vec3 calculateWaveNormal(vec3 pos, float time) {
float dx = AMPLITUDE * FREQUENCY * cos(pos.x * FREQUENCY + time * SPEED);
float dz = AMPLITUDE * FREQUENCY * cos(pos.z * FREQUENCY + time * SPEED);
return normalize(vec3(-dx, 1.0, -dz));
}
void main() {
vec3 displacedPos = aPos;
// Combine two wave directions for a less predictable, more organic look
displacedPos.y += calculateWave(aPos, uTime, vec2(1.0, 0.0));
displacedPos.y += calculateWave(aPos, uTime * 1.2, vec2(0.5, 0.8));
FragPos = vec3(model * vec4(displacedPos, 1.0));
TexCoords = aTexCoords;
// Pass transformed normals and position to the fragment shader
Normal = mat3(transpose(inverse(model))) * calculateWaveNormal(displacedPos, uTime);
gl_Position = projection * view * model * vec4(displacedPos, 1.0);
}

View file

@ -200,6 +200,7 @@ public class VulkanRenderer implements Renderer {
int Index = materialsCache.GetPosition("VoxelTerrain"); int Index = materialsCache.GetPosition("VoxelTerrain");
int Index2 = materialsCache.GetPosition("Blueleaf"); int Index2 = materialsCache.GetPosition("Blueleaf");
int Index3 = materialsCache.GetPosition("Blueleaf2"); int Index3 = materialsCache.GetPosition("Blueleaf2");
Voxel grassVoxel = new Voxel(Index, 0, Voxel grassVoxel = new Voxel(Index, 0,
new Vector2i[]{positionArr[0][1], positionArr[0][3]}, new Vector2i[]{positionArr[0][1], positionArr[0][3]},
new Vector2i[]{positionArr[1][0], positionArr[1][0]}, new Vector2i[]{positionArr[1][0], positionArr[1][0]},
@ -373,8 +374,14 @@ public class VulkanRenderer implements Renderer {
Biome GreenBiome = new Biome(1,2,3,4,new int[]{5,6,7,4},new int[]{4,4,4,4},new int[]{4,18,16,17}); Biome GreenBiome = new Biome(1,2,3,4,new int[]{5,6,7,4},new int[]{4,4,4,4},new int[]{4,18,16,17});
Biome Blueleaf = new Biome(8,9,3,10,new int[]{11,19,20,14},new int[]{5,6,7,8},new int[]{15,16,17,18}); Biome Blueleaf = new Biome(8,9,3,10,new int[]{11,19,20,14},new int[]{5,6,7,8},new int[]{15,16,17,18});
VoxelWorldManager.AddBiome(Blueleaf, "blueleaf");
VoxelWorldManager.AddBiome(GreenBiome, "greenBiome"); VoxelWorldManager.AddBiome(GreenBiome, "greenBiome");
VoxelWorldManager.AddBiome(Blueleaf, "blueleaf");
VoxelWorldManager.GenerateExampleTree(5,8,5,0,1,6,7,"green_tree", 15, 0.5f);
VoxelWorldManager.GenerateExampleTree(12,16,12,0,1,6,7,"green_tree_tall", 15, 0.5f);
VoxelWorldManager.GenerateExampleTree(7,12,7,1,8,6,20,"blue_tree", 30,0.25f);
} }
public GuiRenderer GetGUIRenderer(){return GuiRender;} public GuiRenderer GetGUIRenderer(){return GuiRender;}

View file

@ -25,19 +25,24 @@ public class SharedVoxelTerrainResources {
public static final int DRAW_INDIRECT_COMMAND_SIZE = 4 * Integer.BYTES; public static final int DRAW_INDIRECT_COMMAND_SIZE = 4 * Integer.BYTES;
public static final int COUNTER_BUFFER_SIZE = Integer.BYTES; public static final int COUNTER_BUFFER_SIZE = Integer.BYTES;
public static final int TERRAIN_GENERATION_PUSH_CONSTANT_SIZE = Integer.BYTES * 8; public static final int TERRAIN_GENERATION_PUSH_CONSTANT_SIZE = Integer.BYTES * 10;
public static final int MAX_VOXEL_TYPES = 256; public static final int MAX_VOXEL_TYPES = 256;
public static final int MAX_BIOME_TYPES = 256; public static final int MAX_BIOME_TYPES = 256;
public static final int VOXEL_REG_DATA_SIZE = 104; public static final int VOXEL_REG_DATA_SIZE = 104;
public static final int BIOME_REG_DATA_SIZE = 64; public static final int BIOME_REG_DATA_SIZE = 64;
public static final int MAX_STRUCTURE_TYPES = 128;
public static final int STRUCTURE_REG_DATA_SIZE = 36;
public static final int MAX_STRUCTURE_BLOCKS = 16 * 24 * 16;
private static final String DESC_ID_VOXEL_GENERATION = "VOXEL_SHARED_DESC_GENERATION"; private static final String DESC_ID_VOXEL_GENERATION = "VOXEL_SHARED_DESC_GENERATION";
private static final String DESC_ID_RESET = "VOXEL_SHARED_DESC_RESET"; private static final String DESC_ID_RESET = "VOXEL_SHARED_DESC_RESET";
private static final String DESC_ID_MESH = "VOXEL_SHARED_DESC_MESH"; private static final String DESC_ID_MESH = "VOXEL_SHARED_DESC_MESH";
private static final String DESC_ID_FACE_GRAPHICS = "VOXEL_SHARED_DESC_FACE_GRAPHICS"; private static final String DESC_ID_FACE_GRAPHICS = "VOXEL_SHARED_DESC_FACE_GRAPHICS";
private static final String DESC_ID_VOXEL_REG = "VOXEL_REGISTRY"; private static final String DESC_ID_VOXEL_REG = "VOXEL_REGISTRY";
private static final String DESC_ID_BIOME_REG = "BIOME_REGISTRY"; private static final String DESC_ID_BIOME_REG = "BIOME_REGISTRY";
private static final String DESC_ID_STRUCTURE_REG = "STRUCTURE_REGISTRY";
private final VoxelMeshPool meshPool; private final VoxelMeshPool meshPool;
@ -45,6 +50,8 @@ public class SharedVoxelTerrainResources {
private final VulkanBuffer counters; private final VulkanBuffer counters;
private final VulkanBuffer voxelRegistryBuffer; private final VulkanBuffer voxelRegistryBuffer;
private final VulkanBuffer biomeRegistryBuffer; private final VulkanBuffer biomeRegistryBuffer;
private final VulkanBuffer structureRegistryBuffer;
private final VulkanBuffer structureVoxelDataBuffer;
private final DescriptorSetLayout voxelGenerationLayout; private final DescriptorSetLayout voxelGenerationLayout;
private final DescriptorSetLayout resetLayout; private final DescriptorSetLayout resetLayout;
@ -52,6 +59,7 @@ public class SharedVoxelTerrainResources {
private final DescriptorSetLayout faceGraphicsLayout; private final DescriptorSetLayout faceGraphicsLayout;
private final DescriptorSetLayout voxelDeclarationLayout; private final DescriptorSetLayout voxelDeclarationLayout;
private final DescriptorSetLayout biomeDeclarationLayout; private final DescriptorSetLayout biomeDeclarationLayout;
private final DescriptorSetLayout structureDeclarationLayout;
private static final String MESH_GENERATION_GLSL = "resources/EngineResources/VoxelComputeShaders/meshGenerationShared.glsl"; private static final String MESH_GENERATION_GLSL = "resources/EngineResources/VoxelComputeShaders/meshGenerationShared.glsl";
private static final String VOXEL_GENERATION_GLSL = "resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl"; private static final String VOXEL_GENERATION_GLSL = "resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl";
@ -82,6 +90,14 @@ public class SharedVoxelTerrainResources {
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
structureRegistryBuffer = new VulkanBuffer(VkCtx, (long) MAX_STRUCTURE_TYPES * STRUCTURE_REG_DATA_SIZE,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
structureVoxelDataBuffer = new VulkanBuffer(VkCtx, (long) MAX_STRUCTURE_TYPES * MAX_STRUCTURE_BLOCKS * Integer.BYTES,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
voxelGenerationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ voxelGenerationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT) new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT)
@ -109,6 +125,10 @@ public class SharedVoxelTerrainResources {
biomeDeclarationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ biomeDeclarationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT) new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT)
}); });
structureDeclarationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT),
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, 1, VK_SHADER_STAGE_COMPUTE_BIT)
});
createDescriptorSets(VkCtx); createDescriptorSets(VkCtx);
@ -123,7 +143,7 @@ public class SharedVoxelTerrainResources {
resetPipeline = new ComputePipeline(VkCtx,resetShader,new DescriptorSetLayout[]{resetLayout},generationPushConstants); resetPipeline = new ComputePipeline(VkCtx,resetShader,new DescriptorSetLayout[]{resetLayout},generationPushConstants);
voxelGenerationPipeline = new ComputePipeline(VkCtx,voxelShader, voxelGenerationPipeline = new ComputePipeline(VkCtx,voxelShader,
new DescriptorSetLayout[]{voxelGenerationLayout,biomeDeclarationLayout},generationPushConstants); new DescriptorSetLayout[]{voxelGenerationLayout,biomeDeclarationLayout, structureDeclarationLayout},generationPushConstants);
meshGenerationPipeline = new ComputePipeline(VkCtx, meshShader, meshGenerationPipeline = new ComputePipeline(VkCtx, meshShader,
new DescriptorSetLayout[]{meshGenerationLayout, voxelDeclarationLayout}, generationPushConstants); new DescriptorSetLayout[]{meshGenerationLayout, voxelDeclarationLayout}, generationPushConstants);
@ -166,6 +186,58 @@ public class SharedVoxelTerrainResources {
DescriptorSet biomeDeclareDescriptorSet = allocator.AddDescriptorSet(device, DESC_ID_BIOME_REG, biomeDeclarationLayout); DescriptorSet biomeDeclareDescriptorSet = allocator.AddDescriptorSet(device, DESC_ID_BIOME_REG, biomeDeclarationLayout);
biomeDeclareDescriptorSet.SetBuffer(device, biomeRegistryBuffer, biomeRegistryBuffer.GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); biomeDeclareDescriptorSet.SetBuffer(device, biomeRegistryBuffer, biomeRegistryBuffer.GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
DescriptorSet structureDeclareDescriptorSet = allocator.AddDescriptorSet(device, DESC_ID_STRUCTURE_REG, structureDeclarationLayout);
structureDeclareDescriptorSet.SetBuffer(device, structureRegistryBuffer, structureRegistryBuffer.GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
structureDeclareDescriptorSet.SetBuffer(device, structureVoxelDataBuffer, structureVoxelDataBuffer.GetRequestedSize(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
}
public void UploadStructureRegistryAndData(VulkanContext VkCtx, List<VoxelStructure> structureReg, List<Integer> structureData) {
int structureCount = Math.min(structureReg.size(), MAX_STRUCTURE_TYPES);
long structureRegistryMemoryAddr = structureRegistryBuffer.MapMemory(VkCtx);
ByteBuffer structureRegistryByteBuffer = MemoryUtil.memByteBuffer(
structureRegistryMemoryAddr,
(int) structureRegistryBuffer.GetRequestedSize()
);
for (int i = 0; i < structureCount; i++) {
VoxelStructure structure = structureReg.get(i);
int base = i * STRUCTURE_REG_DATA_SIZE;
int spawnChance = Math.round(structure.SpawnChance() * 1000.0f);
spawnChance = Math.clamp(spawnChance, 0, 1000);
structureRegistryByteBuffer.putInt(base + 0, structure.SizeX());
structureRegistryByteBuffer.putInt(base + 4, structure.SizeY());
structureRegistryByteBuffer.putInt(base + 8, structure.SizeZ());
structureRegistryByteBuffer.putInt(base + 12, structure.BlockOffset());
structureRegistryByteBuffer.putInt(base + 16, structure.SpawnSpacing());
structureRegistryByteBuffer.putInt(base + 20, spawnChance);
structureRegistryByteBuffer.putInt(base + 24, structure.AllowedBiome());
structureRegistryByteBuffer.putInt(base + 28, structure.Flags());
structureRegistryByteBuffer.putInt(base + 32, structure.GroundBlock());
}
structureRegistryBuffer.UnMapMemory(VkCtx);
int blockCount = Math.min(
structureData.size(),
(int) (structureVoxelDataBuffer.GetRequestedSize() / Integer.BYTES)
);
long structureVoxelDataMemoryAddr = structureVoxelDataBuffer.MapMemory(VkCtx);
ByteBuffer structureVoxelDataByteBuffer = MemoryUtil.memByteBuffer(
structureVoxelDataMemoryAddr,
(int) structureVoxelDataBuffer.GetRequestedSize()
);
for (int i = 0; i < blockCount; i++) {
structureVoxelDataByteBuffer.putInt(i * Integer.BYTES, structureData.get(i));
}
structureVoxelDataBuffer.UnMapMemory(VkCtx);
} }
public void UploadBiomeRegistry(VulkanContext VkCtx, List<Biome> biomeReg) { public void UploadBiomeRegistry(VulkanContext VkCtx, List<Biome> biomeReg) {
@ -269,10 +341,13 @@ public class SharedVoxelTerrainResources {
return DESC_ID_BIOME_REG; return DESC_ID_BIOME_REG;
} }
public String structureRegDescriptorID() {
return DESC_ID_STRUCTURE_REG;
}
public String resetDescriptorId() { public String resetDescriptorId() {
return DESC_ID_RESET; return DESC_ID_RESET;
} }
public String meshDescriptorId() { public String meshDescriptorId() {
return DESC_ID_MESH; return DESC_ID_MESH;
} }

View file

@ -0,0 +1,8 @@
package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration;
public final class StructureFlags {
public static final int REPLACE_AIR_ONLY = 1;
public static final int REPLACE_FOLIAGE = 2;
public static final int REQUIRE_FLAT_GROUND = 4;
public static final int HAS_FOUNDATION = 8;
}

View file

@ -41,12 +41,16 @@ public class VoxelChunkGenerator {
pushConstants.putInt(20, VoxelWorldManager.GetVoxelTypeCount()); pushConstants.putInt(20, VoxelWorldManager.GetVoxelTypeCount());
pushConstants.putInt(24, indirectCommandIndex); pushConstants.putInt(24, indirectCommandIndex);
pushConstants.putInt(28, VoxelWorldManager.GetBiomeTypeCount()); pushConstants.putInt(28, VoxelWorldManager.GetBiomeTypeCount());
pushConstants.putInt(32, VoxelWorldManager.GetStructureTypeCount());
pushConstants.putInt(36, VoxelWorldManager.GetWorldSeed());
LongBuffer resetDescriptorSet = stack.longs(allocator.GetDescriptorSet( LongBuffer resetDescriptorSet = stack.longs(allocator.GetDescriptorSet(
resources.resetDescriptorId()).GetVkDescriptorSet()); resources.resetDescriptorId()).GetVkDescriptorSet());
LongBuffer voxelDescriptorSet = stack.longs(allocator.GetDescriptorSet( LongBuffer voxelDescriptorSet = stack.longs(
resources.voxelGenerationDescriptorId()).GetVkDescriptorSet(), allocator.GetDescriptorSet(resources.voxelGenerationDescriptorId()).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.biomeRegDescriptorID()).GetVkDescriptorSet()); allocator.GetDescriptorSet(resources.biomeRegDescriptorID()).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.structureRegDescriptorID()).GetVkDescriptorSet()
);
LongBuffer meshDescriptorSet = stack.longs( LongBuffer meshDescriptorSet = stack.longs(
allocator.GetDescriptorSet(resources.meshDescriptorId()).GetVkDescriptorSet(), allocator.GetDescriptorSet(resources.meshDescriptorId()).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.voxelRegDescriptorID()).GetVkDescriptorSet()); allocator.GetDescriptorSet(resources.voxelRegDescriptorID()).GetVkDescriptorSet());

View file

@ -0,0 +1,4 @@
package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration;
public record VoxelStructure(int SizeX, int SizeY, int SizeZ, int BlockOffset, int SpawnSpacing, float SpawnChance, int AllowedBiome, int GroundBlock, int Flags) {
}

View file

@ -9,11 +9,13 @@ import org.joml.*;
import org.lwjgl.vulkan.VkCommandBuffer; import org.lwjgl.vulkan.VkCommandBuffer;
import org.tinylog.Logger; import org.tinylog.Logger;
import java.lang.Math;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.lwjgl.vulkan.VK13.*; import static org.lwjgl.vulkan.VK13.*;
@ -27,14 +29,97 @@ public class VoxelWorldManager {
private static final ConcurrentHashMap<Vector3i, Boolean> KnownChunks = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<Vector3i, Boolean> KnownChunks = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Vector3i, VoxelChunkVisibility> CulledChunks = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<Vector3i, VoxelChunkVisibility> CulledChunks = new ConcurrentHashMap<>();
private static final List<Voxel> VoxelRegistry = new java.util.concurrent.CopyOnWriteArrayList<>(); private static final List<Voxel> VoxelRegistry = new CopyOnWriteArrayList<>();
private static final ConcurrentHashMap<String, Integer> VoxelNameToIndex = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Integer> VoxelNameToIndex = new ConcurrentHashMap<>();
private static volatile boolean RegistryDirty = false; private static volatile boolean RegistryDirty = false;
private static final List<Biome> BiomeRegistry = new java.util.concurrent.CopyOnWriteArrayList<>(); private static final List<Biome> BiomeRegistry = new CopyOnWriteArrayList<>();
private static final ConcurrentHashMap<String, Integer> BiomeNameToIndex = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Integer> BiomeNameToIndex = new ConcurrentHashMap<>();
private static volatile boolean BiomeRegistryDirty = false; private static volatile boolean BiomeRegistryDirty = false;
private static final List<VoxelStructure> StructureRegistry = new CopyOnWriteArrayList<>();
private static final List<Integer> StructureBlockData = new CopyOnWriteArrayList<>();
private static final ConcurrentHashMap<String, Integer> StructureNameToIndex =new ConcurrentHashMap<>();
private static volatile boolean StructureRegistryDirty = false;
private static int worldSeed = 12432450;
public static int GetWorldSeed(){
return worldSeed;
}
public static int structureIndex(int x, int y, int z, int sizeY, int sizeZ) {
return x * sizeY * sizeZ + y * sizeZ + z;
}
public static synchronized void AddStructure(String name, int sizeX, int sizeY, int sizeZ, int spawnSpacing,
float spawnChance, int allowedBiome, int groundBlock, int flags,
int[] denseBlocks) {
int blockOffset = StructureBlockData.size();
for (int block : denseBlocks) {
StructureBlockData.add(block);
}
VoxelStructure structure = new VoxelStructure(sizeX, sizeY, sizeZ, blockOffset,
spawnSpacing, spawnChance, allowedBiome, groundBlock, flags);
Integer existing = StructureNameToIndex.get(name);
if (existing != null) {
StructureRegistry.set(existing, structure);
} else {
StructureNameToIndex.put(name, StructureRegistry.size());
StructureRegistry.add(structure);
}
StructureRegistryDirty = true;
}
public static List<VoxelStructure> GetStructureRegistry() {
return new ArrayList<>(StructureRegistry);
}
public static List<Integer> GetStructureBlockData() {
return new ArrayList<>(StructureBlockData);
}
public static VoxelStructure GetStructure(String name) {
Integer idx = StructureNameToIndex.get(name);
return idx == null ? null : StructureRegistry.get(idx);
}
public static int GetStructureTypeID(String name) {
Integer idx = StructureNameToIndex.get(name);
return idx == null ? -1 : idx + 1;
}
public static int GetStructureTypeCount() {
return StructureRegistry.size();
}
public static synchronized void GenerateExampleTree(int sizeX, int sizeY, int sizeZ, int Biome, int GroundVoxel, int log, int leaves, String name, int Spacing, float Chance){
int[] blocks = new int[sizeX * sizeY * sizeZ];
for (int y = 0; y < sizeY/2; y++) {
blocks[structureIndex(sizeX/2, y, sizeZ/2, sizeY, sizeZ)] = log;
}
for (int x = 0; x < sizeX; x++) {
for (int y = sizeY/2; y < sizeY; y++) {
for (int z = 0; z < sizeZ; z++) {
int dx = x - sizeX/2;
int dy = y - sizeY/5 * 3;
int dz = z - sizeZ/2;
if (dx * dx + dy * dy + dz * dz <= sizeX) {
blocks[structureIndex(x, y, z, sizeY, sizeZ)] = leaves;
}
}
}
}
VoxelWorldManager.AddStructure(name, sizeX, sizeY, sizeZ, Spacing, Chance,
Biome, GroundVoxel, 0, blocks);
}
public static synchronized void AddVoxel(Voxel voxel, String name) { public static synchronized void AddVoxel(Voxel voxel, String name) {
Integer existing = VoxelNameToIndex.get(name); Integer existing = VoxelNameToIndex.get(name);
if (existing != null) { if (existing != null) {
@ -198,6 +283,10 @@ public class VoxelWorldManager {
BiomeRegistryDirty = false; BiomeRegistryDirty = false;
Resources.UploadBiomeRegistry(VkCtx, GetBiomeRegistry()); Resources.UploadBiomeRegistry(VkCtx, GetBiomeRegistry());
} }
if (StructureRegistryDirty) {
StructureRegistryDirty = false;
Resources.UploadStructureRegistryAndData(VkCtx, GetStructureRegistry(), GetStructureBlockData());
}
if (VoxelRegistry.isEmpty()) { if (VoxelRegistry.isEmpty()) {
return; // nothing to mesh against yet return; // nothing to mesh against yet
@ -292,19 +381,19 @@ public class VoxelWorldManager {
KnownChunks.remove(pos); KnownChunks.remove(pos);
return true; return true;
} }
// int dx = pos.x - centerChunk.x; int dx = pos.x - centerChunk.x;
// int dy = pos.y - centerChunk.y; int dy = pos.y - centerChunk.y;
// int dz = pos.z - centerChunk.z; int dz = pos.z - centerChunk.z;
//
// int distSq = dx * dx + dy * dy + dz * dz; int distSq = dx * dx + dy * dy + dz * dz;
// if (distSq > maxDistSq) { if (distSq > maxDistSq) {
// RenderChunk chunk = entry.getValue(); RenderChunk chunk = entry.getValue();
//
// Resources.meshPool().free(chunk); Resources.meshPool().free(chunk);
// KnownChunks.remove(pos); KnownChunks.remove(pos);
//
// return true; return true;
// } }
return false; return false;
}); });