Làm cách nào để tạo ống kính góc rộng / mắt cá với HLSL?


29

Các khái niệm cần phải được thực hiện để đạt được hiệu ứng của một ống kính góc rộng của các chi khác nhau là gì?

Mã giả và giải thích cụ thể đề cập đến các giai đoạn khác nhau của đường ống nội dung, cũng như thông tin nào cần được truyền từ mã nguồn sang HLSL sẽ rất hữu ích.

Ngoài ra, sự khác biệt giữa việc thực hiện một ống kính góc rộng và mắt cá là gì?

Câu trả lời:


37

Một ống kính góc rộng không nên hoạt động khác so với các mẫu ống kính thông thường khác. Họ chỉ có FOV lớn hơn (theo D3DXMatrixPerspectiveFovLHnghĩa - tôi giả sử bạn sử dụng DirectX) hoặc các giá trị trái / phải và dưới / trên lớn hơn (theo glFrustumnghĩa OpenGL ).

Tôi tin rằng phần thực sự thú vị nằm ở mô hình ống kính mắt cá. Có Fisheye Quake mà bạn có thể nghiên cứu, nó đi kèm với nguồn.

Phép chiếu mắt cá thật

Tuy nhiên, hình chiếu của ống kính mắt cá rất phi tuyến tính. Trong loại phổ biến hơn (theo hiểu biết của tôi, chỉ giới hạn ở các camera giám sát), một điểm Mtrong không gian được chiếu lên bề mặt của một bán cầu đơn vị, sau đó bề mặt đó trải qua một phép chiếu song song lên đĩa đơn vị:

           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

Có những ánh xạ mắt cá khác có thể cho hiệu ứng thú vị hơn. Tuỳ bạn.

Tôi có thể thấy hai cách để thực hiện hiệu ứng mắt cá trong HLSL.

Phương pháp 1: thực hiện phép chiếu trên shader đỉnh

Ưu điểm : hầu như không có gì cần phải thay đổi trong mã. Các shader mảnh là cực kỳ đơn giản. Thay vì:

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

Bạn làm một cái gì đó như thế này (có thể được đơn giản hóa rất nhiều):

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

Nhược điểm : do toàn bộ đường ống kết xuất được cho là biến đổi tuyến tính, phép chiếu kết quả là chính xác cho các đỉnh, nhưng tất cả các thay đổi sẽ sai, cũng như tọa độ kết cấu và hình tam giác vẫn sẽ xuất hiện dưới dạng hình tam giác mặc dù chúng có vẻ bị biến dạng.

Giải pháp thay thế : có thể chấp nhận được để có được xấp xỉ tốt hơn bằng cách gửi một hình học tinh chế đến GPU, với nhiều phân vùng tam giác hơn. Điều này cũng có thể được thực hiện trong một shader hình học, nhưng vì bước này xảy ra sau shader đỉnh, nên shader hình học sẽ khá phức tạp vì nó sẽ phải thực hiện các phép chiếu bổ sung của riêng nó.

Phương pháp 2: thực hiện phép chiếu trên shader mảnh

Một phương pháp khác là kết xuất cảnh bằng cách sử dụng phép chiếu góc rộng, sau đó làm biến dạng hình ảnh để đạt được hiệu ứng mắt cá bằng cách sử dụng trình tạo bóng mảnh toàn màn hình.

Nếu điểm Mcó tọa độ (x,y)trong màn hình mắt cá, điều đó có nghĩa là nó có tọa độ (x,y,z)trên bề mặt bán cầu, với z = sqrt(1-x*x-y*y). Điều đó có nghĩa là nó có tọa độ (ax,ay)trong cảnh của chúng ta được hiển thị bằng FOV thetanhư vậy a = 1/(z*tan(theta/2)). (Không chắc chắn 100% về toán học của tôi ở đây, tôi sẽ kiểm tra lại vào tối nay).

Do đó, shader mảnh sẽ là một cái gì đó như thế này:

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

Ưu điểm : bạn có được một hình chiếu hoàn hảo không có biến dạng ngoài những điều đó do độ chính xác của pixel.

Nhược điểm : bạn không thể xem toàn bộ cảnh, vì FOV không thể đạt tới 180 độ. Ngoài ra, FOV càng lớn, độ chính xác ở trung tâm của hình ảnh càng tệ ... đó chính xác là nơi bạn muốn độ chính xác tối đa.

Giải pháp thay thế : mất độ chính xác có thể được cải thiện bằng cách thực hiện một số đường chuyền kết xuất, ví dụ 5 và thực hiện phép chiếu theo cách của bản đồ khối. Một cách giải quyết rất đơn giản khác là chỉ cần cắt hình ảnh cuối cùng thành FOV mong muốn - ngay cả khi bản thân ống kính có FOV 180 độ, bạn có thể chỉ muốn hiển thị một phần của nó. Đây được gọi là fisheye "full-frame" (thật là mỉa mai, vì nó mang lại ấn tượng rằng bạn có được cái gì đó "đầy đủ" trong khi nó thực sự cắt ảnh).

(Lưu ý: nếu bạn thấy điều này hữu ích nhưng không đủ rõ ràng, xin vui lòng cho tôi biết, tôi cảm thấy như viết một bài viết chi tiết hơn về điều này).


rất hữu ích, và tôi rất hoan nghênh bài viết chi tiết hơn mà bạn muốn viết hết lòng!
SirYakalot

Có thể kết hợp cả hai cách tiếp cận để có kết quả tốt hơn? Đầu tiên làm phép chiếu trong VS để có được mọi thứ trong tầm nhìn và sau đó không dự đoán trong PS và chiếu lại để có được tia UV chính xác và mọi thứ? Có thể cần phải gửi thêm một vài tham số đến PS để hủy dự đoán chính xác thành bản gốc.
Ondrej Petrzilka

3

Nó nên z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y), phải không?

Triển khai GLSL của tôi là:

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

này @Josh, fovTheta đã được tính như thế nào?
tom

1
Tôi chỉ chỉnh sửa câu trả lời này để điều chỉnh định dạng, tôi tin rằng bạn muốn giải quyết trực tiếp @bman.
Josh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.