Skip to content

Commit

Permalink
[graphics] Improve some camera math (#3825)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
water111 and water111 authored Jan 5, 2025
1 parent e0acc2d commit e30dc9e
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 75 deletions.
90 changes: 90 additions & 0 deletions game/graphics/opengl_renderer/background/background_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,68 @@ DoubleDraw setup_tfrag_shader(SharedRenderState* render_state, DrawMode mode, Sh
return draw_settings;
}

std::array<math::Vector4f, 4> 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<math::Vector4f, 4> 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) {
Expand All @@ -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());
Expand Down
32 changes: 10 additions & 22 deletions game/graphics/opengl_renderer/shaders/hfrag.vert
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

float Q = fog_constant / transformed.w;

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;
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);

fogginess = 255 - clamp(-transformed.w + hvdf_offset.w, fog_min, fog_max);

// scissoring area adjust
transformed.y *= SCISSOR_ADJUST * HEIGHT_SCALE;
gl_Position = transformed;
Expand Down
11 changes: 0 additions & 11 deletions game/graphics/opengl_renderer/shaders/merc2.vert
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
28 changes: 7 additions & 21 deletions game/graphics/opengl_renderer/shaders/shrub.vert
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
30 changes: 9 additions & 21 deletions game/graphics/opengl_renderer/shaders/tfrag3.vert
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down

0 comments on commit e30dc9e

Please sign in to comment.