Goodbye voxel models 👋

Fixes #215
Closes #164
Closes #162
Closes #160
Closes #150
Closes #145
This commit is contained in:
IntegratedQuantum 2023-12-01 15:50:31 +01:00
parent 2a46592454
commit 7d23411a22
13 changed files with 38 additions and 541 deletions

View File

@ -4,6 +4,6 @@
"drops" : [
"auto"
],
"model" : "log",
"model" : "cube",
"texture" : "cubyz:cactus"
}

View File

@ -5,8 +5,8 @@
"auto"
],
"rotation" : "log",
"model" : "log",
"model" : "cube",
"texture" : "cubyz:oak_log_side",
"texture_top" : "cubyz:oak_log_rings_top",
"texture_bottom" : "cubyz:oak_log_rings_top"
"texture_bottom" : "cubyz:oak_log_rings_bottom"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 989 B

View File

@ -8,6 +8,6 @@
"emittedLight" : 0x00a09080,
"viewThrough" : true,
"rotation" : "torch",
"model" : "cubyz:torch.obj",
"model" : "torch",
"texture" : "cubyz:torch"
}

View File

@ -4,12 +4,9 @@ in vec3 mvVertexPos;
in vec3 light;
flat in int blockType;
flat in int faceNormal;
flat in int modelIndex;
flat in int isBackFace;
flat in int ditherSeed;
// For raymarching:
flat in ivec3 minPos;
flat in ivec3 maxPos;
in vec3 startPosition;
in vec3 direction;

View File

