mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 19:28:49 -04:00
Goodbye voxel models 👋
Fixes #215 Closes #164 Closes #162 Closes #160 Closes #150 Closes #145
This commit is contained in:
parent
2a46592454
commit
7d23411a22
@ -4,6 +4,6 @@
|
||||
"drops" : [
|
||||
"auto"
|
||||
],
|
||||
"model" : "log",
|
||||
"model" : "cube",
|
||||
"texture" : "cubyz:cactus"
|
||||
}
|
||||
|
@ -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 |
@ -8,6 +8,6 @@
|
||||
"emittedLight" : 0x00a09080,
|
||||
"viewThrough" : true,
|
||||
"rotation" : "torch",
|
||||
"model" : "cubyz:torch.obj",
|
||||
"model" : "torch",
|
||||
"texture" : "cubyz:torch"
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
212
src/models.zig
212
src/models.zig
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user