Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add View Bobbing to Camera #665

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
86960eb
Added View Bobbing to Camera
AlexDavies8 Sep 3, 2024
2ced078
Fixed crash/UB on walking backwards
AlexDavies8 Sep 3, 2024
81b8e6c
Reworked view bobbing to add rotation and more natural feel.
AlexDavies8 Sep 3, 2024
8a3efbd
Fixed tabs/spaces formatting
AlexDavies8 Sep 3, 2024
f1b407a
Rotation of viewbob now affected by strength setting
AlexDavies8 Sep 4, 2024
955b0ed
Reworked view bobbing to not affect selected block.
AlexDavies8 Sep 5, 2024
40802d3
Updated view bobbing to feel less like limping
AlexDavies8 Sep 9, 2024
505bb4c
Fixed minor bug where player was still on ground when in ghost mode
AlexDavies8 Sep 9, 2024
9d1ac23
Limited maximum bobbing strength and removed roll
AlexDavies8 Sep 10, 2024
9111b2b
Reverted from 'n' shape to 'u' shape motion view bobbing
AlexDavies8 Sep 10, 2024
3e63eb5
View bobbing based on https://www.desmos.com/calculator/y7sbbltnmf
AlexDavies8 Sep 10, 2024
c9b0c24
View bobbing now based on actual velocity only, and adjusted bobbing …
AlexDavies8 Sep 11, 2024
bf826a6
Adjusted bobbing slightly
AlexDavies8 Sep 11, 2024
27b7930
Fixed minor issue with bobbing not centering when in air and stopping…
AlexDavies8 Sep 11, 2024
9041c69
Addressed Review Feedback (Trig identities + comments)
AlexDavies8 Sep 12, 2024
1e7794d
Merge remote-tracking branch 'upstream/master'
AlexDavies8 Sep 12, 2024
eaee832
View bobbing scales with player hitbox height
AlexDavies8 Sep 12, 2024
a9e8cf3
Decoupled Camera from Player
AlexDavies8 Sep 20, 2024
fc22875
Merge remote-tracking branch 'upstream/master'
AlexDavies8 Sep 20, 2024
67be310
Removed unnecessary comment
AlexDavies8 Sep 20, 2024
ffd36e7
Fixed issue with background screenshot seams
AlexDavies8 Sep 21, 2024
cc29de7
View bobbing cannot escape eyeBox anymore
AlexDavies8 Sep 26, 2024
cc2dec6
Merge remote-tracking branch 'upstream/master'
AlexDavies8 Sep 26, 2024
862c982
Fixed issue with bob scaling
AlexDavies8 Sep 26, 2024
ca0c4e8
Merge remote-tracking branch 'upstream/master'
AlexDavies8 Sep 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions src/game.zig
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ pub const camera = struct { // MARK: camera
}

pub fn updateViewMatrix() void {
direction = vec.rotateZ(vec.rotateX(Vec3f{0, 1, 0}, -rotation[0]), -rotation[2]);
viewMatrix = Mat4f.identity().mul(Mat4f.rotationX(rotation[0])).mul(Mat4f.rotationZ(rotation[2]));
direction = vec.rotateZ(vec.rotateY(vec.rotateX(Vec3f{0, 1, 0}, -rotation[0]), -rotation[1]), -rotation[2]);
viewMatrix = Mat4f.identity().mul(Mat4f.rotationX(rotation[0])).mul(Mat4f.rotationY(rotation[1])).mul(Mat4f.rotationZ(rotation[2]));
}
};

