至此,我們已經擁有了控制基本幾何圖元的能力,那么這些基本的數學庫能為我們做些什么呢?現在我來畫第二個三角形,本節的代碼利用了第1.1節中顯示Windows視窗的部分,對于目前我們來說,利用什么API和平臺顯示圖形并不重要,我們的目的是將圖形畫在一張Buffer代表的圖上,有了這個基礎以后,利用任務圖形API,在任何平臺,我們的目的只不過是在一張Buffer上畫圖而已, 現在我們有了Triangle類,可以利用它來進行如下代碼的定義:
Trianglef triangle(Vector3f(-1.f, -1.f, 0.f), Vector3f(1.f, -1.f, 0.f), Vector3f(0.f, 1.f, 0.f));
triangle是我們的初始圖元,因為當前的三角形是3D空間中的點,我們不能再簡單的在二維螢屏Buffer中直接繪制,現在我們想象計算機螢屏上有一個窗戶,即我們要繪制的視窗,窗戶外面我們能看到一個三角形,眼睛看向三角形的每一個位置,就像是三角形上的每一個點向我們眼睛發射出了一條條射線, 我們現在要做的是如何能在這扇窗戶上顯示那個三角形,前面章節我們定義了Ray類,現在假設在我們眼睛為射線原點,透過窗戶每個水平垂直位置像三角形發射射線,當射線能夠與窗戶外的三角形相交,說我此條視線我們能夠看到三角形,視線在窗戶上對應位置記錄當前射線與三角形相交點的顏色,當整面窗戶都被我們利用射線看遍,那個這個三角形就完整的記錄并顯示在窗戶上了,具體代碼程序如下:
ClearBuffer(); Vector3f intersectPoint; for (int i = 0; i < WIDTH; ++i) { for (int j = 0; j < HEIGHT; ++j) { int index = j * WIDTH + i; if (triangle.Intersect(rays[i][j], nullptr, &intersectPoint)) { buffer[index] = 0x00ff0000; } } } DrawBuffer((unsigned char *)buffer);
這里遍歷視窗的每個像素,然后用到了射線與三角形的相交檢測來判斷可見性,最后將三角形的顏色寫入Buffer中,最后將Buffer資料交給圖形顯卡,顯示我們的三角形,這一堆射線我們在初始化時進行了計算,假設視窗的寬度為2米,高度就是2.0f * HEIGHT / WIDTH 米,并假設此時我們的眼睛位置為坐標空間的原點,視窗離我們1米遠(注意上面定義的三角形的位置也是基于我們現在的坐標系的), 那么我們可以計算出每條射線如下:
Rayf rays[WIDTH][HEIGHT];
float width_div_2 = 1.0; float height_div_2 = width_div_2 * HEIGHT / WIDTH; float deltaP = width_div_2 * 2.f / WIDTH; for (int i = 0; i < WIDTH; ++i) { for (int j = 0; j < HEIGHT; ++j) { Vector3f dir(-width_div_2 + deltaP * i, height_div_2 - deltaP * j, -1.f); dir.Normalize(); rays[i][j] = Rayf(Vector3f(0, 0, 0), dir); } }
為了讓三角形動起,我們可在每幀中改變三角形每個頂點的位置,下面代碼對三角形頂點進行了旋轉:
for (int i = 0; i < 3; ++i) { triangle[i].RotateByY(0.05f); triangle[i].RotateByZ(0.05f); }
可能已經注意到在三角形的定義時,我們將三角形的z值定義了為0,這樣是為了三角形繞Y軸旋轉時是繞自身軸旋轉的,但我們畢竟已經到了3D世界中,如果三角形的z永遠為0,那他還是相當于在一個平面上,因此這里利用模型到世界空間的矩陣來對三角形進行轉換:
Matrix4x4f triToWoldMatrix; triToWoldMatrix.MakeTransition(Vector3f(0, 0, -5.f)); Trianglef triangleToRender; for (int i = 0; i < 3; ++i) triangleToRender[i] = triToWoldMatrix.Transform(triangle[i]);
上面代碼定義了一個矩陣來轉換三角形自身的點到世界空間中,而triangle的每個點則在自己的模型空間中,這樣,就可以讓三角形的每個點在自身模型空間中做交換,而后利用矩陣轉換后三角形,進行最侄訓制,
有了三角形的繪制,我們還可以利用前面介紹的幾何圖元來繪制一個球,于是我們再定義一個Sphere物件,由于這里的球是沒有方向的,只有一個位置點,因些不需要世界轉換矩陣,只需要在繪制函式里,同樣利用射線進行與球的相交檢測,
Spheref sphere(Vector3f(0.f, 0.f, -5.f), 1.0f); ... Vector3f intersectPoint; for (int i = 0; i < WIDTH; ++i) { for (int j = 0; j < HEIGHT; ++j) { int index = j * WIDTH + i; if (triangleToRender.Intersect(rays[i][j], nullptr, &intersectPoint)) { buffer[index] = 0x00ff0000; } // new add if (sphere.Intersect(rays[i][j], nullptr, &intersectPoint)) { buffer[index] = 0x000000ff; } } }
這里有一個問題,我們將球定義在z值為-8的位置,三角在z值為-5的位置,明明是三角形在前面,我們看到的卻是球體,這主要是由于我們進行射線檢測是有順序的,我們總是記錄了最后一個檢測到的物體的顏色,即使我們可以按模型的距離進行排序,保證了離我們近的模型最后才畫,但兩個模型穿插的情況確無法處理,同時如果對buffer填充值計算非常復雜(如后面會講到光照的計算),從遠到近依次繪制的操作會增加很多額外的開銷,即overdraw現像,因此這里需要一個額外的Buffer我們叫深度Buffer用于存盤模型與我們眼眼之前的距離,我們按由近到遠順序對需要渲染的非透明圖元排序(透明物體需要先繪制遠處的),并記錄繪制點的距離值,當下一個相交點繪制之前,先與深度Buffer進行比較,這個程序稱為深度測驗,只有通過深度測驗的點才會進行最后的著色操作,上面的繪制代碼改變后如下:
float depth[WIDTH * HEIGHT]; ... Vector3f intersectPoint; for (int i = 0; i < WIDTH; ++i) { for (int j = 0; j < HEIGHT; ++j) { int index = j * WIDTH + i; if (triangleToRender.Intersect(rays[i][j], nullptr, &intersectPoint)) { buffer[index] = 0x00ff0000; depth[index] = intersectPoint.z; } if (sphere.Intersect(rays[i][j], nullptr, &intersectPoint) && depth[index] < intersectPoint.z) { buffer[index] = 0x000000ff; depth[index] = intersectPoint.z; } } }
最終的實際效果如下:


在實際工程中,深度Buffer并不直接存盤距離值,同樣,實時渲染時也并非按我們的方式發射射線,可以想象,當整個場景中的物體非常多時,對每個像素都對場景物體進行一次相交檢測是多么耗時的程序,因些射線檢測目前更多用于物理計算,利用射線對物體檢測進行渲染的技術叫光線追蹤技術,雖然已經擁有支持這項技術的rtx顯卡,但更多的還是利用利用光線追蹤為我們離線烘焙出接近真實場景的光照效果,光追操作在實時渲染時起到一個輔助作用,目前實際游戲開發中的實時渲染用到的主要還是光柵化的操作,這正是接下來章節我們要介紹的內容——渲染管線,
全部原碼:
#define UNICODE #include <Windows.h> #include <WinBase.h> #include <stdio.h> #include <math.h> #include <float.h> #include "Math/Triangle.hpp" #include "Math/Vector3.hpp" #include "Math/Matrix4x4.hpp" using namespace Magic; const int FRAMES_PER_SECOND = 120; const int SKIP_TICKS = 1000 / FRAMES_PER_SECOND; const int WIDTH = 800; const int HEIGHT = 600; unsigned long long nextGameTick = GetTickCount(); HWND hWnd; BITMAPINFO bmpInfo; unsigned int buffer[WIDTH * HEIGHT]; float depth[WIDTH * HEIGHT]; Rayf rays[WIDTH][HEIGHT]; Trianglef triangle(Vector3f(-1.f, -1.f, 0.f), Vector3f(1.f, -1.f, 0.f), Vector3f(0.f, 1.f, 0.f)); Trianglef triangleToRender = triangle; Matrix4x4f triToWoldMatrix; Spheref sphere(Vector3f(0.f, 0.f, -8.f), 1.0f); void Update(int delta); void ClearBuffer(); void Render(); void DrawBuffer(unsigned char *buffer); void Initalize(); LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; default: break; } return DefWindowProc(hwnd, uMsg, wParam, lParam); } int main(int argc, char *argv[]) { // Register the window class. const wchar_t CLASS_NAME[] = L"Window"; HINSTANCE hInstance = GetModuleHandle(NULL); WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WindowProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadCursor(NULL, IDC_ICON); wcex.hCursor = LoadCursor(NULL, IDI_APPLICATION); wcex.hbrBackground = NULL; wcex.lpszMenuName = 0; wcex.lpszClassName = CLASS_NAME; wcex.hIconSm = 0; RegisterClassEx(&wcex); DWORD style = WS_SYSMENU | WS_BORDER | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; RECT clientSize; clientSize.top = 0; clientSize.left = 0; clientSize.right = WIDTH; clientSize.bottom = HEIGHT; AdjustWindowRect(&clientSize, style, FALSE); int realWidth = clientSize.right - clientSize.left; int realHeight = clientSize.bottom - clientSize.top; int windowLeft = (GetSystemMetrics(SM_CXSCREEN) - realWidth) / 2; int windowTop = (GetSystemMetrics(SM_CYSCREEN) - realHeight) / 2; // Create the window. hWnd = CreateWindowEx( 0, // Optional window styles. CLASS_NAME, // Window class L"Triangle", // Window text style, // Window style // Size and position windowLeft, windowTop, realWidth, realHeight, NULL, // Parent window NULL, // Menu hInstance, // Instance handle NULL // Additional application data ); if (hWnd == NULL) { return 0; } ShowWindow(hWnd, SW_SHOWNORMAL); UpdateWindow(hWnd); MoveWindow(hWnd, windowLeft, windowTop, realWidth, realHeight, TRUE); ShowCursor(TRUE); memset(&bmpInfo, 0, sizeof(BITMAPINFO)); bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmpInfo.bmiHeader.biWidth = WIDTH; bmpInfo.bmiHeader.biHeight = HEIGHT; bmpInfo.bmiHeader.biPlanes = 1; bmpInfo.bmiHeader.biBitCount = 32; bmpInfo.bmiHeader.biCompression = BI_RGB; Initalize(); // Run the message loop. bool isRunning = true; MSG msg = { }; while (isRunning) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); if (msg.message == WM_QUIT) isRunning = false; } unsigned long long curTick = ::GetTickCount(); long long sleepTime = nextGameTick - curTick; if (sleepTime <= 0) { nextGameTick = curTick + SKIP_TICKS; Update(SKIP_TICKS - (int)sleepTime); Render(); } else { Sleep(sleepTime); } } return 0; } void Update(int delta) { for (int i = 0; i < 3; ++i) { triangle[i].RotateByY(0.05f); triangle[i].RotateByZ(0.05f); } static float change = 0.2; sphere.Center().z += change; if (sphere.Center().z > -3.f || sphere.Center().z < -10.f) change = -change; } void ClearBuffer() { memset(buffer, 0, sizeof(buffer)); for (int i = 0; i < WIDTH * HEIGHT; ++i) depth[i] = -FLT_MAX; } void Render() { ClearBuffer(); for (int i = 0; i < 3; ++i) { triangleToRender[i] = triToWoldMatrix.Transform(triangle[i]); } Vector3f intersectPoint; for (int i = 0; i < WIDTH; ++i) { for (int j = 0; j < HEIGHT; ++j) { int index = j * WIDTH + i; if (triangleToRender.Intersect(rays[i][j], nullptr, &intersectPoint)) { buffer[index] = 0x00ff0000; depth[index] = intersectPoint.z; } if (sphere.Intersect(rays[i][j], nullptr, &intersectPoint) && depth[index] < intersectPoint.z) { auto sphereNormal = intersectPoint - sphere.Center(); sphereNormal.Normalize(); float dot = sphereNormal.DotProduct(-rays[i][j].Direction()); buffer[index] = 0x000000ff * dot; depth[index] = intersectPoint.z; } } } DrawBuffer((unsigned char *)buffer); } void DrawBuffer(unsigned char * buffer) { HDC hdc = GetDC(hWnd); StretchDIBits(hdc, 0, 0, WIDTH, HEIGHT, 0, HEIGHT, WIDTH, -HEIGHT, buffer, &bmpInfo, DIB_RGB_COLORS, SRCCOPY); ReleaseDC(hWnd, hdc); } void Initalize() { float width_div_2 = 1.0; float height_div_2 = width_div_2 * HEIGHT / WIDTH; float deltaP = width_div_2 * 2.f / WIDTH; for (int i = 0; i < WIDTH; ++i) { for (int j = 0; j < HEIGHT; ++j) { Vector3f dir(-width_div_2 + deltaP * i, height_div_2 - deltaP * j, -1.f); dir.Normalize(); rays[i][j] = Rayf(Vector3f(0, 0, 0), dir); } } triToWoldMatrix.MakeTransition(Vector3f(0, 0, -5.f)); }
本文來自博客園,作者:毅安,轉載請注明原文鏈接:https://www.cnblogs.com/primarycode/p/16514701.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/500201.html
標籤:其他
