godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
86.37k stars 19.24k forks source link

INV_PROJECTION_MATRIX incorrect for orthogonal cameras #68878

Open alekseym88 opened 1 year ago

alekseym88 commented 1 year ago

Godot version

4.0.dev (84c404f6bc) and same for 4.0b4

System information

MacOS Monterey 12.0.1 Intel Iris Graphics 550 1536 MB

Issue description

When I'm trying to use VIEW_MATRIX transform to change world pos to view pos, it seems get not so correct data. Probably it's due to wrong scaling in .w part of matrix. I'm using orthogonal camera, but it will be the same for perspective

Steps to reproduce

  1. Add camera and plane in your project, add to plane this shader:
    
    shader_type spatial;
    render_mode unshaded;

void fragment() {

vec3 clip_uv = vec3(SCREEN_UV * 2.0 - 1.0, 0.0);
vec4 view_space_uv = INV_PROJECTION_MATRIX * vec4(clip_uv, 1.0);
vec3 view_uv = view_space_uv.xyz/view_space_uv.w;

vec3 pos = vec3(2.0, 2.0, 0.0); // take it as world_space coordinate

vec4 view_space_pos = VIEW_MATRIX * vec4(pos, 1.0);
vec3 view_pos = view_space_pos.xyz/view_space_pos.w;

vec4 clip_space = PROJECTION_MATRIX * VIEW_MATRIX * vec4(pos, 1.0);
vec3 clip_pos = clip_space.xyz/clip_space.w;

ALBEDO = view_uv - view_pos;

    // to see where in camera view our point
if(distance(clip_pos, clip_uv) < 0.05){
    ALBEDO = vec3(1.0, 0.0, 1.0); 
}

}

we are trying to shift screen uv in view space by our pos value in view space, but result is not correct:

<img width="761" alt="Screenshot 2022-11-19 at 14 00 48" src="https://user-images.githubusercontent.com/4573901/202837669-82439f87-9e79-4c3c-890e-5ca0d91dad0e.png">

2. But if you transform pos to clip space first and than bring it back to view space:

shader_type spatial; render_mode unshaded;

void fragment() {

vec3 pos = vec3(2.0, 2.0, 0.0); 

vec3 clip_uv = vec3(SCREEN_UV * 2.0 - 1.0, 0.0);

vec4 view_space_uv = INV_PROJECTION_MATRIX * vec4(clip_uv, 1.0);
vec3 view_uv = view_space_uv.xyz/view_space_uv.w;

vec4 view_space_pos = VIEW_MATRIX * vec4(pos, 1.0);
vec3 view_pos = view_space_pos.xyz/view_space_pos.w;

vec4 clip_space = PROJECTION_MATRIX * VIEW_MATRIX * vec4(pos, 1.0);
vec3 clip_pos = clip_space.xyz/clip_space.w;

//ALBEDO = view_uv - view_pos;

vec4 view_test = INV_PROJECTION_MATRIX * vec4(clip_pos, 1.0);
vec3 pos_test = view_test.xyz/view_test.w;

ALBEDO = view_uv - pos_test;

    // to see where in camera view our point
if(distance(clip_pos, clip_uv) < 0.05){
    ALBEDO = vec3(1.0, 0.0, 0.0);
}

}


it will shift uv correctly:
<img width="763" alt="Screenshot 2022-11-19 at 14 02 33" src="https://user-images.githubusercontent.com/4573901/202837757-dfd069ca-3e76-4bf6-ad8f-a68149a71aaf.png">

but you can notice, that it's a bit not so straight, but scale of uv and pos in view space are about correct now

### Minimal reproduction project

[test_pos.zip](https://github.com/godotengine/godot/files/10046386/test_pos.zip)
Calinou commented 1 year ago

Can you reproduce this in 3.5.1?

clayjohn commented 1 year ago

I think the issue here is actually with the INV_PROJECTION_MATRIX. If you for ALBEDO = clip_uv - clip_pos; then the UVs offset as expected. Additionally, VIEW_MATRIX and PROJECTION_MATRIX are both used when rendering any object. If either one of them was broken objects would not render correctly. INV_PROJECTION_MATRIX however, is only exposed to users, so problems with it only appear in custom shaders

Edit: Okay I can confirm something is off in INV_PROJECTION_MATRIX Taking a value from world space into view space and back leaves the value unchanged. But taking something from view space into clip space and back changes the value.

Edit: And after further testing, the issue is only present for orthogonal cameras, perspective cameras work fine. This explains why this hasn't been caught earlier.

alekseym88 commented 1 year ago

@clayjohn yes, confirm this. As a temporary solution can use inverse(PROJECTION_MATRIX) instead INV_PROJECTION_MATRIX, and result looks fine:

Screenshot 2022-11-20 at 10 19 32
Scony commented 1 year ago

I was just implementing full screen quad and lost 2h of debugging due to this. Definitely INV_PROJECTION_MATRIX is broken under orthogonal camera (it's fine under projection camera).

With the inverse(PROJECTION_MATRIX) it's fine.

Code:

shader_type spatial;
render_mode unshaded;

uniform vec4 color : source_color = vec4(0.0);

void vertex()
{
  POSITION = vec4(VERTEX, 1);
}

void fragment()
{
  ALBEDO = color.rgb;
  ALPHA = 0.9;

  // depth is encoded on first channel and is <0; 1>
  float depth = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).x;
  // transform depth from <0; 1> value to corresponding point in projection space <-1,-1,0; 1,1,1>*
  // * note that projection space in Vulkan is different than in OpenGL
  vec3 normalized_device_coordinates = vec3(SCREEN_UV * 2.0 - 1.0, depth);
  // move depth point from projection space to world space
  // * note that INV_PROJECTION_MATRIX is broken atm. so, let's use inverse(PROJECTION_MATRIX)
  vec4 world_position =
      INV_VIEW_MATRIX * inverse(PROJECTION_MATRIX) * vec4(normalized_device_coordinates, 1.0);
  world_position.xyz /= world_position.w;

CC @cybereality

npip99 commented 7 months ago

I just lost some time debugging as well, but thankfully I found this by googling problems with orthogonal cameras instead of binary searching my shader code to find the issue (Which is quite hard to do since the only debug output you can make are colors).

I feel like INV_PROJECTION_MATRIX being broken without explanation is a pretty major issue, orthogonal cameras are just broken for any water shaders that use it for depth. Or really, any shader that uses linear depth for any purpose not just water.

At the minimum can we get a more descriptive error? ("Do not use INV_PROJECTION_MATRIX with orthogonal cameras; use inverse(PROJECTION_MATRIX) instead"). But maybe that's harder to code than just fixing the issue, I'm not sure. For now, this bug without any warning / documentation is a bit rough.