From e30dc9ecc753bf7a5aa7cfcf72ffb8248edcb5ec Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sun, 5 Jan 2025 09:57:48 -0500 Subject: [PATCH] [graphics] Improve some camera math (#3825) I finally went through and worked out the math for the camera matrix, and improved how it works for PC rendering. I was able to finally avoid the double perspective divide issue, which I always thought would cause accuracy issues. This will help tfrag, tie (no envmap), shrub, and hfrag have less z-fighting in cases where the camera and the thing you're looking at are pretty close, but the entire level is far from the origin - like jak 3 temple. I was able to modify the camera matrix so we don't have to do all the weird scaling/addition in the shader. Here's a screenshot from the temple oracle checkpoint, cropped from 4k. This used to have a lot of fighting issues. ![image](https://github.com/user-attachments/assets/4e11157f-ccf5-4f14-98a9-4a0b34da0cd2) It doesn't help issues where the thing you're looking at is very far away (jak 1 mountains, some jak 2 city stuff). It also doesn't help with jak's skirt/scarf, since those use a different renderer. There's definitely more to do here, but this is a good starting point and proof that I can at least figure out the math. Co-authored-by: water111 --- .../background/background_common.cpp | 90 +++++++++++++++++++ .../opengl_renderer/shaders/hfrag.vert | 28 ++---- .../opengl_renderer/shaders/merc2.vert | 11 --- .../opengl_renderer/shaders/shrub.vert | 28 ++---- .../opengl_renderer/shaders/tfrag3.vert | 30 ++----- 5 files changed, 114 insertions(+), 73 deletions(-) diff --git a/game/graphics/opengl_renderer/background/background_common.cpp b/game/graphics/opengl_renderer/background/background_common.cpp index d3e85fb0d1..b3f9f6ca56 100644 --- a/game/graphics/opengl_renderer/background/background_common.cpp +++ b/game/graphics/opengl_renderer/background/background_common.cpp @@ -171,6 +171,68 @@ DoubleDraw setup_tfrag_shader(SharedRenderState* render_state, DrawMode mode, Sh return draw_settings; } +std::array make_new_cam_mat(const math::Vector4f cam_T_w[4], + const math::Vector4f persp[4], + float fog_constant, + float hvdf_z) { + // renderers may eventually have tricks to do things in local coordinates - so use the convention + // that the shader has already subtracted off the camera translation from the vertex position. + // (I think this could help with accuracy too, since you aren't rotating and subtracting two large + // vectors that are very close to each other) + + // this is the perspective x-scaling. This is used to map to a 256-pixel buffer. + const float game_pxx = persp[0][0]; + // on PC, OpenGL uses normalized coordinates for drawing, so divide by the pixel width. + // in the game, the perspective divide includes a multiplication by the fog constant, for PC, + // just include this multiply here so we can let OpenGL do the perspective multiply. + const float pc_pxx = fog_constant * game_pxx / 256.f; + + // this is the perspective y-scaling. + const float game_pyy = persp[1][1]; + // same logic as y - there's a later SCISSOR scaling in the shader that expects this ratio. + const float pc_pyy = -fog_constant * game_pyy / 128.f; + + // the depth is considered twice. Once, as the value to write into the depth buffer, which is + // scaled for PC here: + const float depth_scale = fog_constant * persp[2][2] / 8388608; + + // and once as the value used for perspective divide + const float game_pzw = persp[2][3]; + const float game_depth_offset = persp[3][2]; + + // set up PC scaling values + math::Vector3f persp_scale(pc_pxx, pc_pyy, depth_scale); + + // it turns out that shifting the depth buffer to line up with OpenGL is equivalent to adding + // transformed.w * (hvdf_z / 8388608.f - 1.f) to the depth value. We know that w is just depth * + // pzw, so we can include the effect here: + const float pc_z_offset = (hvdf_z / 8388608.f - 1.f); + persp_scale.z() += pc_z_offset * game_pzw; + + std::array result; + for (auto& x : result) { + x.set_zero(); + } + + // fill out the upper 3x3 - simply scale the rotation matrix by the perspective scale. + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + result[row][col] = cam_T_w[row][col] * persp_scale[col]; + } + } + + // fill out the right most column. This converts world-space points to depth for divide, scaled by + // pzw. for now, copy the game. + for (int row = 0; row < 3; row++) { + result[row][3] = cam_T_w[row][2] * game_pzw; + } + + // depth buffer offset - now needs to be scaled by the PC depth buffer scaling too + result[3][2] = fog_constant * game_depth_offset / 8388608; + + return result; +} + void first_tfrag_draw_setup(const GoalBackgroundCameraData& settings, SharedRenderState* render_state, ShaderId shader) { @@ -181,8 +243,36 @@ void first_tfrag_draw_setup(const GoalBackgroundCameraData& settings, glUniform1i(glGetUniformLocation(id, "decal"), false); glUniform1i(glGetUniformLocation(id, "tex_T0"), 0); glUniformMatrix4fv(glGetUniformLocation(id, "camera"), 1, GL_FALSE, settings.camera[0].data()); + + auto newcam = + make_new_cam_mat(settings.rot, settings.perspective, settings.fog.x(), settings.hvdf_off.z()); + + /* + fmt::print("camera:\n{}\n{}\n{}\n{}\n", settings.camera[0].to_string_aligned(), + settings.camera[1].to_string_aligned(), settings.camera[2].to_string_aligned(), + settings.camera[3].to_string_aligned()); + + fmt::print("camera2:\n{}\n{}\n{}\n{}\n", newcam[0].to_string_aligned(), + newcam[1].to_string_aligned(), newcam[2].to_string_aligned(), + newcam[3].to_string_aligned()); + + fmt::print("persp:\n{}\n{}\n{}\n{}\n", settings.perspective[0].to_string_aligned(), + settings.perspective[1].to_string_aligned(), + settings.perspective[2].to_string_aligned(), + settings.perspective[3].to_string_aligned()); + fmt::print("rot:\n{}\n{}\n{}\n{}\n", settings.rot[0].to_string_aligned(), + settings.rot[1].to_string_aligned(), settings.rot[2].to_string_aligned(), + settings.rot[3].to_string_aligned()); + fmt::print("ctrans: {}\n", settings.trans.to_string_aligned()); + fmt::print("hvdf: {}\n", settings.hvdf_off.to_string_aligned()); + */ + + glUniformMatrix4fv(glGetUniformLocation(id, "pc_camera"), 1, GL_FALSE, newcam[0].data()); + glUniform4f(glGetUniformLocation(id, "hvdf_offset"), settings.hvdf_off[0], settings.hvdf_off[1], settings.hvdf_off[2], settings.hvdf_off[3]); + glUniform4f(glGetUniformLocation(id, "cam_trans"), settings.trans[0], settings.trans[1], + settings.trans[2], settings.trans[3]); glUniform1f(glGetUniformLocation(id, "fog_constant"), settings.fog.x()); glUniform1f(glGetUniformLocation(id, "fog_min"), settings.fog.y()); glUniform1f(glGetUniformLocation(id, "fog_max"), settings.fog.z()); diff --git a/game/graphics/opengl_renderer/shaders/hfrag.vert b/game/graphics/opengl_renderer/shaders/hfrag.vert index be46fe4e53..676b3a51d3 100644 --- a/game/graphics/opengl_renderer/shaders/hfrag.vert +++ b/game/graphics/opengl_renderer/shaders/hfrag.vert @@ -6,7 +6,8 @@ layout (location = 2) in ivec2 uv; layout (location = 3) in int vi; uniform vec4 hvdf_offset; -uniform mat4 camera; +uniform vec4 cam_trans; +uniform mat4 pc_camera; uniform float fog_constant; uniform float fog_min; uniform float fog_max; @@ -25,27 +26,14 @@ void main() { tex_coord.x = (uv.x == 1) ? 1.f : 0.f; tex_coord.y = (uv.y == 1) ? 1.f : 0.f; - vec4 transformed = -camera[3]; - transformed -= camera[0] * 32768.f * vx; - transformed -= camera[1] * position_in; - transformed -= camera[2] * 32768.f * vz; + vec3 vert = position_in - cam_trans.xyz; + vec4 transformed = -pc_camera[3]; + transformed -= pc_camera[0] * (32768.f * vx - cam_trans.x); + transformed -= pc_camera[1] * (position_in - cam_trans.y); + transformed -= pc_camera[2] * (32768.f * vz - cam_trans.z); - float Q = fog_constant / transformed.w; + fogginess = 255 - clamp(-transformed.w + hvdf_offset.w, fog_min, fog_max); - fogginess = 255 - clamp(-transformed.w + hvdf_offset.w, fog_min, fog_max); - - transformed.xyz *= Q; - // offset - transformed.xyz += hvdf_offset.xyz; - // correct xy offset - transformed.xy -= (2048.); - // correct z scale - transformed.z /= (8388608); - transformed.z -= 1; - // correct xy scale - transformed.x /= (256); - transformed.y /= -(128); - transformed.xyz *= transformed.w; // scissoring area adjust transformed.y *= SCISSOR_ADJUST * HEIGHT_SCALE; gl_Position = transformed; diff --git a/game/graphics/opengl_renderer/shaders/merc2.vert b/game/graphics/opengl_renderer/shaders/merc2.vert index fa591e5436..4eeb08801f 100644 --- a/game/graphics/opengl_renderer/shaders/merc2.vert +++ b/game/graphics/opengl_renderer/shaders/merc2.vert @@ -56,17 +56,6 @@ maddz.xyzw vf26, vf28, vf25 ``` */ void main() { - // vec4 transformed = -perspective3.xyzw; - // transformed += -perspective0 * position_in.x; - // transformed += -perspective1 * position_in.y; - // transformed += -perspective2 * position_in.z; - - - // vec4 transformed = -hmat3.xyzw; - // transformed += -hmat0 * position_in.x; - // transformed += -hmat1 * position_in.y; - // transformed += -hmat2 * position_in.z; - vec4 p = vec4(position_in, 1); vec4 vtx_pos = -bones[mats[0]].X * p * weights_in[0]; vec3 rotated_nrm = bones[mats[0]].R * normal_in * weights_in[0]; diff --git a/game/graphics/opengl_renderer/shaders/shrub.vert b/game/graphics/opengl_renderer/shaders/shrub.vert index 51a6ca6cc7..2e14f98e61 100644 --- a/game/graphics/opengl_renderer/shaders/shrub.vert +++ b/game/graphics/opengl_renderer/shaders/shrub.vert @@ -11,6 +11,8 @@ uniform float fog_constant; uniform float fog_min; uniform float fog_max; uniform int decal; +uniform vec4 cam_trans; +uniform mat4 pc_camera; uniform sampler1D tex_T10; // note, sampled in the vertex shader on purpose. out vec4 fragment_color; @@ -33,31 +35,15 @@ void main() { // the itof0 is done in the preprocessing step. now we have floats. // Step 3, the camera transform - vec4 transformed = -camera[3]; - transformed -= camera[0] * position_in.x; - transformed -= camera[1] * position_in.y; - transformed -= camera[2] * position_in.z; - - // compute Q - float Q = fog_constant / transformed.w; + vec3 vert = position_in - cam_trans.xyz; + vec4 transformed = -pc_camera[3]; + transformed -= pc_camera[0] * vert.x; + transformed -= pc_camera[1] * vert.y; + transformed -= pc_camera[2] * vert.z; // do fog! fogginess = 255 - clamp(-transformed.w + hvdf_offset.w, fog_min, fog_max); - // perspective divide! - transformed.xyz *= Q; - // offset - transformed.xyz += hvdf_offset.xyz; - // correct xy offset - transformed.xy -= (2048.); - // correct z scale - transformed.z /= (8388608); - transformed.z -= 1; - // correct xy scale - transformed.x /= (256); - transformed.y /= -(128); - // hack - transformed.xyz *= transformed.w; // scissoring area adjust transformed.y *= SCISSOR_ADJUST * HEIGHT_SCALE; gl_Position = transformed; diff --git a/game/graphics/opengl_renderer/shaders/tfrag3.vert b/game/graphics/opengl_renderer/shaders/tfrag3.vert index 59e810752f..99ce303c7a 100644 --- a/game/graphics/opengl_renderer/shaders/tfrag3.vert +++ b/game/graphics/opengl_renderer/shaders/tfrag3.vert @@ -5,6 +5,8 @@ layout (location = 1) in vec3 tex_coord_in; layout (location = 2) in int time_of_day_index; uniform vec4 hvdf_offset; +uniform vec4 cam_trans; +uniform mat4 pc_camera; uniform mat4 camera; uniform float fog_constant; uniform float fog_min; @@ -31,32 +33,18 @@ void main() { // the itof0 is done in the preprocessing step. now we have floats. - // Step 3, the camera transform - vec4 transformed = -camera[3]; - transformed -= camera[0] * position_in.x; - transformed -= camera[1] * position_in.y; - transformed -= camera[2] * position_in.z; - // compute Q - float Q = fog_constant / transformed.w; + // Step 3, the camera transform + vec3 vert = position_in - cam_trans.xyz; + vec4 transformed = -pc_camera[3]; + transformed.w = 0; + transformed -= pc_camera[0] * vert.x; + transformed -= pc_camera[1] * vert.y; + transformed -= pc_camera[2] * vert.z; // do fog! fogginess = 255 - clamp(-transformed.w + hvdf_offset.w, fog_min, fog_max); - // perspective divide! - transformed.xyz *= Q; - // offset - transformed.xyz += hvdf_offset.xyz; - // correct xy offset - transformed.xy -= (2048.); - // correct z scale - transformed.z /= (8388608); - transformed.z -= 1; - // correct xy scale - transformed.x /= (256); - transformed.y /= -(128); - // hack - transformed.xyz *= transformed.w; // scissoring area adjust transformed.y *= SCISSOR_ADJUST * HEIGHT_SCALE; gl_Position = transformed;