Fake reflections and transparency (both absorptive and regular transparency are possible)

This commit is contained in:
IntegratedQuantum 2023-07-16 14:09:18 +02:00
parent 7cb347ce3c
commit 5ca4ab6a9d
12 changed files with 494 additions and 357 deletions

View File

@ -1,48 +0,0 @@
#version 430
in vec2 outTexCoord;
flat in float textureIndex;
in vec3 outColor;
in vec3 mvVertexPos;
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec4 position;
struct Fog {
bool activ;
vec3 color;
float density;
};
uniform sampler2DArray texture_sampler;
uniform sampler2DArray emissionSampler;
uniform Fog fog;
uniform int selectedIndex;
vec4 ambientC;
void setupColors(vec3 textCoord) {
ambientC = texture(texture_sampler, textCoord);
}
vec4 calcFog(vec3 pos, vec4 color, Fog fog) {
float distance = length(pos);
float fogFactor = 1.0/exp((distance*fog.density)*(distance*fog.density));
fogFactor = clamp(fogFactor, 0.0, 1.0);
vec3 resultColor = mix(fog.color, color.xyz, fogFactor);
return vec4(resultColor.xyz, color.w + 1 - fogFactor);
}
void main() {
setupColors(vec3(outTexCoord, textureIndex));
if (ambientC.a != 1) discard;
fragColor = ambientC*vec4(outColor, 1);
fragColor.rgb += texture(emissionSampler, vec3(outTexCoord, textureIndex)).rgb;
if (fog.activ) {
fragColor = calcFog(mvVertexPos, fragColor, fog);
}
fragColor.rgb /= 4;
position = vec4(mvVertexPos, 1);
}

View File

@ -3,11 +3,8 @@
in vec3 mvVertexPos;
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec4 position;
void main() {
fragColor = vec4(0, 0, 0, 1);
fragColor.rgb /= 4;
position = vec4(mvVertexPos, 1);
}

View File

@ -1,49 +0,0 @@
#version 430
layout (location=0) in vec3 position;
layout (location=1) in vec3 texCoord;
layout (location=2) in vec3 vertexNormal;
layout (location=3) in int easyLight;
out vec2 outTexCoord;
flat out float textureIndex;
out vec3 mvVertexPos;
out vec3 outColor;
uniform mat4 projectionMatrix;
uniform vec3 ambientLight;
uniform vec3 directionalLight;
uniform mat4 viewMatrix;
uniform vec3 modelPosition;
layout(std430, binding = 0) buffer _animationTimes
{
int animationTimes[];
};
layout(std430, binding = 1) buffer _animationFrames
{
int animationFrames[];
};
uniform int time;
vec3 calcLight(int srgb) {
float s = (srgb >> 24) & 255;
float r = (srgb >> 16) & 255;
float g = (srgb >> 8) & 255;
float b = (srgb >> 0) & 255;
s = s*(1 - dot(directionalLight, vertexNormal));
r = max(s*ambientLight.x, r);
g = max(s*ambientLight.y, g);
b = max(s*ambientLight.z, b);
return vec3(r, g, b);
}
void main() {
outColor = calcLight(easyLight)*0.003890625;
vec4 mvPos = viewMatrix*vec4(position + modelPosition, 1);
gl_Position = projectionMatrix*mvPos;
outTexCoord = texCoord.xy;
textureIndex = texCoord.z + time / animationTimes[int(texCoord.z)] % animationFrames[int(texCoord.z)];
mvVertexPos = mvPos.xyz;
}

View File

