【GAMES101】作业2--三角形光栅化
文章目錄
- 前言
- 作業(yè)要求
- 額外說明
- 評分
- 解答
- 1 復(fù)制粘貼上節(jié)課的get_projection_matrix
- 2 判斷像素點是否在三角形內(nèi)部insideTriangle
- 對應(yīng)知識
- 對應(yīng)代碼
- 3 光柵化
- 步驟概覽
- 1. 找到Bounding Box
- 2. 在Bounding box 內(nèi)遍歷所有元素,判斷是否在三角形內(nèi)部
- 3. 根據(jù)已有代碼來得到深度值,也就是z_interpolated
- 4. 如果當前位置深度比depth_buf更小,則更新顏色值并保存深度值
- 代碼總結(jié)
- 結(jié)果
前言
本文為GAMES101現(xiàn)代計算機圖形學(xué)入門 的學(xué)習(xí)筆記系列。
我們的系列筆記將分為兩部分:
原課程為2020年2月閆令琪所教授的 GAMES101 現(xiàn)代計算機圖形學(xué)入門。
課程主頁:https://sites.cs.ucsb.edu/~lingqi/teaching/games101.html
(幻燈片和課程錄像均在此處)
課程共計22節(jié)。作業(yè)共計8次。
針對人群:計算機圖形學(xué)入門新手
教材:
Steve Marschner and Peter Shirley的"Fundamentals of Computer Graphics"
第三版或更新版本。目前無官方中文版。
民間翻譯:https://www.stubbornhuang.com/1812/
筆記目錄
2022-6-9
作業(yè)要求
在上次作業(yè)中,雖然我們在屏幕上畫出一個線框三角形,但這看起來并不是那么的有趣。所以這一次我們繼續(xù)推進一步——在屏幕上畫出一個實心三角形,換言之,柵格化一個三角形。上一次作業(yè)中,在視口變化之后,我們調(diào)用了函數(shù)rasterize_wireframe(const Triangle& t)。但這一次,你需要自己填寫并調(diào)用函數(shù) rasterize_triangle(const Triangle& t)。
該函數(shù)的內(nèi)部工作流程如下:
你需要修改的函數(shù)如下:
- rasterize_triangle(): 執(zhí)行三角形柵格化算法
- static bool insideTriangle(): 測試點是否在三角形內(nèi)。你可以修改此函數(shù)的定義,這意味著,你可以按照自己的方式更新返回類型或函數(shù)參數(shù)。
額外說明
因為我們只知道三角形三個頂點處的深度值,所以對于三角形內(nèi)部的像素,我們需要用插值的方法得到其深度值。我們已經(jīng)為你處理好了這一部分,因為有關(guān)這方面的內(nèi)容尚未在課程中涉及。插值的深度值被儲存在量 z_interpolated中。
請注意我們是如何初始化 depth buffer 和注意 z values 的符號。為了方便同學(xué)們寫代碼,我們將 z 進行了反轉(zhuǎn),保證都是正數(shù),并且越大表示離視點越遠。
在此次作業(yè)中,你無需處理旋轉(zhuǎn)變換,只需為模型變換返回一個單位矩陣。最后,我們提供了兩個 hard-coded 三角形來測試你的實現(xiàn),如果程序?qū)崿F(xiàn)正確,你將看到如下所示的輸出圖像:
在你自己的計算機或虛擬機上下載并使用我們更新的框架代碼。你會注意到,在 main.cpp 下的 get_projection_matrix() 函數(shù)是空的。請復(fù)制粘貼你在第一次作業(yè)中的實現(xiàn)來填充該函數(shù)。
評分
- [5 分] 正確地提交所有必須的文件,且代碼能夠編譯運行。
- [20 分] 正確實現(xiàn)三角形柵格化算法。
- [10 分] 正確測試點是否在三角形內(nèi)。
- [10 分] 正確實現(xiàn) z-buffer 算法, 將三角形按順序畫在屏幕上。
- [提高項 5 分] 用 super-sampling 處理 Anti-aliasing : 你可能會注意到,當我們放大圖像時,圖像邊緣會有鋸齒感。我們可以用 super-sampling來解決這個問題,即對每個像素進行 2 * 2 采樣,并比較前后的結(jié)果 (這里并不需要考慮像素與像素間的樣本復(fù)用)。需要注意的點有,對于像素內(nèi)的每一個樣本都需要維護它自己的深度值,即每一個像素都需要維護一個 sample list。最后,如果你實現(xiàn)正確的話,你得到的三角形不應(yīng)該有不正常的黑邊。
解答
我們需要填寫的兩個函數(shù)都在rasterize.cpp當中。并且我們需要復(fù)制粘貼上一次作業(yè)寫的get_projection_matrix。
我們首先需要寫一個判斷像素點是否在三角形內(nèi)部的算法。這個函數(shù)的返回值就是一個bool變量。這個函數(shù)名為static bool insideTriangle()
rasterize_triangle()函數(shù)是用來三角形柵格化的。它分為以下三步:
1 復(fù)制粘貼上節(jié)課的get_projection_matrix
我們先復(fù)制粘貼get_projection_matrix
如下
2 判斷像素點是否在三角形內(nèi)部insideTriangle
題目中給出的API如下
static bool insideTriangle(int x, int y, const Vector3f* _v) { // TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2] }我們接受一個x,接受一個y,這代表了當前點的像素編號(整數(shù))。接受一個向量引用 _v,這代表了三角形的三個頂點坐標。
對應(yīng)知識
課堂筆記2–向量與線性代數(shù)
對應(yīng)代碼
static bool insideTriangle(float x, float y, const Vector3f* _v) { // TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]const Eigen::Vector2f P(x, y);const Eigen::Vector2f A = _v[0].head(2), B = _v[1].head(2), C = _v[2].head(2);const Eigen::Vector2f AP = P - A;const Eigen::Vector2f BP = P - B;const Eigen::Vector2f CP = P - C;const Eigen::Vector2f AB = B - A;const Eigen::Vector2f BC = C - B;const Eigen::Vector2f CA = A - C;float eq1 = AB[0] * AP[1] - AB[1] * AP[0];float eq2 = BC[0] * BP[1] - BC[1] * BP[0]; float eq3 = CA[0] * CP[1] - CA[1] * CP[0];if( eq1 > 0 && eq2 > 0 && eq3>0)return true;else if(eq1 < 0 && eq2<0 && eq3<0)return true;else return false; }注意以下幾點:
應(yīng)該注意const Vector3f* _v這個參數(shù)
這個參數(shù)是一個Vector3f指針,實際上代表的是一個數(shù)組。數(shù)組中的每個元素是一個Vector3f類型,
也就是代表了一個頂點的坐標。每個點用_v[0],_v[1],_v[2]表示。而每個_v[0]就是一個Vector3f類型的點。整個數(shù)組代表了多個點。這里一般認為傳入的是三個點,即一個三角形。
注意我們做的是2D模擬,因此只是傳入了x、y坐標。而Vector3f是一個3D的坐標,因此我們只使用它的前兩個坐標。在Eigen當中就是使用.head(2)函數(shù)
二維的向量對于Eigen來說是無法做叉乘的。Eigen只支持三維叉乘。所以我們要手動寫叉乘。
a×b=(a1,a2)T×(b1,b2)T=a1b2?a2b1a\times b = (a1, a2)^T \times (b1, b2)^T \\ = a1b2 - a2 b1 a×b=(a1,a2)T×(b1,b2)T=a1b2?a2b1
請注意,上面的寫法是不嚴謹?shù)?#xff0c;因為叉乘是不改變張量的階的。向量叉乘,得到的應(yīng)該還是向量。所以這里只是表示了大小,而沒有表示方向。實際上,方向是指向垂直于紙面(向外或者內(nèi))。如果想要嚴謹?shù)貙懗鰜?#xff0c;只需要把第三個分量寫為0,然后按照3D向量叉乘公式(也就是行列式),如下,寫出來即可。你會發(fā)現(xiàn)只剩下了k基矢的項。
3 光柵化
步驟概覽
上文說到
rasterize_triangle()函數(shù)是用來三角形柵格化的。它分為以下4步:
我們依次來做這四步(其中第三步已經(jīng)被寫好了)
1. 找到Bounding Box
怎么找呢?其實就是找三角形的x坐標和y坐標的最大最小值而已!
比如下面這張圖
我們的bounding box就是所在范圍的9個格子。
對應(yīng)的代碼就是
注意:根據(jù)課堂筆記6–光柵化(深度測試與抗鋸齒),其實坐標值和整數(shù)編號值只是差了0.5
我們找bounding box,是為了接下來做for循環(huán)遍歷。所以假如找到的是float類型的,不那么方便循環(huán)。因為坐標值和整數(shù)編號值只是差了0.5,所以我們就往外拓展一點點boundingbox。如下面的代碼所示。
//我們將bounding box稍微擴大一點,得到整數(shù)值,方便循環(huán)xmin = (int)std::floor(xmin);xmax = (int)std::ceil(xmax);ymin = (int)std::floor(ymin);ymax = (int)std::ceil(ymax);2. 在Bounding box 內(nèi)遍歷所有元素,判斷是否在三角形內(nèi)部
這一步很簡單,就是寫兩個for循環(huán),然后再利用第一步寫了的insideTriangle函數(shù)來判斷
//2. 在Bounding box 內(nèi)遍歷所有元素,判斷是否在三角形內(nèi)部for (int x = xmin; x <= xmax; x++){for (int y = ymin ; y <= ymax; y++){//像素的坐標值只是比整數(shù)編號值大0.5而已。if (insideTriangle(x + 0.5, y + 0.5, t.v)){我們這里唯一要注意的,就是insideTriangle函數(shù)的使用方法。
這個函數(shù)接收三個參數(shù),前兩個就是坐標值的x,y而已。剛才已經(jīng)說了,坐標值就是像素編號+0.5而已。
第三個參數(shù)實際上是一個數(shù)組,這個數(shù)組的每個元素都是一個3維Vector3f類型的點。
我們現(xiàn)在所擁有的參數(shù),是const Triangle& t,這是一個三角形,是代碼框架自定義的類型。它的類型定義位于Triangle.hpp
class Triangle{public:Vector3f v[3]; /*the original coordinates of the triangle, v0, v1, v2 in counter clockwise order*/... };我們只要看它的第一個成員變量,叫做v,它恰好是一個數(shù)組,數(shù)組的每個元素是個Vector3f的點。這就對應(yīng)上我們需要的參數(shù)。
3. 根據(jù)已有代碼來得到深度值,也就是z_interpolated
直接取消注釋已有的代碼即可
//3.根據(jù)已有代碼來得到深度值,也就是z_interpolated // If so, use the following code to get the interpolated z value.auto [alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();z_interpolated *= w_reciprocal;這一步的目的,就是把三角形三個頂點的深度值插值成三角形內(nèi)每個像素點的深度值。
4. 如果當前位置深度比depth_buf更小,則更新顏色值并保存深度值
很簡單,假如該點當前的深度值比buffer中的更小,就更新顏色,并且保存新的深度值。
這就是z-buffer算法。這個算法很簡單,其實就是尋找最小值而已。找到了最小值,就覆蓋原來的最小值,并且更新顏色。depth_buf保存的就是原來的最小值。
//4. 如果當前位置深度比depth_buf(類rasterizer的一個成員)更小,則更新顏色值。if (z_interpolated < depth_buf[get_index(x, y)]){//TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.set_pixel(Vector3f(x, y, z_interpolated), t.getColor());depth_buf[get_index(x, y)] = z_interpolated;}我們這里要注意2點:
depth_buf是自定義的Triangle類型的一個成員變量。
它就是個一維浮點數(shù)數(shù)組而已。使用方法就是用數(shù)組下標。但是這里要注意了:我們的像素編號是兩個(x和y),所以要先把它轉(zhuǎn)換成一個。轉(zhuǎn)換的方法通俗易懂,就是一行行地排排坐,排完一行再排一行。從左到右,從上到下。
如圖所示
甚至代碼框架里面已經(jīng)給你寫好了轉(zhuǎn)換的函數(shù)
int rst::rasterizer::get_index(int x, int y) {return (height-1-y)*width + x; }這里的height和width也是整數(shù)。實際上,由于OpenCV也許是從左上到右下排列的,所以它可能用的是(height-1-y)。這里就是把數(shù)字轉(zhuǎn)換一下。
但是這些都不用我們操心,我們只要使用get_index函數(shù)就行了。
它接收兩個參數(shù),第一個就是點坐標,第二個就是RGB顏色。
第一個參數(shù)我們就給當前像素點的坐標即可,Vector3f(x, y, z_interpolated)
第二個參數(shù),我們就給t.getColor()
這個函數(shù)就是Triangle所需要繪制的顏色值。
代碼總結(jié)
第一個函數(shù)
static bool insideTriangle(float x, float y, const Vector3f* _v) { // TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]const Eigen::Vector2f P(x, y);const Eigen::Vector2f A = _v[0].head(2), B = _v[1].head(2), C = _v[2].head(2);const Eigen::Vector2f AP = P - A;const Eigen::Vector2f BP = P - B;const Eigen::Vector2f CP = P - C;const Eigen::Vector2f AB = B - A;const Eigen::Vector2f BC = C - B;const Eigen::Vector2f CA = A - C;float eq1 = AB[0] * AP[1] - AB[1] * AP[0];float eq2 = BC[0] * BP[1] - BC[1] * BP[0]; float eq3 = CA[0] * CP[1] - CA[1] * CP[0];if( eq1 > 0 && eq2 > 0 && eq3>0)return true;else if(eq1 < 0 && eq2<0 && eq3<0)return true;else return false; }第二個函數(shù)
//Screen space rasterization void rst::rasterizer::rasterize_triangle(const Triangle& t) {auto v = t.toVector4();// TODO : Find out the bounding box of current triangle.// iterate through the pixel and find if the current pixel is inside the triangle//1. 找到Bounding Boxfloat xmin = std::min(std::min(v[0].x(), v[1].x()), v[2].x());float ymin = std::min(std::min(v[0].y(), v[1].y()), v[2].y());float xmax = std::max(std::max(v[0].x(), v[1].x()), v[2].x());float ymax = std::max(std::max(v[0].y(), v[1].y()), v[2].y());//我們將bounding box稍微擴大一點,得到整數(shù)值,方便循環(huán)xmin = (int)std::floor(xmin);xmax = (int)std::ceil(xmax);ymin = (int)std::floor(ymin);ymax = (int)std::ceil(ymax);//2. 在Bounding box 內(nèi)遍歷所有元素,判斷是否在三角形內(nèi)部for (int x = xmin; x <= xmax; x++){for (int y = ymin ; y <= ymax; y++){//像素的坐標值只是比整數(shù)編號值大0.5而已。if (insideTriangle(x + 0.5, y + 0.5, t.v)){//3.根據(jù)已有代碼來得到深度值,也就是z_interpolated // If so, use the following code to get the interpolated z value.auto [alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();z_interpolated *= w_reciprocal;//4. 如果當前位置深度比depth_buf(類rasterizer的一個成員)更小,則更新顏色值。if (z_interpolated < depth_buf[get_index(x, y)]){//TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.set_pixel(Vector3f(x, y, z_interpolated), t.getColor());depth_buf[get_index(x, y)] = z_interpolated;}}}} }結(jié)果
總結(jié)
以上是生活随笔為你收集整理的【GAMES101】作业2--三角形光栅化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: andriod中3g模块没有mac地址的
- 下一篇: 支付宝扫码转银行卡技术/隐藏部分卡号