Expand Down Expand Up @@ -295,6 +295,9 @@ pub const Player = struct { // MARK: Player
pub var eyePos: Vec3d = .{0, 0, 0};
pub var eyeVel: Vec3d = .{0, 0, 0};
pub var eyeStep: @Vector(3, bool) = .{false, false, false};
pub var bobTime: f64 = 0;
pub var bobVel: f64 = 0;
pub var bobMag: f64 = 0;
pub var id: u32 = 0;
pub var isFlying: Atomic(bool) = Atomic(bool).init(false);
pub var isGhost: Atomic(bool) = Atomic(bool).init(false);
Expand Down Expand Up @@ -345,6 +348,23 @@ pub const Player = struct { // MARK: Player
return eyePos + super.pos + desiredEyePos;
}

pub fn applyViewBobbingPosBlocking(pos: Vec3d) Vec3d {
mutex.lock();
defer mutex.unlock();
const xBob = @sin(bobTime) * 0.5 * 0.05; // Horizontal Component
const zBob = -@abs(@cos(bobTime)) * 0.05; // Vertical Component
const bobVec = vec.rotateZ(Vec3d{ xBob * bobMag, 0, zBob * bobMag }, -camera.rotation[2]);
return pos + bobVec;
}

pub fn applyViewBobbingRotBlocking(rot: Vec3f) Vec3f {
mutex.lock();
defer mutex.unlock();
const xRot: f32 = @as(f32, @floatCast(@cos(bobTime + 0.20) * -0.005 * bobMag));
const yRot: f32 = @as(f32, @floatCast(@sin(bobTime) * 0.003 * bobMag));
return rot + Vec3f{ xRot, yRot, 0 };
}

pub fn getVelBlocking() Vec3d {
mutex.lock();
defer mutex.unlock();
Expand Down Expand Up @@ -665,6 +685,19 @@ pub fn update(deltaTime: f64) void { // MARK: update()
Player.selectedSlot = @intCast(@mod(newSlot, 12));
main.Window.scrollOffset = 0;
}

// View Bobbing
// Smooth lerping of bobTime with framerate independent damping
const fac: f64 = 1 - std.math.exp(-10 * deltaTime);
var targetBobVel: f64 = 0;
if (movementSpeed > 0) {
targetBobVel = vec.length(vec.xy(Player.getVelBlocking()) * @as(vec.Vec2d, @splat(std.math.pow(f64, movementSpeed / 4, 0.7)))) / movementSpeed;
}
Player.bobVel = Player.bobVel * (1 - fac) + targetBobVel * fac;
if (Player.onGround) { // No view bobbing in the air
Player.bobTime += std.math.pow(f64, Player.bobVel, 0.7) * 8 * deltaTime;
}
Player.bobMag = @min(@sqrt(Player.bobVel), 2) * settings.viewBobStrength;

// This our model for movement on a single frame:
// dv/dt = a - λ·v
Expand Down
10 changes: 10 additions & 0 deletions src/gui/windows/graphics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ fn resolutionScaleCallback(newValue: u16) void {
main.Window.GLFWCallbacks.framebufferSize(null, main.Window.width, main.Window.height);
}

fn viewBobbingCallback(newValue: f32) void {
settings.viewBobStrength = newValue;
settings.save();
}

fn viewBobbingFormatter(allocator: main.utils.NeverFailingAllocator, value: f32) []const u8 {
return std.fmt.allocPrint(allocator.allocator, "#ffffffView Bobbing Strength: {d:.2}", .{value}) catch unreachable;
}

pub fn onOpen() void {
const list = VerticalList.init(.{padding, 16 + padding}, 300, 16);
list.add(ContinuousSlider.init(.{0, 0}, 128, 10.0, 154.0, @floatFromInt(settings.fpsCap orelse 144), &fpsCapCallback, &fpsCapFormatter));
Expand All @@ -108,6 +117,7 @@ pub fn onOpen() void {
list.add(CheckBox.init(.{0, 0}, 128, "Vertical Synchronization", settings.vsync, &vsyncCallback));
list.add(DiscreteSlider.init(.{0, 0}, 128, "#ffffffAnisotropic Filtering: ", "{}x", &anisotropy, switch(settings.anisotropicFiltering) {1 => 0, 2 => 1, 4 => 2, 8 => 3, 16 => 4, else => 2}, &anisotropicFilteringCallback));
list.add(DiscreteSlider.init(.{0, 0}, 128, "#ffffffResolution Scale: ", "{}%", &resolutions, @as(u16, @intFromFloat(@log2(settings.resolutionScale) + 2.0)), &resolutionScaleCallback));
list.add(ContinuousSlider.init(.{0, 0}, 128, 0.0, 1.0, settings.viewBobStrength, &viewBobbingCallback, &viewBobbingFormatter));
list.finish(.center);
window.rootComponent = list.toComponent();
window.contentSize = window.rootComponent.?.pos() + window.rootComponent.?.size() + @as(Vec2f, @splat(padding));
Expand Down
2 changes: 1 addition & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ pub fn main() void { // MARK: main()
if(!isHidden) {
c.glEnable(c.GL_CULL_FACE);
c.glEnable(c.GL_DEPTH_TEST);
renderer.render(game.Player.getEyePosBlocking());
renderer.render();
// Render the GUI
gui.windowlist.gpu_performance_measuring.startQuery(.gui);
c.glDisable(c.GL_CULL_FACE);
Expand Down
43 changes: 28 additions & 15 deletions src/renderer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ pub fn updateViewport(width: u31, height: u31, fov: f32) void {
worldFrameBuffer.unbind();
}

pub fn render(playerPosition: Vec3d) void {
// TODO: player bobbing
pub fn render() void {
if(game.world) |world| {
// TODO: Handle colors and sun position in the world.
var ambient: Vec3f = undefined;
Expand All @@ -132,7 +131,7 @@ pub fn render(playerPosition: Vec3d) void {
const skyColor = vec.xyz(world.clearColor);
game.fog.skyColor = skyColor;

renderWorld(world, ambient, skyColor, playerPosition);
renderWorld(world, ambient, skyColor);
const startTime = std.time.milliTimestamp();
mesh_storage.updateMeshes(startTime + maximumMeshTime);
} else {
Expand Down Expand Up @@ -164,12 +163,27 @@ pub fn crosshairDirection(rotationMatrix: Mat4f, fovY: f32, width: u31, height:
return adjusted;
}

pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPos: Vec3d) void { // MARK: renderWorld()
pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f) void { // MARK: renderWorld()
worldFrameBuffer.bind();
c.glViewport(0, 0, lastWidth, lastHeight);
gpu_performance_measuring.startQuery(.clear);
worldFrameBuffer.clear(Vec4f{skyColor[0], skyColor[1], skyColor[2], 1});
gpu_performance_measuring.stopQuery();

const playerPos = game.Player.getEyePosBlocking();
const playerRot = game.camera.rotation;
const cameraPos = game.Player.applyViewBobbingPosBlocking(playerPos);
const cameraRot = game.Player.applyViewBobbingRotBlocking(playerRot);

// Get view matrix before view bobbing is applied
game.camera.updateViewMatrix();
const baseViewMatrix = game.camera.viewMatrix;

const oldCameraRot = game.camera.rotation;
defer game.camera.rotation = oldCameraRot;
game.camera.rotation = cameraRot;

// View matrix after view bobbing is applied
game.camera.updateViewMatrix();

// Uses FrustumCulling on the chunks.
Expand All @@ -181,10 +195,9 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
gpu_performance_measuring.startQuery(.animation);
blocks.meshes.preProcessAnimationData(time);
gpu_performance_measuring.stopQuery();


// Update the uniforms. The uniforms are needed to render the replacement meshes.
chunk_meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight, playerPos);
chunk_meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight, cameraPos);

c.glActiveTexture(c.GL_TEXTURE0);
blocks.meshes.blockTextureArray.bind();
Expand All @@ -199,9 +212,9 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
const meshes = mesh_storage.updateAndGetRenderChunks(world.conn, playerPos, settings.renderDistance);

gpu_performance_measuring.startQuery(.chunk_rendering_preparation);
const direction = crosshairDirection(game.camera.viewMatrix, lastFov, lastWidth, lastHeight);
const direction = crosshairDirection(baseViewMatrix, lastFov, lastWidth, lastHeight);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we are using crosshairDirection instead of just game.camera.direction? game.camera.direction seems to do the same without having to extract the direction from the view matrix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#323
Try moving the crosshair in menu mode. Check if block selection is still accurate after removing this code.

MeshSelection.select(playerPos, direction, game.Player.inventory__SEND_CHANGES_TO_SERVER.items[game.Player.selectedSlot]);
MeshSelection.render(game.projectionMatrix, game.camera.viewMatrix, playerPos);
MeshSelection.render(game.projectionMatrix, game.camera.viewMatrix, cameraPos);

chunk_meshing.beginRender();

Expand All @@ -212,13 +225,13 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
}
gpu_performance_measuring.stopQuery();
if(chunkList.items.len != 0) {
chunk_meshing.drawChunksIndirect(chunkList.items, game.projectionMatrix, ambientLight, playerPos, false);
chunk_meshing.drawChunksIndirect(chunkList.items, game.projectionMatrix, ambientLight, cameraPos, false);
}

gpu_performance_measuring.startQuery(.entity_rendering);
entity.ClientEntityManager.render(game.projectionMatrix, ambientLight, .{1, 0.5, 0.25}, playerPos);
entity.ClientEntityManager.render(game.projectionMatrix, ambientLight, .{1, 0.5, 0.25}, cameraPos);

itemdrop.ItemDropRenderer.renderItemDrops(game.projectionMatrix, ambientLight, playerPos, time);
itemdrop.ItemDropRenderer.renderItemDrops(game.projectionMatrix, ambientLight, cameraPos, time);
gpu_performance_measuring.stopQuery();

// Render transparent chunk meshes:
Expand All @@ -237,11 +250,11 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
while(true) {
if(i == 0) break;
i -= 1;
meshes[i].prepareTransparentRendering(playerPos, &chunkList);
meshes[i].prepareTransparentRendering(cameraPos, &chunkList);
}
gpu_performance_measuring.stopQuery();
if(chunkList.items.len != 0) {
chunk_meshing.drawChunksIndirect(chunkList.items, game.projectionMatrix, ambientLight, playerPos, true);
chunk_meshing.drawChunksIndirect(chunkList.items, game.projectionMatrix, ambientLight, cameraPos, true);
}
}
c.glDepthMask(c.GL_TRUE);
Expand Down Expand Up @@ -287,7 +300,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo

c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0);

entity.ClientEntityManager.renderNames(game.projectionMatrix, playerPos);
entity.ClientEntityManager.renderNames(game.projectionMatrix, cameraPos);
gpu_performance_measuring.stopQuery();
}

Expand Down Expand Up @@ -556,7 +569,7 @@ pub const MenuBackGround = struct {
// Draw to frame buffer.
buffer.bind();
c.glClear(c.GL_DEPTH_BUFFER_BIT | c.GL_STENCIL_BUFFER_BIT | c.GL_COLOR_BUFFER_BIT);
main.renderer.render(game.Player.getEyePosBlocking());
main.renderer.render();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that your method will now always apply bobbing to any render call.
This call however requires a stable camera, otherwise the recorded background image will contain seems.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure what you mean here in terms of background seams, but yes, view bobbing will apply to all render calls that also render the world, although shouldn't happen on the title screen as that's a different code path. The only thing I can think that could be done differently would be to let the camera store its own position, so no player-related data needs to be passed to the renderer, and then handle the special case of the block selection somehow as we don't want to have view bobbing affect it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, decoupling the player and the camera position might be a good idea generally, as it would allow for third person/spectator and other camera effects to be handled in some update loop instead of directly in the rendering code.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, decoupling the player and the camera position might be a good idea generally, as it would allow for third person/spectator and other camera effects to be handled in some update loop instead of directly in the rendering code.

Hard agree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure what you mean here in terms of background seams

If you press Ctrl+print screen, then the game records a background image at your location. This is done by rendering the screen 4 times with a 90° angle in between. If your camera is angled from view bobbing, then this means that these 4 images are rotated, and don't fit together seamlessly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still broken. If I record a background image while moving then it contains seams between the recorded images. Here take a look:
client_1146

// Copy the pixels directly from OpenGL
buffer.bind();
c.glReadPixels(0, 0, size, size, c.GL_RGBA, c.GL_UNSIGNED_BYTE, pixels.ptr);
Expand Down
2 changes: 2 additions & 0 deletions src/settings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub var musicVolume: f32 = 1;

pub var leavesQuality: u16 = 2;

pub var viewBobStrength: f32 = 0.5;


pub var storageTime: i64 = 5000;

Expand Down