OpenGL教程 学习笔记
文章目錄
- OpenGL教程
- 1 概念
- 1.1 是什么?
- 1.2 核心模式與固定渲染管線模式
- 1.3 狀態(tài)機(jī)
- 1.4 視口(Viewport)
- 1.5 渲染(**Render**):從3D點(diǎn)云到屏幕圖像的過(guò)程
- 1.6 著色器(Shader):處理數(shù)據(jù)的程序
- 2 基本內(nèi)容
- 2.1 頂點(diǎn)輸入
- 2.2 頂點(diǎn)著色器(Vertex Shader)
- 2.3 片段著色器(Fragment Shader)
- 2.4 著色器程序(Program)
- 2.5 紋理
- 3 坐標(biāo)變換
- 3.1 矩陣運(yùn)算
- 3.1.1 向量相乘
- 點(diǎn)乘
- 叉乘
- 3.1.2 矩陣
- 3.2 坐標(biāo)運(yùn)算
- 3.2.1 局部空間
- 3.2.2 模型矩陣:局部——世界坐標(biāo)
- 3.2.3 視圖矩陣:世界——觀察坐標(biāo)
- 3.2.4 投影矩陣:觀察——裁剪空間坐標(biāo)
- 3.2.5 正射投影
- 3.2.6 透視投影
- 3.3 攝像機(jī)
- 3.3.1 Look At
- 3.3.2 歐拉角
- 4 光照
- 4.1 光照模型
- 4.1.1 環(huán)境光照
- 4.1.2 漫反射光照
- 4.1.3 鏡面光照
- 4.2 材質(zhì)
- 4.3 光源
- 4.3.1 平行光
- 4.3.2 點(diǎn)光源
- 4.3.3 聚光源
- 5 模型
- 5.1 網(wǎng)格
- 6 深度:遮擋
- 7 混合:透明度
- 8 面剔除:丟棄背向面
- 8.1 環(huán)繞順序
GLSL 語(yǔ)法https://blog.csdn.net/xhm01291212/article/details/79270836
python目錄結(jié)構(gòu) https://www.cnblogs.com/xiao-apple36/p/8884398.html
OpenGL教程
1 概念
1.1 是什么?
一般它被認(rèn)為是一個(gè)API,包含了一系列可以操作圖形、圖像的函數(shù)。然而,OpenGL本身并不是一個(gè)API,它僅僅是一個(gè)規(guī)范。OpenGL規(guī)范嚴(yán)格規(guī)定了每個(gè)函數(shù)該如何執(zhí)行,以及它們的輸出值。
1.2 核心模式與固定渲染管線模式
早期的OpenGL使用立即渲染模式(Immediate mode,也就是固定渲染管線),這個(gè)模式下繪制圖形很方便。OpenGL的大多數(shù)功能都被庫(kù)隱藏起來(lái),開(kāi)發(fā)者很少有控制OpenGL如何進(jìn)行計(jì)算的自由。
而開(kāi)發(fā)者迫切希望能有更多的靈活性,固定渲染管線效率太低。因此從OpenGL3.2開(kāi)始,規(guī)范文檔開(kāi)始廢棄立即渲染模式,并鼓勵(lì)開(kāi)發(fā)者在OpenGL的核心模式(Core-profile)下進(jìn)行開(kāi)發(fā),這個(gè)分支的規(guī)范完全移除了舊的特性。
1.3 狀態(tài)機(jī)
OpenGL自身是一個(gè)巨大的狀態(tài)機(jī)(State Machine):一系列的變量描述OpenGL此刻應(yīng)當(dāng)如何運(yùn)行。OpenGL的狀態(tài)通常被稱為OpenGL上下文(Context)。我們通常使用如下途徑去更改OpenGL狀態(tài):設(shè)置選項(xiàng),操作緩沖。最后,我們使用當(dāng)前OpenGL上下文來(lái)渲染。
假設(shè)當(dāng)我們想告訴OpenGL去畫(huà)線段而不是三角形的時(shí)候,我們通過(guò)改變一些上下文變量來(lái)改變OpenGL狀態(tài),從而告訴OpenGL如何去繪圖。一旦我們改變了OpenGL的狀態(tài)為繪制線段,下一個(gè)繪制命令就會(huì)畫(huà)出線段而不是三角形。
1.4 視口(Viewport)
OpenGL渲染窗口的尺寸大小,即視口(Viewport),通過(guò)調(diào)用glViewport函數(shù)來(lái)設(shè)置窗口的維度(Dimension):OpenGL幕后使用glViewport中定義的位置和寬高進(jìn)行2D坐標(biāo)的轉(zhuǎn)換,將OpenGL中的位置坐標(biāo)轉(zhuǎn)換為你的屏幕坐標(biāo)。
glViewport(0, 0, 800, 600); # 前兩個(gè)參數(shù)控制窗口左下角的位置。第三個(gè)和第四個(gè)參數(shù)控制渲染窗口的寬度和高度(像素)1.5 渲染(Render):從3D點(diǎn)云到屏幕圖像的過(guò)程
在OpenGL中,任何事物都在3D空間中,而屏幕和窗口卻是2D像素?cái)?shù)組,這導(dǎo)致OpenGL的大部分工作都是關(guān)于把3D坐標(biāo)轉(zhuǎn)變?yōu)檫m應(yīng)你屏幕的2D像素。3D坐標(biāo)轉(zhuǎn)為2D坐標(biāo)的處理過(guò)程是由OpenGL的圖形渲染管線(Graphics Pipeline,大多譯為管線,實(shí)際上指的是一堆原始圖形數(shù)據(jù)途經(jīng)一個(gè)輸送管道,期間經(jīng)過(guò)各種變化處理最終出現(xiàn)在屏幕的過(guò)程)管理的。
圖形渲染管線可以被劃分為兩個(gè)主要部分:
-
第一部分把你的3D坐標(biāo)轉(zhuǎn)換為2D坐標(biāo),
-
第二部分是把2D坐標(biāo)轉(zhuǎn)變?yōu)閷?shí)際的有顏色的像素。
1.6 著色器(Shader):處理數(shù)據(jù)的程序
圖形渲染管線可以被劃分為幾個(gè)階段,每個(gè)階段將會(huì)把前一個(gè)階段的輸出作為輸入。所有這些階段都是高度專門(mén)化的(它們都有一個(gè)特定的函數(shù)),并且很容易并行執(zhí)行。正是由于它們具有并行執(zhí)行的特性,當(dāng)今大多數(shù)顯卡都有成千上萬(wàn)的小處理核心,它們?cè)贕PU上為每一個(gè)(渲染管線)階段運(yùn)行各自的小程序,從而在圖形渲染管線中快速處理你的數(shù)據(jù)。這些小程序叫做著色器(Shader)。OpenGL著色器是用OpenGL著色器語(yǔ)言(OpenGL Shading Language, GLSL)寫(xiě)成的
- 頂點(diǎn)數(shù)據(jù) Vertex Data
頂點(diǎn)數(shù)據(jù)(Vertex Data):以數(shù)組的形式傳遞3個(gè)3D坐標(biāo)作為圖形渲染管線的輸入,用來(lái)表示一個(gè)三角形,這個(gè)數(shù)組叫做頂點(diǎn)數(shù)據(jù)(Vertex Data);頂點(diǎn)數(shù)據(jù)是一系列頂點(diǎn)的集合。一個(gè)頂點(diǎn)(Vertex)是一個(gè)3D坐標(biāo)的數(shù)據(jù)的集合。
- 頂點(diǎn)屬性(Vertex Attribute):
而頂點(diǎn)數(shù)據(jù)是用頂點(diǎn)屬性(Vertex Attribute)表示的,它可以包含任何我們想用的數(shù)據(jù),比如每個(gè)頂點(diǎn)由一個(gè)3D位置和一些顏色值組成
- 圖元(Primitive)
為了讓OpenGL知道我們的坐標(biāo)和顏色值構(gòu)成的到底是什么**,OpenGL需要你去指定這些數(shù)據(jù)所表示的渲染類型**。我們是希望把這些數(shù)據(jù)渲染成一系列的點(diǎn)?一系列的三角形?還是僅僅是一個(gè)長(zhǎng)長(zhǎng)的線?做出的這些提示叫做圖元(Primitive),任何一個(gè)繪制指令的調(diào)用都將把圖元傳遞給OpenGL。這是其中的幾個(gè):GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP
2 基本內(nèi)容
2.1 頂點(diǎn)輸入
開(kāi)始繪制圖形之前,我們必須先給OpenGL輸入一些頂點(diǎn)數(shù)據(jù)。OpenGL是一個(gè)3D圖形庫(kù),所以我們?cè)贠penGL中指定的所有坐標(biāo)都是3D坐標(biāo)(x、y 和 z)。OpenGL僅當(dāng)3D坐標(biāo)在3個(gè)軸(x、y和z)上都為[-1.0,1.0]的范圍內(nèi)時(shí)才處理它。所有在所謂的標(biāo)準(zhǔn)化設(shè)備坐標(biāo)范圍內(nèi)的坐標(biāo)才會(huì)最終呈現(xiàn)在屏幕上
屏幕坐標(biāo)系,(0, 0)坐標(biāo)是這個(gè)圖像的中心,而不是左上角
-
X軸朝右
-
Y軸朝上
-
Z軸指向您后面,通常深度可以理解為z坐標(biāo),它代表一個(gè)像素在空間中和你的距離
由于我們希望渲染一個(gè)三角形,我們一共要指定三個(gè)頂點(diǎn),每個(gè)頂點(diǎn)都有一個(gè)3D位置。定義為一個(gè)float數(shù)組。
float vertices[] = {-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f, 0.5f, 0.0f };1、標(biāo)準(zhǔn)化設(shè)備坐標(biāo)——>屏幕空間坐標(biāo):glViewport視口變換(Viewport Transform)完成的。 2、屏幕空間坐標(biāo)——>變換為片段——>片段著色器2.2 頂點(diǎn)著色器(Vertex Shader)
作用是坐標(biāo)變換,輸出經(jīng)過(guò)轉(zhuǎn)化之后的位置坐標(biāo)
我們需要做的第一件事是用著色器語(yǔ)言GLSL(OpenGL Shading Language)編寫(xiě)頂點(diǎn)著色器。
輸入:頂點(diǎn)坐標(biāo)
輸出:坐標(biāo)gl_Position和其它屬性 vec4(),四維坐標(biāo)
attribute vec3 position; // 點(diǎn)云空間坐標(biāo) void main() {gl_Position = vec4(scale * position,1.0);// 注意最后一個(gè)分量是用在透視除法(Perspective Division)上。 }相關(guān)變量
-
uniform:一致變量,全局變量,對(duì)所有頂點(diǎn)或片斷都一樣
-
attribute:頂點(diǎn)屬性,每頂點(diǎn)不同
-
varying:可變變量,用于頂點(diǎn)、片斷著色器間傳遞自定義數(shù)據(jù),在圖元裝配和光柵化過(guò)程,varying變量會(huì)被插值處理
2.3 片段著色器(Fragment Shader)
片段著色器所做的是計(jì)算像素最后的顏色輸出。在計(jì)算機(jī)圖形中顏色被表示為有4個(gè)元素的數(shù)組:紅色、綠色、藍(lán)色和alpha(透明度)分量,通常縮寫(xiě)為RGBA。每個(gè)顏色分量的強(qiáng)度設(shè)置在[0.0,1.0]之間。
片段著色器需要一個(gè)vec4顏色輸出變量,因?yàn)槠沃餍枰梢粋€(gè)最終輸出的顏色。如果你在片段著色器沒(méi)有定義輸出顏色,OpenGL會(huì)把你的物體渲染為黑色(或白色)。
uniform是全局變量,我們可以在任何著色器中定義它們,而無(wú)需通過(guò)頂點(diǎn)著色器作為中介。
uniform vec4 color; void main() {// 片段著色器只需要一個(gè)輸出變量,這個(gè)變量是一個(gè)4分量向量,它表示的是最終的輸出顏色gl_FragColor = color; }2.4 著色器程序(Program)
著色器鏈接為一個(gè)著色器程序?qū)ο?/strong>,然后在渲染對(duì)象的時(shí)候激活這個(gè)著色器程序。已激活著色器程序的著色器將在我們發(fā)送渲染調(diào)用的時(shí)候被使用。我們必須在渲染前指定OpenGL該如何解釋頂點(diǎn)數(shù)據(jù),對(duì)應(yīng)最后一個(gè)參數(shù)。為了讓OpenGL知道我們的坐標(biāo)和顏色值構(gòu)成的到底是什么**,OpenGL需要你去指定這些數(shù)據(jù)所表示的渲染類型**。做出的這些提示叫做圖元(Primitive)
gloo.Program(vertex, fragment, count=len(self.xyz)).draw(gl.GL_QUADS)2.5 紋理
紋理是一個(gè)2D圖片,它可以用來(lái)添加物體的細(xì)節(jié);你可以想象紋理是一張繪有磚塊的紙,無(wú)縫折疊貼合到你的3D的房子上,這樣你的房子看起來(lái)就像有磚墻外表了。因?yàn)槲覀兛梢栽谝粡垐D片上插入非常多的細(xì)節(jié),這樣就可以讓物體非常精細(xì)而不用指定額外的頂點(diǎn)。
為了能夠把紋理映射到三角形上,我們需要指定三角形的每個(gè)頂點(diǎn)各自對(duì)應(yīng)紋理的哪個(gè)部分。這樣每個(gè)頂點(diǎn)就會(huì)關(guān)聯(lián)著一個(gè)紋理坐標(biāo),用來(lái)標(biāo)明該從紋理圖像的哪個(gè)部分采樣。之后在圖形的其它片段上進(jìn)行片段插值(Fragment Interpolation)
加載紋理
使用紋理之前要做的第一件事是把它們加載到我們的應(yīng)用中。正確地加載圖像并生成一個(gè)紋理對(duì)象,在渲染之前先把它綁定到合適的紋理單元上:
earthTexture_01 = np.array(Image.open(".//resources//image//世界地圖4.jpg"))GPU上傳上傳點(diǎn)云紋理坐標(biāo)、紋理圖像
program = gloo.Program(vertex, fragment, count=len(xyz)) program['texture'] = texture代碼中實(shí)現(xiàn),在球類里面已經(jīng)計(jì)算了紋理坐標(biāo)
def sphere(r=1.0, m=100, n=100):"""計(jì)算球面點(diǎn)云坐標(biāo)和紋理坐標(biāo):param r: 半徑:param m: 經(jīng)線數(shù):param n: 緯線數(shù):return: 坐標(biāo)和紋理坐標(biāo)"""t = np.linspace(0, np.pi, m)p = np.linspace(0, 2 * np.pi, n)positions = []tex_positions = []normal = []for i in range(m - 1):for j in range(n - 1):x = r * np.sin(t[i]) * np.cos(p[j])y = r * np.sin(t[i]) * np.sin(p[j])z = r * np.cos(t[i])positions.append([x, y, z])tex_positions.append([p[j] / np.pi / 2, t[i] / np.pi]) # 就相當(dāng)于把整個(gè)圖片按長(zhǎng)分成2pi份,按高分成pi份x = r * np.sin(t[i + 1]) * np.cos(p[j])y = r * np.sin(t[i + 1]) * np.sin(p[j])z = r * np.cos(t[i + 1])positions.append([x, y, z])tex_positions.append([p[j] / np.pi / 2, t[i + 1] / np.pi])x = r * np.sin(t[i + 1]) * np.cos(p[j + 1])y = r * np.sin(t[i + 1]) * np.sin(p[j + 1])z = r * np.cos(t[i + 1])positions.append([x, y, z])tex_positions.append([p[j + 1] / np.pi / 2, t[i + 1] / np.pi])x = r * np.sin(t[i]) * np.cos(p[j + 1])y = r * np.sin(t[i]) * np.sin(p[j + 1])z = r * np.cos(t[i])positions.append([x, y, z])tex_positions.append([p[j + 1] / np.pi / 2, t[i] / np.pi])positions = np.array(positions)tex_positions = np.array(tex_positions)normal = np.array(positions)return positions, tex_positions, normal3 坐標(biāo)變換
3.1 矩陣運(yùn)算
3.1.1 向量相乘
兩個(gè)向量相乘是一種很奇怪的情況。普通的乘法在向量上是沒(méi)有定義的,因?yàn)樗谝曈X(jué)上是沒(méi)有意義的。但是在相乘的時(shí)候我們有兩種特定情況可以選擇:一個(gè)是點(diǎn)乘(Dot Product),記作vˉ?kˉvˉ?kˉ,另一個(gè)是叉乘(Cross Product),記作vˉ×kˉvˉ×kˉ。
點(diǎn)乘
兩個(gè)向量的點(diǎn)乘等于它們的數(shù)乘結(jié)果乘以兩個(gè)向量之間夾角的余弦值。可能聽(tīng)起來(lái)有點(diǎn)費(fèi)解,我們來(lái)看一下公式:
那么要計(jì)算兩個(gè)單位向量間的夾角,我們可以使用反余弦函數(shù)cos?1 ,可得結(jié)果是143.1度。現(xiàn)在我們很快就計(jì)算出了這兩個(gè)向量的夾角。點(diǎn)乘會(huì)在計(jì)算光照的時(shí)候非常有用。
叉乘
叉乘只在3D空間中有定義,它需要兩個(gè)不平行向量作為輸入,**生成一個(gè)正交于兩個(gè)輸入向量的第三個(gè)向量。**如果輸入的兩個(gè)向量也是正交的,那么叉乘之后將會(huì)產(chǎn)生3個(gè)互相正交的向量。接下來(lái)的教程中這會(huì)非常有用。下面的圖片展示了3D空間中叉乘的樣子:
兩個(gè)正交向量A和B叉積,輸出得到一個(gè)正交于兩個(gè)輸入向量的第三個(gè)向量
3.1.2 矩陣
數(shù)乘
現(xiàn)在我們也就能明白為什么這些單獨(dú)的數(shù)字要叫做**標(biāo)量(Scalar)了。簡(jiǎn)單來(lái)說(shuō),標(biāo)量就是用它的值縮放(Scale)**矩陣的所有元素,上面中所有的元素都被放大了2倍。
單位矩陣
這種變換矩陣使一個(gè)向量完全不變:
縮放矩陣
如果我們把縮放變量表示為(S1,S2,S3)我們可以為任意向量(x,y,z)定義一個(gè)縮放矩陣:
位移矩陣
對(duì)于位移來(lái)說(shuō)它們是第四列最上面的3個(gè)值。如果我們把位移向量表示為(Tx,Ty,Tz),我們就能把位移矩陣定義為:這才是把3維坐標(biāo)變成四維坐標(biāo)的原因:方便對(duì)左邊通過(guò)矩陣進(jìn)行平移操作,有了位移矩陣我們就可以在3個(gè)方向(x、y、z)上移動(dòng)物體,它是我們的變換工具箱中非常有用的一個(gè)變換矩陣。
旋轉(zhuǎn)矩陣
在3D空間中旋轉(zhuǎn)需要定義一個(gè)角和一個(gè)旋轉(zhuǎn)軸(Rotation Axis)。物體會(huì)沿著給定的旋轉(zhuǎn)軸旋轉(zhuǎn)特定角度。使用三角學(xué),給定一個(gè)角度,可以把一個(gè)向量變換為一個(gè)經(jīng)過(guò)旋轉(zhuǎn)的新向量。這通常是使用一系列正弦和余弦函數(shù)(一般簡(jiǎn)稱sin和cos)各種巧妙的組合得到的。.轉(zhuǎn)半圈會(huì)旋轉(zhuǎn)360/2 = 180度,向右旋轉(zhuǎn)1/5圈表示向右旋轉(zhuǎn)360/5 = 72度。
矩陣的轉(zhuǎn)置
矩陣的逆
3.2 坐標(biāo)運(yùn)算
為了將坐標(biāo)從一個(gè)坐標(biāo)系變換到另一個(gè)坐標(biāo)系,我們需要用到幾個(gè)變換矩陣,最重要的幾個(gè)分別是模型(Model)、觀察(View)、投影(Projection)三個(gè)矩陣。
-
局部坐標(biāo):以對(duì)象自己為中心,是對(duì)象相對(duì)于局部原點(diǎn)的坐標(biāo)。
-
世界空間坐標(biāo):物體在一個(gè)更大的空間所處的坐標(biāo),可以確定與其他物體的相對(duì)位置,和其它物體一起相對(duì)于世界的原點(diǎn)進(jìn)行擺放。
-
**觀察空間坐標(biāo):以觀察者為坐標(biāo),使得每個(gè)坐標(biāo)都是從攝像機(jī)或者說(shuō)觀察者的角度進(jìn)行觀察的。**確定對(duì)于觀察者來(lái)講的相對(duì)位置關(guān)系
-
**投影坐標(biāo):**投影到裁剪坐標(biāo)。裁剪坐標(biāo)會(huì)被處理至-1.0到1.0的范圍內(nèi),并判斷哪些頂點(diǎn)將會(huì)出現(xiàn)在屏幕上。
-
**視口變換:**將裁剪坐標(biāo)變換為屏幕坐標(biāo),視口變換將位于-1.0到1.0范圍的坐標(biāo)變換到由glViewport函數(shù)所定義的坐標(biāo)范圍內(nèi)。最后變換出來(lái)的坐標(biāo)將會(huì)送到光柵器,將其轉(zhuǎn)化為片段。
例如,當(dāng)需要對(duì)物體進(jìn)行修改的時(shí)候,在局部空間中來(lái)操作會(huì)更說(shuō)得通;
如果要對(duì)一個(gè)物體做出一個(gè)相對(duì)于其它物體位置的操作時(shí),在世界坐標(biāo)系中來(lái)做這個(gè)才更說(shuō)得通。
3.2.1 局部空間
局部空間是指物體所在的坐標(biāo)空間,即對(duì)象最開(kāi)始所在的地方。想象你在一個(gè)建模軟件(比如說(shuō)Blender)中創(chuàng)建了一個(gè)立方體。你創(chuàng)建的立方體的原點(diǎn)有可能位于(0, 0, 0),你的模型的所有頂點(diǎn)都是在局部空間中:它們相對(duì)于你的物體來(lái)說(shuō)都是局部的。
3.2.2 模型矩陣:局部——世界坐標(biāo)
如果我們將我們所有的物體導(dǎo)入到程序當(dāng)中,它們有可能會(huì)全擠在世界的原點(diǎn)(0, 0, 0)上,我們想為每一個(gè)物體定義一個(gè)位置,從而能在更大的世界當(dāng)中放置它們。這就是你希望物體變換到的空間。物體的坐標(biāo)將會(huì)從局部變換到世界空間;該變換是由模型矩陣(Model Matrix)實(shí)現(xiàn)的。
模型矩陣是一種變換矩陣,它能通過(guò)對(duì)物體進(jìn)行位移、縮放、旋轉(zhuǎn)來(lái)將它置于它本應(yīng)該在的位置或朝向。
你可以將它想像為變換一個(gè)房子,你需要先將它縮小(它在局部空間中太大了),并將其位移至郊區(qū)的一個(gè)小鎮(zhèn),然后在y軸上往左旋轉(zhuǎn)一點(diǎn)以搭配附近的房子。你也可以把上一節(jié)將箱子到處擺放在場(chǎng)景中用的那個(gè)矩陣大致看作一個(gè)模型矩陣;我們將箱子的局部坐標(biāo)變換到場(chǎng)景/世界中的不同位置。
glm.translate(m, self.orbit[0], self.orbit[1], self.orbit[2]) # 公轉(zhuǎn), # translate表示瞬時(shí)坐標(biāo)的位移變換經(jīng)過(guò)怎樣的變換能得到單位矩陣,而這個(gè)返回的變換矩陣就是就得到模型矩陣 # 所以求model矩陣的思路是,算出局部坐標(biāo)的瞬時(shí)位置,反推局部——世界的位移矩陣 # glm::translate() 創(chuàng)建一個(gè)位移矩陣,第一個(gè)參數(shù)是目標(biāo)矩陣,第二個(gè)參數(shù)是位移的方向向量 # 返回的矩陣是能實(shí)現(xiàn)位移的矩陣,3.2.3 視圖矩陣:世界——觀察坐標(biāo)
觀察空間就是從攝像機(jī)的視角所觀察到的空間。而這通常是由一系列的位移和旋轉(zhuǎn)的組合來(lái)完成,平移/旋轉(zhuǎn)場(chǎng)景從而使得特定的對(duì)象被變換到攝像機(jī)的前方。這些組合在一起的變換通常存儲(chǔ)在一個(gè)觀察矩陣(View Matrix)里,它被用來(lái)將世界坐標(biāo)變換到觀察空間。
3.2.4 投影矩陣:觀察——裁剪空間坐標(biāo)
為了將頂點(diǎn)坐標(biāo)從觀察變換到裁剪空間,我們需要定義一個(gè)投影矩陣(Projection Matrix),它指定了一個(gè)范圍的坐標(biāo),比如在每個(gè)維度上的-1000到1000。投影矩陣接著會(huì)將在這個(gè)指定的范圍內(nèi)的坐標(biāo)變換為標(biāo)準(zhǔn)化設(shè)備坐標(biāo)的范圍(-1.0, 1.0)。所有在范圍外的坐標(biāo)不會(huì)被映射到在-1.0到1.0的范圍之間,所以會(huì)被裁剪掉
3.2.5 正射投影
效果不真實(shí)
定義了可見(jiàn)的坐標(biāo),它由由寬、高、近(Near)平面和遠(yuǎn)(Far)平面所指定。任何出現(xiàn)在近平面之前或遠(yuǎn)平面之后的坐標(biāo)都會(huì)被裁剪掉。正射平截頭體直接將平截頭體內(nèi)部的所有坐標(biāo)映射為標(biāo)準(zhǔn)化設(shè)備坐標(biāo),因?yàn)槊總€(gè)向量的w分量都沒(méi)有進(jìn)行改變;如果w分量等于1.0,透視除法則不會(huì)改變這個(gè)坐標(biāo)
3.2.6 透視投影
透視(Perspective):離你越遠(yuǎn)的東西看起來(lái)更小。
OpenGL要求所有可見(jiàn)的坐標(biāo)都落在-1.0到1.0范圍內(nèi),作為頂點(diǎn)著色器最后的輸出,頂點(diǎn)坐標(biāo)的每個(gè)分量都會(huì)除以它的w分量,距離觀察者越遠(yuǎn)頂點(diǎn)坐標(biāo)就會(huì)越小。這是也是w分量非常重要的另一個(gè)原因,它能夠幫助我們進(jìn)行透視投影。最后的結(jié)果坐標(biāo)就是處于標(biāo)準(zhǔn)化設(shè)備空間中的。
創(chuàng)建了一個(gè)定義了可視空間的大平截頭體,它的第一個(gè)參數(shù)定義了fov的值,它表示的是視野(Field of View),并且設(shè)置了觀察空間的大小。如果想要一個(gè)真實(shí)的觀察效果,它的值通常設(shè)置為45.0f。第二個(gè)參數(shù)設(shè)置了寬高比,由視口的寬除以高所得。第三和第四個(gè)參數(shù)設(shè)置了平截頭體的近和遠(yuǎn)平面。我們通常設(shè)置近距離為0.1f,而遠(yuǎn)距離設(shè)為100.0f。所有在近平面和遠(yuǎn)平面內(nèi)且處于平截頭體內(nèi)的頂點(diǎn)都會(huì)被渲染。
perspective 函數(shù)使用
glm::perspective(float fovy, float aspect, float zNear, float zFar);
-
第一個(gè)參數(shù)為視錐上下面之間的夾角
-
第二個(gè)參數(shù)為寬高比,即視窗的寬/高
-
第三第四個(gè)參數(shù)分別為近截面和遠(yuǎn)界面的深度
所以整體的坐標(biāo)變換就是這樣
uniform float scale; // 模型縮放因子 uniform mat4 model; // 模型矩陣 uniform mat4 view; // 視圖矩陣 uniform mat4 projection; // 投影矩陣 uniform mat4 viewport; // 視口矩陣 attribute vec3 position; // 點(diǎn)云空間坐標(biāo)void main(){gl_Position = viewport * projection * view * model * vec4(scale * position,1.0); }3.3 攝像機(jī)
當(dāng)我們討論攝像機(jī)/觀察空間(Camera/View Space)的時(shí)候,是在討論以攝像機(jī)的視角作為場(chǎng)景原點(diǎn)時(shí)場(chǎng)景中所有的頂點(diǎn)坐標(biāo):觀察矩陣把所有的世界坐標(biāo)變換為相對(duì)于攝像機(jī)位置與方向的觀察坐標(biāo)。要定義一個(gè)攝像機(jī),我們需要?jiǎng)?chuàng)建一個(gè)三個(gè)單位軸相互垂直的、以攝像機(jī)的位置為原點(diǎn)的坐標(biāo)系。
攝像機(jī)位置
攝像機(jī)位置簡(jiǎn)單來(lái)說(shuō)就是世界空間中一個(gè)指向攝像機(jī)位置的向量。不要忘記正z軸是從屏幕指向你的,如果我們希望攝像機(jī)向后移動(dòng),我們就沿著z軸的正方向移動(dòng)。
Z0 = 200 eyeAt = np.array([0, 0, Z0])攝像機(jī)方向
指的是攝像機(jī)指向哪個(gè)方向。讓攝像機(jī)指向場(chǎng)景原點(diǎn):(0, 0, 0)。
用場(chǎng)景原點(diǎn)向量減去攝像機(jī)位置向量的結(jié)果就是攝像機(jī)的指向向量。由于我們知道攝像機(jī)指向z軸負(fù)方向,但我們希望方向向量(Direction Vector)指向攝像機(jī)的z軸正方向。如果我們交換相減的順序,我們就會(huì)獲得一個(gè)指向攝像機(jī)正z軸方向的向量:
lookAt = np.array([0, 0, 0]) # cam - target 就是獲得的攝像機(jī)方向方向向量(Direction Vector)并不是最好的名字,因?yàn)樗鼘?shí)際上指向從它到目標(biāo)向量的相反方向(譯注:注意看前面的那個(gè)圖,藍(lán)色的方向向量大概指向z軸的正方向,與攝像機(jī)實(shí)際指向的方向是正好相反的)。
右軸
它代表攝像機(jī)空間的x軸的正方向。把上向量和第二步得到的方向向量進(jìn)行叉乘。兩個(gè)向量叉乘的結(jié)果會(huì)同時(shí)垂直于兩向量,因此我們會(huì)得到指向x軸正方向的那個(gè)向量
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));上軸
一個(gè)指向攝像機(jī)的正y軸向量,現(xiàn)在我們已經(jīng)有了x軸向量和z軸向量,獲取一個(gè)指向攝像機(jī)的正y軸向量就相對(duì)簡(jiǎn)單了:我們把右向量和方向向量進(jìn)行叉乘:
eyeUp = np.array([0, 1, 0])3.3.1 Look At
將所有坐標(biāo)變換到攝像機(jī)的視圖矩陣
現(xiàn)在我們有了3個(gè)相互垂直的軸和一個(gè)定義攝像機(jī)空間的位置坐標(biāo),用矩陣來(lái)表示這個(gè)坐標(biāo)軸,用這個(gè)矩陣乘以任何向量來(lái)將其變換到那個(gè)坐標(biāo)空間。這正是LookAt矩陣所做的
其中R是右向量,U是上向量,D是方向向量,P是攝像機(jī)位置向量。注意,位置向量是相反的,因?yàn)槲覀冏罱K希望把世界平移到與我們自身移動(dòng)的相反方向。**把這個(gè)LookAt矩陣作為觀察矩陣可以很高效地把所有世界坐標(biāo)變換到剛剛定義的觀察空間。**LookAt矩陣就像它的名字表達(dá)的那樣:它會(huì)創(chuàng)建一個(gè)看著(Look at)給定目標(biāo)的觀察矩陣。
def view(cam, tar, u): #u是上"""計(jì)算視圖矩陣:將世界坐標(biāo)系的坐標(biāo)變換到觀察坐標(biāo)系,也就是相機(jī)的視圖當(dāng)中"""cam = np.array(cam, np.float32)tar = np.array(tar, np.float32)u = np.array(u, np.float32)f = tar - cam # 攝像機(jī)方向,從原點(diǎn)指向攝像機(jī)的坐標(biāo)f = f/np.linalg.norm(f) # 求求二范數(shù),也就是模長(zhǎng)u = u/np.linalg.norm(u) s = np.cross(f, u) # 返回兩個(gè)向量的叉積。 得到攝像機(jī)坐標(biāo)的右軸u = np.cross(s, f) # 得到攝像機(jī)坐標(biāo)的上軸R = np.array([[ s[0], s[1], s[2], 0], # R代表三維坐標(biāo)軸[ u[0], u[1], u[2], 0],[-f[0],-f[1],-f[2], 0],[ 0, 0, 0, 1]]) T = np.array([[ 1, 0, 0, -cam[0]], # T代表相機(jī)的坐標(biāo)[ 0, 1, 0, -cam[1]],[ 0, -0, 1, -cam[2]],[ 0, 0, 0, 1]])v2 = np.matmul(R, T) # 矩陣相乘既得到了lookAt矩陣也就是我們的觀察矩陣v2 = np.transpose(v2) # 轉(zhuǎn)置,行變列return v2在代碼中,我們是將LookAt矩陣直接賦值給view矩陣的,然后把view傳遞到vertex shader中。那么view矩陣是干嘛的呢?view是負(fù)責(zé)把世界坐標(biāo)系轉(zhuǎn)換成用攝像機(jī)的視角所觀察到的坐標(biāo)系當(dāng)中。
LookAt
快速構(gòu)建攝像機(jī)坐標(biāo)系的方法(構(gòu)建結(jié)果就是這個(gè)LookAt矩陣)
坐標(biāo)系空間變換方法(世界空間->觀察空間)。用LookAt矩陣左乘某向量X,就可以將X從世界空間變換到觀察空間。
3.3.2 歐拉角
歐拉角(Euler Angle)是可以表示3D空間中任何旋轉(zhuǎn)的3個(gè)值,俯仰角是描述我們?nèi)绾瓮匣蛲驴吹慕?#xff0c;可以在第一張圖中看到。第二張圖展示了偏航角,偏航角表示我們往左和往右看的程度。滾轉(zhuǎn)角代表我們?nèi)绾?strong>翻滾攝像機(jī),通常在太空飛船的攝像機(jī)中使用。每個(gè)歐拉角都有一個(gè)值來(lái)表示,把三個(gè)角結(jié)合起來(lái)我們就能夠計(jì)算3D空間中任何的旋轉(zhuǎn)向量了。
對(duì)于我們的攝像機(jī)系統(tǒng)來(lái)說(shuō),我們只關(guān)心俯仰角和偏航角,所以我們不會(huì)討論滾轉(zhuǎn)角。給定一個(gè)俯仰角和偏航角,我們可以把它們轉(zhuǎn)換為一個(gè)代表新的方向向量的3D向量。我們可以看到x分量取決于cos(yaw)的值,z值同樣取決于偏航角的正弦值。這樣我們就有了一個(gè)可以把俯仰角和偏航角轉(zhuǎn)化為用來(lái)自由旋轉(zhuǎn)視角的攝像機(jī)的3維方向向量了。
# 設(shè)置視點(diǎn)位置和方向 Z0 = 200 eyeAt = np.array([0, 0, Z0]) # cam lookAt = np.array([0, 0, 0]) # 目標(biāo)衛(wèi)星 eyeUp = np.array([0, 1, 0]) # 自轉(zhuǎn)軸 view_0 = myMath.view(eyeAt, lookAt, eyeUp)#計(jì)算視圖矩陣def view(cam, tar, u):"""計(jì)算視圖矩陣"""cam = np.array(cam, np.float32)tar = np.array(tar, np.float32)u = np.array(u, np.float32)f = tar - camf = f/np.linalg.norm(f)u = u/np.linalg.norm(u)s = np.cross(f, u)u = np.cross(s, f)R = np.array([[ s[0], s[1], s[2], 0],[ u[0], u[1], u[2], 0],[-f[0],-f[1],-f[2], 0],[ 0, 0, 0, 1]])T = np.array([[ 1, 0, 0, -cam[0]],[ 0, 1, 0, -cam[1]],[ 0, -0, 1, -cam[2]],[ 0, 0, 0, 1]])v2 = np.matmul(R, T)v2 = np.transpose(v2)# print(v2)return v24 光照
當(dāng)我們?cè)贠penGL中創(chuàng)建一個(gè)光源時(shí),我們希望給光源一個(gè)顏色。我們將光源設(shè)置為白色。當(dāng)我們把光源的顏色與物體的顏色值相乘,所得到的就是這個(gè)物體所反射的顏色(也就是我們所感知到的顏色),使用不同的光源顏色來(lái)顯現(xiàn)不同的顏色。
uniform vec3 objectColor; uniform vec3 lightColor; void main() {FragColor = vec4(lightColor * objectColor, 1.0); } def __init__(self, color=(1.0, 1.0, 0, 1.0)): # 構(gòu)造器傳入self.fragment = """void main(){gl_FragColor = vec4"""+str(color)+""";} """4.1 光照模型
OpenGL的光照使用的是簡(jiǎn)化的光照模型,對(duì)現(xiàn)實(shí)的情況進(jìn)行近似,這樣處理起來(lái)會(huì)更容易一些,而且看起來(lái)也差不多一樣。這些光照模型都是基于我們對(duì)光的物理特性的理解。
比如馮氏光照模型(Phong Lighting Model)。馮氏光照模型的主要結(jié)構(gòu)由3個(gè)分量組成:環(huán)境(Ambient)、漫反射(Diffuse)和鏡面(Specular)光照。下面這張圖展示了這些光照分量看起來(lái)的樣子:
- 環(huán)境光照(Ambient Lighting):即使在黑暗的情況下,世界上通常也仍然有一些光亮(月亮、遠(yuǎn)處的光),所以物體幾乎永遠(yuǎn)不會(huì)是完全黑暗的。為了模擬這個(gè),我們會(huì)使用一個(gè)環(huán)境光照常量,它永遠(yuǎn)會(huì)給物體一些顏色。
- 漫反射光照(Diffuse Lighting):模擬光源對(duì)物體的方向性影響(Directional Impact)。它是馮氏光照模型中視覺(jué)上最顯著的分量。物體的某一部分越是正對(duì)著光源,它就會(huì)越亮。
- 鏡面光照(Specular Lighting):模擬有光澤物體上面出現(xiàn)的亮點(diǎn)。鏡面光照的顏色相比于物體的顏色會(huì)更傾向于光的顏色。
4.1.1 環(huán)境光照
我們使用一個(gè)很小的常量(光照)顏色,添加到物體片段的最終顏色中,實(shí)現(xiàn)即便場(chǎng)景中沒(méi)有直接的光源也能看起來(lái)存在一些發(fā)散的光。
void main() {float ambientStrength = 0.1;vec3 ambient = ambientStrength * lightColor;vec3 result = ambient * objectColor;FragColor = vec4(result, 1.0); }4.1.2 漫反射光照
漫反射是指光線被粗糙表面無(wú)規(guī)則地向各個(gè)方向反射的現(xiàn)象。很多物體,如植物、墻壁、衣服等,其表面粗看起來(lái)似乎是平滑,但用放大鏡仔細(xì)觀察,就會(huì)看到其表面是凹凸不平的,所以本來(lái)是平行的太陽(yáng)光被這些表面反射后,就彌漫地射向不同方向。
圖左上方有一個(gè)光源,它所發(fā)出的光線落在物體的一個(gè)片段上。為了測(cè)量光線和片段的角度,我們使用一個(gè)叫做**法向量(Normal Vector)**的東西,它是垂直于片段表面的一個(gè)向量(這里以黃色箭頭表示),這兩個(gè)向量之間的角度很容易就能夠通過(guò)點(diǎn)乘計(jì)算出來(lái),我們知道兩個(gè)單位向量的夾角越小,它們點(diǎn)乘的結(jié)果越傾向于1。θ越大,光對(duì)片段顏色的影響就應(yīng)該越小。
所以,計(jì)算漫反射光照需要什么?
- 法向量:一個(gè)垂直于頂點(diǎn)表面的向量。
- 定向的光線:作為光源的位置與片段的位置之間向量差的方向向量。為了計(jì)算這個(gè)光線,我們需要光的位置向量和片段的位置向量。
注意
目前片段著色器里的計(jì)算都是在世界空間坐標(biāo)中進(jìn)行的。所以,我們是不是應(yīng)該把法向量也轉(zhuǎn)換為世界空間坐標(biāo)?基本正確,但是這不是簡(jiǎn)單地把它乘以一個(gè)模型矩陣就能搞定的。
首先,法向量只是一個(gè)方向向量,不能表達(dá)空間中的特定位置。同時(shí),法向量沒(méi)有齊次坐標(biāo)(頂點(diǎn)位置中的w分量)。這意味著,位移不應(yīng)該影響到法向量。因此,如果我們打算把法向量乘以一個(gè)模型矩陣,我們就要從矩陣中移除位移部分,只選用模型矩陣左上角3×3的矩陣。對(duì)于法向量,我們只希望對(duì)它實(shí)施縮放和旋轉(zhuǎn)變換。
其次,如果模型矩陣執(zhí)行了不等比縮放,頂點(diǎn)的改變會(huì)導(dǎo)致法向量不再垂直于表面了。因此,我們不能用這樣的模型矩陣來(lái)變換法向量。下面的圖展示了應(yīng)用了不等比縮放的模型矩陣對(duì)法向量的影響:
每當(dāng)我們應(yīng)用一個(gè)不等比縮放時(shí),法向量就不會(huì)再垂直于對(duì)應(yīng)的表面了,這樣光照就會(huì)被破壞。
修復(fù)這個(gè)行為的訣竅是使用一個(gè)為法向量專門(mén)定制的模型矩陣。這個(gè)矩陣稱之為法線矩陣,大部分的資源都會(huì)將法線矩陣定義為應(yīng)用到模型-觀察矩陣上的操作,但是由于我們只在世界空間中進(jìn)行操作(不是在觀察空間),我們只使用模型矩陣。
Normal = mat3(transpose(inverse(model))) * aNormal;在漫反射光照部分,光照表現(xiàn)并沒(méi)有問(wèn)題,這是因?yàn)槲覀儧](méi)有對(duì)物體本身執(zhí)行任何縮放操作,所以并不是必須要使用一個(gè)法線矩陣,僅僅讓模型矩陣乘以法線也可以。可是,如果你進(jìn)行了不等比縮放,使用法線矩陣去乘以法向量就是必不可少的了。
4.1.3 鏡面光照
鏡面光照也是依據(jù)光的方向向量和物體的法向量來(lái)決定的,當(dāng)我們?nèi)タ垂獗晃矬w所反射的那個(gè)方向的時(shí)候,我們會(huì)看到一個(gè)高光。觀察向量是鏡面光照附加的一個(gè)變量,我們可以使用觀察者世界空間位置和片段的位置來(lái)計(jì)算它。之后,我們計(jì)算鏡面光強(qiáng)度,用它乘以光源的顏色,再將它加上環(huán)境光和漫反射分量。
下一步,我們計(jì)算視線方向向量,和對(duì)應(yīng)的沿著法線軸的反射向量:
vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm);需要注意的是我們對(duì)lightDir向量進(jìn)行了取反。reflect函數(shù)要求第一個(gè)向量是從光源指向片段位置的向量,但是lightDir當(dāng)前正好相反,是從片段指向光源(由先前我們計(jì)算lightDir向量時(shí),減法的順序決定)。為了保證我們得到正確的reflect向量,我們通過(guò)對(duì)lightDir向量取反來(lái)獲得相反的方向。第二個(gè)參數(shù)要求是一個(gè)法向量,所以我們提供的是已標(biāo)準(zhǔn)化的norm向量。
一個(gè)物體的反光度越高,反射光的能力越強(qiáng),散射得越少,高光點(diǎn)就會(huì)越小。在下面的圖片里,你會(huì)看到不同反光度的視覺(jué)效果影響:
positions = np.array(positions) //非常奇怪?法向直接等于點(diǎn)云坐標(biāo)? normal = np.array(positions) sun_direction = np.array(sat[0].orbit_xyz(Recorder.t)) - np.array(sat[1].orbit_xyz(Recorder.t)) //光的位置減片段在地心赤道春分坐標(biāo)系中的衛(wèi)星和相機(jī)瞬時(shí)坐標(biāo)vertex = """ attribute vec3 normal; // 點(diǎn)云法向量 (1*3)varying vec3 v_normal; // 向片斷著色程序傳遞法向量void main(){vec4 n = model * vec4(normal,0.0); // model是模型矩陣,定義了自我中心坐標(biāo)軸轉(zhuǎn)化成世界坐標(biāo)的一系列變換v_normal = normalize(vec3(n[0],n[1],n[2]));//歸一化處理保證點(diǎn)乘結(jié)果就是夾角} """fragment = """varying vec3 v_normal; void main(){float cosine = max(0.0,dot(normalize(sun_direction),v_normal)); // 太陽(yáng)光線的方向點(diǎn)乘法向就是夾角,點(diǎn)乘結(jié)果就是鏡面發(fā)射影響。結(jié)果值再乘以光的顏色,。兩個(gè)向量之間的角度越大,反射分量就會(huì)越小:float i_r = 3*(c[0]*cosine+0.00*c[0])/3.14; // c 代表的是color的太陽(yáng)光數(shù)組float i_g = 3*(c[1]*cosine+0.00*c[1])/3.14; float i_b = 3*(c[2]*cosine+0.00*c[2])/3.14; color = vec4(i_r,i_g,i_b,1.0);gl_FragColor = color;} """4.2 材質(zhì)
4.3 光源
4.3.1 平行光
當(dāng)一個(gè)光源處于很遠(yuǎn)的地方時(shí),來(lái)自光源的每條光線就會(huì)近似于互相平行。不論物體和/或者觀察者的位置,看起來(lái)好像所有的光都來(lái)自于同一個(gè)方向。因?yàn)樗械墓饩€都是平行的,所以物體與光源的相對(duì)位置是不重要的,因?yàn)閷?duì)場(chǎng)景中每一個(gè)物體光的方向都是一致的。由于光的位置向量保持一致,場(chǎng)景中每個(gè)物體的光照計(jì)算將會(huì)是類似的。
我們可以定義一個(gè)光線方向向量而不是位置向量來(lái)模擬一個(gè)定向光。著色器的計(jì)算基本保持不變,但這次我們將直接使用光的direction向量而不是通過(guò)direction來(lái)計(jì)算lightDir向量。
sun_direction = np.array(Sun.orbit)當(dāng)我們將位置向量定義為一個(gè)vec4時(shí),很重要的一點(diǎn)是要將w分量設(shè)置為1.0,這樣變換和投影才能正確應(yīng)用。然而,當(dāng)我們定義一個(gè)方向向量為vec4的時(shí)候,我們不想讓位移有任何的效果(因?yàn)樗鼉H僅代表的是方向),所以我們將w分量設(shè)置為0.0。
方向向量就會(huì)像這樣來(lái)表示:vec4(0.2f, 1.0f, 0.3f, 0.0f)。這也可以作為一個(gè)快速檢測(cè)光照類型的工具:你可以檢測(cè)w分量是否等于1.0,來(lái)檢測(cè)它是否是光的位置向量;w分量等于0.0,則它是光的方向向量,這樣就能根據(jù)這個(gè)來(lái)調(diào)整光照計(jì)算了:
if(lightVector.w == 0.0) // 注意浮點(diǎn)數(shù)據(jù)類型的誤差// 執(zhí)行定向光照計(jì)算 else if(lightVector.w == 1.0)// 根據(jù)光源的位置做光照計(jì)算(與上一節(jié)一樣)4.3.2 點(diǎn)光源
點(diǎn)光源是處于世界中某一個(gè)位置的光源,它會(huì)朝著所有方向發(fā)光,但光線會(huì)隨著距離逐漸衰減。
4.3.3 聚光源
5 模型
我們不太能夠?qū)ο袷欠孔印⑵嚮蛘呷诵谓巧@樣的復(fù)雜形狀手工定義所有的頂點(diǎn)、法線和紋理坐標(biāo)。我們想要的是將這些模型(Model)導(dǎo)入(Import)到程序當(dāng)中,模型通常都由3D藝術(shù)家在Blender、3DS Max或者M(jìn)aya這樣的3D建模工具制作。這些工具將會(huì)在導(dǎo)出到模型文件的時(shí)候自動(dòng)生成所有的頂點(diǎn)坐標(biāo)、頂點(diǎn)法線以及紋理坐標(biāo)。
5.1 網(wǎng)格
通常每個(gè)模型都由幾個(gè)子模型/形狀組合而成。組合模型的每個(gè)單獨(dú)的形狀就叫做一個(gè)網(wǎng)格(Mesh)。比如說(shuō)有一個(gè)人形的角色:藝術(shù)家通常會(huì)將頭部、四肢、衣服、武器建模為分開(kāi)的組件,并將這些網(wǎng)格組合而成的結(jié)果表現(xiàn)為最終的模型。一個(gè)網(wǎng)格是我們?cè)贠penGL中繪制物體所需的最小單位(頂點(diǎn)數(shù)據(jù)、索引和材質(zhì)屬性)。一個(gè)模型(通常)會(huì)包括多個(gè)網(wǎng)格。
一個(gè)網(wǎng)格應(yīng)該至少需要一系列的頂點(diǎn),每個(gè)頂點(diǎn)包含一個(gè)位置向量、一個(gè)法向量和一個(gè)紋理坐標(biāo)向量。一個(gè)網(wǎng)格還應(yīng)該包含用于索引繪制的索引以及紋理形式的材質(zhì)數(shù)據(jù)(漫反射/鏡面光貼圖)。
6 深度:遮擋
判斷哪些是被遮擋的部分不應(yīng)該被顯示
當(dāng)深度測(cè)試(Depth Testing)被啟用的時(shí)候,OpenGL會(huì)將一個(gè)片段的深度值與深度緩沖的內(nèi)容進(jìn)行對(duì)比。OpenGL會(huì)執(zhí)行一個(gè)深度測(cè)試,如果這個(gè)測(cè)試通過(guò)了的話,深度緩沖將會(huì)更新為新的深度值。如果深度測(cè)試失敗了,片段將會(huì)被丟棄。
gl_FragCoord的x和y分量代表了片段的屏幕空間坐標(biāo)(其中(0, 0)位于左下角)。gl_FragCoord中也包含了一個(gè)z分量,它包含了片段真正的深度值。z值就是需要與深度緩沖內(nèi)容所對(duì)比的那個(gè)值。
7 混合:透明度
OpenGL中,混合(Blending)通常是實(shí)現(xiàn)物體透明度(Transparency)的一種技術(shù)。透明就是說(shuō)一個(gè)物體(或者其中的一部分)不是純色(Solid Color)的,它的顏色是物體本身的顏色和它背后其它物體的顏色的不同強(qiáng)度結(jié)合。一個(gè)有色玻璃窗是一個(gè)透明的物體,玻璃有它自己的顏色,但它最終的顏色還包含了玻璃之后所有物體的顏色。這也是混合這一名字的出處,我們混合(Blend)(不同物體的)多種顏色為一種顏色。所以透明度能讓我們看穿物體。
**一個(gè)物體的透明度是通過(guò)它顏色的aplha值來(lái)決定的。**Alpha顏色值是顏色向量的第四個(gè)分量,設(shè)置為1.0,讓這個(gè)物體的透明度為0.0,而當(dāng)alpha值為0.0時(shí)物體將會(huì)是完全透明的。當(dāng)alpha值為0.5時(shí),物體的顏色有50%是來(lái)自物體自身的顏色,50%來(lái)自背后物體的顏色。
實(shí)現(xiàn):丟棄(Discard)顯示紋理中透明部分的片段,不將這些片段存儲(chǔ)到顏色緩沖中。
8 面剔除:丟棄背向面
OpenGL能夠檢查所有面向(Front Facing)觀察者的面,并渲染它們,而丟棄那些背向(Back Facing)的面,節(jié)省我們很多的片段著色器調(diào)用。但我們?nèi)砸嬖VOpenGL哪些面是正向面(Front Face),哪些面是背向面(Back Face)。OpenGL使用了一個(gè)很聰明的技巧,分析頂點(diǎn)數(shù)據(jù)的環(huán)繞順序(Winding Order)。
8.1 環(huán)繞順序
當(dāng)我們定義一組三角形頂點(diǎn)時(shí),我們會(huì)以特定的環(huán)繞順序來(lái)定義它們,可能是順時(shí)針(Clockwise)的,也可能是逆時(shí)針(Counter-clockwise)的。每個(gè)三角形由3個(gè)頂點(diǎn)所組成,我們會(huì)從三角形中間來(lái)看,為這3個(gè)頂點(diǎn)設(shè)定一個(gè)環(huán)繞順序。
觀察者所面向的所有三角形頂點(diǎn)就是我們所指定的正確環(huán)繞順序了,而立方體另一面的三角形頂點(diǎn)則是以相反的環(huán)繞順序所渲染的。這樣的結(jié)果就是,我們所面向的三角形將會(huì)是正向三角形,而背面的三角形則是背向三角形。通過(guò)這個(gè)順序能甄別面向還是背向
glCullFace函數(shù)有三個(gè)可用的選項(xiàng):
- GL_BACK:只剔除背向面。
- GL_FRONT:只剔除正向面。
- GL_FRONT_AND_BACK:剔除正向面和背向面。
總結(jié)
以上是生活随笔為你收集整理的OpenGL教程 学习笔记的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JAVA模板模式,简历模板(例子)
- 下一篇: 用ANSYS画矩形_ANSYS软件使用的