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