ฉันจะสร้างเลนส์มุมกว้าง / เลนส์ Fisheye ด้วย HLSL ได้อย่างไร


29

อะไรคือแนวคิดที่จะต้องดำเนินการเพื่อให้ได้ผลของเลนส์มุมกว้างที่มีความแตกต่างกัน?

Pseudocode และคำอธิบายเฉพาะที่อ้างอิงถึงขั้นตอนต่าง ๆ ของไปป์ไลน์เนื้อหารวมถึงข้อมูลใดที่จำเป็นต้องส่งผ่านจากซอร์สโค้ดไปยัง HLSL จะมีประโยชน์มาก

นอกจากนี้อะไรคือความแตกต่างระหว่างการนำเลนส์มุมกว้างไปใช้กับเลนส์ฟิชอาย?

คำตอบ:


37

เลนส์มุมกว้างไม่ควรทำงานต่างจากเลนส์รุ่นอื่นทั่วไป พวกเขามี FOV ที่มากขึ้น (ตามD3DXMatrixPerspectiveFovLHความหมาย - ฉันสมมติว่าคุณใช้ DirectX) หรือใหญ่กว่าค่าซ้าย / ขวาและล่าง / บนสุด (ในglFrustumความหมายOpenGL )

ฉันเชื่อว่าส่วนที่น่าสนใจคือการสร้างแบบจำลองเลนส์ฟิชอาย มีFisheye Quakeที่คุณสามารถเรียนได้มันมาพร้อมกับแหล่งที่มา

การฉายฟิชอายที่แท้จริง

อย่างไรก็ตามการฉายภาพของเลนส์ Fisheye นั้นไม่ใช่แบบเส้นตรงสูง จากความรู้ทั่วไปของฉันซึ่ง จำกัด อยู่ที่กล้องตรวจการณ์) 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: ทำการฉายภาพบน Shader ที่

ข้อได้เปรียบ : เกือบไม่มีอะไรจำเป็นต้องเปลี่ยนแปลงในรหัส ชิ้นส่วน shader ง่ายมาก ค่อนข้างมากกว่า:

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

ข้อเสีย : เนื่องจากการเรนเดอร์ไปป์ไลน์ทั้งหมดถูกคิดว่าเป็นการแปลงเชิงเส้นดังนั้นการฉายภาพที่เกิดขึ้นจึงเป็นสิ่งที่แน่นอนสำหรับจุดยอด แต่การเปลี่ยนแปลงทั้งหมดจะผิดรวมไปถึงพิกัดพื้นผิวและสามเหลี่ยมจะยังคงเป็น

วิธีแก้ปัญหา : สามารถยอมรับการประมาณที่ดีกว่าโดยการส่งรูปเรขาคณิตที่ละเอียดอ่อนไปยัง GPU พร้อมกับส่วนย่อยที่เป็นรูปสามเหลี่ยม สิ่งนี้อาจดำเนินการในรูปทรงเรขาคณิต แต่เนื่องจากขั้นตอนนี้เกิดขึ้นหลังจากจุดยอดรูปทรงเรขาคณิตรูปทรงจะค่อนข้างซับซ้อนเนื่องจากจะต้องดำเนินการคาดการณ์เพิ่มเติมของตัวเอง

วิธีที่ 2: ทำการฉายบน shader ส่วน

อีกวิธีหนึ่งก็คือการทำให้ฉากโดยใช้การฉายภาพมุมกว้างจากนั้นบิดเบือนภาพเพื่อให้ได้เอฟเฟ็กต์ฟิชอายโดยใช้ตัวแบ่งส่วนแบบเต็มหน้าจอ

หากจุดMที่มีพิกัด(x,y)ในหน้าจอฟิชอายก็หมายความว่ามันมีพิกัดบนพื้นผิวซีกโลกด้วย(x,y,z) z = sqrt(1-x*x-y*y)ซึ่งหมายความว่ามันมีพิกัด(ax,ay)ในฉากของเราแสดงผลด้วย FOV ของดังกล่าวว่าtheta a = 1/(z*tan(theta/2))(ไม่แน่ใจ 100% เกี่ยวกับคณิตศาสตร์ของฉันที่นี่ฉันจะตรวจสอบอีกครั้งในคืนนี้)

ชิ้นส่วน shader จะเป็นดังนี้:

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

ข้อได้เปรียบ : คุณจะได้ภาพที่สมบูรณ์แบบโดยไม่มีการบิดเบือนนอกเหนือจากความถูกต้องของพิกเซล

ข้อเสียเปรียบ : คุณไม่สามารถดูทั้งฉากได้เนื่องจาก FOV ไม่สามารถไปถึง 180 องศาได้ ยิ่งค่า FOV ยิ่งมากเท่าไหร่ความแม่นยำก็จะยิ่งแย่ลงที่จุดศูนย์กลางของภาพ ... ซึ่งเป็นจุดที่คุณต้องการความแม่นยำสูงสุดอย่างแม่นยำ

วิธีแก้ปัญหา : การสูญเสียความแม่นยำสามารถปรับปรุงได้โดยดำเนินการเรนเดอร์หลายตัวอย่างเช่น 5 และทำการฉายภาพในลักษณะของแผนที่ลูกบาศก์ วิธีแก้ปัญหาที่ง่ายมากอีกวิธีหนึ่งคือการครอบตัดภาพสุดท้ายไปยัง FOV ที่ต้องการ - แม้ว่าตัวเลนส์เองจะมี FOV 180 องศาคุณอาจต้องการแสดงเพียงบางส่วนเท่านั้น สิ่งนี้เรียกว่าฟิชอาย "ฟูลเฟรม" (ซึ่งค่อนข้างน่าขันเพราะมันให้ความรู้สึกว่าคุณได้รับ "เต็ม" บางอย่างในขณะที่มันครอบตัดภาพ)

(หมายเหตุ: หากคุณพบว่ามีประโยชน์ แต่ไม่ชัดเจนพอโปรดบอกฉันฉันรู้สึกเหมือนเขียนบทความโดยละเอียดเกี่ยวกับเรื่องนี้)


มีประโยชน์มากและฉันยินดีต้อนรับบทความที่มีรายละเอียดมากขึ้นที่คุณต้องการเขียนอย่างเต็มที่!
SirYakalot

เป็นไปได้ไหมที่จะรวมทั้งสองวิธีเข้าด้วยกันเพื่อให้ได้ผลลัพธ์ที่ดีขึ้น? ขั้นแรกให้ทำประมาณการใน VS เพื่อให้ทุกอย่างเข้ามาดูจากนั้นยกเลิกโครงการใน PS และฉายอีกครั้งเพื่อรับ UV ที่ถูกต้องและทุกอย่าง คุณอาจจำเป็นต้องส่งพารามิเตอร์เพิ่มเติมอีกสองสามรายการไปที่ PS เพื่อยกเลิกโครงการอย่างถูกต้องให้เป็นต้นฉบับ
Ondrej Petrzilka

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

เฮ้ @Josh, คำนวณ fovTheta อย่างไร?
ทอม

1
ฉันเพิ่งแก้ไขคำตอบนี้เพื่อปรับการจัดรูปแบบฉันเชื่อว่าคุณต้องการที่อยู่ @bman โดยตรง
Josh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.