一、透視投影
1.方法概述
????????Perspective projection(透視投影)是一種模擬人眼觀察三維空間物體時的視覺效果的投影方法。它通過模擬觀察者從一個特定視點觀察三維場景的方式來創建二維圖像。在透視投影中,遠處的物體看起來比近處的物體小,這與我們的日常視覺經驗相符。
1.1關鍵特點
-
單點透視:最基本的透視投影形式,所有消失點匯聚在一個點上,這個點稱為消失點。這種投影適用于觀察者直接面對的場景。
-
雙點透視:當場景有一個角落或邊緣時使用,消失點分布在兩個不同的點上。
-
三點透視:用于非常傾斜或垂直的視角,消失點分布在三個不同的點上。
-
消失點:在透視投影中,平行線在無限遠處相交的點稱為消失點。這些點幫助確定物體在空間中的相對位置和大小。
-
透視網格:藝術家和設計師經常使用透視網格來幫助繪制透視圖像。網格提供了一個框架,用于確定物體在透視空間中的位置。
-
比例和尺寸:在透視投影中,物體的大小與其與觀察者的距離成反比。物體越遠,看起來越小。
-
深度感:透視投影通過大小變化和重疊來創造深度感,使圖像看起來更立體。
1.2 相關應用
-
藝術和設計中的應用:透視投影在繪畫、建筑繪圖、游戲設計和電影制作等領域中非常重要,因為它幫助創造真實感和空間感。
-
數學模型:在計算機圖形學中,透視投影可以通過數學模型實現,其中3D坐標被轉換為2D屏幕坐標,同時考慮視角和消失點。
-
透視投影矩陣:在3D圖形編程中,透視投影矩陣用于將3D場景轉換為2D視圖,這是通過一系列線性變換實現的。
????????透視投影是理解和創建三維空間中物體如何在二維平面上呈現的基礎,它對于任何涉及空間表現的領域都是至關重要的。
2.數學推導
2.1視錐體到視平面的投影
在透視投影中,我們首先需要將視錐體中的頂點投影到視平面上。這可以通過相似三角形的性質來實現。對于三維空間中的一條直線,其參數方程可以表示為:
其中(x0?,y0?,z0?) 為直線上的一點,t 為參數。根據相似三角形原理,我們可以得到:
其中 f 為相機焦距。當 t→∞ 時,我們可以得到滅點的坐標:
這表明滅點只取決于直線的方向,而與直線上具體的點無關。
2.2?視錐體到規則觀察體的映射
接下來,我們需要將視錐體映射到規則觀察體(CVV)中進行裁剪。這涉及到將頂點坐標轉換為齊次坐標,并構造出一個變換矩陣來完成映射。透視投影矩陣的數學推導過程可以表示為:
????????其中,N 是近平面距離,f 是遠平面距離,R,L,T,B 分別是視口的右邊界、左邊界、上邊界和下邊界值。
2.3?透視除法
在將點映射到NDC(標準設備坐標系)之后,我們需要進行透視除法,即將 x′, y′, z′ 坐標除以 w′ 來得到最終的屏幕坐標:
2.4 視口變換和比例恢復
最后,我們需要將NDC坐標映射到屏幕坐標系中,并進行比例恢復。這涉及到將NDC坐標轉換為柵格坐標系統,其中單位是像素。這個過程可以通過以下公式實現:
其中,PixelWidthPixelWidth 和 PixelHeightPixelHeight 分別是屏幕的像素寬度和高度。
二、等距圓柱投影
1.方法概述
????????Equirectangular-perspective projection(等距柱狀透視投影)是一種將球面(如地球)映射到平面矩形上的方法。這種投影方式保持了垂直和水平方向的一致間距,因此它在全景成像和虛擬現實應用中非常流行,因為它允許輕松地將圖像拼接成廣角視圖。
1.1關鍵特點
- 等距柱狀投影:也被稱為地理或等距圓柱投影,它將緯度和經度線均勻地投影到平面上。
- 形狀和面積失真:這種投影可能會扭曲形狀和面積,尤其是在極地附近,但它提供了一種直觀的全景圖像布局,通過允許從球面到矩形坐標的簡單映射。
- 虛擬現實應用:在虛擬現實應用中,等距柱狀圖像可以顯示在360度環境中,為用戶創造沉浸式體驗。
- 全景圖像創建:等距柱狀格式通常與通過特殊相機或設置創建的球面全景圖一起使用。
- 渲染技術:在視頻游戲和模擬中,等距柱狀投影的渲染技術對于創建無縫環境以增強用戶參與度至關重要。
等距柱狀投影的主要特點是其2:1的寬高比,能夠覆蓋360°的水平視場和180°的垂直視場,同時保持點之間的角關系,但會在極地附近扭曲形狀。這種投影方式在全景攝影中被廣泛用于創建完整的360度水平和180度垂直視圖。
1.2相關應用
????????等距柱狀透視投影(Equirectangular projection)在多個領域有著廣泛的應用,以下是一些主要的應用場景:
????????全景圖像和虛擬現實(VR):等距柱狀透視投影是全景圖像和虛擬現實中常用的一種投影方式。它能夠將360度的全景圖像映射到2D平面上,為用戶提供沉浸式的體驗。在VR應用中,這種投影方式可以讓用戶感受到身臨其境的效果,例如通過Three.js實現全景圖VR,將全景圖貼到球體上,實現虛擬環境的模擬。
????????全球數據集:等距柱狀投影因其簡單的地理關系,成為了全球柵格數據集的標準,例如Celestia和NASA World Wind。這種投影方式使得圖像像素在地圖上的位置與其對應的地理位置之間的關系變得簡單直接。
????????地圖制作:盡管等距柱狀投影在導航或地籍測繪中使用較少,因為它引入了畸變,但它在主題地圖制作中找到了主要應用。這種投影方式能夠將球面上的經緯線投影到圓柱面上,然后展開成平面,適用于地圖的制作和展示。
????????3D全景場景生成:在3D全景場景生成中,等距柱狀投影被用來將3D球體映射到2D平面,這對于生成詳細、一致且合理的全景圖至關重要。通過這種投影,可以合成360°×90°視角的全景圖,并逐步擴展至360°×180°視角的全景圖。
2.數學推導
2.1 正向投影
正向投影是將球面坐標(經度和緯度)轉換為平面坐標的過程。假設我們有一個點在球面上,其球面坐標為(φ, λ),其中φ是緯度,λ是經度。等距柱狀透視投影的正向投影公式如下:
- 經度(x):?x=λ
- 緯度(y):?y=?
這里,λ被直接映射為x坐標,而φ被直接映射為y坐標。這種投影方式保持了經度和緯度的等距特性,即在球面上等距的經線和緯線在投影后仍然保持等距。
2.2 反向投影
反向投影是將平面坐標轉換回球面坐標的過程。給定平面上的點(x, y),我們可以通過以下公式計算其對應的球面坐標(φ, λ):
- 經度(λ):?λ=x
- 緯度(φ):??=y
這里,x坐標直接對應于經度,y坐標直接對應于緯度。
2.3 歸一化與反歸一化
在實際應用中,我們經常需要將平面坐標歸一化到[-1, 1]的范圍內,以適應不同的顯示和處理需求。歸一化的公式如下:
反歸一化的公式則為:
這些公式允許我們將平面坐標在[-1, 1]范圍內歸一化,并在需要時將其反歸一化回原始的經度和緯度值。
2.4 笛卡爾坐標系轉化
在某些應用中,我們需要將球面坐標轉換為笛卡爾坐標。對于單位球面上的點,轉換公式如下:
????????X=sin(?)cos(λ)
????????Y=sin(?)sin(λ)
????????Z=cos(?)
這些公式將球面坐標(φ, λ)轉換為笛卡爾坐標系中的點(X, Y, Z)。
三、相關代碼詳解
1.透視投影
1.1 數學推導
????????透視投影的數學推導可以通過多種編程語言實現,這里我提供一個簡單的Python代碼示例,用于演示如何將三維空間中的點通過透視投影轉換到二維平面上。這個例子中,我們將使用一個簡單的透視投影矩陣來實現這一轉換。
import numpy as npdef perspective_projection(point, fov, aspect_ratio, near, far):"""對三維點進行透視投影。參數:point -- 三維空間中的點 (x, y, z)fov -- 視場角,以度為單位aspect_ratio -- 寬高比near -- 近裁剪面far -- 遠裁剪面返回:二維平面上的投影點 (x', y', w')"""# 將視場角從度轉換為弧度fov_rad = np.radians(fov)# 計算透視投影矩陣f = 1 / np.tan(fov_rad / 2)projection_matrix = np.array([[f / aspect_ratio, 0, 0, 0],[0, f, 0, 0],[0, 0, (far + near) / (near - far), (2 * far * near) / (near - far)],[0, 0, -1, 0]])# 將點轉換為齊次坐標point_homogeneous = np.array([point[0], point[1], point[2], 1])# 應用透視投影矩陣projected_point = np.dot(projection_matrix, point_homogeneous)# 歸一化w分量projected_point /= projected_point[3]return projected_point[:2]# 示例使用
point_3d = (1, 2, 3) # 三維空間中的點
fov_degrees = 60 # 視場角
aspect_ratio = 16 / 9 # 寬高比
near_plane = 0.1 # 近裁剪面
far_plane = 100 # 遠裁剪面projected_point = perspective_projection(point_3d, fov_degrees, aspect_ratio, near_plane, far_plane)
print("Projected Point:", projected_point)
????????函數定義:perspective_projection
函數接受一個三維點和透視投影的參數(視場角、寬高比、近裁剪面和遠裁剪面)。視場角轉換:將視場角從度轉換為弧度,因為三角函數在Python中使用弧度。透視投影矩陣:構建一個4x4的透視投影矩陣,這個矩陣將用于將三維點轉換到二維平面。點轉換:將三維點轉換為齊次坐標(添加一個1作為w分量)。應用投影矩陣:將透視投影矩陣應用于齊次坐標點。歸一化:將結果除以w分量以歸一化坐標。返回結果:返回歸一化后的二維坐標。
1.2 實際應用
????????在實際的項目中,透視投影通常用于圖形渲染和3D圖形處理。以下是一個使用Python和OpenGL庫PyOpenGL實現的簡單示例,這個示例創建了一個窗口,并在其中渲染了一個簡單的3D立方體,使用透視投影來模擬相機視角。
import glfw
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader# 頂點著色器
vertex_shader = """
#version 330 core
layout (location = 0) in vec3 aPos;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);
}
"""# 片段著色器
fragment_shader = """
#version 330 core
out vec4 FragColor;void main()
{FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
"""def create_shaders():vertex = compileShader(vertex_shader, GL_VERTEX_SHADER)fragment = compileShader(fragment_shader, GL_FRAGMENT_SHADER)return compileProgram(vertex, fragment)def main():if not glfw.init():returnwindow = glfw.create_window(800, 600, "Perspective Projection Example", None, None)if not window:glfw.terminate()returnglfw.make_context_current(window)shader = create_shaders()glUseProgram(shader)# 設置視口glViewport(0, 0, 800, 600)# 設置投影矩陣projection = glm.perspective(glm.radians(45.0), 800/600, 0.1, 100.0)glUniformMatrix4fv(glGetUniformLocation(shader, "projection"), 1, GL_FALSE, glm.value_ptr(projection))# 設置視圖矩陣view = glm.lookAt(glm.vec3(4, 3, 3), glm.vec3(0, 0, 0), glm.vec3(0, 1, 0))glUniformMatrix4fv(glGetUniformLocation(shader, "view"), 1, GL_FALSE, glm.value_ptr(view))vertices = [-0.5, -0.5, 0.0, 1.0,0.5, -0.5, 0.0, 1.0,0.0, 0.5, 0.0, 1.0]VBO = glGenBuffers(1)VAO = glGenVertexArrays(1)glBindVertexArray(VAO)glBindBuffer(GL_ARRAY_BUFFER, VBO)glBufferData(GL_ARRAY_BUFFER, (GLfloat * len(vertices))(*vertices), GL_STATIC_DRAW)glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)glEnableVertexAttribArray(0)while not glfw.window_should_close(window):glClearColor(0.2, 0.3, 0.3, 1.0)glClear(GL_COLOR_BUFFER_BIT)# 更新模型矩陣model = glm.rotate(glm.mat4(1.0), glfw.get_time(), glm.vec3(0, 1, 0))glUniformMatrix4fv(glGetUniformLocation(shader, "model"), 1, GL_FALSE, glm.value_ptr(model))glDrawArrays(GL_TRIANGLES, 0, 3)glfw.swap_buffers(window)glfw.poll_events()glDeleteVertexArrays(1, [VAO])glDeleteBuffers(1, [VBO])glfw.terminate()import glm
main()
????????初始化GLFW和OpenGL:設置窗口和OpenGL上下文。著色器編譯:編譯頂點著色器和片段著色器,并鏈接成程序。設置視口和投影矩陣:設置視口大小,并創建透視投影矩陣。設置視圖矩陣:創建視圖矩陣,模擬相機視角。頂點數據和緩沖區:定義頂點數據,并創建VBO和VAO。渲染循環:在渲染循環中,清除顏色緩沖區,更新模型矩陣,并繪制三角形。
2.等距圓柱投影
2.1 數學推導
等距圓柱投影(Equirectangular Projection)的數學推導可以通過編程語言實現,這里提供一個簡單的Python代碼示例,用于演示如何將球面坐標(緯度和經度)轉換為等距圓柱投影的平面坐標。
import numpy as npdef spherical_to_equirectangular(latitude, longitude, scale=1.0):"""將球面坐標(緯度和經度)轉換為等距圓柱投影的平面坐標。參數:latitude -- 緯度,范圍:-π/2 到 π/2longitude -- 經度,范圍:-π 到 πscale -- 縮放因子,默認為1.0返回:平面坐標 (x, y)"""# 將緯度和經度轉換為弧度lat_rad = np.radians(latitude)lon_rad = np.radians(longitude)# 計算等距圓柱投影的平面坐標x = scale * lon_rady = scale * np.sin(lat_rad)return x, ydef equirectangular_to_spherical(x, y, scale=1.0):"""將等距圓柱投影的平面坐標轉換回球面坐標(緯度和經度)。參數:x -- 平面坐標的x分量y -- 平面坐標的y分量scale -- 縮放因子,默認為1.0返回:球面坐標 (latitude, longitude)"""# 計算緯度和經度longitude = np.degrees(x / scale)latitude = np.degrees(np.arcsin(y / scale))return latitude, longitude# 示例使用
latitude = -30 # 緯度
longitude = 60 # 經度# 球面坐標到等距圓柱投影坐標
x, y = spherical_to_equirectangular(latitude, longitude)
print(f"Equirectangular Coordinates: ({x}, {y})")# 等距圓柱投影坐標到球面坐標
lat, lon = equirectangular_to_spherical(x, y)
print(f"Spherical Coordinates: ({lat}, {lon})")
(1)球面坐標到等距圓柱投影坐標:spherical_to_equirectangular
?函數接受緯度和經度作為輸入,并將其轉換為等距圓柱投影的平面坐標。緯度(latitude
)和經度(longitude
)首先被轉換為弧度。然后,使用公式?x = scale * lon_rad
?和?y = scale * np.sin(lat_rad)
?計算平面坐標。
(2)等距圓柱投影坐標到球面坐標:equirectangular_to_spherical
?函數接受平面坐標作為輸入,并將其轉換回球面坐標。使用公式?longitude = np.degrees(x / scale)
?和?latitude = np.degrees(np.arcsin(y / scale))
?計算緯度和經度。
2.2 實際應用
????????使用Python和OpenGL庫來實現一個簡單的全景圖像查看器,它將展示如何將球面坐標轉換為等距圓柱投影坐標,并在屏幕上顯示。
import glfw
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader
import numpy as np# 頂點著色器
vertex_shader = """
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;out vec2 TexCoord;uniform mat4 projection;
uniform mat4 view;void main()
{gl_Position = projection * view * vec4(aPos, 1.0);TexCoord = aTexCoord;
}
"""# 片段著色器
fragment_shader = """
#version 330 core
out vec4 FragColor;in vec2 TexCoord;uniform sampler2D texture1;void main()
{FragColor = texture(texture1, TexCoord);
}
"""def create_shaders():vertex = compileShader(vertex_shader, GL_VERTEX_SHADER)fragment = compileShader(fragment_shader, GL_FRAGMENT_SHADER)return compileProgram(vertex, fragment)def main():if not glfw.init():returnwindow = glfw.create_window(800, 600, "Equirectangular Projection Example", None, None)if not window:glfw.terminate()returnglfw.make_context_current(window)shader = create_shaders()glUseProgram(shader)# 設置視口glViewport(0, 0, 800, 600)# 設置投影矩陣projection = np.array([[1.0, 0.0, 0.0, 0.0],[0.0, 1.0, 0.0, 0.0],[0.0, 0.0, 1.0, 0.0],[0.0, 0.0, 0.0, 1.0]]) # 單位矩陣作為投影矩陣,因為我們處理的是2D圖像glUniformMatrix4fv(glGetUniformLocation(shader, "projection"), 1, GL_FALSE, projection)glUniformMatrix4fv(glGetUniformLocation(shader, "view"), 1, GL_FALSE, projection) # 同樣使用單位矩陣# 加載紋理texture = glGenTextures(1)glBindTexture(GL_TEXTURE_2D, texture)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)# 加載全景圖像(這里需要一個全景圖像文件路徑)with open("path_to_your_equirectangular_image.jpg", "rb") as f:bin = f.read()img = bytearray(bin)glBindTexture(GL_TEXTURE_2D, texture)glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 400, 0, GL_RGB, GL_UNSIGNED_BYTE, img)glGenerateMipmap(GL_TEXTURE_2D)# 頂點數據vertices = [-1.0, -1.0, 0.0, 0.0, 0.0,1.0, -1.0, 0.0, 1.0, 0.0,1.0, 1.0, 0.0, 1.0, 1.0,-1.0, 1.0, 0.0, 0.0, 1.0]VBO = glGenBuffers(1)VAO = glGenVertexArrays(1)glBindVertexArray(VAO)glBindBuffer(GL_ARRAY_BUFFER, VBO)glBufferData(GL_ARRAY_BUFFER, (GLfloat * len(vertices))(*vertices), GL_STATIC_DRAW)glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), ctypes.c_void_p(0))glEnableVertexAttribArray(0)glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), ctypes.c_void_p(12))glEnableVertexAttribArray(1)while not glfw.window_should_close(window):glClearColor(0.1, 0.1, 0.1, 1.0)glClear(GL_COLOR_BUFFER_BIT)glActiveTexture(GL_TEXTURE0)glBindTexture(GL_TEXTURE_2D, texture)glUniform1i(glGetUniformLocation(shader, "texture1"), 0)glDrawArrays(GL_TRIANGLE_FAN, 0, 4)glfw.swap_buffers(window)glfw.poll_events()glDeleteVertexArrays(1, [VAO])glDeleteBuffers(1, [VBO])glfw.terminate()import ctypes
main()
????????初始化GLFW和OpenGL:設置窗口和OpenGL上下文。著色器編譯:編譯頂點著色器和片段著色器,并鏈接成程序。設置視口和投影矩陣:設置視口大小,并使用單位矩陣作為投影矩陣。加載紋理:加載全景圖像作為紋理。頂點數據和緩沖區:定義頂點數據,并創建VBO和VAO。渲染循環:在渲染循環中,清除顏色緩沖區,綁定紋理,并繪制四邊形。