Discrepency between get_camera_projection() and PROJECTION_MATRIX

Godot Version

Godot Engine v4.2.2.stable.mono.official.15073afe3

Issue

I have a simple project in which there is a camera pointing at a mesh with a shader attached to it. There is a global shader uniform CAMERA_PROJECTION of type mat4, which the camera sets on _ready with the value get_camera_projection() like so:

func _ready():
	RenderingServer.global_shader_parameter_set("CAMERA_PROJECTION", get_camera_projection())

The shader for the mesh looks like this:

shader_type spatial;

global uniform mat4 CAMERA_PROJECTION;
uniform sampler2D color_tex : source_color;

void vertex() {
	POSITION  = (CAMERA_PROJECTION * MODELVIEW_MATRIX * vec4(VERTEX, 1.0));
}

void fragment() {
	ALBEDO = texture(color_tex, UV).rgb;
}

My assumption was that using the uniform CAMERA_PROJECTION here would be identical to using the shader built-in PROJECTION_MATRIX since I am simply passing the camera’s projection matrix directly to the shader. However that is not what happens. I did this simple test with an orthogonal camera and a perspective camera and they had different results (neither of which line up like I was hoping they would).

When I did this for a perspective camera, the output (of the shader projecting with CAMERA_PROJECTION) was flipped along the y-axis but was otherwise identical.

When using an orthogonal camera, the mesh simply does not appear on screen.

Is there something I’m misunderstanding about these concepts? I printed the results of get_camera_projection() in the camera’s _ready function and checked by hand that they were correct. So I’m confused how things go wrong when passed to the shader.

Btw, I tried printing RenderingServer.global_shader_parameter_get("CAMERA_PROJECTION") but it is always null, even in calls during the camera’s _process method.

Any help with this would be appreciated. Thanks!

I ran into this same problem while working with compute shaders. I went into the Godot source code and found it’s doing a correction to the projection matrix. It’s converting it from a projection matrix for an OpenGL clip space to a Vulkan clip space.

// copied from Godot source code method RasterizerSceneGLES3::_setup_environment at /drivers/gles3/rasterizer_scene_gles3.cpp, line 1538
Projection correction;
correction.set_depth_correction(p_flip_y, true, false);
Projection projection = correction * p_render_data->cam_projection;

Anyways there is a similar function to make correction matrixes in GDScript. Sadly it is missing a few arguments. For my particular case it was insufficient, so I just translated the c++ method into c#. Anyways this is what my solution ended up looking like roughly.

var correction = CreateDepthCorrection(true, true, true);
var projection = correction * camera.GetCameraProjection();

Projection CreateDepthCorrection(bool flipY, bool reverseZ, bool remapZ)
{
    // translated from the Godot source code method Projection::set_depth_correction at /core/math/projection.cpp, line 787
    return new Projection(
        new Vector4(1f, 0f, 0f, 0f), 
        new Vector4(0f, (!flipY) ? 1 : (-1), 0f, 0f), 
        new Vector4(0f, 0f, remapZ ? (reverseZ ? -0.5f : 0.5f) : (reverseZ ? -1.0f : 1.0f), 0f), 
        new Vector4(0f, 0f, remapZ ? 0.5f : 0.0f, 1f));
}

Anyways, only two years late…