@ -14,7 +14,6 @@ uniform sampler2DArray texture_sampler;
uniform sampler2DArray emissionSampler;
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec4 position;
struct Fog {
bool activ;
@ -205,13 +204,8 @@ void main() {
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.
if (fragColor.a < 1) discard;
if (fog.activ) {
// Underwater fog in lod(assumes that the fog is maximal):
fragColor = vec4((1 - fragColor.a) * waterFog.color.xyz + fragColor.a * fragColor.xyz, 1);
}
fragColor.rgb += mipMapSample(emissionSampler, textureCoords, textureIndex, lod).rgb;
if (fog.activ) {
@ -219,7 +213,6 @@ void main() {
}
if(!renderedToItemTexture) {
fragColor.rgb /= 4;
position = vec4(mvVertexPos, 1);
}
// TODO: Update the depth.
}

View File

@ -0,0 +1,190 @@
#version 430
in vec3 mvVertexPos;
flat in int blockType;
flat in int faceNormal;
flat in int modelIndex;
in vec3 startPosition;
in vec3 direction;
uniform int time;
uniform vec3 ambientLight;
uniform sampler2DArray texture_sampler;
uniform sampler2DArray emissionSampler;
layout (location = 0, index = 0) out vec4 fragColor;
layout (location = 0, index = 1) out vec4 blendColor;
struct Fog {
bool activ;
vec3 color;
float density;
};
struct AnimationData {
int frames;
int time;
};
layout(std430, binding = 0) buffer _animation
{
AnimationData animation[];
};
layout(std430, binding = 1) buffer _textureIndices
{
int textureIndices[][6];
};
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)
);
uniform Fog fog;
uniform Fog waterFog; // TODO: Select fog from texture
uniform bool renderedToItemTexture;
vec4 calcFog(vec3 pos, vec4 color, Fog fog) {
float distance = length(pos);
float fogFactor = 1.0/exp((distance*fog.density)*(distance*fog.density));
fogFactor = clamp(fogFactor, 0.0, 1.0);
vec4 resultColor = mix(vec4(fog.color, 1), color, fogFactor);
return resultColor;
}
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, 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;
}
ivec3 random3to3(ivec3 v) {
v &= 15;
ivec3 fac = ivec3(11248723, 105436839, 45399083);
int seed = v.x*fac.x ^ v.y*fac.y ^ v.z*fac.z;
v = seed*fac;
return v;
}
float snoise(vec3 v){ // TODO: Maybe use a cubemap.
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
// First corner
vec3 i = floor(v + dot(v, C.yyy));
vec3 x0 = v - i + dot(i, C.xxx);
// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min(g.xyz, l.zxy);
vec3 i2 = max(g.xyz, l.zxy);
// x0 = x0 - 0. + 0.0 * C
vec3 x1 = x0 - i1 + 1.0*C.xxx;
vec3 x2 = x0 - i2 + 2.0*C.xxx;
vec3 x3 = x0 - 1. + 3.0*C.xxx;
// Get gradients:
ivec3 rand = random3to3(ivec3(i));
vec3 p0 = vec3(rand);
rand = random3to3((ivec3(i + i1)));
vec3 p1 = vec3(rand);
rand = random3to3((ivec3(i + i2)));
vec3 p2 = vec3(rand);
rand = random3to3((ivec3(i + 1)));
vec3 p3 = vec3(rand);
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m*m;
return 42.0*dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)))/(1 << 31);
}
void main() {
float variance = perpendicularFwidth(direction);
int textureIndex = textureIndices[blockType][faceNormal];
textureIndex = textureIndex + time / animation[textureIndex].time % animation[textureIndex].frames;
float normalVariation = normalVariations[faceNormal];
float lod = getLod(ivec3(startPosition), faceNormal, direction, variance);
ivec2 textureCoords = getTextureCoords(ivec3(startPosition), faceNormal);
fragColor = mipMapSample(texture_sampler, textureCoords, textureIndex, lod)*vec4(ambientLight*normalVariation, 1);
if (fragColor.a == 1) discard;
if (fog.activ) {
// TODO: Underwater fog if possible.
}
blendColor = fragColor;
fragColor.rgb = vec3(0);
if(fragColor.a < 1) {
// Fake reflection:
// TODO: Make the amount configurable.
// TODO: Also allow this for opaque pixels.
// TODO: Change this when it rains.
// TODO: Normal mapping.
// TODO: Allow textures to contribute to this term.
fragColor.rgb += 0.1*vec3(snoise(normalize(reflect(direction, normals[faceNormal])))) + vec3(0.2);
fragColor.a = 1;
} else {
fragColor.rgb += mipMapSample(emissionSampler, textureCoords, textureIndex, lod).rgb;
}
if (fog.activ) {
fragColor = calcFog(mvVertexPos, fragColor, fog);
}
if(!renderedToItemTexture) {
fragColor.rgb /= 4;
}
// TODO: Update the depth.
}

View File

