UnityФорумПрограммирование

Получение мировой позиции из карты глубины в Unity

#0
18:56, 8 апр 2020

Всем привет.
Реализовываю deffered shading на юнити, надо получить мировые координаты из координат экрана и буфера глубины.
Тема обсуждена миллион раз, тривиальна и даже спрашивать как-то неудобно. Но что-то у меня идёт не так:
Вот что стандартный выдаёт шейдер если выводить в качестве выходного света

float4(i.pos_world.y, 0, 0, 1.0)
Изображение

Тут всё как надо.
Теперь пытаемся повторить то же самое с получением координаты из карты глубины в пост процесс шейдере:

1. Передаём в него обратные projective и view матрицы:

        Matrix4x4 PI = cam.projectionMatrix.inverse;
        Matrix4x4 VI = cam.worldToCameraMatrix.inverse;

        material.SetMatrix("PI", PI);
        material.SetMatrix("VI", VI);

2. Вычисляем, собственно, мировую позицию в шейдере:

float3 screenToWorld(float2 uv) {
   float depth = tex2D(_CameraDepthTexture, uv).x;
   float z = depth * 2.0 - 1.0;

   float4 clipSpacePosition = float4(uv.x * 2.0 - 1.0, uv.y * 2.0 - 1.0, z, 1.0);
   float4 viewSpacePosition = mul(PI, clipSpacePosition);
   viewSpacePosition /= viewSpacePosition.w;

   float4 worldSpacePosition = mul(VI, viewSpacePosition);

   return worldSpacePosition.xyz;
}

Вместо результата как на первой картинке получается просто залитый красным экран.
Текстуру глубины проверил - там вроде всё в порядке.
Матрицы тоже посмотрел. Не знаю в чём причина.
Спасибо заранее за ваше время.

#1
19:07, 8 апр 2020

https://forum.unity.com/threads/world-position-from-depth.151466/

Такой пример смотрел?

KaronatoR
> Matrix4x4 PI = cam.projectionMatrix.inverse;
> Matrix4x4 VI = cam.worldToCameraMatrix.inverse;

Достаточно одной матрицы. Вычисляешь луч от позиции камеры и умножаешь его на глубину.

+ script
+ shader

В surface шейдере вычисляется worldPos автоматом, через него без матриц можно посчитать worldDirection. Осталось только найти такую возможность в пост обработке. Также есть уже готовые обратные матрицы.

В принципе тоже самое без мудрежа с матрицами.

+ Показать
#2
19:33, 8 апр 2020

foxes
Так работает.
Но что не так было с моим кодом?
Просто мой код понятен, а вот что тут происходит:

        var p = GL.GetGPUProjectionMatrix(cam.projectionMatrix, false);
        p[2, 3] = p[3, 2] = 0.0f;
        p[3, 3] = 1.0f;
        var clipToWorld = Matrix4x4.Inverse(p * cam.worldToCameraMatrix) * Matrix4x4.TRS(new Vector3(0, 0, -p[2, 2]), Quaternion.identity, Vector3.one);
        material.SetMatrix("clipToWorld", clipToWorld);

так сходу и не скажешь. Да и не элегантно как-то вручную значения в матрицах перебивать (

#3
19:36, 8 апр 2020

Причём я попробовал вместо вот "твоей" матрицы передать вот это:

        Matrix4x4 clipToWorld = (cam.projectionMatrix * cam.worldToCameraMatrix).inverse;
        material.SetMatrix("clipToWorld", clipToWorld);

Ничего хорошего из этого не вышло.

#4
19:57, 8 апр 2020

KaronatoR
> Просто мой код понятен, а вот что тут происходит:
проекционная матрица тут как таковая не используется полностью за ненадобностью, поскольку ее задачу полностью заменяет луч из камеры. Из проекционной матрицы берутся только угол зрения и пропорции экрана остальное "обнуляется".
KaronatoR
> new Vector3(0, 0, -p[2, 2]),
Дополнительно создается матрица которая инвертирует одну ось.
От этого всего можно избавиться, я уже написал как. Тебе нужна только worldViewDir она же worldDirection.

#5
20:04, 8 апр 2020

> float3 worldPos = mul(_Object2World, vertex).xyz;

Вот это не совсем понятно. У нас же нету вертекса. Мы его ищем как раз.

#6
20:14, 8 апр 2020

KaronatoR
> float4 clip = float4(o.vertex.xy, 0.0, 1.0);
У тебя же как то этот пример заработал

float4 clip = float4(o.vertex.xy, 0.0, 1.0);

Пробуй это

#7
0:04, 9 апр 2020

Супер, всё работает =)

Не совсем понял вот эту строчку:

positionCS = half4(uv * 2 - 1, depth, 1) * LinearEyeDepth(depth);

Зачем мы тут домножаем на LinearEyeDepth(depth)?
По идее это расстояние от камеры до объекта.
То есть мы берём "точку на экране", в компоненту Z записываем значение из буфера глубины.
И потом ещё домножаем это на расстояние. Есть подозрение, что так мы компенсируем перспективное искажение но я не уверен.

#8
0:48, 9 апр 2020

KaronatoR
> Зачем мы тут домножаем на LinearEyeDepth(depth)?
Здесь все сложнее чем просто домножение, поскольку есть еще матрица _MatrixHClipToWorld где также есть преобразование данной величины.

Во вторых это не домножение, а скорее деление

// Z buffer to linear depth
inline float LinearEyeDepth( float z )
{
    return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);
}

В процессе вычисления проекции получаются координаты не только XYZ но еще и W, на нее все делиться. И если W=Z (не столько равно сколько пропорционально) то получается перспективное искажение. Если посмотришь в 11 ячейку проекционной матрицы то там будет -1. Для ортогональной проекции это будет 0.

W=x*_matrix[3]+y*_matrix[7]+z*_matrix[11]+_matrix[15];
или в упрощенном виде, исключая преобразования поворота и смещения модельной и видовой матриц.
W=-z;

KaronatoR
> Есть подозрение, что так мы компенсируем перспективное искажение
В общем так и есть, и еще возвращает масштаб глубины от [0..1] в [near..far]
Из за того что глубина, в процессе все этих преобразований, получается "(z*a+b)/z" ее приходиться вот так переворачивать.

#9
9:41, 9 апр 2020

foxes
Большое спасибо, всё понял.
Ты очень помог, ещё раз спасибо.

UnityФорумПрограммирование

Тема в архиве.

Тема закрыта.