计算机安全原理与实践_《计算机图形学原理及实践》学习笔记之第三章
第三章 一個(gè)古老的繪制器
1525年,阿爾布雷·丟勒 制作了一幅木刻畫,展示了一種可以繪制任一形體透視圖的方法。
本章我們將開發(fā)一個(gè)軟件來模擬丟勒展示的方法。
- 丟勒視角繪制算法的偽代碼
- 該算法有三個(gè)方面值得注意,這三個(gè)方面都體現(xiàn)在遍歷所有采樣點(diǎn)的循環(huán)種
- 該循環(huán)面向的是 可見 采樣點(diǎn),因此,判定采樣點(diǎn)的可見性很重要(對于人類,一個(gè)點(diǎn)能不能看到,我們的大腦有清除的認(rèn)知,但對于計(jì)算機(jī),這是需要進(jìn)行判定的)
- 可能存在無限數(shù)量的可見采樣點(diǎn)
- 當(dāng)細(xì)線觸碰畫框而不是穿過畫框內(nèi)的空白區(qū)域該如何處理(即物體有一部分超出 顯示界面時(shí))
- 對于 第二個(gè)問題,可用使用 逼近 繪制來解決,即選擇有限數(shù)量的采樣點(diǎn),使得在紙上的這些標(biāo)記能夠較好地呈現(xiàn)出物體的外形。關(guān)于這一部分本書后面會(huì)有大量的討論,具體討論在這里先咱暫時(shí)擱置。
- 對于 第三個(gè)問題,剔除視域(眼睛或相機(jī)能看見的那一部分世界)外的采樣點(diǎn),這是圖形學(xué)中一個(gè)常見的操作,可以避免將繪制時(shí)間浪費(fèi)在 視域 之外。該操作成為 裁剪。這里我們會(huì)使用一個(gè)非常簡單的點(diǎn)裁剪版本。
- 對于 第一個(gè)問題,即 可見性問題。 根據(jù) 丟勒 所示方法,要確定采樣點(diǎn) P 是否可見,用戶只需要將指針定在 P 點(diǎn),然后觀察細(xì)線是沿著一條直線直達(dá)螺絲釘?shù)目籽?#xff0c;還是途中遇到魯特琴的某處或其它物體而產(chǎn)生了彎折。 即,從 魯特琴上一點(diǎn)出發(fā),向 螺絲釘孔眼(觀察者) 方向射出一道射線,如果射線能夠直達(dá)觀察者,途中沒有接觸其它點(diǎn),則說明 該出發(fā)點(diǎn)對于觀察者可見。 這里,我們暫時(shí)忽略可見性檢測。
- 實(shí)現(xiàn)
- 設(shè)墻上螺絲釘孔眼作為坐標(biāo)系原點(diǎn),記為E(作為"視點(diǎn)")。
- 令繪畫的畫框,位于 z = 1 平面上,即觀察者到畫框平面的距離為 一個(gè)單位長度
- 記 畫框平面上距離孔眼最近的點(diǎn)為 T;其坐標(biāo)為 (0, 0, 1)
- 令 y 軸豎直向上, x 軸沿水平方向
- 平面畫框的范圍由角點(diǎn) ( x m i n y m i n , 1 ) (x_{min} y_{min}, 1)(xmin?ymin?,1)、( x m a x , y m a x , 1 ) (x_{max}, y_{max}, 1)(xmax?,ymax?,1) 定義。這里我們做簡化處理,設(shè)畫框是一個(gè)正方形,即長寬相等 x m a x ? x m i n = y m a x ? y m i n x_{max} - x_{min} = y_{max} - y_{min}xmax??xmin?=ymax??ymin?
- 畫框中畫紙的左下角為記為 ( x m i n , y m i n ) (x_{min}, y_{min})(xmin?,ymin?), 右上角記為 ( x m a x , y m a x ) (x_{max}, y_{max})(xmax?,ymax?)
- 設(shè)我們正在觀察物體上的點(diǎn) P(x, y, z)
- 連接 PE 即丟勒模型中的細(xì)線,會(huì)穿過畫紙,設(shè)穿過的該點(diǎn)為 P ‘ = ( x ‘ , y ‘ , z ‘ ) P` = (x`, y`, z`)P‘=(x‘,y‘,z‘)
- 可以得 z ‘ = 1 z` = 1z‘=1 ,因此 P ‘ P`P‘ 在畫紙上的坐標(biāo)為 ( x ‘ , y ‘ ) (x`, y`)(x‘,y‘)
- 接下來就是計(jì)算這個(gè) x ‘ , y ‘ x`, y`x‘,y‘
- 對于該問題,我們可以用相似三角形進(jìn)行求解。因?yàn)?z ‘ = 1 z` = 1z‘=1 這一條件,就很好求解了。
- 得出:
frac{x`} {x} = frac{z`} {z}
frac{y`} {y} = frac{z`} {z}
- 因 z` = 1 得:
x` = frac{x} {z}
y` = frac{y} {z}
- 這樣即可得到 P` 的坐標(biāo)
- 丟勒繪制算法的一個(gè)簡單實(shí)現(xiàn)版本
Input: a scene cotaining some objects Output: a drawing of the objects initialize drawing to be blank foreach object o foreach visible point P = (x, y ,z) of o if(x_min <= (x/z) <= x_max and y_min <= (y/z) <= y_max) make a point on the drawing at location (x/z, y/z)
- 為了和我們后面將采取的更一般性的方法一致。這里,我們默認(rèn) x軸正向是朝左的,現(xiàn)在我們要讓 x軸 反過來,即讓 x軸正向朝右,那么代碼中的結(jié)果 x 就要加個(gè) 負(fù)號
if(x_min <= (x/z) <= x_max and y_min <= (y/z) <= y_max) make a point on the drawing at location (-x/z, y/z)
- 繪圖
- 接下來繪制一個(gè)立方體,立方體有 8個(gè) 頂點(diǎn),給出它們的模型坐標(biāo)
| 0 1 2 3 4 5 6 7 | (-0.5, -0.5, -0.5) (-0.5, 0.5, -0.5) (0.5, 0.5, -0.5) (0.5, -0.5, -0.5) (-0.5, -0.5, 0.5) (-0.5, 0.5, 0.51) (0.5, 0.5, 0.5) (0.5, -0.5, 0.5) |
- 需要注意的是,我們的視點(diǎn)是(0, 0, 0),那么這樣,這個(gè)立方體就包裹了視點(diǎn),因此,我們讓立方體在 z 軸方向移動(dòng)三個(gè)單位,即 立方體所有坐標(biāo)的 z += 3
- 接下來 根據(jù) 丟勒的繪制算法,我們需要在立方體表面采樣大量的點(diǎn)來進(jìn)行繪制。但實(shí)際上這是不必要的。
- 若 A、B 是一條邊的兩個(gè)端點(diǎn),我們將 A 和 B 映射到圖紙上的 A ‘ A`A‘ 和 B ‘ B`B‘ ,可以發(fā)現(xiàn) A 和 B 之間的點(diǎn),也都映射到了 A ‘ A`A‘ 和 B ‘ B`B‘ 的連線上。這一點(diǎn)是可以幾何證明出來的。
- 而立方體屬于 線框模型,即可以用幾個(gè)頂點(diǎn) 和 頂點(diǎn)之間的連線得到的邊 來進(jìn)行描述。那么實(shí)際上,我們只需要繪制出 立方體的頂點(diǎn),然后進(jìn)行連線即可。
- 需要注意的是,空間中的直線 其在平面上的投影 不一定為 直線,如果該直線穿過了 投影中心 (螺絲孔眼/觀察者),則其在平面上的投影是一個(gè)點(diǎn),我們認(rèn)為其是無意義的。
- 現(xiàn)在給立方體模型添加一個(gè) 邊表,每條邊用兩端點(diǎn)的點(diǎn)的索引表示:
| 0 1 2 3 4 5 6 7 8 9 10 11 | (0, 1) (1, 2) (2, 3) (3, 0) (0, 4) (1, 5) (2, 6) (3, 7) (4, 5) (5, 6) (6, 7) (7, 4) |
- 繪制線段時(shí)會(huì)面臨兩個(gè)選擇
- 是逐條邊進(jìn)行迭代,對每一條邊,分別計(jì)算它們端點(diǎn)的投影位置,再將這兩個(gè)投影點(diǎn)連接在一起。
- 還是先遍歷每一個(gè)頂點(diǎn),計(jì)算各頂點(diǎn)的投影點(diǎn),然后再基于計(jì)算得到的投影點(diǎn)逐邊進(jìn)行迭代。
- 由于每個(gè)頂點(diǎn)由三條邊共享,對于第一個(gè)選擇 每個(gè)頂點(diǎn)需要計(jì)算 三次
- 而第二個(gè)選擇則需要對數(shù)據(jù)進(jìn)行重復(fù)訪問
- 這兩種選擇取決于任務(wù)是在 硬件上實(shí)現(xiàn) 還是 軟件上實(shí)現(xiàn),對此會(huì)在后面的章節(jié)進(jìn)行討論。 我們當(dāng)前選擇 第二個(gè)選擇。
- 之后我們還要思考裁剪問題,在投影后,一條邊的一個(gè)端點(diǎn)可能會(huì)在圖紙內(nèi),而另一個(gè)則可能跑到圖紙外了。對于這種情況,這里我們暫時(shí)不作討論,我們現(xiàn)在認(rèn)為 畫框外的部分不會(huì)被繪制(對于 WPF 而言 確實(shí)是這樣)
- 給出此時(shí)的偽代碼:
- 最后還需要注意程序所繪圖形顯示在 ”矩形窗口“ 之內(nèi),而窗口的坐標(biāo)從 ( x m i n , y m i n ) (x_{min}, y_{min})(xmin?,ymin?) 到 ( x m a x , y m a x ) (x_{max}, y_{max})(xmax?,ymax?)。
- 我們可以去除這一坐標(biāo)區(qū)間的限制。而采用在圖形庫中常用的、在 x 和 y 兩個(gè)方向上均為 0~1 的區(qū)間。可按下面的方法 對 x 坐標(biāo)進(jìn)行轉(zhuǎn)換
- 首先將 x 坐標(biāo) 減去 x m i n x_{min}xmin?,這樣新的坐標(biāo)將位于 0 ~ x m a x ? x m i n x_{max} - x_{min}xmax??xmin? 的范圍,再讓它除以 x m a x ? x m i n x_{max} - x_{min}xmax??xmin? 新的 x 坐標(biāo)就映射到 0~1 范圍了。
- 我們可以去除這一坐標(biāo)區(qū)間的限制。而采用在圖形庫中常用的、在 x 和 y 兩個(gè)方向上均為 0~1 的區(qū)間。可按下面的方法 對 x 坐標(biāo)進(jìn)行轉(zhuǎn)換
x_{new} = frac {x - x_{min}} {x_{max} - x_{min}}
- y 坐標(biāo)同理
- 但之前為了讓畫面右側(cè)方向?qū)?yīng)場景 x 坐標(biāo)增加方向,我們改變了 x 的符號。那么 x 重映射后其實(shí)是 -1 ~ 0 范圍,因此我們還要讓新的 x + 1
- 這些位于 0~1 范圍的坐標(biāo)常稱為 標(biāo)準(zhǔn)化的設(shè)備坐標(biāo):它們給出了顯示設(shè)備從左到右、從上到下的取值范圍。
- 對一個(gè)典型的顯示器而言,其豎直方向坐標(biāo)的取值范圍值常為 0~1,而水平方向坐標(biāo)的取值范圍則為 0~1.33
- 這一標(biāo)準(zhǔn)化處理公式需要記住
- 給出偽代碼:
- 程序
我們將使用一個(gè)簡單的 WPF 程序來實(shí)現(xiàn)該算法。
- 書中給出的 C# 代碼還是為了讓讀者理解這一章討論的投射算法。里面的 Dot、Segament 需要自實(shí)現(xiàn),可以在 http://cgpp.net 即本書官網(wǎng)下載。
- 局限性
- 顯然我們這里的代碼非常簡單,且無法應(yīng)用于更廣泛 和 更高級的場景中。
- 例如如果立方體每個(gè)面有不同的顏色,這里我們只是把邊繪制了出來,無法繪制立方體面的顏色
- 我們這里也沒有對光進(jìn)行模擬。我們能看到物體正是因?yàn)楣鈴奈矬w表面射入了我們的眼睛。
- 我們對于模型數(shù)據(jù)的表示缺乏通用性。我們可以將建模數(shù)據(jù)存入一個(gè)可被程序讀取的文件,該文件具有規(guī)范的格式。例如該文件中存儲(chǔ)的 先是頂點(diǎn)的數(shù)目,跟著一個(gè)訂點(diǎn)表,然后是邊的數(shù)目 跟著一個(gè)邊表。
練習(xí)
假設(shè)在丟勒木刻畫中,不僅標(biāo)記了點(diǎn),還在點(diǎn)附近標(biāo)記了細(xì)線另一端砝碼距離地面的高度。該數(shù)字即為視點(diǎn)距離采樣點(diǎn)的距離。如果 魯特琴被帶走了,而又想在畫中畫一盞燈,且燈在魯特琴的前面,那么我們就可以根據(jù)之前標(biāo)記的距離,來把燈合成到原本的畫中。
這類似于 基于深度的畫面合成,其是 z-buffer 的許多應(yīng)用之一。
在每個(gè)采樣點(diǎn)處記錄的深度值類似于在 z-buufer 中存儲(chǔ)的值,盡管并非同一值。
可以使用點(diǎn)的索引來表示面,稱為 索引面集,我們用 ( P 0 , P 1 , P 2 , . . . ) (P_0, P_1, P_2, ...)(P0?,P1?,P2?,...) 來表示一個(gè)面。對于立方體而言,我們可以用 ( P 2 ? P 1 ) × ( P 1 ? P 0 ) (P_2- P_1) × (P_1 - P_0)(P2??P1?)×(P1??P0?) 來表示該面的法向量,請構(gòu)建立方體的 索引面集,使得每個(gè)面的法向量都朝外。
畫個(gè)三棱柱
我們只需要修改傳入的 點(diǎn)集 和 邊集 即可
總結(jié)
以上是生活随笔為你收集整理的计算机安全原理与实践_《计算机图形学原理及实践》学习笔记之第三章的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 房贷逾期一个月严重吗 这些后果你知道多
- 下一篇: 开发一个出生年份的下拉选择框供用户选择_