Как мне создать широкоугольный объектив / объектив «рыбий глаз» с HLSL?

29

Какие концепции необходимо реализовать, чтобы добиться эффекта широкоугольной линзы разных конечностей?

Псевдокод и конкретные пояснения, относящиеся к различным этапам конвейера контента, а также к тому, какую информацию необходимо передать из исходного кода в HLSL, были бы очень полезными.

Кроме того, каковы различия между реализацией широкоугольного объектива и "рыбий глаз"?

SirYakalot
источник

Ответы:

37

Широкоугольный объектив не должен вести себя иначе, чем другие обычные модели объективов. Они просто имеют больший FOV (в D3DXMatrixPerspectiveFovLHсмысле - я предполагаю, что вы используете DirectX) или большие левые / правые и нижние / верхние значения (в glFrustumсмысле OpenGL ).

Я считаю, что действительно интересная часть заключается в моделировании объектива «рыбий глаз». Есть « Рыбий глаз», который вы можете изучать, он поставляется с источником.

Истинная рыбий глаз

Однако проекция объектива типа «рыбий глаз» весьма нелинейна. В более распространенном (насколько мне известно, видеонаблюдении) виде объектива точка Mв пространстве проецируется на поверхность полусферы устройства, затем эта поверхность подвергается параллельному проецированию на диск устройства:

           M
             x                 M: world position
              \                M': projection of M on the unit hemisphere
               \  ______       M": projection of M' on the unit disc (= the screen)
             M'_x'      `-.
             ,' |\         `.
            /   | \          \
           /    |  \          \
          |     |   \          |
__________|_____|____\_________|_________
                M"    O        1

Существуют и другие сопоставления типа «рыбий глаз», которые могут дать более интересные эффекты. Тебе решать.

Я вижу два способа реализации эффекта «рыбий глаз» в HLSL.

Способ 1: выполнить проекцию на вершинный шейдер

Преимущество : почти ничего не нужно менять в коде. Фрагмент шейдера предельно прост. Скорее, чем:

...
float4 screenPoint = mul(worldPoint, worldViewProjMatrix);
...

Вы делаете что-то вроде этого (возможно, может быть сильно упрощено):

...
// This is optional, but it computes the z coordinate we will
// need later for Z sorting.
float4 out_Point = mul(in_Point, worldViewProjMatrix);

// This retrieves the world position so that we can project on
// the hemisphere. Normalise this vector and you get M'
float4 tmpPoint = mul(in_Point, worldViewMatrix);

// This computes the xy coordinates of M", which happen to
// be the same as M'.
out_Point.xy = tmpPoint.xy / length(tmpPoint.xyz);
...

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

Обходные пути : было бы приемлемо получить лучшее приближение, отправив уточненную геометрию в графический процессор с большим количеством подразделений треугольника. Это также может быть выполнено в геометрическом шейдере, но поскольку этот шаг выполняется после вершинного шейдера, геометрический шейдер будет довольно сложным, поскольку ему придется выполнять собственные дополнительные проекции.

Способ 2: выполнить проекцию на фрагмент шейдера

Другой метод - визуализация сцены с использованием широкоугольной проекции, а затем искажение изображения для достижения эффекта «рыбий глаз» с использованием полноэкранного фрагментного шейдера.

Если точка Mимеет координаты (x,y)на экране «рыбий глаз», это означает, что она имеет координаты (x,y,z)на поверхности полушария, с z = sqrt(1-x*x-y*y). Это означает, что у нас были координаты (ax,ay)в нашей сцене, отрисованные с помощью поля зрения thetaтакого типа a = 1/(z*tan(theta/2)). (Не уверен на 100% насчет моей математики здесь, я проверю еще раз сегодня вечером).

Следовательно, фрагментный шейдер будет выглядеть примерно так:

void main(in float4 in_Point : POSITION,
          uniform float u_Theta,
          uniform sampler2D u_RenderBuffer,
          out float4 out_FragColor : COLOR)
{
    z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y);
    float a = 1.0 / (z * tan(u_Theta * 0.5));
    out_FragColor = tex2D(u_RenderBuffer, (in_Point.xy - 0.5) * 2.0 * a);
}

Преимущество : вы получаете идеальную проекцию без искажений, кроме тех, что из-за точности пикселей.

Недостаток : вы не можете физически просматривать всю сцену, поскольку поле обзора не может достигать 180 градусов. Кроме того, чем больше FOV, тем хуже точность в центре изображения ... именно там, где вам нужна максимальная точность.

Обходные пути : потерю точности можно улучшить, выполнив несколько проходов рендеринга, например, 5, и выполните проекцию в виде карты куба. Другой очень простой обходной путь - просто обрезать конечное изображение до требуемого поля зрения - даже если объектив имеет угол обзора 180 градусов, вы можете захотеть отрендерить только его часть. Это называется «полнокадровым» «рыбьим глазом» (что довольно иронично, поскольку создается впечатление, что вы получаете «полное» что-то, пока оно фактически обрезает изображение).

(Примечание: если вы сочли это полезным, но недостаточно ясным, скажите, пожалуйста, я хочу написать более подробную статью об этом).

Сэм Хоцевар
источник
очень полезно, и я бы приветствовал более подробную статью, которую вы хотите написать от всего сердца!
SirYakalot
Возможно ли объединить оба подхода, чтобы получить лучшие результаты? Сначала выполните проекцию в VS, чтобы все было видно, а затем отмените проекцию в PS и снова спроектируйте, чтобы получить правильные UV и все? Может потребоваться отправить еще несколько параметров в PS, чтобы правильно спроецировать на оригинал.
Ондрей Петрзилка
3

Должно быть z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y), верно?

Моя реализация GLSL:

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float fovTheta; // FOV's theta

// fisheye
void main (void)
{   
    vec2 uv = v_texCoord - 0.5;
    float z = sqrt(1.0 - uv.x * uv.x - uv.y * uv.y);
    float a = 1.0 / (z * tan(fovTheta * 0.5));
//  float a = (z * tan(fovTheta * 0.5)) / 1.0; // reverse lens
    gl_FragColor = texture2D(u_texture, (uv* a) + 0.5);
}
Ътап
источник
эй @ Джош, как вычислили fovTheta?
Том
1
Я только отредактировал этот ответ, чтобы настроить форматирование, я думаю, что вы хотите обратиться к @bman напрямую.
Джош