diff --git a/assets/cubyz/shaders/skybox/star.fs b/assets/cubyz/shaders/skybox/star.fs new file mode 100644 index 00000000..9adea8b8 --- /dev/null +++ b/assets/cubyz/shaders/skybox/star.fs @@ -0,0 +1,14 @@ +#version 430 + +in vec3 pos; +in flat vec3 centerPos; +in flat vec3 color; + +layout (location = 0, index = 0) out vec4 fragColor; + +void main() { + if (dot(pos - centerPos, pos - centerPos) > 1.0/12.0) + discard; + + fragColor = vec4(color, 1); +} diff --git a/assets/cubyz/shaders/skybox/star.vs b/assets/cubyz/shaders/skybox/star.vs new file mode 100644 index 00000000..e32cc15b --- /dev/null +++ b/assets/cubyz/shaders/skybox/star.vs @@ -0,0 +1,29 @@ +#version 430 + +struct star { + vec4 vertexPositions[3]; + + vec3 pos; + float padding1; + vec3 color; + float padding2; +}; + +layout (std430, binding = 12) buffer _starBuffer { + star starData[]; +}; + +uniform mat4 mvp; +uniform float starOpacity; + +out vec3 pos; +out flat vec3 centerPos; +out flat vec3 color; + +void main() { + gl_Position = mvp*vec4(starData[gl_VertexID/3].vertexPositions[gl_VertexID%3].xyz, 1); + + pos = starData[gl_VertexID/3].vertexPositions[gl_VertexID%3].xyz; + centerPos = starData[gl_VertexID/3].pos; + color = starData[gl_VertexID/3].color*starOpacity; +} diff --git a/assets/cubyz/star.png b/assets/cubyz/star.png new file mode 100644 index 00000000..92efdfc5 Binary files /dev/null and b/assets/cubyz/star.png differ diff --git a/src/game.zig b/src/game.zig index b681d525..6250c8cb 100644 --- a/src/game.zig +++ b/src/game.zig @@ -641,7 +641,7 @@ pub const Player = struct { // MARK: Player }; pub const World = struct { // MARK: World - const dayCycle: u63 = 12000; // Length of one in-game day in 100ms. Midnight is at DAY_CYCLE/2. Sunrise and sunset each take about 1/16 of the day. Currently set to 20 minutes + pub const dayCycle: u63 = 12000; // Length of one in-game day in 100ms. Midnight is at DAY_CYCLE/2. Sunrise and sunset each take about 1/16 of the day. Currently set to 20 minutes conn: *Connection, manager: *ConnectionManager, diff --git a/src/gui/windows/gpu_performance_measuring.zig b/src/gui/windows/gpu_performance_measuring.zig index e9d62fa3..1033af5c 100644 --- a/src/gui/windows/gpu_performance_measuring.zig +++ b/src/gui/windows/gpu_performance_measuring.zig @@ -14,6 +14,7 @@ const GuiComponent = gui.GuiComponent; pub const Samples = enum(u8) { screenbuffer_clear, clear, + skybox, animation, chunk_rendering_preparation, chunk_rendering_previous_visible, @@ -33,6 +34,7 @@ pub const Samples = enum(u8) { const names = [_][]const u8{ "Screenbuffer clear", "Clear", + "Skybox", "Pre-processing Block Animations", "Chunk Rendering Preparation", "Chunk Rendering Previous Visible", diff --git a/src/random.zig b/src/random.zig index 3d91fbac..498be4cd 100644 --- a/src/random.zig +++ b/src/random.zig @@ -53,6 +53,17 @@ pub fn nextFloatSigned(seed: *u64) f32 { return @as(f32, @floatFromInt(@as(i24, @bitCast(nextInt(u24, seed)))))/(1 << 23); } +pub fn nextFloatExp(seed: *u64) f32 { + return -@log(nextFloat(seed)); +} + +pub fn nextFloatGauss(seed: *u64) f32 { + const a = nextFloat(seed); + const b = nextFloat(seed); + + return @sqrt(-2.0*@log(a))*@cos(2.0*std.math.pi*b); +} + pub fn nextFloatVector(len: comptime_int, seed: *u64) @Vector(len, f32) { var result: @Vector(len, f32) = undefined; inline for(0..len) |i| { diff --git a/src/renderer.zig b/src/renderer.zig index 71d4e40c..2f1ec264 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -72,6 +72,7 @@ pub fn init() void { MenuBackGround.init() catch |err| { std.log.err("Failed to initialize the Menu Background: {s}", .{@errorName(err)}); }; + Skybox.init(); chunk_meshing.init(); mesh_storage.init(); reflectionCubeMap = .init(); @@ -86,6 +87,7 @@ pub fn deinit() void { Bloom.deinit(); MeshSelection.deinit(); MenuBackGround.deinit(); + Skybox.deinit(); mesh_storage.deinit(); chunk_meshing.deinit(); reflectionCubeMap.deinit(); @@ -134,11 +136,10 @@ pub fn render(playerPosition: Vec3d, deltaTime: f64) void { ambient[0] = @max(0.1, world.ambientLight); ambient[1] = @max(0.1, world.ambientLight); ambient[2] = @max(0.1, world.ambientLight); - const skyColor = vec.xyz(world.clearColor); - game.fog.skyColor = skyColor; + game.fog.skyColor = vec.xyz(world.clearColor); itemdrop.ItemDisplayManager.update(deltaTime); - renderWorld(world, ambient, skyColor, playerPosition); + renderWorld(world, ambient, Skybox.getSkyColor(), playerPosition); const startTime = std.time.milliTimestamp(); mesh_storage.updateMeshes(startTime + maximumMeshTime); } else { @@ -183,6 +184,10 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo const time: u32 = @intCast(std.time.milliTimestamp() & std.math.maxInt(u32)); + gpu_performance_measuring.startQuery(.skybox); + Skybox.render(); + gpu_performance_measuring.stopQuery(); + gpu_performance_measuring.startQuery(.animation); blocks.meshes.preProcessAnimationData(time); gpu_performance_measuring.stopQuery(); @@ -611,6 +616,170 @@ pub const MenuBackGround = struct { } }; +pub const Skybox = struct { + var starShader: Shader = undefined; + var starUniforms: struct { + mvp: c_int, + starOpacity: c_int, + } = undefined; + + var starVao: c_uint = undefined; + + var starSsbo: graphics.SSBO = undefined; + + const numStars = 10000; + + fn getStarPos(seed: *u64) Vec3f { + const x: f32 = @floatCast(main.random.nextFloatGauss(seed)); + const y: f32 = @floatCast(main.random.nextFloatGauss(seed)); + const z: f32 = @floatCast(main.random.nextFloatGauss(seed)); + + const r = std.math.cbrt(main.random.nextFloat(seed))*5000.0; + + return vec.normalize(Vec3f{x, y, z})*@as(Vec3f, @splat(r)); + } + + fn getStarColor(temperature: f32, light: f32, image: graphics.Image) Vec3f { + const rgbCol = image.getRGB(@intFromFloat(std.math.clamp(temperature/15000.0*@as(f32, @floatFromInt(image.width)), 0.0, @as(f32, @floatFromInt(image.width - 1)))), 0); + var rgb: Vec3f = @floatFromInt(Vec3i{rgbCol.r, rgbCol.g, rgbCol.b}); + rgb /= @splat(255.0); + + rgb *= @as(Vec3f, @splat(light)); + + const m = @reduce(.Max, rgb); + if(m > 1.0) { + rgb /= @as(Vec3f, @splat(m)); + } + + return rgb; + } + + fn init() void { + const starColorImage = graphics.Image.readFromFile(main.stackAllocator, "assets/cubyz/star.png") catch |err| { + std.log.err("Failed to load star image: {s}", .{@errorName(err)}); + return; + }; + defer starColorImage.deinit(main.stackAllocator); + + starShader = Shader.initAndGetUniforms("assets/cubyz/shaders/skybox/star.vs", "assets/cubyz/shaders/skybox/star.fs", "", &starUniforms); + starShader.bind(); + + var starData: [numStars*20]f32 = undefined; + + const starDist = 200.0; + + const off: f32 = @sqrt(3.0)/6.0; + + const triVertA = Vec3f{0.5, starDist, -off}; + const triVertB = Vec3f{-0.5, starDist, -off}; + const triVertC = Vec3f{0.0, starDist, @sqrt(3.0)/2.0 - off}; + + var seed: u64 = 0; + + for(0..numStars) |i| { + var pos: Vec3f = undefined; + + var radius: f32 = undefined; + + var temperature: f32 = undefined; + + var light: f32 = 0; + + while(light < 0.1) { + pos = getStarPos(&seed); + + radius = @floatCast(main.random.nextFloatExp(&seed)*4 + 0.2); + + temperature = @floatCast(@abs(main.random.nextFloatGauss(&seed)*3000.0 + 5000.0) + 1000.0); + + // 3.6e-12 can be modified to change the brightness of the stars + light = (3.6e-12*radius*radius*temperature*temperature*temperature*temperature)/(vec.dot(pos, pos)); + } + + pos = vec.normalize(pos)*@as(Vec3f, @splat(starDist)); + + const normPos = vec.normalize(pos); + + const color = getStarColor(temperature, light, starColorImage); + + const latitude: f32 = @floatCast(std.math.asin(normPos[2])); + const longitude: f32 = @floatCast(std.math.atan2(-normPos[0], normPos[1])); + + const mat = Mat4f.rotationZ(longitude).mul(Mat4f.rotationX(latitude)); + + const posA = vec.xyz(mat.mulVec(.{triVertA[0], triVertA[1], triVertA[2], 1.0})); + const posB = vec.xyz(mat.mulVec(.{triVertB[0], triVertB[1], triVertB[2], 1.0})); + const posC = vec.xyz(mat.mulVec(.{triVertC[0], triVertC[1], triVertC[2], 1.0})); + + starData[i*20 ..][0..3].* = posA; + starData[i*20 + 4 ..][0..3].* = posB; + starData[i*20 + 8 ..][0..3].* = posC; + + starData[i*20 + 12 ..][0..3].* = pos; + starData[i*20 + 16 ..][0..3].* = color; + } + + starSsbo = graphics.SSBO.initStatic(f32, &starData); + + c.glGenVertexArrays(1, &starVao); + c.glBindVertexArray(starVao); + c.glEnableVertexAttribArray(0); + } + + pub fn deinit() void { + starShader.deinit(); + starSsbo.deinit(); + c.glDeleteVertexArrays(1, &starVao); + } + + pub fn getSkyColor() Vec3f { + return game.fog.skyColor*@as(Vec3f, @splat(@reduce(.Add, game.fog.skyColor)/3.0)); + } + + pub fn render() void { + const viewMatrix = game.camera.viewMatrix; + + const time = game.world.?.gameTime.load(.monotonic); + + var starOpacity: f32 = 0; + const dayTime = @abs(@mod(time, game.World.dayCycle) -% game.World.dayCycle/2); + if(dayTime < game.World.dayCycle/4 - game.World.dayCycle/16) { + starOpacity = 1; + } else if(dayTime > game.World.dayCycle/4 + game.World.dayCycle/16) { + starOpacity = 0; + } else { + starOpacity = 1 - @as(f32, @floatFromInt(dayTime - (game.World.dayCycle/4 - game.World.dayCycle/16)))/@as(f32, @floatFromInt(game.World.dayCycle/8)); + } + + if(starOpacity != 0) { + c.glDisable(c.GL_CULL_FACE); + c.glDisable(c.GL_DEPTH_TEST); + + c.glBlendFunc(c.GL_ONE, c.GL_ONE); + c.glEnable(c.GL_BLEND); + + starShader.bind(); + + const starMatrix = game.projectionMatrix.mul(viewMatrix.mul(Mat4f.rotationX(@as(f32, @floatFromInt(time))/@as(f32, @floatFromInt(main.game.World.dayCycle))))); + + starSsbo.bind(12); + + c.glUniform1f(starUniforms.starOpacity, starOpacity); + c.glUniformMatrix4fv(starUniforms.mvp, 1, c.GL_TRUE, @ptrCast(&starMatrix)); + + c.glBindVertexArray(starVao); + c.glDrawArrays(c.GL_TRIANGLES, 0, numStars*3); + + c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, 0); + + c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); + + c.glEnable(c.GL_CULL_FACE); + c.glEnable(c.GL_DEPTH_TEST); + } + } +}; + pub const Frustum = struct { // MARK: Frustum const Plane = struct { pos: Vec3f,