diff --git a/assets/cubyz/shaders/chunks/chunk_fragment.fs b/assets/cubyz/shaders/chunks/chunk_fragment.fs index 3f9d076e..2993ef1a 100644 --- a/assets/cubyz/shaders/chunks/chunk_fragment.fs +++ b/assets/cubyz/shaders/chunks/chunk_fragment.fs @@ -222,4 +222,5 @@ void main() { } fragColor.rgb /= 4; position = vec4(mvVertexPos, 1); + // TODO: Update the depth. } diff --git a/assets/cubyz/shaders/item_drop.fs b/assets/cubyz/shaders/item_drop.fs index dd4852ef..ddb48e87 100644 --- a/assets/cubyz/shaders/item_drop.fs +++ b/assets/cubyz/shaders/item_drop.fs @@ -4,8 +4,10 @@ in vec3 startPosition; in vec3 direction; in vec3 cameraSpacePos; flat in int faceNormal; -flat in uint voxelModel; -flat in uvec3 size; +flat in int voxelModel; +flat in int blockType; +flat in uvec3 lower; +flat in uvec3 upper; layout(location = 0) out vec4 fragColor; layout(location = 1) out vec4 position; @@ -19,14 +21,10 @@ struct Fog { uniform vec3 ambientLight; uniform mat4 projectionMatrix; uniform float sizeScale; +uniform int time; uniform Fog fog; -layout(std430, binding = 2) buffer _voxelModels -{ - uint voxelModels[]; -}; - const float[6] normalVariations = float[6]( 1.0, //vec3(0, 1, 0), 0.80, //vec3(0, -1, 0), @@ -36,11 +34,6 @@ const float[6] normalVariations = float[6]( 0.85 //vec3(0, 0, -1) ); -uint getVoxel(uvec3 pos) { - uint index = (pos.x | pos.y*size.x)*size.z | pos.z; - return voxelModels[voxelModel + index]; -} - vec4 calcFog(vec3 pos, vec4 color, Fog fog) { float distance = length(pos); float fogFactor = 1.0/exp((distance*fog.density)*(distance*fog.density)); @@ -49,11 +42,207 @@ vec4 calcFog(vec3 pos, vec4 color, Fog fog) { return vec4(resultColor.xyz, color.w + 1 - fogFactor); } +// blockDrops ------------------------------------------------------------------------------------------------------------------------ + +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) +); + +#define modelSize 16 +struct VoxelModel { + ivec4 minimum; + ivec4 maximum; + uint bitPackedData[modelSize*modelSize*modelSize/8]; +}; + +struct AnimationData { + int frames; + int time; +}; + +layout(std430, binding = 0) buffer _animation +{ + AnimationData animation[]; +}; +layout(std430, binding = 1) buffer _textureIndices +{ + int textureIndices[][6]; +}; +layout(std430, binding = 4) buffer _blockVoxelModels +{ + VoxelModel blockVoxelModels[]; +}; +uniform sampler2DArray texture_sampler; +uniform sampler2DArray emissionSampler; + +int getVoxel(int voxelIndex) { + voxelIndex = (voxelIndex & 0xf) | (voxelIndex>>1 & 0xf0) | (voxelIndex>>2 & 0xf00); + int shift = 4*(voxelIndex & 7); + int arrayIndex = voxelIndex >> 3; + return (int(blockVoxelModels[voxelModel].bitPackedData[arrayIndex])>>shift & 15) - 6; +} + +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 + vec3 step = sign(direction); + vec3 stepInIndex = step*vec3(1 << 10, 1 << 5, 1); + int overflowMask = 1<<14 | 1<<9 | 1<<4; + vec3 t1 = (floor(startPosition) - startPosition)/direction; + vec3 tDelta = 1/(direction); + vec3 t2 = t1 + tDelta; + tDelta = abs(tDelta); + vec3 tMax = max(t1, t2) - tDelta; + if(direction.x == 0) tMax.x = 1.0/0.0; + if(direction.y == 0) tMax.y = 1.0/0.0; + if(direction.z == 0) tMax.z = 1.0/0.0; + + ivec3 voxelPos = ivec3(floor(startPosition)); + int voxelIndex = voxelPos.x<<10 | voxelPos.y<<5 | voxelPos.z; // Stores the position as 0b0xxxx0yyyy0zzzz + + int lastNormal = faceNormal; + int block = getVoxel(voxelIndex); + float total_tMax = 0; + + int size = 16; + ivec3 sizeMask = ivec3(size - 1); + int it = 0; + while(block > 0 && it < 48) { + it++; + vec3 tNext = tMax + block*tDelta; + total_tMax = min(tNext.x, min(tNext.y, tNext.z)); + vec3 missingSteps = floor((total_tMax - tMax)/tDelta + 0.00001); + voxelIndex += int(dot(missingSteps, stepInIndex)); + tMax += missingSteps*tDelta; + if((voxelIndex & overflowMask) != 0) + return RayMarchResult(false, 0, 0, ivec3(0, 0, 0)); + block = getVoxel(voxelIndex); + } + if(total_tMax != 0) { + if(tMax.x > tMax.y) { + if(tMax.x > tMax.z) { + lastNormal = 2 + (1 + int(step.x))/2; + } else { + lastNormal = 4 + (1 + int(step.z))/2; + } + } else { + if(tMax.y > tMax.z) { + lastNormal = 0 + (1 + int(step.y))/2; + } else { + lastNormal = 4 + (1 + int(step.z))/2; + } + } + } + voxelPos.x = voxelIndex>>10 & 15; + voxelPos.y = voxelIndex>>5 & 15; + voxelPos.z = voxelIndex & 15; + int textureDir = -block; + if(textureDir == 6) textureDir = lastNormal; + vec3 modifiedCameraSpacePos = cameraSpacePos*(1 + total_tMax*sizeScale*length(direction)/length(cameraSpacePos)); + vec4 projection = projectionMatrix*vec4(modifiedCameraSpacePos, 1); + float depth = projection.z/projection.w; + gl_FragDepth = ((gl_DepthRange.diff * depth) + gl_DepthRange.near + gl_DepthRange.far)/2.0; + return RayMarchResult(true, lastNormal, textureDir, voxelPos); +} + +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 varianceX = dFdx(direction); + vec3 varianceY = dFdx(direction); + varianceX += direction; + varianceX = varianceX*length(direction)/length(varianceX); + varianceX -= direction; + varianceY += direction; + varianceY = varianceY*length(direction)/length(varianceY); + varianceY -= direction; + vec3 variance = abs(varianceX) + abs(varianceY); + return 8*length(variance); +} + +vec4 mipMapSample(sampler2DArray texture, ivec2 textureCoords, int 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; +} + +void mainBlockDrop() { + RayMarchResult result; + float variance = perpendicularFwidth(direction); + if(variance <= 4.0) { + result = rayMarching(startPosition, direction); + } else { + result = RayMarchResult(true, faceNormal, faceNormal, ivec3(startPosition)); // At some point it doesn't make sense to even draw the model. + } + if(!result.hitAThing) discard; + int textureIndex = textureIndices[blockType][result.textureDir]; + textureIndex = textureIndex + time / animation[textureIndex].time % animation[textureIndex].frames; + 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(ambientLight*normalVariation, 1); + + if (fragColor.a <= 0.1f) fragColor.a = 1; // TODO: Proper alpha handling. + + fragColor.rgb += mipMapSample(emissionSampler, textureCoords, textureIndex, lod).rgb; + + if (fog.activ) { + fragColor = calcFog(startPosition, fragColor, fog); + } + fragColor.rgb /= 4; + position = vec4(startPosition, 1); +} + +// itemDrops ------------------------------------------------------------------------------------------------------------------------- + +layout(std430, binding = 2) buffer _itemVoxelModels +{ + uint itemVoxelModels[]; +}; + +uint getVoxel(uvec3 pos) { + uint index = (pos.x | pos.y*upper.x)*upper.z | pos.z; + return itemVoxelModels[voxelModel + index]; +} + vec4 decodeColor(uint block) { return vec4(block >> 16 & uint(255), block >> 8 & uint(255), block & uint(255), block >> 24 & uint(255))/255.0; } -void main() { +void mainItemDrop() { // Implementation of "A Fast Voxel Traversal Algorithm for Ray Tracing" http://www.cse.yorku.ca/~amana/research/grid.pdf ivec3 step = ivec3(sign(direction)); vec3 t1 = (floor(startPosition) - startPosition)/direction; @@ -70,7 +259,7 @@ void main() { uint block = getVoxel(voxelPosition); float total_tMax = 0; - uvec3 sizeMask = size - 1; + uvec3 sizeMask = upper - 1; while(block == 0) { if(tMax.x < tMax.y) { @@ -127,3 +316,11 @@ void main() { fragColor.rgb /= 4; position = vec4(modifiedCameraSpacePos, 1); } + +void main() { + if(blockType != 0) { + mainBlockDrop(); + } else { + mainItemDrop(); + } +} diff --git a/assets/cubyz/shaders/item_drop.vs b/assets/cubyz/shaders/item_drop.vs index c74d166f..d3d9fbe3 100644 --- a/assets/cubyz/shaders/item_drop.vs +++ b/assets/cubyz/shaders/item_drop.vs @@ -6,36 +6,59 @@ out vec3 startPosition; out vec3 direction; out vec3 cameraSpacePos; flat out int faceNormal; -flat out uint voxelModel; -flat out uvec3 size; +flat out int voxelModel; +flat out int blockType; +flat out uvec3 lower; +flat out uvec3 upper; uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform mat4 modelMatrix; uniform int modelIndex; +uniform int block; uniform float sizeScale; -layout(std430, binding = 2) buffer _voxelModels +layout(std430, binding = 2) buffer _itemVoxelModels { - uint voxelModels[]; + uint itemVoxelModels[]; +}; + +#define modelSize 16 +struct VoxelModel { + ivec4 minimum; + ivec4 maximum; + uint bitPackedData[modelSize*modelSize*modelSize/8]; +}; + +layout(std430, binding = 4) buffer _blockVoxelModels +{ + VoxelModel blockVoxelModels[]; }; void main() { - int x = positionAndNormals >> 2 & 1; - int y = positionAndNormals >> 1 & 1; - int z = positionAndNormals >> 0 & 1; + ivec3 pos = ivec3 ( + positionAndNormals >> 2 & 1, + positionAndNormals >> 1 & 1, + positionAndNormals >> 0 & 1 + ); faceNormal = positionAndNormals >> 3; - uint voxelModelIndex = modelIndex; - size.x = voxelModels[voxelModelIndex++]; - size.y = voxelModels[voxelModelIndex++]; - size.z = voxelModels[voxelModelIndex++]; + int voxelModelIndex = modelIndex; + bool isBlock = block != 0; + if(isBlock) { + lower = uvec3(blockVoxelModels[voxelModelIndex].minimum.xyz); + upper = uvec3(blockVoxelModels[voxelModelIndex].maximum.xyz); + } else { + upper.x = itemVoxelModels[voxelModelIndex++]; + upper.y = itemVoxelModels[voxelModelIndex++]; + upper.z = itemVoxelModels[voxelModelIndex++]; + lower = uvec3(0); + } voxelModel = voxelModelIndex; + blockType = block; - startPosition.x = float(size.x)*0.999*x; - startPosition.y = float(size.y)*0.999*y; - startPosition.z = float(size.z)*0.999*z; + startPosition = lower + vec3(upper)*0.999*pos; - vec4 worldSpace = modelMatrix*vec4(vec3(x*size.x, y*size.y, z*size.z)*sizeScale + sizeScale/2, 1); + vec4 worldSpace = modelMatrix*vec4(pos*(upper - lower)*sizeScale + sizeScale/2, 1); direction = (transpose(mat3(modelMatrix))*worldSpace.xyz).xyz; vec4 cameraSpace = viewMatrix*worldSpace; diff --git a/src/itemdrop.zig b/src/itemdrop.zig index 779e9d8e..8ffb7421 100644 --- a/src/itemdrop.zig +++ b/src/itemdrop.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const blocks = @import("blocks.zig"); const chunk_zig = @import("chunk.zig"); const Chunk = chunk_zig.Chunk; const game = @import("game.zig"); @@ -560,7 +561,11 @@ pub const ItemDropRenderer = struct { @"fog.color": c_int, @"fog.density": c_int, modelIndex: c_int, + block: c_int, sizeScale: c_int, + time: c_int, + texture_sampler: c_int, + emissionSampler: c_int, } = undefined; var itemModelSSBO: graphics.SSBO = undefined; @@ -738,8 +743,11 @@ pub const ItemDropRenderer = struct { return (try voxelModels.findOrCreate(compareObject, ItemVoxelModel.init)).index; } - pub fn renderItemDrops(projMatrix: Mat4f, ambientLight: Vec3f, playerPos: Vec3d) !void { + pub fn renderItemDrops(projMatrix: Mat4f, ambientLight: Vec3f, playerPos: Vec3d, time: u32) !void { itemShader.bind(); + c.glUniform1i(itemUniforms.texture_sampler, 0); + c.glUniform1i(itemUniforms.emissionSampler, 1); + c.glUniform1i(itemUniforms.time, @truncate(u31, time)); c.glUniform1i(itemUniforms.@"fog.activ", if(game.fog.active) 1 else 0); c.glUniform3fv(itemUniforms.@"fog.color", 1, @ptrCast([*c]const f32, &game.fog.color)); c.glUniform1f(itemUniforms.@"fog.density", game.fog.density); @@ -769,9 +777,17 @@ pub const ItemDropRenderer = struct { modelMatrix = modelMatrix.mul(Mat4f.rotationY(-rot[1])); modelMatrix = modelMatrix.mul(Mat4f.rotationZ(-rot[2])); c.glUniformMatrix4fv(itemUniforms.modelMatrix, 1, c.GL_FALSE, @ptrCast([*c]const f32, &modelMatrix)); - const index = try getModelIndex(item); - c.glUniform1i(itemUniforms.modelIndex, index); + if(item == .baseItem and item.baseItem.block != null) { + const blockType = item.baseItem.block.?; + const block = blocks.Block{.typ = blockType, .data = 0}; + c.glUniform1i(itemUniforms.modelIndex, block.mode().model(block).modelIndex); + c.glUniform1i(itemUniforms.block, blockType); + } else { + const index = try getModelIndex(item); + c.glUniform1i(itemUniforms.modelIndex, index); + c.glUniform1i(itemUniforms.block, 0); + } c.glBindVertexArray(itemVAO); c.glDrawElements(c.GL_TRIANGLES, 36, c.GL_UNSIGNED_INT, null); } diff --git a/src/renderer.zig b/src/renderer.zig index e1042971..844e264e 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -267,7 +267,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo entity.ClientEntityManager.render(game.projectionMatrix, ambientLight, .{1, 0.5, 0.25}, playerPos); - try itemdrop.ItemDropRenderer.renderItemDrops(game.projectionMatrix, ambientLight, playerPos); + try itemdrop.ItemDropRenderer.renderItemDrops(game.projectionMatrix, ambientLight, playerPos, time); // // Render transparent chunk meshes: // NormalChunkMesh.bindTransparentShader(ambientLight, directionalLight.getDirection(), time);