@ -4,7 +4,6 @@ out vec4 fragColor;
in vec2 texCoords;
uniform sampler2D color;
uniform sampler2D position;
void main() {
fragColor = texture(color, texCoords);

View File

@ -13,7 +13,6 @@ struct Fog {
uniform Fog fog;
uniform sampler2D color;
uniform sampler2D position;
vec4 calcFog(vec3 pos, vec4 color, Fog fog) {
float distance = length(pos);
@ -24,6 +23,6 @@ vec4 calcFog(vec3 pos, vec4 color, Fog fog) {
}
void main() {
fragColor = calcFog(texture(position, texCoords).xyz, texture(color, texCoords)*vec4(4, 4, 4, 1), fog);
// TODO: Reconstruct position from the depth value. fragColor = calcFog(texture(position, texCoords).xyz, texture(color, texCoords)*vec4(4, 4, 4, 1), fog);
fragColor.rgb /= 4;
}

View File

@ -10,7 +10,6 @@ flat in uvec3 lower;
flat in uvec3 upper;
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec4 position;
struct Fog {
bool activ;
@ -223,7 +222,6 @@ void mainBlockDrop() {
fragColor = calcFog(startPosition, fragColor, fog);
}
fragColor.rgb /= 4;
position = vec4(startPosition, 1);
}
// itemDrops -------------------------------------------------------------------------------------------------------------------------
@ -314,7 +312,6 @@ void mainItemDrop() {
fragColor = calcFog(modifiedCameraSpacePos, color, fog);
}
fragColor.rgb /= 4;
position = vec4(modifiedCameraSpacePos, 1);
}
void main() {

View File

@ -1,66 +0,0 @@
#version 430
in vec2 outTexCoord;
flat in float textureIndex;
in vec3 outColor;
in vec3 mvVertexPos;
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec4 position;
struct Fog {
bool activ;
vec3 color;
float density;
};
uniform sampler2DArray texture_sampler;
uniform sampler2DArray emissionSampler;
uniform sampler2D break_sampler;
uniform Fog fog;
uniform Fog waterFog; // TODO: Select fog from texture
uniform vec2 windowSize;
uniform vec3 ambientLight;
uniform sampler2D positionBuffer;
uniform sampler2D colorBuffer;
uniform bool drawFrontFace;
vec4 ambientC;
void setupColors(vec3 textCoord) {
ambientC = texture(texture_sampler, textCoord);
}
vec4 calcFog(vec3 pos, vec4 color, Fog fog) {
float distance = length(pos);
float fogFactor = 1.0/exp((distance*fog.density)*(distance*fog.density));
fogFactor = clamp(fogFactor, 0.0, 1.0);
vec3 resultColor = mix(fog.color, color.xyz, fogFactor);
return vec4(resultColor.xyz, color.w + 1 - fogFactor);
}
void main() {
setupColors(vec3(outTexCoord, textureIndex));
if (ambientC.a == 1) discard;
fragColor = ambientC*vec4(outColor, 1);
fragColor.rgb += texture(emissionSampler, vec3(outTexCoord, textureIndex)).rgb;
if (fog.activ) {
fragColor = calcFog(mvVertexPos, fragColor, fog);
// Underwater fog:
if (drawFrontFace) { // There is only fog betwen front and back face of the same volume.
vec2 frameBufferPos = gl_FragCoord.xy/windowSize;
vec4 oldColor = texture(colorBuffer, frameBufferPos);
oldColor.rgb *= 4;
vec3 oldPosition = texture(positionBuffer, frameBufferPos).xyz;
oldColor = calcFog(oldPosition - mvVertexPos, oldColor, waterFog);
fragColor = vec4((1 - fragColor.a) * oldColor.xyz + fragColor.a * fragColor.xyz, 1);
}
}
fragColor.rgb /= 4;
position = vec4(mvVertexPos, 1);
}

View File

@ -1,49 +0,0 @@
#version 430
layout (location=0) in vec3 position;
layout (location=1) in vec3 texCoord;
layout (location=2) in vec3 vertexNormal;
layout (location=3) in int easyLight;
out vec2 outTexCoord;
flat out float textureIndex;
out vec3 mvVertexPos;
out vec3 outColor;
uniform mat4 projectionMatrix;
uniform vec3 ambientLight;
uniform vec3 directionalLight;
uniform mat4 viewMatrix;
uniform vec3 modelPosition;
layout(std430, binding = 0) buffer _animationTimes
{
int animationTimes[];
};
layout(std430, binding = 1) buffer _animationFrames
{
int animationFrames[];
};
uniform int time;
vec3 calcLight(int srgb) {
float s = (srgb >> 24) & 255;
float r = (srgb >> 16) & 255;
float g = (srgb >> 8) & 255;
float b = (srgb >> 0) & 255;
s = s*(1 - dot(directionalLight, vertexNormal));
r = max(s*ambientLight.x, r);
g = max(s*ambientLight.y, g);
b = max(s*ambientLight.z, b);
return vec3(r, g, b);
}
void main() {
outColor = calcLight(easyLight)*0.003890625;
vec4 mvPos = viewMatrix*vec4(position + modelPosition, 1);
gl_Position = projectionMatrix*mvPos;
outTexCoord = texCoord.xy;
textureIndex = texCoord.z + time / animationTimes[int(texCoord.z)] % animationFrames[int(texCoord.z)];
mvVertexPos = mvPos.xyz;
}

View File

@ -368,6 +368,7 @@ pub const Chunk = struct {
pub const meshing = struct {
var shader: Shader = undefined;
var transparentShader: Shader = undefined;
pub var uniforms: struct {
projectionMatrix: c_int,
viewMatrix: c_int,
@ -387,6 +388,25 @@ pub const meshing = struct {
voxelSize: c_int,
renderedToItemTexture: c_int,
} = undefined;
pub var transparentUniforms: struct {
projectionMatrix: c_int,
viewMatrix: c_int,
modelPosition: c_int,
screenSize: c_int,
ambientLight: c_int,
@"fog.activ": c_int,
@"fog.color": c_int,
@"fog.density": c_int,
texture_sampler: c_int,
emissionSampler: c_int,
@"waterFog.activ": c_int,
@"waterFog.color": c_int,
@"waterFog.density": c_int,
time: c_int,
visibilityMask: c_int,
voxelSize: c_int,
renderedToItemTexture: c_int,
} = undefined;
var vao: c_uint = undefined;
var vbo: c_uint = undefined;
var faces: std.ArrayList(u32) = undefined;
@ -394,6 +414,7 @@ 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);
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
const lut = [_]u32{0, 1, 2, 2, 1, 3};
@ -415,6 +436,7 @@ pub const meshing = struct {
pub fn deinit() void {
shader.deinit();
transparentShader.deinit();
c.glDeleteVertexArrays(1, &vao);
c.glDeleteBuffers(1, &vbo);
faces.deinit();
@ -444,6 +466,29 @@ pub const meshing = struct {
c.glBindVertexArray(vao);
}
pub fn bindTransparentShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f, time: u32) void {
transparentShader.bind();
c.glUniform1i(transparentUniforms.@"fog.activ", if(game.fog.active) 1 else 0);
c.glUniform3fv(transparentUniforms.@"fog.color", 1, @ptrCast(&game.fog.color));
c.glUniform1f(transparentUniforms.@"fog.density", game.fog.density);
c.glUniformMatrix4fv(transparentUniforms.projectionMatrix, 1, c.GL_FALSE, @ptrCast(&projMatrix));
c.glUniform1i(transparentUniforms.texture_sampler, 0);
c.glUniform1i(transparentUniforms.emissionSampler, 1);
c.glUniformMatrix4fv(transparentUniforms.viewMatrix, 1, c.GL_FALSE, @ptrCast(&game.camera.viewMatrix));
c.glUniform3f(transparentUniforms.ambientLight, ambient[0], ambient[1], ambient[2]);
c.glUniform1i(transparentUniforms.time, @as(u31, @truncate(time)));
c.glUniform1i(transparentUniforms.renderedToItemTexture, 0);
c.glBindVertexArray(vao);
}
pub const FaceData = switch(@import("builtin").target.cpu.arch.endian()) {
.Little => packed struct {
position: u32,
@ -455,94 +500,61 @@ pub const meshing = struct {
},
};
pub const ChunkMesh = struct {
pos: ChunkPosition,
size: i32,
chunk: std.atomic.Atomic(?*Chunk),
const PrimitiveMesh = struct {
faces: std.ArrayList(FaceData),
bufferAllocation: graphics.LargeBuffer.Allocation = .{.start = 0, .len = 0},
coreCount: u31 = 0,
neighborStart: [7]u31 = [_]u31{0} ** 7,
vertexCount: u31 = 0,
generated: bool = false,
mutex: std.Thread.Mutex = std.Thread.Mutex{},
visibilityMask: u8 = 0xff,
pub fn init(allocator: Allocator, pos: ChunkPosition) ChunkMesh {
return ChunkMesh{
.pos = pos,
.size = chunkSize*pos.voxelSize,
.faces = std.ArrayList(FaceData).init(allocator),
.chunk = std.atomic.Atomic(?*Chunk).init(null),
};
}
pub fn deinit(self: *ChunkMesh) void {
fn deinit(self: *PrimitiveMesh) void {
faceBuffer.free(self.bufferAllocation) catch unreachable;
self.faces.deinit();
if(self.chunk.load(.Monotonic)) |ch| {
main.globalAllocator.destroy(ch);
}
}
fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: u3) bool {
const rotatedModel = blocks.meshes.model(block);
const model = &models.voxelModels.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.dirDown => model.min[1] != 0,
Neighbors.dirUp => model.max[1] != 16,
Neighbors.dirNegZ => model.min[2] != 0,
Neighbors.dirPosZ => model.max[2] != 16,
else => unreachable,
};
return block.typ != 0 and (
freestandingModel
or other.typ == 0
or (!std.meta.eql(block, other) and other.viewThrough())
or blocks.meshes.model(other).modelIndex != 0 // TODO: make this more strict to avoid overdraw.
);
}
pub fn regenerateMainMesh(self: *ChunkMesh, chunk: *Chunk) !void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
fn reset(self: *PrimitiveMesh) void {
self.faces.clearRetainingCapacity();
var n: u32 = 0;
var x: u8 = 0;
while(x < chunkSize): (x += 1) {
var y: u8 = 0;
while(y < chunkSize): (y += 1) {
var z: u8 = 0;
while(z < chunkSize): (z += 1) {
const block = (&chunk.blocks)[getIndex(x, y, z)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
if(block.typ == 0) continue;
// Check all neighbors:
for(Neighbors.iterable) |i| {
n += 1;
const x2 = x + Neighbors.relX[i];
const y2 = y + Neighbors.relY[i];
const z2 = z + Neighbors.relZ[i];
if(x2&chunkMask != x2 or y2&chunkMask != y2 or z2&chunkMask != z2) continue; // Neighbor is outside the chunk.
const neighborBlock = (&chunk.blocks)[getIndex(x2, y2, z2)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
if(canBeSeenThroughOtherBlock(block, neighborBlock, i)) {
try self.faces.append(constructFaceData(block, i, @intCast(x2), @intCast(y2), @intCast(z2)));
}
}
}
}
}
}
if(self.chunk.load(.Monotonic)) |oldChunk| {
main.globalAllocator.destroy(oldChunk);
}
self.chunk.store(chunk, .Monotonic);
fn resetToCore(self: *PrimitiveMesh) void {
self.faces.shrinkRetainingCapacity(self.coreCount);
}
fn append(self: *PrimitiveMesh, face: FaceData) !void {
try self.faces.append(face);
}
fn updateCore(self: *PrimitiveMesh) void {
self.coreCount = @intCast(self.faces.items.len);
self.neighborStart = [_]u31{self.coreCount} ** 7;
}
fn addFace(self: *ChunkMesh, faceData: FaceData, fromNeighborChunk: ?u3) !void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
fn startNeighbor(self: *PrimitiveMesh, neighbor: usize) void {
self.neighborStart[neighbor] = @intCast(self.faces.items.len);
}
fn replaceNeighbors(self: *PrimitiveMesh, neighbor: usize, additionalNeighborFaces: []FaceData) !void {
var rangeStart = self.neighborStart[neighbor ^ 1];
var rangeEnd = self.neighborStart[(neighbor ^ 1)+1];
try self.faces.replaceRange(rangeStart, rangeEnd - rangeStart, additionalNeighborFaces);
for(self.neighborStart[1+(neighbor ^ 1)..]) |*neighborStart| {
neighborStart.* = neighborStart.* - (rangeEnd - rangeStart) + @as(u31, @intCast(additionalNeighborFaces.len));
}
try self.uploadData();
}
fn finish(self: *PrimitiveMesh) !void {
self.neighborStart[6] = @intCast(self.faces.items.len);
try self.uploadData();
}
fn uploadData(self: *PrimitiveMesh) !void {
self.vertexCount = @intCast(6*self.faces.items.len);
try faceBuffer.realloc(&self.bufferAllocation, @intCast(8*self.faces.items.len));
faceBuffer.bufferSubData(self.bufferAllocation.start, FaceData, self.faces.items);
}
fn addFace(self: *PrimitiveMesh, faceData: FaceData, fromNeighborChunk: ?u3) !void {
var insertionIndex: u31 = undefined;
if(fromNeighborChunk) |neighbor| {
insertionIndex = self.neighborStart[neighbor];
@ -559,8 +571,7 @@ pub const meshing = struct {
try self.faces.insert(insertionIndex, faceData);
}
fn removeFace(self: *ChunkMesh, faceData: FaceData, fromNeighborChunk: ?u3) void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
fn removeFace(self: *PrimitiveMesh, faceData: FaceData, fromNeighborChunk: ?u3) void {
var searchStart: u32 = undefined;
var searchEnd: u32 = undefined;
if(fromNeighborChunk) |neighbor| {
@ -586,8 +597,7 @@ pub const meshing = struct {
@panic("Couldn't find the face to remove. This case is not handled.");
}
fn changeFace(self: *ChunkMesh, oldFaceData: FaceData, newFaceData: FaceData, fromNeighborChunk: ?u3) void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
fn changeFace(self: *PrimitiveMesh, oldFaceData: FaceData, newFaceData: FaceData, fromNeighborChunk: ?u3) void {
var searchRange: []FaceData = undefined;
if(fromNeighborChunk) |neighbor| {
searchRange = self.faces.items[self.neighborStart[neighbor]..self.neighborStart[neighbor+1]];
@ -603,6 +613,140 @@ pub const meshing = struct {
}
std.log.err("Couldn't find the face to replace.", .{});
}
};
pub const ChunkMesh = struct {
pos: ChunkPosition,
size: i32,
chunk: std.atomic.Atomic(?*Chunk),
opaqueMesh: PrimitiveMesh,
transparentMesh: PrimitiveMesh,
generated: bool = false,
mutex: std.Thread.Mutex = std.Thread.Mutex{},
visibilityMask: u8 = 0xff,
pub fn init(allocator: Allocator, pos: ChunkPosition) ChunkMesh {
return ChunkMesh{
.pos = pos,
.size = chunkSize*pos.voxelSize,
.opaqueMesh = .{
.faces = std.ArrayList(FaceData).init(allocator)
},
.transparentMesh = .{
.faces = std.ArrayList(FaceData).init(allocator)
},
.chunk = std.atomic.Atomic(?*Chunk).init(null),
};
}
pub fn deinit(self: *ChunkMesh) void {
self.opaqueMesh.deinit();
self.transparentMesh.deinit();
if(self.chunk.load(.Monotonic)) |ch| {
main.globalAllocator.destroy(ch);
}
}
pub fn isEmpty(self: *const ChunkMesh) bool {
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 freestandingModel = rotatedModel.modelIndex != models.fullCube and switch(rotatedModel.permutation.permuteNeighborIndex(neighbor)) {
Neighbors.dirNegX => model.min[0] != 0,
Neighbors.dirPosX => model.max[0] != 16,
Neighbors.dirDown => model.min[1] != 0,
Neighbors.dirUp => model.max[1] != 16,
Neighbors.dirNegZ => model.min[2] != 0,
Neighbors.dirPosZ => model.max[2] != 16,
else => unreachable,
};
return block.typ != 0 and (
freestandingModel
or other.typ == 0
or (!std.meta.eql(block, other) and other.viewThrough())
or blocks.meshes.model(other).modelIndex != 0 // TODO: make this more strict to avoid overdraw.
);
}
pub fn regenerateMainMesh(self: *ChunkMesh, chunk: *Chunk) !void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
self.opaqueMesh.reset();
self.transparentMesh.reset();
var n: u32 = 0;
var x: u8 = 0;
while(x < chunkSize): (x += 1) {
var y: u8 = 0;
while(y < chunkSize): (y += 1) {
var z: u8 = 0;
while(z < chunkSize): (z += 1) {
const block = (&chunk.blocks)[getIndex(x, y, z)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
if(block.typ == 0) continue;
// Check all neighbors:
for(Neighbors.iterable) |i| {
n += 1;
const x2 = x + Neighbors.relX[i];
const y2 = y + Neighbors.relY[i];
const z2 = z + Neighbors.relZ[i];
if(x2&chunkMask != x2 or y2&chunkMask != y2 or z2&chunkMask != z2) continue; // Neighbor is outside the chunk.
const neighborBlock = (&chunk.blocks)[getIndex(x2, y2, z2)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
if(canBeSeenThroughOtherBlock(block, neighborBlock, i)) {
if(block.transparent()) {
try self.transparentMesh.append(constructFaceData(block, i, @intCast(x2), @intCast(y2), @intCast(z2)));
} else {
try self.opaqueMesh.append(constructFaceData(block, i, @intCast(x2), @intCast(y2), @intCast(z2)));
}
}
}
}
}
}
if(self.chunk.swap(chunk, .Monotonic)) |oldChunk| {
main.globalAllocator.destroy(oldChunk);
}
self.transparentMesh.updateCore();
self.opaqueMesh.updateCore();
}
fn addFace(self: *ChunkMesh, faceData: FaceData, fromNeighborChunk: ?u3, transparent: bool) !void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
if(transparent) {
try self.transparentMesh.addFace(faceData, fromNeighborChunk);
} else {
try self.opaqueMesh.addFace(faceData, fromNeighborChunk);
}
}
fn removeFace(self: *ChunkMesh, faceData: FaceData, fromNeighborChunk: ?u3, transparent: bool) void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
if(transparent) {
self.transparentMesh.removeFace(faceData, fromNeighborChunk);
} else {
self.opaqueMesh.removeFace(faceData, fromNeighborChunk);
}
}
fn changeFace(self: *ChunkMesh, oldFaceData: FaceData, newFaceData: FaceData, fromNeighborChunk: ?u3, oldTransparent: bool, newTransparent: bool) !void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
if(oldTransparent) {
if(newTransparent) {
self.transparentMesh.changeFace(oldFaceData, newFaceData, fromNeighborChunk);
} else {
self.transparentMesh.removeFace(oldFaceData, fromNeighborChunk);
try self.opaqueMesh.addFace(newFaceData, fromNeighborChunk);
}
} else {
if(newTransparent) {
self.opaqueMesh.removeFace(oldFaceData, fromNeighborChunk);
try self.transparentMesh.addFace(newFaceData, fromNeighborChunk);
} else {
self.opaqueMesh.changeFace(oldFaceData, newFaceData, fromNeighborChunk);
}
}
}
pub fn updateBlock(self: *ChunkMesh, _x: i32, _y: i32, _z: i32, newBlock: Block) !void { // TODO: Investigate bug when placing blocks.
const x = _x & chunkMask;
@ -635,22 +779,22 @@ pub const meshing = struct {
if(canBeSeenThroughOtherBlock(oldBlock, neighborBlock, neighbor) != newVisibility) {
if(newVisibility) { // Adding the face
if(neighborMesh == self) {
try self.addFace(newFaceData, null);
try self.addFace(newFaceData, null, newBlock.transparent());
} else {
try neighborMesh.addFace(newFaceData, neighbor);
try neighborMesh.addFace(newFaceData, neighbor, newBlock.transparent());
}
} else { // Removing the face
if(neighborMesh == self) {
self.removeFace(oldFaceData, null);
self.removeFace(oldFaceData, null, oldBlock.transparent());
} else {
neighborMesh.removeFace(oldFaceData, neighbor);
neighborMesh.removeFace(oldFaceData, neighbor, oldBlock.transparent());
}
}
} else if(newVisibility) { // Changing the face
if(neighborMesh == self) {
self.changeFace(oldFaceData, newFaceData, null);
try self.changeFace(oldFaceData, newFaceData, null, oldBlock.transparent(), newBlock.transparent());
} else {
neighborMesh.changeFace(oldFaceData, newFaceData, neighbor);
try neighborMesh.changeFace(oldFaceData, newFaceData, neighbor, oldBlock.transparent(), newBlock.transparent());
}
}
}
@ -661,36 +805,34 @@ pub const meshing = struct {
if(canBeSeenThroughOtherBlock(neighborBlock, oldBlock, neighbor ^ 1) != newVisibility) {
if(newVisibility) { // Adding the face
if(neighborMesh == self) {
try self.addFace(newFaceData, null);
try self.addFace(newFaceData, null, newBlock.transparent());
} else {
try self.addFace(newFaceData, neighbor);
try self.addFace(newFaceData, neighbor, newBlock.transparent());
}
} else { // Removing the face
if(neighborMesh == self) {
self.removeFace(oldFaceData, null);
self.removeFace(oldFaceData, null, oldBlock.transparent());
} else {
self.removeFace(oldFaceData, neighbor);
self.removeFace(oldFaceData, neighbor, oldBlock.transparent());
}
}
} else if(newVisibility) { // Changing the face
if(neighborMesh == self) {
self.changeFace(oldFaceData, newFaceData, null);
try self.changeFace(oldFaceData, newFaceData, null, oldBlock.transparent(), newBlock.transparent());
} else {
self.changeFace(oldFaceData, newFaceData, neighbor);
try self.changeFace(oldFaceData, newFaceData, neighbor, oldBlock.transparent(), newBlock.transparent());
}
}
}
}
if(neighborMesh != self) try neighborMesh.uploadData();
if(neighborMesh != self) {
try neighborMesh.opaqueMesh.uploadData();
try neighborMesh.transparentMesh.uploadData();
}
}
self.chunk.load(.Monotonic).?.blocks[getIndex(x, y, z)] = newBlock;
try self.uploadData();
}
fn uploadData(self: *ChunkMesh) !void {
self.vertexCount = @intCast(6*self.faces.items.len);
try faceBuffer.realloc(&self.bufferAllocation, @intCast(8*self.faces.items.len));
faceBuffer.bufferSubData(self.bufferAllocation.start, FaceData, self.faces.items);
try self.opaqueMesh.uploadData();
try self.transparentMesh.uploadData();
}
pub inline fn constructFaceData(block: Block, normal: u32, x: u32, y: u32, z: u32) FaceData {
@ -704,17 +846,21 @@ pub const meshing = struct {
pub fn uploadDataAndFinishNeighbors(self: *ChunkMesh) !void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
const chunk = self.chunk.load(.Monotonic) orelse return; // In the mean-time the mesh was discarded and recreated and all the data was lost.
self.faces.shrinkRetainingCapacity(self.coreCount);
self.opaqueMesh.resetToCore();
self.transparentMesh.resetToCore();
for(Neighbors.iterable) |neighbor| {
self.neighborStart[neighbor] = @intCast(self.faces.items.len);
self.opaqueMesh.startNeighbor(neighbor);
self.transparentMesh.startNeighbor(neighbor);
var nullNeighborMesh = renderer.RenderStructure.getNeighbor(self.pos, self.pos.voxelSize, neighbor);
if(nullNeighborMesh) |neighborMesh| {
std.debug.assert(neighborMesh != self);
neighborMesh.mutex.lock();
defer neighborMesh.mutex.unlock();
if(neighborMesh.generated) {
var additionalNeighborFaces = std.ArrayList(FaceData).init(main.threadAllocator);
defer additionalNeighborFaces.deinit();
var additionalNeighborFacesOpaque = std.ArrayList(FaceData).init(main.threadAllocator);
defer additionalNeighborFacesOpaque.deinit();
var additionalNeighborFacesTransparent = std.ArrayList(FaceData).init(main.threadAllocator);
defer additionalNeighborFacesTransparent.deinit();
var x3: u8 = if(neighbor & 1 == 0) @intCast(chunkMask) else 0;
var x1: u8 = 0;
while(x1 < chunkSize): (x1 += 1) {
@ -742,20 +888,23 @@ pub const meshing = struct {
var block = (&chunk.blocks)[getIndex(x, y, z)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
var otherBlock = (&neighborMesh.chunk.load(.Monotonic).?.blocks)[getIndex(otherX, otherY, otherZ)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) {
try additionalNeighborFaces.append(constructFaceData(block, neighbor, otherX, otherY, otherZ));
if(block.transparent()) {
try additionalNeighborFacesTransparent.append(constructFaceData(block, neighbor, otherX, otherY, otherZ));
} else {
try additionalNeighborFacesOpaque.append(constructFaceData(block, neighbor, otherX, otherY, otherZ));
}
}
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1)) {
try self.faces.append(constructFaceData(otherBlock, neighbor ^ 1, x, y, z));
if(otherBlock.transparent()) {
try self.transparentMesh.append(constructFaceData(otherBlock, neighbor ^ 1, x, y, z));
} else {
try self.opaqueMesh.append(constructFaceData(otherBlock, neighbor ^ 1, x, y, z));
}
}
}
}
var rangeStart = neighborMesh.neighborStart[neighbor ^ 1];
var rangeEnd = neighborMesh.neighborStart[(neighbor ^ 1)+1];
try neighborMesh.faces.replaceRange(rangeStart, rangeEnd - rangeStart, additionalNeighborFaces.items);
for(neighborMesh.neighborStart[1+(neighbor ^ 1)..]) |*neighborStart| {
neighborStart.* = neighborStart.* - (rangeEnd - rangeStart) + @as(u31, @intCast(additionalNeighborFaces.items.len));
}
try neighborMesh.uploadData();
try neighborMesh.opaqueMesh.replaceNeighbors(neighbor, additionalNeighborFacesOpaque.items);
try neighborMesh.transparentMesh.replaceNeighbors(neighbor, additionalNeighborFacesTransparent.items);
continue;
}
}
@ -795,7 +944,11 @@ pub const meshing = struct {
var block = (&chunk.blocks)[getIndex(x, y, z)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
var otherBlock = (&neighborMesh.chunk.load(.Monotonic).?.blocks)[getIndex(otherX, otherY, otherZ)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1)) {
try self.faces.append(constructFaceData(otherBlock, neighbor ^ 1, x, y, z));
if(otherBlock.transparent()) {
try self.transparentMesh.append(constructFaceData(otherBlock, neighbor ^ 1, x, y, z));
} else {
try self.opaqueMesh.append(constructFaceData(otherBlock, neighbor ^ 1, x, y, z));
}
}
}
}
@ -803,8 +956,8 @@ pub const meshing = struct {
return error.LODMissing;
}
}
self.neighborStart[6] = @intCast(self.faces.items.len);
try self.uploadData();
try self.opaqueMesh.finish();
try self.transparentMesh.finish();
self.generated = true;
}
@ -812,7 +965,7 @@ pub const meshing = struct {
if(!self.generated) {
return;
}
if(self.vertexCount == 0) return;
if(self.opaqueMesh.vertexCount == 0) return;
c.glUniform3f(
uniforms.modelPosition,
@floatCast(@as(f64, @floatFromInt(self.pos.wx)) - playerPosition[0]),
@ -821,7 +974,23 @@ pub const meshing = struct {
);
c.glUniform1i(uniforms.visibilityMask, self.visibilityMask);
c.glUniform1i(uniforms.voxelSize, self.pos.voxelSize);
c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.vertexCount, c.GL_UNSIGNED_INT, null, self.bufferAllocation.start/8*4);
c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.opaqueMesh.vertexCount, c.GL_UNSIGNED_INT, null, self.opaqueMesh.bufferAllocation.start/8*4);
}
pub fn renderTransparent(self: *ChunkMesh, playerPosition: Vec3d) void { // TODO: Transparency sorting
if(!self.generated) {
return;
}
if(self.transparentMesh.vertexCount == 0) return;
c.glUniform3f(
transparentUniforms.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(transparentUniforms.visibilityMask, self.visibilityMask);
c.glUniform1i(transparentUniforms.voxelSize, self.pos.voxelSize);
c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.transparentMesh.vertexCount, c.GL_UNSIGNED_INT, null, self.transparentMesh.bufferAllocation.start/8*4);
}
};
};

View File

@ -34,12 +34,10 @@ var fogUniforms: struct {
fog_activ: c_int,
fog_color: c_int,
fog_density: c_int,
position: c_int,
color: c_int,
} = undefined;
var deferredRenderPassShader: graphics.Shader = undefined;
var deferredUniforms: struct {
position: c_int,
color: c_int,
} = undefined;
@ -66,20 +64,17 @@ pub fn deinit() void {
const buffers = struct {
var buffer: c_uint = undefined;
var colorTexture: c_uint = undefined;
var positionTexture: c_uint = undefined;
var depthBuffer: c_uint = undefined;
fn init() void {
c.glGenFramebuffers(1, &buffer);
c.glGenRenderbuffers(1, &depthBuffer);
c.glGenTextures(1, &colorTexture);
c.glGenTextures(1, &positionTexture);
updateBufferSize(Window.width, Window.height);
c.glBindFramebuffer(c.GL_FRAMEBUFFER, buffer);
c.glFramebufferTexture2D(c.GL_FRAMEBUFFER, c.GL_COLOR_ATTACHMENT0, c.GL_TEXTURE_2D, colorTexture, 0);
c.glFramebufferTexture2D(c.GL_FRAMEBUFFER, c.GL_COLOR_ATTACHMENT1, c.GL_TEXTURE_2D, positionTexture, 0);
c.glFramebufferRenderbuffer(c.GL_FRAMEBUFFER, c.GL_DEPTH_STENCIL_ATTACHMENT, c.GL_RENDERBUFFER, depthBuffer);
@ -90,7 +85,6 @@ const buffers = struct {
c.glDeleteFramebuffers(1, &buffer);
c.glDeleteRenderbuffers(1, &depthBuffer);
c.glDeleteTextures(1, &colorTexture);
c.glDeleteTextures(1, &positionTexture);
}
fn regenTexture(texture: c_uint, internalFormat: c_int, format: c_uint, width: u31, height: u31) void {
@ -105,13 +99,12 @@ const buffers = struct {
c.glBindFramebuffer(c.GL_FRAMEBUFFER, buffer);
regenTexture(colorTexture, c.GL_RGB10_A2, c.GL_RGB, width, height);
regenTexture(positionTexture, c.GL_RGB16F, c.GL_RGB, width, height);
c.glBindRenderbuffer(c.GL_RENDERBUFFER, depthBuffer);
c.glRenderbufferStorage(c.GL_RENDERBUFFER, c.GL_DEPTH24_STENCIL8, width, height);
c.glBindRenderbuffer(c.GL_RENDERBUFFER, 0);
const attachments = [_]c_uint{c.GL_COLOR_ATTACHMENT0, c.GL_COLOR_ATTACHMENT1};
const attachments = [_]c_uint{c.GL_COLOR_ATTACHMENT0};
c.glDrawBuffers(attachments.len, &attachments);
c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0);
@ -120,8 +113,6 @@ const buffers = struct {
fn bindTextures() void {
c.glActiveTexture(c.GL_TEXTURE3);
c.glBindTexture(c.GL_TEXTURE_2D, colorTexture);
c.glActiveTexture(c.GL_TEXTURE4);
c.glBindTexture(c.GL_TEXTURE_2D, positionTexture);
}
fn bind() void {
@ -270,7 +261,23 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
try itemdrop.ItemDropRenderer.renderItemDrops(game.projectionMatrix, ambientLight, playerPos, time);
// // Render transparent chunk meshes:
// Render transparent chunk meshes:
chunk.meshing.bindTransparentShaderAndUniforms(game.projectionMatrix, ambientLight, time);
c.glUniform1i(chunk.meshing.transparentUniforms.@"waterFog.activ", if(waterFog.active) 1 else 0);
c.glUniform3fv(chunk.meshing.transparentUniforms.@"waterFog.color", 1, @ptrCast(&waterFog.color));
c.glUniform1f(chunk.meshing.transparentUniforms.@"waterFog.density", waterFog.density);
c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_SRC1_COLOR);
{
var i: usize = meshes.items.len;
while(true) {
if(i == 0) break;
i -= 1;
meshes.items[i].renderTransparent(playerPos);
}
}
c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA);
// NormalChunkMesh.bindTransparentShader(ambientLight, directionalLight.getDirection(), time);
buffers.bindTextures();
@ -308,7 +315,6 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
// fogShader.setUniform(FogUniforms.loc_fog_color, waterFog.getColor());
// fogShader.setUniform(FogUniforms.loc_fog_density, waterFog.getDensity());
// glUniform1i(FogUniforms.loc_color, 3);
// glUniform1i(FogUniforms.loc_position, 4);
// glBindVertexArray(Graphics.rectVAO);
// glDisable(GL_DEPTH_TEST);
@ -323,7 +329,6 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
buffers.bindTextures();
deferredRenderPassShader.bind();
c.glUniform1i(deferredUniforms.color, 3);
c.glUniform1i(deferredUniforms.position, 4);
c.glBindFramebuffer(c.GL_FRAMEBUFFER, activeFrameBuffer);
@ -1097,7 +1102,7 @@ pub const RenderStructure = struct {
@floatFromInt(size),
@floatFromInt(size),
@floatFromInt(size),
}) and node.?.mesh.visibilityMask != 0 and node.?.mesh.vertexCount != 0) {
}) and node.?.mesh.visibilityMask != 0 and !node.?.mesh.isEmpty()) {
try meshes.append(&node.?.mesh);
}
if(lod+1 != storageLists.len and node.?.mesh.generated) {