@ -3,13 +3,10 @@
out vec3 mvVertexPos;
out vec3 light;
flat out int blockType;
flat out int modelIndex;
flat out int faceNormal;
flat out int isBackFace;
flat out int ditherSeed;
// For raymarching:
flat out ivec3 minPos;
flat out ivec3 maxPos;
out vec3 startPosition;
out vec3 direction;
@ -29,17 +26,14 @@ layout(std430, binding = 3) buffer _faceData
FaceData faceData[];
};
#define modelSize 16
struct VoxelModel {
struct ModelInfo {
ivec4 minimum;
ivec4 maximum;
uint bitPackedData[modelSize*modelSize*modelSize/32];
uint bitPackedTextureData[modelSize*modelSize*modelSize/8];
};
layout(std430, binding = 4) buffer _voxelModels
{
VoxelModel voxelModels[];
ModelInfo voxelModels[];
};
uniform int voxelSize;
@ -151,7 +145,7 @@ void main() {
ditherSeed = encodedPositionAndNormalsAndPermutation & 15;
blockType = blockAndModel & 65535;
modelIndex = blockAndModel >> 16;
int modelIndex = blockAndModel >> 16;
ivec3 position = ivec3(
encodedPositionAndNormalsAndPermutation & 31,
@ -175,8 +169,6 @@ void main() {
ivec3 lowerBound = voxelModels[modelIndex].minimum.xyz;
ivec3 size = voxelModels[modelIndex].maximum.xyz - voxelModels[modelIndex].minimum.xyz;
size += (voxelSize - 1)*16;
minPos = lowerBound;
maxPos = lowerBound + size;
totalOffset = lowerBound + size*totalOffset;
position += totalOffset - 16*voxelSize*ivec3(normals[normal]);

View File

@ -4,11 +4,8 @@ in vec3 mvVertexPos;
in vec3 light;
flat in int blockType;
flat in int faceNormal;
flat in int modelIndex;
flat in int isBackFace;
flat in int ditherSeed;
flat in ivec3 minPos;
flat in ivec3 maxPos;
in vec3 startPosition;
in vec3 direction;

View File

@ -1,239 +0,0 @@
#version 450
in vec3 mvVertexPos;
in vec3 light; // TODO: This doesn't work here.
flat in int blockType;
flat in int faceNormal;
flat in int modelIndex;
flat in int isBackFace;
flat in int ditherSeed;
// For raymarching:
flat in ivec3 minPos;
flat in ivec3 maxPos;
in vec3 startPosition;
in vec3 direction;
uniform sampler2DArray texture_sampler;
uniform sampler2DArray emissionSampler;
layout(location = 0) out vec4 fragColor;
#define modelSize 16
struct VoxelModel {
ivec4 minimum;
ivec4 maximum;
uint bitPackedData[modelSize*modelSize*modelSize/32];
uint bitPackedTextureData[modelSize*modelSize*modelSize/8];
};
struct TextureData {
uint textureIndices[6];
uint absorption;
float reflectivity;
float fogDensity;
uint fogColor;
};
layout(std430, binding = 1) buffer _textureData
{
TextureData textureData[];
};
layout(std430, binding = 4) buffer _voxelModels
{
VoxelModel voxelModels[];
};
const float[6] normalVariations = float[6](
1.0, //vec3(0, 1, 0),
0.84, //vec3(0, -1, 0),
0.92, //vec3(1, 0, 0),
0.92, //vec3(-1, 0, 0),
0.96, //vec3(0, 0, 1),
0.88 //vec3(0, 0, -1)
);
const vec3[6] normals = vec3[6](
vec3(0, 1, 0),
vec3(0, -1, 0),
vec3(1, 0, 0),
vec3(-1, 0, 0),
vec3(0, 0, 1),
vec3(0, 0, -1)
);
int getVoxel(ivec3 voxelPos) {
voxelPos &= 15;
int voxelIndex = (voxelPos.x << 8) | (voxelPos.y << 4) | (voxelPos.z);
int shift = (voxelIndex & 31);
int arrayIndex = voxelIndex >> 5;
return (int(voxelModels[modelIndex].bitPackedData[arrayIndex])>>shift & 1);
}
int getTexture(ivec3 voxelPos) {
voxelPos &= 15;
int voxelIndex = (voxelPos.x << 8) | (voxelPos.y << 4) | (voxelPos.z);
int shift = 4*(voxelIndex & 7);
int arrayIndex = voxelIndex >> 3;
return (int(voxelModels[modelIndex].bitPackedTextureData[arrayIndex])>>shift & 15);
}
struct RayMarchResult {
bool hitAThing;
int normal;
int textureDir;
ivec3 voxelPosition;
};
RayMarchResult rayMarching(vec3 startPosition, vec3 direction) { // TODO: Mipmapped voxel models. (or maybe just remove them when they are far enough away?)
// Branchless implementation of "A Fast Voxel Traversal Algorithm for Ray Tracing" http://www.cse.yorku.ca/~amana/research/grid.pdf
if(direction.x == 0) {
direction.x = 1e-10;
}
if(direction.y == 0) {
direction.y = 1e-10;
}
if(direction.z == 0) {
direction.z = 1e-10;
}
vec3 step = sign(direction);
ivec3 stepi = ivec3(step);
vec3 t1 = (floor(startPosition) - startPosition)/direction;
vec3 tDelta = 1/direction;
vec3 t2 = t1 + tDelta;
tDelta = abs(tDelta);
vec3 tMax = max(t1, t2);
ivec3 voxelPos = ivec3(floor(startPosition));
ivec3 compare = mix(-maxPos, minPos, lessThan(direction, vec3(0)));
ivec3 inversionMasks = mix(ivec3(~0), ivec3(0), lessThan(direction, vec3(0)));
int lastNormal = faceNormal;
int block = getVoxel(voxelPos);
int size = 16;
ivec3 sizeMask = ivec3(size - 1);
vec3 lastStep = vec3(0, 0, 0);
while(block != 0) {
bvec3 gt1 = lessThanEqual(tMax.xyz, tMax.yzx);
bvec3 gt2 = lessThanEqual(tMax.xyz, tMax.zxy);
bvec3 and = bvec3(gt1.x && gt2.x, gt1.y && gt2.y, gt1.z && gt2.z);
lastStep = vec3(and);
voxelPos += -ivec3(and) & stepi;
tMax += lastStep*tDelta;
/*
Here I use a trick to avoid integer multiplication.
The correct equation would be
sign*pos > compare
((sign > 0) ? pos : -pos) > compare // Expanding the left hand side (sign != 0 for all practical purposes)
((sign > 0) ? pos : ~pos+1) > compare // 2's complement
((sign > 0) ? pos : ~pos) > compare2 // putting the +1 into the compare constant
inversionMasks ^ pos > compare2 // xor can be used to conditionally invert a number
*/
if(any(lessThan(voxelPos^inversionMasks, compare)))
return RayMarchResult(false, 0, 0, ivec3(0, 0, 0));
block = getVoxel(voxelPos);
}
if(lastStep.x != 0) {
lastNormal = 2 + int(step.x == 1);
} else if(lastStep.y != 0) {
lastNormal = 0 + int(step.y == 1);
} else if(lastStep.z != 0) {
lastNormal = 4 + int(step.z == 1);
}
int textureDir = getTexture(voxelPos);
if(textureDir == 6) {
textureDir = lastNormal;
}
return RayMarchResult(true, lastNormal, textureDir, voxelPos & 15);
}
ivec2 getTextureCoords(ivec3 voxelPosition, int textureDir) {
switch(textureDir) {
case 0:
return ivec2(15 - voxelPosition.x, voxelPosition.z);
case 1:
return ivec2(voxelPosition.x, voxelPosition.z);
case 2:
return ivec2(15 - voxelPosition.z, voxelPosition.y);
case 3:
return ivec2(voxelPosition.z, voxelPosition.y);
case 4:
return ivec2(voxelPosition.x, voxelPosition.y);
case 5:
return ivec2(15 - voxelPosition.x, voxelPosition.y);
}
}
float getLod(ivec3 voxelPosition, int normal, vec3 direction, float variance) {
return max(0, min(4, log2(variance*length(direction)/abs(dot(vec3(normals[normal]), direction)))));
}
float perpendicularFwidth(vec3 direction) { // Estimates how big fwidth would be if the fragment normal was perpendicular to the light direction.
vec3 variance = dFdx(direction);
variance += direction;
variance = variance*length(direction)/length(variance);
variance -= direction;
return 16*length(variance);
}
vec4 mipMapSample(sampler2DArray texture, ivec2 textureCoords, uint textureIndex, float lod) { // TODO: anisotropic filtering?
int lowerLod = int(floor(lod));
int higherLod = lowerLod+1;
float interpolation = lod - lowerLod;
vec4 lower = texelFetch(texture, ivec3(textureCoords >> lowerLod, textureIndex), lowerLod);
vec4 higher = texelFetch(texture, ivec3(textureCoords >> higherLod, textureIndex), higherLod);
return higher*interpolation + (1 - interpolation)*lower;
}
float ditherThresholds[16] = float[16] (
1/17.0, 9/17.0, 3/17.0, 11/17.0,
13/17.0, 5/17.0, 15/17.0, 7/17.0,
4/17.0, 12/17.0, 2/17.0, 10/17.0,
16/17.0, 8/17.0, 14/17.0, 6/17.0
);
ivec2 random1to2(int v) {
ivec4 fac = ivec4(11248723, 105436839, 45399083, 5412951);
int seed = v.x*fac.x ^ fac.y;
return seed*fac.zw;
}
uint random3to1u(ivec3 v) {
v &= 15;
ivec4 fac = ivec4(11248723, 105436839, 45399083, 5412951);
int seed = v.x*fac.x ^ v.y*fac.y ^ v.z*fac.z;
return uint(seed)*uint(fac.w);
}
bool passDitherTest(float alpha) {
ivec2 screenPos = ivec2(gl_FragCoord.xy);
screenPos += random1to2(ditherSeed);
screenPos &= 3;
return alpha > ditherThresholds[screenPos.x*4 + screenPos.y];
}
void main() {
RayMarchResult result;
float variance = perpendicularFwidth(direction);
const float threshold = 1;
const float interpolationRegion = 1.25;
float interp = (variance - threshold)/threshold/(interpolationRegion - 1);
if(!passDitherTest(interp)) {
result = rayMarching(startPosition, direction);
} else {
result = RayMarchResult(true, faceNormal, faceNormal, ivec3(startPosition) & 15); // At some point it doesn't make sense to even draw the model.
}
if(!result.hitAThing) discard;
uint textureIndex = textureData[blockType].textureIndices[result.textureDir];
float normalVariation = normalVariations[result.normal];
float lod = getLod(result.voxelPosition, result.normal, direction, variance);
ivec2 textureCoords = getTextureCoords(result.voxelPosition, result.textureDir);
fragColor = mipMapSample(texture_sampler, textureCoords, textureIndex, lod)*vec4(light*normalVariation, 1);
if(!passDitherTest(fragColor.a)) discard;
fragColor.a = 1;
fragColor.rgb += mipMapSample(emissionSampler, textureCoords, textureIndex, lod).rgb;
// TODO: Update the depth.
}

View File

@ -393,7 +393,6 @@ pub const Chunk = struct {
pub const meshing = struct {
var shader: Shader = undefined;
var voxelShader: Shader = undefined;
var transparentShader: Shader = undefined;
const UniformStruct = struct {
projectionMatrix: c_int,
@ -413,7 +412,6 @@ pub const meshing = struct {
zFar: c_int,
};
pub var uniforms: UniformStruct = undefined;
pub var voxelUniforms: UniformStruct = undefined;
pub var transparentUniforms: UniformStruct = undefined;
var vao: c_uint = undefined;
var vbo: c_uint = undefined;
@ -424,7 +422,6 @@ pub const meshing = struct {
pub fn init() !void {
shader = try Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/chunk_fragment.fs", &uniforms);
voxelShader = try Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/voxel_model_fragment.fs", &voxelUniforms);
transparentShader = try Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/transparent_fragment.fs", &transparentUniforms);
var rawData: [6*3 << (3*chunkShift)]u32 = undefined; // 6 vertices per face, maximum 3 faces/block
@ -485,14 +482,6 @@ pub const meshing = struct {
c.glBindVertexArray(vao);
}
pub fn bindVoxelShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f) void {
voxelShader.bind();
bindCommonUniforms(&voxelUniforms, projMatrix, ambient);
c.glBindVertexArray(vao);
}
pub fn bindTransparentShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f) void {
transparentShader.bind();
@ -805,7 +794,6 @@ pub const meshing = struct {
chunk: *Chunk,
lightingData: *[6]lighting.ChannelChunk,
opaqueMesh: PrimitiveMesh,
voxelMesh: PrimitiveMesh,
transparentMesh: PrimitiveMesh,
lastNeighborsSameLod: [6]?*const ChunkMesh = [_]?*const ChunkMesh{null} ** 6,
lastNeighborsHigherLod: [6]?*const ChunkMesh = [_]?*const ChunkMesh{null} ** 6,
@ -831,7 +819,6 @@ pub const meshing = struct {
.pos = pos,
.size = chunkSize*pos.voxelSize,
.opaqueMesh = .{},
.voxelMesh = .{},
.transparentMesh = .{},
.chunk = chunk,
.lightingData = lightingData,
@ -841,7 +828,6 @@ pub const meshing = struct {
pub fn deinit(self: *ChunkMesh) void {
std.debug.assert(self.refCount.load(.Monotonic) == 0);
self.opaqueMesh.deinit();
self.voxelMesh.deinit();
self.transparentMesh.deinit();
main.globalAllocator.free(self.currentSorting);
main.globalAllocator.free(self.sortingOutputBuffer);
@ -872,15 +858,15 @@ pub const meshing = struct {
}
pub fn isEmpty(self: *const ChunkMesh) bool {
return self.opaqueMesh.vertexCount == 0 and self.voxelMesh.vertexCount == 0 and self.transparentMesh.vertexCount == 0;
return self.opaqueMesh.vertexCount == 0 and self.transparentMesh.vertexCount == 0;
}
fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: u3) bool {
const rotatedModel = blocks.meshes.model(block);
const model = &models.voxelModels.items[rotatedModel.modelIndex];
const model = &models.models.items[rotatedModel.modelIndex];
const freestandingModel = rotatedModel.modelIndex != models.fullCube and switch(rotatedModel.permutation.permuteNeighborIndex(neighbor)) {
Neighbors.dirNegX => model.min[0] != 0,
Neighbors.dirPosX => model.max[0] != 16,
Neighbors.dirPosX => model.max[0] != 16, // TODO: Use a bitfield inside the models or something like that.
Neighbors.dirDown => model.min[1] != 0,
Neighbors.dirUp => model.max[1] != 16,
Neighbors.dirNegZ => model.min[2] != 0,
@ -899,7 +885,6 @@ pub const meshing = struct {
renderer.RenderStructure.addMeshToStorage(self);
self.mutex.lock();
self.opaqueMesh.reset();
self.voxelMesh.reset();
self.transparentMesh.reset();
var n: u32 = 0;
var x: u8 = 0;
@ -925,11 +910,7 @@ pub const meshing = struct {
}
try self.transparentMesh.appendCore(constructFaceData(block, i, x2, y2, z2, false));
} else {
if(blocks.meshes.model(block).modelIndex == 0) {
try self.opaqueMesh.appendCore(constructFaceData(block, i, x2, y2, z2, false));
} else {
try self.voxelMesh.appendCore(constructFaceData(block, i, x2, y2, z2, false));
}
try self.opaqueMesh.appendCore(constructFaceData(block, i, x2, y2, z2, false)); // TODO: Create multiple faces for non-cube meshes.
}
}
}
@ -957,11 +938,7 @@ pub const meshing = struct {
if(transparent) {
try self.transparentMesh.addFace(faceData, fromNeighborChunk);
} else {
if(faceData.blockAndModel.modelIndex == 0) {
try self.opaqueMesh.addFace(faceData, fromNeighborChunk);
} else {
try self.voxelMesh.addFace(faceData, fromNeighborChunk);
}
try self.opaqueMesh.addFace(faceData, fromNeighborChunk);
}
}
@ -969,11 +946,7 @@ pub const meshing = struct {
if(transparent) {
self.transparentMesh.removeFace(faceData, fromNeighborChunk);
} else {
if(faceData.blockAndModel.modelIndex == 0) {
self.opaqueMesh.removeFace(faceData, fromNeighborChunk);
} else {
self.voxelMesh.removeFace(faceData, fromNeighborChunk);
}
self.opaqueMesh.removeFace(faceData, fromNeighborChunk);
}
}
@ -1095,20 +1068,17 @@ pub const meshing = struct {
fn clearNeighbor(self: *ChunkMesh, neighbor: u3, comptime isLod: bool) void {
self.opaqueMesh.clearNeighbor(neighbor, isLod);
self.voxelMesh.clearNeighbor(neighbor, isLod);
self.transparentMesh.clearNeighbor(neighbor, isLod);
}
fn finishData(self: *ChunkMesh) !void {
std.debug.assert(!self.mutex.tryLock());
try self.opaqueMesh.finish(self);
try self.voxelMesh.finish(self);
try self.transparentMesh.finish(self);
}
pub fn uploadData(self: *ChunkMesh) !void {
try self.opaqueMesh.uploadData(self.isNeighborLod);
try self.voxelMesh.uploadData(self.isNeighborLod);
try self.transparentMesh.uploadData(self.isNeighborLod);
}
@ -1177,11 +1147,7 @@ pub const meshing = struct {
}
try neighborMesh.transparentMesh.appendNeighbor(constructFaceData(block, neighbor, otherX, otherY, otherZ, false), neighbor ^ 1, false);
} else {
if(blocks.meshes.model(block).modelIndex == 0) {
try neighborMesh.opaqueMesh.appendNeighbor(constructFaceData(block, neighbor, otherX, otherY, otherZ, false), neighbor ^ 1, false);
} else {
try neighborMesh.voxelMesh.appendNeighbor(constructFaceData(block, neighbor, otherX, otherY, otherZ, false), neighbor ^ 1, false);
}
try neighborMesh.opaqueMesh.appendNeighbor(constructFaceData(block, neighbor, otherX, otherY, otherZ, false), neighbor ^ 1, false);
}
}
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1)) {
@ -1191,11 +1157,7 @@ pub const meshing = struct {
}
try self.transparentMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, false);
} else {
if(blocks.meshes.model(otherBlock).modelIndex == 0) {
try self.opaqueMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, false);
} else {
try self.voxelMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, false);
}
try self.opaqueMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, false);
}
}
}
@ -1266,11 +1228,7 @@ pub const meshing = struct {
if(otherBlock.transparent()) {
try self.transparentMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, true);
} else {
if(blocks.meshes.model(otherBlock).modelIndex == 0) {
try self.opaqueMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, true);
} else {
try self.voxelMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, true);
}
try self.opaqueMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, true);
}
}
if(block.hasBackFace()) {
@ -1300,20 +1258,6 @@ pub const meshing = struct {
c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.opaqueMesh.vertexCount, c.GL_UNSIGNED_INT, null, self.opaqueMesh.bufferAllocation.start*4);
}
pub fn renderVoxelModels(self: *ChunkMesh, playerPosition: Vec3d) void {
if(self.voxelMesh.vertexCount == 0) return;
c.glUniform3f(
voxelUniforms.modelPosition,
@floatCast(@as(f64, @floatFromInt(self.pos.wx)) - playerPosition[0]),
@floatCast(@as(f64, @floatFromInt(self.pos.wy)) - playerPosition[1]),
@floatCast(@as(f64, @floatFromInt(self.pos.wz)) - playerPosition[2])
);
c.glUniform1i(voxelUniforms.visibilityMask, self.visibilityMask);
c.glUniform1i(voxelUniforms.voxelSize, self.pos.voxelSize);
quadsDrawn += self.voxelMesh.vertexCount/6;
c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.voxelMesh.vertexCount, c.GL_UNSIGNED_INT, null, self.voxelMesh.bufferAllocation.start*4);
}
pub fn renderTransparent(self: *ChunkMesh, playerPosition: Vec3d) !void {
if(self.transparentMesh.vertexCount == 0) return;

View File

@ -1856,7 +1856,7 @@ pub fn generateBlockTexture(blockType: u16) !Texture {
if(block.mode().model(block).modelIndex == 0) {
main.chunk.meshing.bindShaderAndUniforms(projMatrix, .{1, 1, 1});
} else {
main.chunk.meshing.bindVoxelShaderAndUniforms(projMatrix, .{1, 1, 1});
std.log.err("TODO: Item textures for non-cube models.", .{});
}
}
const uniforms = if(block.transparent()) &main.chunk.meshing.transparentUniforms else &main.chunk.meshing.uniforms;

View File

@ -17,7 +17,6 @@ pub const Samples = enum(u8) {
clear,
animation,
chunk_rendering,
voxel_model_rendering,
entity_rendering,
transparent_rendering,
bloom_extract_downsample,
@ -32,7 +31,6 @@ const names = [_][]const u8 {
"Clear",
"Pre-processing Block Animations",
"Chunk Rendering",
"Voxel Model Rendering",
"Entity Rendering",
"Transparent Rendering",
"Bloom - Extract color and downsample",

View File

@ -7,178 +7,14 @@ const main = @import("main.zig");
const vec = @import("vec.zig");
const Vec3i = vec.Vec3i;
var voxelModelSSBO: graphics.SSBO = undefined;
var modelSSBO: graphics.SSBO = undefined;
pub const modelShift: u4 = 4;
pub const modelSize: u16 = @as(u16, 1) << modelShift;
pub const modelMask: u16 = modelSize - 1;
const VoxelModel = extern struct {
min: Vec3i,
const Model = extern struct {
min: Vec3i, // TODO: Should contain a list of quads instead, with vertex positions.
max: Vec3i,
bitPackedData: [modelSize*modelSize*modelSize/32]u32,
bitPackedTexture: [modelSize*modelSize*modelSize/8]u32,
pub fn init(self: *VoxelModel, distributionFunction: *const fn(u4, u4, u4) ?u4) void {
if(@sizeOf(VoxelModel) != 16 + 16 + modelSize*modelSize*modelSize*4/32 + modelSize*modelSize*modelSize*4/8) @compileError("Expected Vec3i to have 16 byte alignment.");
@memset(&self.bitPackedData, 0);
@memset(&self.bitPackedTexture, 0);
self.min = @splat(16);
self.max = @splat(0);
for(0..modelSize) |_x| {
const x: u4 = @intCast(_x);
for(0..modelSize) |_y| {
const y: u4 = @intCast(_y);
for(0..modelSize) |_z| {
const z: u4 = @intCast(_z);
const isSolid = distributionFunction(x, y, z);
const voxelIndex = (_x << 2*modelShift) + (_y << modelShift) + _z;
const shift = @as(u5, @intCast(voxelIndex & 31));
const arrayIndex = voxelIndex >> 5;
const shiftTexture = 4*@as(u5, @intCast(voxelIndex & 7));
const arrayIndexTexture = voxelIndex >> 3;
if(isSolid) |texture| {
std.debug.assert(texture <= 6);
self.min = @min(self.min, Vec3i{x, y, z});
self.max = @max(self.max, Vec3i{x, y, z});
self.bitPackedData[arrayIndex] &= ~(@as(u32, 1) << shift);
self.bitPackedTexture[arrayIndexTexture] |= @as(u32, texture) << shiftTexture;
} else {
self.bitPackedData[arrayIndex] |= @as(u32, 1) << shift;
}
}
}
}
self.max += @splat(1);
}
// TODO
};
fn cube(_: u4, _: u4, _: u4) ?u4 {
return 6;
}
const Fence = struct {
fn fence0(_x: u4, _y: u4, _z: u4) ?u4 {
const x = @max(@as(i32, _x)-8, -@as(i32, _x)+7);
const y = @max(@as(i32, _y)-8, -@as(i32, _y)+7);
const z = @max(@as(i32, _z)-8, -@as(i32, _z)+7);
_ = y;
if(x < 2 and z < 2) return 6;
return null;
}
fn fence1(_x: u4, _y: u4, _z: u4) ?u4 {
const x = @max(@as(i32, _x)-8, -@as(i32, _x)+7);
const y = @max(@as(i32, _y)-8, -@as(i32, _y)+7);
const z = @max(@as(i32, _z)-8, -@as(i32, _z)+7);
if(x < 2 and z < 2) return 6;
if(y < 5 and y >= 2) {
if(z == 0 and _x < 8) return 6;
}
return null;
}
fn fence2_neighbor(_x: u4, _y: u4, _z: u4) ?u4 {
const x = @max(@as(i32, _x)-8, -@as(i32, _x)+7);
const y = @max(@as(i32, _y)-8, -@as(i32, _y)+7);
const z = @max(@as(i32, _z)-8, -@as(i32, _z)+7);
if(x < 2 and z < 2) return 6;
if(y < 5 and y >= 2) {
if(z == 0 and _x < 8) return 6;
if(_z < 8 and x == 0) return 6;
}
return null;
}
fn fence2_oppose(_x: u4, _y: u4, _z: u4) ?u4 {
const x = @max(@as(i32, _x)-8, -@as(i32, _x)+7);
const y = @max(@as(i32, _y)-8, -@as(i32, _y)+7);
const z = @max(@as(i32, _z)-8, -@as(i32, _z)+7);
if(x < 2 and z < 2) return 6;
if(y < 5 and y >= 2) {
if(z == 0) return 6;
}
return null;
}
fn fence3(_x: u4, _y: u4, _z: u4) ?u4 {
const x = @max(@as(i32, _x)-8, -@as(i32, _x)+7);
const y = @max(@as(i32, _y)-8, -@as(i32, _y)+7);
const z = @max(@as(i32, _z)-8, -@as(i32, _z)+7);
if(x < 2 and z < 2) return 6;
if(y < 5 and y >= 2) {
if(z == 0 and _x >= 8) return 6;
if(x == 0) return 6;
}
return null;
}
fn fence4(_x: u4, _y: u4, _z: u4) ?u4 {
const x = @max(@as(i32, _x)-8, -@as(i32, _x)+7);
const y = @max(@as(i32, _y)-8, -@as(i32, _y)+7);
const z = @max(@as(i32, _z)-8, -@as(i32, _z)+7);
if(x < 2 and z < 2) return 6;
if(y < 5 and y >= 2) {
if(x == 0 or z == 0) return 6;
}
return null;
}
};
fn log(_x: u4, _y: u4, _z: u4) ?u4 {
const x = @as(f32, @floatFromInt(_x)) - 7.5;
const y = @as(f32, @floatFromInt(_y)) - 7.5;
const z = @as(f32, @floatFromInt(_z)) - 7.5;
if(x*x + z*z < 7.2*7.2) {
if(y > 0) return Neighbors.dirUp;
return Neighbors.dirDown;
}
if(x*x + z*z < 8.0*8.0) {
if(@abs(x) > @abs(z)) {
if(x < 0) return Neighbors.dirNegX;
return Neighbors.dirPosX;
} else {
if(z < 0) return Neighbors.dirNegZ;
return Neighbors.dirPosZ;
}
}
return null;
}
fn sphere(_x: u4, _y: u4, _z: u4) ?u4 {
const x = @as(f32, @floatFromInt(_x)) - 7.5;
const y = @as(f32, @floatFromInt(_y)) - 7.5;
const z = @as(f32, @floatFromInt(_z)) - 7.5;
if(x*x + y*y + z*z < 8.0*8.0) {
return 6;
}
return null;
}
fn grass(x: u4, y: u4, z: u4) ?u4 {
var seed = main.random.initSeed2D(542642, .{x, z});
var val = main.random.nextFloat(&seed);
val *= val*16;
if(val > @as(f32, @floatFromInt(y))) {
return 6;
}
return null;
}
fn octahedron(_x: u4, _y: u4, _z: u4) ?u4 {
var x = _x;
var y = _y;
var z = _z;
if((x == 0 or x == 15) and (y == 0 or y == 15)) return 6;
if((x == 0 or x == 15) and (z == 0 or z == 15)) return 6;
if((z == 0 or z == 15) and (y == 0 or y == 15)) return 6;
x = @min(x, 15 - x);
y = @min(y, 15 - y);
z = @min(z, 15 - z);
if(x + y + z > 16) return 6;
return null;
}
var nameToIndex: std.StringHashMap(u16) = undefined;
pub fn getModelIndex(string: []const u8) u16 {
@ -188,46 +24,26 @@ pub fn getModelIndex(string: []const u8) u16 {
};
}
pub var voxelModels: std.ArrayList(VoxelModel) = undefined;
pub var models: std.ArrayList(Model) = undefined;
pub var fullCube: u16 = 0;
// TODO: Allow loading from world assets.
// TODO: Editable player models.
// TODO: Entity models.
pub fn init() !void {
voxelModels = std.ArrayList(VoxelModel).init(main.globalAllocator);
models = std.ArrayList(Model).init(main.globalAllocator);
nameToIndex = std.StringHashMap(u16).init(main.globalAllocator);
try nameToIndex.put("cube", @intCast(voxelModels.items.len));
fullCube = @intCast(voxelModels.items.len);
(try voxelModels.addOne()).init(cube);
try nameToIndex.put("cube", @intCast(models.items.len));
fullCube = @intCast(models.items.len);
try models.append(.{.min = .{0, 0, 0}, .max = .{16, 16, 16}});
try nameToIndex.put("log", @intCast(voxelModels.items.len));
(try voxelModels.addOne()).init(log);
try nameToIndex.put("fence", @intCast(voxelModels.items.len));
(try voxelModels.addOne()).init(Fence.fence0);
(try voxelModels.addOne()).init(Fence.fence1);
(try voxelModels.addOne()).init(Fence.fence2_neighbor);
(try voxelModels.addOne()).init(Fence.fence2_oppose);
(try voxelModels.addOne()).init(Fence.fence3);
(try voxelModels.addOne()).init(Fence.fence4);
try nameToIndex.put("sphere", @intCast(voxelModels.items.len));
(try voxelModels.addOne()).init(sphere);
try nameToIndex.put("grass", @intCast(voxelModels.items.len));
(try voxelModels.addOne()).init(grass);
try nameToIndex.put("octahedron", @intCast(voxelModels.items.len));
(try voxelModels.addOne()).init(octahedron);
voxelModelSSBO = graphics.SSBO.initStatic(VoxelModel, voxelModels.items);
voxelModelSSBO.bind(4);
modelSSBO = graphics.SSBO.initStatic(Model, models.items);
modelSSBO.bind(4);
}
pub fn deinit() void {
voxelModelSSBO.deinit();
modelSSBO.deinit();
nameToIndex.deinit();
voxelModels.deinit();
models.deinit();
}

View File

@ -215,14 +215,6 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
}
gpu_performance_measuring.stopQuery();
gpu_performance_measuring.startQuery(.voxel_model_rendering);
chunk.meshing.bindVoxelShaderAndUniforms(game.projectionMatrix, ambientLight);
for(meshes) |mesh| {
mesh.renderVoxelModels(playerPos);
}
gpu_performance_measuring.stopQuery();
// for(int i = 0; i < visibleReduced.size; i++) {
// ReducedChunkMesh mesh = visibleReduced.array[i];
// mesh.render(playerPosition);
@ -729,9 +721,9 @@ pub const MeshSelection = struct {
if(block.typ != 0) {
// Check the true bounding box (using this algorithm here: https://tavianator.com/2011/ray_box.html):
const model = blocks.meshes.model(block);
const voxelModel = &models.voxelModels.items[model.modelIndex];
const transformedMin = model.permutation.transform(voxelModel.min - @as(Vec3i, @splat(8))) + @as(Vec3i, @splat(8));
const transformedMax = model.permutation.transform(voxelModel.max - @as(Vec3i, @splat(8))) + @as(Vec3i, @splat(8));
const modelData = &models.models.items[model.modelIndex];
const transformedMin = model.permutation.transform(modelData.min - @as(Vec3i, @splat(8))) + @as(Vec3i, @splat(8));
const transformedMax = model.permutation.transform(modelData.max - @as(Vec3i, @splat(8))) + @as(Vec3i, @splat(8));
const min: Vec3d = @floatFromInt(@min(transformedMin, transformedMax));
const max: Vec3d = @floatFromInt(@max(transformedMin ,transformedMax));
const voxelPosFloat: Vec3d = @floatFromInt(voxelPos);
@ -858,9 +850,9 @@ pub const MeshSelection = struct {
c.glPolygonOffset(-2, 0);
const block = RenderStructure.getBlockFromRenderThread(_selectedBlockPos[0], _selectedBlockPos[1], _selectedBlockPos[2]) orelse return;
const model = blocks.meshes.model(block);
const voxelModel = &models.voxelModels.items[model.modelIndex];
const transformedMin = model.permutation.transform(voxelModel.min - @as(Vec3i, @splat(8))) + @as(Vec3i, @splat(8));
const transformedMax = model.permutation.transform(voxelModel.max - @as(Vec3i, @splat(8))) + @as(Vec3i, @splat(8));
const modelData = &models.models.items[model.modelIndex];
const transformedMin = model.permutation.transform(modelData.min - @as(Vec3i, @splat(8))) + @as(Vec3i, @splat(8));
const transformedMax = model.permutation.transform(modelData.max - @as(Vec3i, @splat(8))) + @as(Vec3i, @splat(8));
const min: Vec3f = @floatFromInt(@min(transformedMin, transformedMax));
const max: Vec3f = @floatFromInt(@max(transformedMin ,transformedMax));
drawCube(projectionMatrix, viewMatrix, @as(Vec3d, @floatFromInt(_selectedBlockPos)) - playerPos, min/@as(Vec3f, @splat(16.0)), max/@as(Vec3f, @splat(16.0)));
@ -1523,7 +1515,7 @@ pub const RenderStructure = struct {
}
mutex.unlock();
// Remove empty meshes.
if(mesh.opaqueMesh.vertexCount != 0 or mesh.voxelMesh.vertexCount != 0 or mesh.transparentMesh.vertexCount != 0) {
if(!mesh.isEmpty()) {
try meshList.append(mesh);
}
}