作者: rockogl (行!!) 看板: OpenGL
標題: [資料] 關於3D繪圖的原理簡介
時間: Wed Apr 30 18:42:30 2003
這個是我之前在巴哈姆特TVGame貼的文章
有興趣簡單地知道原理的就看看
---------------------------------------------------------------
即時3D繪圖的真面目
by rockogl
前言:
前一陣子因為有人在網路上提到了即時3D成像的原理
所以我閒來無事就回應了一篇文章
不過因為後來想想乾脆給多一些人知道也不錯
於是我就稍微修正一下本來要寫成社刊的一篇文章
也給有興趣想學D3D/OpenGL甚至是想自己寫API的人一些概念
以下就是現行即時3D繪圖(by Primitive)的概說
因為我現在正在初學用OpenGL去Render即時畫面
因此以下的文章主要就由OpenGL這個函式庫的觀點來看
也許有人想知道
最近這一年紅極一時的Pixel Shader和Vertex Shader到底是什麼東西呢?
不過在提到這之前
還是先從基本即時3D繪圖的圖形處理流程說起
3D繪圖在API(OpenGL)方面主要有以下動作
1.Command Buffer
就是先把繪圖指令丟到Queue中
等時機到再一口氣完成Command指示的Render動作
這個東西OpenGL才有
2.Transform & Lighting
計算頂點座標轉換和燈光對頂點(Vertex)顏色的影響等等
3.Rasterization
計算並填入Pixel到Frame Buffer中,以畫出頂點圍成的Polygon
首先是Command Buffer
這個Buffer基本上是把API Call的指令丟到Command Buffer中
OpenGL有這東西
但是D3D沒有
這個好處是為了讓畫面能一次畫出不要分段
不過事實上在Double Buffer的情況下這東西的影響程度其實不大
只是提醒一下OpenGL有這個東西而已
再來是Transform & Lighting
Transform部分:
說明Transform前
先說一下Vertex
3D繪圖的一個重要東西是Vertex(頂點)
Vertex資訊包含:
1.端點本身的座標位置 (x,y,z,1.0) <-為何最後是存放1.0等等在解釋
2.法向量(給光源計算用) (a,b,c,1.0)
3.色彩 (Red,Green,Blue,Alpha)
4.材質座標 (s,t,r,q)
各都是4個單精度float
透過Vertex 我們可以弄出
點 (1個Vertex)
線段 (2個Vertex)
三角形(3個Vertex)
四邊形(4個Vertex,Vertex都在同一個平面)
n邊形(n個Vertex,Vertex都在同一個平面)
然而在3D繪圖中有所謂投影&座標轉換的動作
座標轉換可以讓一個物體在程式設計師不直接更改Vertex資料情況下整個變換位置
而投影則把原來3D空間座標轉換成2D平面對應的座標
投影基本上有垂直投影(物體不論遠近都一樣大)&透視投影(物體遠的小近的大)
以上就是我們所說的幾何轉換(Geometry Transform)
而Transform主要是利用矩陣計算的
怎麼說呢?
高中有學過矩陣可以用來計算座標轉換
但是只在2D平面適用
這裡我們來看看3D的:
對於要變換的座標 (x,y,z)
我們可以表示成一個矩陣B |x∣
|y|
|z|
|1|
而座標平移和投影關鍵就在前面A矩陣上:
A B C
|1 0 0 0| |x| |x|
|0 1 0 0|x|y|=>|y|
|0 0 1 0| |z| |z| (最後一個1沒用)
|0 0 0 1| |1| |1|
這樣就可以產生一個新的矩陣C
而矩陣C所代表的座標(x,y,z)就是轉換後的座標
=====================================================
以下是以前初學時
我問網友yoco關於線性代數的東西時教我的一些基本座標轉換用矩陣運算
今天拿出來給大家參考:
1. 放大縮小矩陣(Scale)
想要把 (x,y,z) 變成本來的 t 倍
|t 0 0 0| |x| |tx|
|0 t 0 0|x|y|=>|ty| ==> 得到 (tx,ty,tz)
|0 0 t 0| |z| |tz|
|0 0 0 1| |1| |1 |
ps. 若只想對 y 放大,則用 |1 0 0 0| ,對 x z 同理。
|0 t 0 0|
|0 0 1 0|
|0 0 0 1|
2. 位移矩陣(Transfer)
想要把 (x,y,z) 位移 (a,b,c)
|1 0 0 a| |x| |x+a|
|0 1 0 b|x|y|=>|y+b| ==> 得到 (x+a,y+b,z+c)
|0 0 1 c| |z| |z+c|
|0 0 0 1| |1| |1 |
(就是因為平移矩陣這類矩陣,所以座標點資料才預留1.0)
3. 旋轉變換(Rotate)
A) P(x,y) round the center Q(0,0) d
New point = [ x y ] * [ cos(d) sin(d) ]
[ -sin(d) cos(d) ]
B) 3D point p(x,y,z) rotate
[ 1 0 0 ]
x-axis [ 0 cos sin ]
[ 0 -sin cos ]
[ cos 0 -sin ]
y-axis [ 0 1 0 ]
[ sin 0 cos ]
[ cos sin 0 ]
z-axis [ -sin cos 0 ]
[ 0 0 1 ]
如果要沿任意軸旋轉,可以看相關的書籍
=======================================================
如果轉換有時需要放大外加平移
我們只要這樣
放大矩陣(4x4) * 平移矩陣(4x4) * 座標矩陣(4x1)
就可以產生一個同時包含這2個功能(放大跟平移)在內的矩陣
這樣就能把所有物體的座標依照這個新矩陣轉到場景的原始座標系統
當然最後還有把空間投影到平面的動作,才能把Polygon畫到平面上
這個處理方法跟場景一樣,也是乘上一個矩陣
不過這矩陣是投影矩陣,他會把空間中的頂點所在座標轉換成Frame Buffer為基準的座標
而投影後,Z值在0.0-1.0之間的Pixel才會被畫出來,其他的就不畫出來
所以離眼睛視點太遠或太近的都看不到
材質座標的轉換也是這樣計算
但是材質座標有自己的另一份座標系統
所以獨立於ModelView還有Projection轉換之外
不過這裡要注意的是,法向量實際上在硬體計算中是不參加座標轉換的
而是改由讓光源逆向轉換到物件所在座標系統,以實作lighting的計算
因為轉換光源到物件的座標系統(一般遊戲用顯示裝置最多只有8個位置相關的光源資料)
比轉換一堆法向量到光源的座標系統還省計算
同時也能避免一堆法向量轉換後還要把他Normalize(向量長度變1)的動作
反正這只是相對的位置資訊而已
我們主要還是為了得知光線照在物體的強度而已
轉換哪個去遷就哪個其實沒差
再來是Lighting
OpenGL API會提供光源的設定
包含光源位置,光源本身的顏色,照射方向(如果光線有向的話)
還有角度範圍限制(手電筒這類不往四面八方照的情況)
對於有向光線來說
基本上就是利用向量的內積計算
讓法向量和"指向光源"的向量作dot product運算
只要這兩個向量事前都有先令其長度為1.0
那麼dot product算出來的值必定會介於-1.0~1.0之間
然後由剛才dot product結果乘上光源本身顏色
求出光線在每個頂點的RGB強度
然後去跟物體的Color的RGB值分別相乘
就可以得到Diffuse的光線
當然光線不是只有這種
事實上還有因為光線散射造成Ambient這種無向性光源
所以還要讓Ambient光線顏色乘上頂點RGB顏色值算出來的RGB值相加
以得出最後要Render時的頂點顏色
不過對於光滑表面的物質這樣其實還不夠
在真實世界中另外還有一個光源屬性是Specular HighLight
當光照在物體表面,然後依照反射定律直接反射到眼睛時
就會造成高亮度的反光效果
也許有碰過3DStudio MAX的人會說直接用HDRI(高動態範圍圖像)來作Lighting不就得了
不過,當時開發3D API的人可能沒想這麼多吧(笑)
OpenGL的Color Range計算到超出去雖然是會發生的
但是OpenGL處理的時候都會把超出1.0的當作1.0
不會因為弄了RGB是0.8 0.8 34.0的顏色值
就保留這個看起來是白色的亮藍色的顏色數值
所以要模擬HDR方面除了改用D3D9之外
目前OpenGL用core功能只有自己處理一途
也許會有人覺得即時3D繪圖怎麼這麼陽春
別忘了197X那時的3D繪圖
只有光線和物體之間的繪圖演算法完成度比較高
物體和物體之間的光線往來
則是198X之後的Ray Tracing/Radiosity/光子貼圖等等演算方式才實作到一定水準
雖然說現在早就是200X年了
但是目前硬體的即時3D繪圖實作還在Primitive成像&其他相關方法的階段
最多就只到開放可程式化(能夠自訂運算)而已
而目前的硬體能力還不能把Ray Tracing即時化
更不用說是後續運算量更大,卻也更真實的演算法
所以現在這種運算資源不足的情況下,就得靠美學功夫
去自行把3D畫面調整到盡量看起來很像真實世界而已
而事實上現在很多的即時3D繪圖特效,其出發點都是如此
順帶一提
對Vertex計算Lighting有一個很大的問題
因為Lighting在OpenGL方面是以Vertex為單位去計算的
如果今天有個點光源在四邊形中央的正上方
四邊形的四個Vertex的顏色先被算過,才用Linear的方式把四邊形中間的Pixel算出來
因此四邊形被Lighting之後 中間不會得到一個亮圓的圖案
這時就必須靠Per Pixel Lighting(或稱為Phong Shading)才能達到
但是OpenGL本身沒這玩意,所以要仰賴其他方法
(所以Quake 3 Arena要用Lightmap原因就在這)
很幸運的D3D有這東西
然後就是Rasterization(掃描顯像)
Rasterization會把經過T&L處理過的資訊
一個點一個點的寫到Frame Buffer上
在這過程中
除了要計算Vertex投影後圍成的Polygon(多邊形)範圍,不要畫超出去之外
還要看Polygon上的Vertex的顏色是什麼
根據材質座標把材質和Polygon端點決定的整個Polygon內部的顏色一點一點相乘
再畫對應的顏色上去
如果有開Z-Depth/Stencil檢查
還要看這兩個Buffer值是不是合乎條件(像是要畫的點前方已經有個點蓋住了)
才能在FrameBuffer產生出最後的畫面
事實上因為Rasterization是一個點一個點弄的
多邊形邊界的點是看有沒有超出去
有就不畫,沒有就畫
所以當邊界是斜的時候,就會有階梯狀邊緣產生(因為不會有像素顏色強度只有一半的)
這就是所謂的鋸齒
至於解決方法....除了用各種反鋸齒方法來解決之外
螢幕解析度沒趨近到無限大之前,是根本不可能完全解決的
至於Fog
在OpenGL中要看這個情況:
void glHint(GLenum target,GLenum mode);
如果用了glHint(GL_FOG_HINT,GL_NICEST)
那麼Fog的Color會在Rasterization中計算
也就是依照Depth值去算出每個Pixel的Fog值
如果是glHint(GL_FOG_HINT,GL_FASTEST)
那就會把Fog顏色依照投影後的Vertex的Z值
去加算Fog顏色在Vertex上
然後Render Polygon時會受到數個Vertex影響而弄上Fog顏色
當然在Fog是Linear Mode時 要用Vertex還是用Per-Pixel是沒有差的
不過真實世界的Fog Color影響事實上是隨著物體距離指數遞增
因為物體反射或是自身發出而到達眼睛的光,在通過空氣中水滴或是空氣等物質時
被吸收或是折射或是反彈掉的光都是固定比例
光線通過空氣或是霧的強度衰減
就像是0.9 0.9^2 0.9^3這樣遞減,剛好是指數關係..........
所以用exp計算法時,Fog要是Per-Vertex計算
那在大的平面一定會破功
(因為Polygon中的Pixel計算是根據Vertex用Linear法算出來的)
以上是一個沒有可程式化單元的即時3D繪圖流程
那可程式化有什麼不同呢?
事實上目的還是一樣,不過有點小變化
可程式化主要就是
本來顯示卡有一個類似固定機器生產線那樣幫你算圖形
這個一成不變的機器,變成是一個程式去運作..........
而你可以自己寫一個程式去取代原先固定的計算方式
比如
如果要使用動畫那種區塊式上色的方法(稱為Cell Shading)
(以下用ARB_Vertex_Program的Vertex程式舉例)
!!ARBvp1.0
# Vertex Program for cel shading
# Parameters
PARAM mvp[4] = { state.matrix.mvp }; #modelview projection matrix
PARAM lightPosition =
program.env[0]; #object space light position
# Per vertex inputs
ATTRIB iPos = vertex.position; #position
ATTRIB iCol0 = vertex.color; #color
ATTRIB iNorm =
vertex.normal; #normal
# Temporaries
TEMP lightVector; #light vector
TEMP normLightVector; #normalized light vector
# Outputs
OUTPUT oPos = result.position; #position
OUTPUT oCol0 = result.color; #primary color
OUTPUT oTex0 = result.texcoord[0]; #texture coordinate set 0
#以上是對應input的Vertex資料跟output的Vertex資料用
# 乘上場景矩陣&投影矩陣 使Vertex對應到Clipping座標
DP4 oPos.x, mvp[0], iPos;
DP4 oPos.y, mvp[1], iPos;
DP4 oPos.z, mvp[2], iPos;
DP4 oPos.w, mvp[3], iPos;
# Vertex Color不處理直接輸出
MOV oCol0, iCol0;
# 計算點光源在每個Vertex的Light向量
SUB lightVector, lightPosition, iPos;
# 向量單位化
# (向量長度變成1.0
# 因為和長度同樣是1.0的Normal向量內積才會讓內積的Value落在-1.0~1.0)
DP3 normLightVector.w, lightVector, lightVector;
RSQ normLightVector.w, normLightVector.w;
MUL normLightVector.xyz, normLightVector.w
, lightVector;
# 內積運算,並把得到的Value當成是1D Texture的Coord
# 這樣只要把區塊Table的Texture指定為0號Texture
# 便可根據Texture Coord弄出區塊式的上色效果
DP3 oTex0.x, normLightVector, iNorm;
END
然後東西就變成
http://www.deadpenguin.org/cel/gremd3.jpg這樣
至於Pixel Shader也有很多用途
主要是自行決定Texture Combine成最後Pixel的計算方式
(原先預設都是用顏色相乘去Combine多個在同一個Polygon上的Texture)
如果要計算Pixel Based的東西(如Dot3 Bump Mapping的Texture組合計算)就要用到
以上
--
這是我自己寫的小遊戲程式-對戰俄羅斯:
http://home.pchome.com.tw/web/rockogl/file/Tetris.zip電腦要有支援OpenGL ICD的3D加速卡才能跑的順喔
--
┌─────◆程式設計樂園◆─────┐ ╱ ╱  ̄ ▌ ̄  ̄ ╲╱ BBS 城邦
│
CSZone.kkcity.com.tw │ ╲ ╲ ╴ ▌ ▌ ▏ KK免費撥接
└──《From:
140.115.214.72
》──┘ 電話:40586000 帳號:kkcity 密碼:kkcity