標籤:

從零開始手敲次世代遊戲引擎(十四)

接上一篇,對代碼進行一個簡單的說明。

首先我們依然是加入OpenGL的頭文件。這裡需要特別注意的是Clang預設是大小寫敏感的,所以必須如下書寫:

#include <GL/gl.h>n

前面提到過,OpenGL是在顯卡的驅動程序裡面實現的。而顯卡的驅動程序因廠商不同而不同。而且,同一個廠商也會有許多不同版本的驅動程序。因此,我們的程序不應該也不可能直接與驅動程序進行鏈接。因此,OpenGL的API是一種2進位的API,需要在運行的時候動態地去查找這些API所在的位置(也就是這些API在內存上的地址)

我們這次的程序看起來很長,但實際上一大半都是在進行這個工作而已。如果使用Glew等OpenGL Loader(也就是載入工具),那麼這些代碼都可以省去。但是,在使用方便的工具之前,了解它們實際上都在做些什麼,是很有意思的。

/////////////n// DEFINES //n/////////////n#define WGL_DRAW_TO_WINDOW_ARB 0x2001n#define WGL_ACCELERATION_ARB 0x2003n#define WGL_SWAP_METHOD_ARB 0x2007n#define WGL_SUPPORT_OPENGL_ARB 0x2010n#define WGL_DOUBLE_BUFFER_ARB 0x2011n#define WGL_PIXEL_TYPE_ARB 0x2013n#define WGL_COLOR_BITS_ARB 0x2014n#define WGL_DEPTH_BITS_ARB 0x2022n#define WGL_STENCIL_BITS_ARB 0x2023n#define WGL_FULL_ACCELERATION_ARB 0x2027n#define WGL_SWAP_EXCHANGE_ARB 0x2028n#define WGL_TYPE_RGBA_ARB 0x202Bn#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091n#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092n#define GL_ARRAY_BUFFER 0x8892n#define GL_STATIC_DRAW 0x88E4n#define GL_FRAGMENT_SHADER 0x8B30n#define GL_VERTEX_SHADER 0x8B31n#define GL_COMPILE_STATUS 0x8B81n#define GL_LINK_STATUS 0x8B82n#define GL_INFO_LOG_LENGTH 0x8B84n#define GL_TEXTURE0 0x84C0n#define GL_BGRA 0x80E1n#define GL_ELEMENT_ARRAY_BUFFER 0x8893n

這是定義了一些識別子。這個定義是OpenGL規範事先規定好的,只是一個規定,並沒有什麼道理。顯卡廠商按照這個規定寫驅動,我們也必須按照這個規定寫程序,兩邊才能對接起來。

WGL是微軟提供的OpenGL的兼容版本。

下面的是OpenGL的API的類型定義。這些定義,同樣的,是OpenGL規範的規定。

//////////////n// TYPEDEFS //n//////////////ntypedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats);ntypedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC, HGLRC hShareContext, const int *attribList);ntypedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval);ntypedef void (APIENTRY * PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);ntypedef void (APIENTRY * PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);ntypedef void (APIENTRY * PFNGLBINDVERTEXARRAYPROC) (GLuint array);ntypedef void (APIENTRY * PFNGLBUFFERDATAPROC) (GLenum target, ptrdiff_t size, const GLvoid *data, GLenum usage);ntypedef void (APIENTRY * PFNGLCOMPILESHADERPROC) (GLuint shader);ntypedef GLuint(APIENTRY * PFNGLCREATEPROGRAMPROC) (void);ntypedef GLuint(APIENTRY * PFNGLCREATESHADERPROC) (GLenum type);ntypedef void (APIENTRY * PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);ntypedef void (APIENTRY * PFNGLDELETEPROGRAMPROC) (GLuint program);ntypedef void (APIENTRY * PFNGLDELETESHADERPROC) (GLuint shader);ntypedef void (APIENTRY * PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays);ntypedef void (APIENTRY * PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader);ntypedef void (APIENTRY * PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index);ntypedef void (APIENTRY * PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);ntypedef void (APIENTRY * PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays);ntypedef GLint(APIENTRY * PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const char *name);ntypedef void (APIENTRY * PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, char *infoLog);ntypedef void (APIENTRY * PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);ntypedef void (APIENTRY * PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, char *infoLog);ntypedef void (APIENTRY * PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);ntypedef void (APIENTRY * PFNGLLINKPROGRAMPROC) (GLuint program);ntypedef void (APIENTRY * PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const char* *string, const GLint *length);ntypedef void (APIENTRY * PFNGLUSEPROGRAMPROC) (GLuint program);ntypedef void (APIENTRY * PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);ntypedef void (APIENTRY * PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const char *name);ntypedef GLint(APIENTRY * PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const char *name);ntypedef void (APIENTRY * PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);ntypedef void (APIENTRY * PFNGLACTIVETEXTUREPROC) (GLenum texture);ntypedef void (APIENTRY * PFNGLUNIFORM1IPROC) (GLint location, GLint v0);ntypedef void (APIENTRY * PFNGLGENERATEMIPMAPPROC) (GLenum target);ntypedef void (APIENTRY * PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index);ntypedef void (APIENTRY * PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value);ntypedef void (APIENTRY * PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value);n

然後我們用這些函數指針類型定義我們自己的函數指針,用來存儲OpenGL API的調用(跳轉)地址:

PFNGLATTACHSHADERPROC glAttachShader;nPFNGLBINDBUFFERPROC glBindBuffer;nPFNGLBINDVERTEXARRAYPROC glBindVertexArray;nPFNGLBUFFERDATAPROC glBufferData;nPFNGLCOMPILESHADERPROC glCompileShader;nPFNGLCREATEPROGRAMPROC glCreateProgram;nPFNGLCREATESHADERPROC glCreateShader;nPFNGLDELETEBUFFERSPROC glDeleteBuffers;nPFNGLDELETEPROGRAMPROC glDeleteProgram;nPFNGLDELETESHADERPROC glDeleteShader;nPFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;nPFNGLDETACHSHADERPROC glDetachShader;nPFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;nPFNGLGENBUFFERSPROC glGenBuffers;nPFNGLGENVERTEXARRAYSPROC glGenVertexArrays;nPFNGLGETATTRIBLOCATIONPROC glGetAttribLocation;nPFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;nPFNGLGETPROGRAMIVPROC glGetProgramiv;nPFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;nPFNGLGETSHADERIVPROC glGetShaderiv;nPFNGLLINKPROGRAMPROC glLinkProgram;nPFNGLSHADERSOURCEPROC glShaderSource;nPFNGLUSEPROGRAMPROC glUseProgram;nPFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;nPFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation;nPFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;nPFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv;nPFNGLACTIVETEXTUREPROC glActiveTexture;nPFNGLUNIFORM1IPROC glUniform1i;nPFNGLGENERATEMIPMAPPROC glGenerateMipmap;nPFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray;nPFNGLUNIFORM3FVPROC glUniform3fv;nPFNGLUNIFORM4FVPROC glUniform4fv;nnPFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;nPFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;nPFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;n

OpenGL的API分為核心API,擴展API,以及平台API。核心API是那些成熟的,已經正式納入某個版本的OpenGL規範的API。擴展API是那些廠商拓展的,還沒有正式納入某個版本的OpenGL的API。平台API則是那些和平台密切相關的,比如生成上下文,改變解析度,改變刷新率的API。上面以wgl開頭的,為平台API。

隨著顯卡的發展和OpenGL的版本升級,這些API都是在動態改變的。[*1]

typedef struct VertexTypen{ntVectorType position;ntVectorType color;n} VertexType;n

這是定義了一個頂點所包含的數據(正式名稱稱為屬性)的個數和類型。這個在前面的文章當中就已經用到過了。頂點數據結構對於渲染的性能指標有著很大的影響。因為在GPU工作的時候,它會將這些數據挨個讀入到內部寄存器,然後交給內部的Shader執行器去執行我們寫的Shader代碼。這個數據結構越複雜,佔用的內部寄存器就越多,那麼可以並行執行的Shader就會減少,從而導致整個渲染時間增加。

Epic在使用UE4製作Paragon的時候,就遇到了這個問題。因為從別的角度來看,頂點數據結構以及Shader的通用程度也是十分重要的。UE4廣泛使用基於物理的渲染,所以它的頂點數據結構是比較複雜的,包括很多參數。同時,Shader也是在執行期進行綁定的。也就是說,在UE4當中,並不是像我們現在這樣,將頂點的數據結構和Shader一一對應起來。這就造成了在執行的時候,讀入寄存器的大量頂點數據當中的相當一部分屬性,對於大部分的Shader來說,其實是用不到的。這就白白的浪費了GPU的寄存器,降低了並行執行的數量,拉長了渲染時間。

因此,Epic在Paragon項目當中,採用了一些特別的手法,將不需要的頂點屬性剔除,不讀入GPU寄存器。但是實現這個是有前提條件的,比如AMD的GCN架構,因為它在執行VS Shader之前,有一個被稱為是LS的階段,專門用來處理數據載入的,那麼就可以通過改變這個LS來實現這樣的目的。

const char VS_SHADER_SOURCE_FILE[] = "color.vs";nconst char PS_SHADER_SOURCE_FILE[] = "color.ps";n

這裡定義了VS Shader和PS Shader的程序文件名。在我們之前的DX的例子里,我們是在離線情況下先將Shader編譯成二進位,再在運行時讀入到GPU當中去的。這裡我們演示了另外一種方法,直接讀取Shader的源代碼,然後在運行時內進行編譯,之後再設置到GPU當中去。這種方法實質上允許我們在執行的時候動態生成Shader代碼給自己使用。就類似Java/Javascript的JIT編譯。我們現在有很多網遊是支持「熱更新」的,這種「熱更新」往往就是採用了這種手段。當然,不僅僅是Shader,還包括遊戲本身的邏輯,UI等(比如Lua語言)。

但是這種方法從安全形度來看是有巨大的隱患的。它意味著程序可以執行任何代碼,包括未知的代碼。因此,在一些成熟的平台,比如PS4平台上,這種功能是被禁止掉的。也就是說,只能使用離線編譯好的代碼。

float g_worldMatrix[16];nfloat g_viewMatrix[16];nfloat g_projectionMatrix[16];n

這個就是所謂的MVP矩陣,是實現3D空間到2D空間的投影,攝像機語言,以及動畫的關鍵。之前的文章我們渲染的都是靜態的圖像;而這裡我們加入了這個MVP矩陣,我們就可以渲染動畫了。

接下來都是一些輔助性函數(子過程),我們先跳過它們,看我們的主函數(WinMain)

首先我們可以看到的一個大的改變是我們創建了兩次Window。

// fill in the struct with the needed informationn wc.cbSize = sizeof(WNDCLASSEX);n wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;n wc.lpfnWndProc = DefWindowProc;n wc.hInstance = hInstance;n wc.hCursor = LoadCursor(NULL, IDC_ARROW);n wc.hbrBackground = (HBRUSH)COLOR_WINDOW;n wc.lpszClassName = _T("Temporary");nn // register the window classn RegisterClassEx(&wc);nn // create the temporary window for OpenGL extension setup.n hWnd = CreateWindowEx(WS_EX_APPWINDOW,n _T("Temporary"), // name of the window classn _T("Temporary"), // title of the windown WS_OVERLAPPEDWINDOW, // window stylen 0, // x-position of the windown 0, // y-position of the windown 640, // width of the windown 480, // height of the windown NULL, // we have no parent window, NULLn NULL, // we arent using menus, NULLn hInstance, // application handlen NULL); // used with multiple windows, NULLnn // Dont show the window.n ShowWindow(hWnd, SW_HIDE);nn InitializeExtensions(hWnd);nn DestroyWindow(hWnd);n hWnd = NULL;nn // clear out the window class for usen ZeroMemory(&wc, sizeof(WNDCLASSEX));nn // fill in the struct with the needed informationn wc.cbSize = sizeof(WNDCLASSEX);n wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;n wc.lpfnWndProc = WindowProc;n wc.hInstance = hInstance;n wc.hCursor = LoadCursor(NULL, IDC_ARROW);n wc.hbrBackground = (HBRUSH)COLOR_WINDOW;n wc.lpszClassName = _T("Hello, Engine!");nn // register the window classn RegisterClassEx(&wc);nn // create the window and use the result as the handlen hWnd = CreateWindowEx(WS_EX_APPWINDOW,n _T("Hello, Engine!"), // name of the window classn _T("Hello, Engine!"), // title of the windown WS_OVERLAPPEDWINDOW, // window stylen 300, // x-position of the windown 300, // y-position of the windown 960, // width of the windown 540, // height of the windown NULL, // we have no parent window, NULLn NULL, // we arent using menus, NULLn hInstance, // application handlen NULL); // used with multiple windows, NULLnn InitializeOpenGL(hWnd, 960, 540, SCREEN_DEPTH, SCREEN_NEAR, true);n

這是因為在Windows當中,獲取OpenGL API入口的API,它本身也是一個OpenGL擴展函數。OpenGL的API必須在創建了一個Draw Context(DC)的情況下,才可以使用。因此我們先創建了一個純粹以載入DC為目的的臨時窗口,將OpenGL API的入口地址全部載入到我們事先定義好的函數指針之中。

這裡需要注意的有幾個點:

  1. 註冊的windows class (wc)的wc.style,必須包含CS_OWNDC。這個標誌強迫Windows為使用該windows class的每一個窗口創建一個單獨的DC。如果沒有這個標誌,那麼DC會在所有使用這個windows class的窗口之間共用,那麼這是一個很危險的事情。(也就是某個窗口改變了frame buffer的格式會導致其它同一個windows class的窗口的frame buffer的格式也改變)
  2. 由於在調用wgl系的函數之前,要先確定frame buffer的pixel format,也就是frame buffer的格式,我們在第一次創建窗口之後,InitializeExtensions裡面,指定了預設的pixel format。(因為這個時候我們OpenGL API的函數入口都沒有找到,沒有辦法知道顯卡所支持的pixel format)然而不幸的是,這個SetPixelFormat對於每一個窗口,只能執行一次。因此,在我們完成InitializeExtensions之後,獲得了OpenGL API的入口,想要將Frame buffer指定為我們喜歡的格式的時候,我們必須關閉當前這個(臨時)窗口,重新創建一個窗口(也就是重新創建DC)
  3. 相同的window class的窗口是共用一個消息處理函數(windowproc)的。

    About Window Classes

    從而,如果我們在創建兩次窗口的時候,使用了同一個window class,而且第二次創建緊接著第一次窗口的銷毀之後(就如同我們這個例子)的話,那麼我們在第二次創建窗口之後,很可能會收到第一次創建的窗口被銷毀時發過來的WM_DESTROY消息。那麼會導致我們第二個窗口立即被關閉。(如果我們像這個例子這樣處理了WM_DESTROY的話)

接下來我們讀入Shader程序,並對其進行編譯和綁定(指設定GPU相關的寄存器,使其指向我們的程序在內存當中的地址,以及綁定Shader程序(在這個例子里是VS Shader)輸入參數的格式。(在這個例子里就是我們VertexType的兩個屬性: position, color)

// Bind the shader input variables.n glBindAttribLocation(g_shaderProgram, 0, "inputPosition");n glBindAttribLocation(g_shaderProgram, 1, "inputColor");n

這其實就是告訴GPU,怎麼去看我們輸入的頂點數據。(否則,從GPU來看輸入就是一塊內存區域,一連串的byte,不知道每個頂點的數據從哪裡開始,從哪裡結束,各是什麼意思)

然後是初始化我們的頂點數據和索引數據。索引數據並不是必須的。比如我們前一篇就沒有用到索引數據。但是在一個實際的遊戲當中,物體都是比較複雜的,每個頂點被多個三角面所用。所以,相對於帶有許多屬性的頂點數據來說,索引數據要輕量得多。與其讓頂點數據不斷重複出現(因為每3個頂點才能組成一個三角形,對於相鄰的三角形,它們有1-2個頂點是共用的,這1-2個頂點就會重複出現),不如讓索引重複出現。(頂點-索引機制就是先告訴GPU所有不同的頂點的數據,然後告訴GPU把哪些點連起來形成一個面,很像我們小時候玩的按數字連點畫圖遊戲)

所以對於正方體來說,它一共有8個不同的頂點;6個面,每個面用一條對角線切成兩個三角形的話,一共是12個三角形;每個三角形需要3個索引來描述,一共也就是36個索引。

VertexType vertices[] = {n // Position: x y z Color:r g bn {{ 1.0f, 1.0f, 1.0f }, { 1.0f, 0.0f, 0.0f }},n {{ 1.0f, 1.0f, -1.0f }, { 0.0f, 1.0f, 0.0f }},n {{ -1.0f, 1.0f, -1.0f }, { 0.0f, 0.0f, 1.0f }},n {{ -1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 0.0f }},n {{ 1.0f, -1.0f, 1.0f }, { 1.0f, 0.0f, 1.0f }},n {{ 1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f, 1.0f }},n {{ -1.0f, -1.0f, -1.0f }, { 0.5f, 1.0f, 0.5f }},n {{ -1.0f, -1.0f, 1.0f }, { 1.0f, 0.5f, 1.0f }},n };n uint16_t indices[] = { 1, 2, 3, 3, 2, 6, 6, 7, 3, 3, 0, 1, 0, 3, 7, 7, 6, 4, 4, 6, 5, 0, 7, 4, 1, 0, 4, 1, 4, 5, 2, 1, 5, 2, 5, 6 };n

索引的排列有一定技巧。在當代的GPU當中,為了高速化,內部有各種各樣的Cache,也就是緩存。GPU是按照索引順序依次對頂點的坐標進行變換(一般就是乘以我們給它的MVP矩陣,把空間的點先按照我們的指示進行移動旋轉,然後投射到屏幕坐標(2D)當中)。但是就如我們看到的,索引有重複。對於重複的索引所代表的頂點進行重複的計算是沒有意義的。事實上,GPU會在一定程度上Cache之前計算的結果。如果後面的索引在這個Cache裡面找到了,則直接利用前面計算的結果,而不重新計算。

然而,現實遊戲當中的頂點是千萬-億級別的。GPU不可能有那麼大的Cache全部記住。事實上能夠記住的只有最近的幾個而已。因此在排列這些索引的時候就很有講究了。盡量讓重複的排得近一點。這個裡面有演算法的,這裡就先不展開,放在後面的文章討論。

另外需要注意的是,為了減輕GPU的負擔,大多數的表面都是有正反的。對於反面朝向我們的三角形,如果沒有特別指定,GPU會直接將它丟棄掉。GPU判斷這個正反是通過3個頂點在投射到屏幕空間之後,按照索引的順序去看,是逆時針順序還是順時針順序判斷的。因此,在我們創建索引的時候,需要將空間幾何體首先展平(也就是製作貼圖的時候的UV展開),然後根據在那個平面上的逆時針方向編製每個三角形的索引。

(圖片來自網路)

好了,接下來就是計算綁定MVP了。MVP的特點是每幀更新一次(當然也可以不變),在單幀的繪製過程當中,它是一個常數。所以,MVP在GPU當中存放的地方,也叫Constant Buffer。這個Constant,就是指在一幀當中,它們是常數(同樣的還有光照數據等)

// Update world matrix to rotate the modeln rotateAngle += PI / 120;n float rotationMatrixY[16];n float rotationMatrixZ[16];n MatrixRotationY(rotationMatrixY, rotateAngle);n MatrixRotationZ(rotationMatrixZ, rotateAngle);n MatrixMultiply(g_worldMatrix, rotationMatrixZ, rotationMatrixY);nn // Generate the view matrix based on the cameras position.n CalculateCameraPosition();nn // Set the color shader as the current shader program and set the matrices that it will use for rendering.n glUseProgram(g_shaderProgram);n SetShaderParameters(g_worldMatrix, g_viewMatrix, g_projectionMatrix);n

我們這裡使用了一個局部的靜態變數,rotateAngle,來存儲一個角度因變數(自變數是時間)。然後我們根據這個角度因變數計算出Y軸和Z軸的旋轉矩陣。這兩個矩陣相乘,就是我們的M矩陣。它實現了我們模型的動畫。注意OpenGL是右手坐標系,這個和DX是不一樣的。

同時我們根據鏡頭的當前姿態,計算出View矩陣。這個矩陣決定了我們觀察的位置。

而P矩陣是我們提前根據視口的狀態以及攝像機的FOV計算好的。在攝像機FOV不變化,屏幕解析度不變化,視口不變化的情況下,它是不變化的。

在SetShaderParameter當中,我們查找到已經位於內存(而且是GPU可見的內存)當中的Shader的MVP變數的地址(也就是佔位符的地址),把這些計算的結果覆蓋上去。

最後我們調用繪圖指令,命令GPU開始一幀的繪製:

// Render the vertex buffer using the index buffer.n glDrawElements(GL_TRIANGLES, g_indexCount, GL_UNSIGNED_SHORT, 0);n

所以我們可以看到,其實一個標準的(基本)繪製流程就是CPU先設置好繪圖的上下文(frame buffer,顯卡的狀態等),然後決定畫面當中繪製哪些物體,將這些物體的頂點以及索引數據調入內存的一片區域,將這個區域暴露給GPU並綁定在GPU的特定寄存器上;計算每個物體的MVP,將結果更新到繪製該物體的Shader當中;簽發繪製指令,讓GPU完成物體的繪製工作。

最後我們再來看一下Shader。OpenGL的Shader是用一種稱為GLSL的語言書寫的。基本語法等依然是來自於C/C++語言。與DX的HLSL相比,大致上有以下區別:

  1. 沒有特殊寄存器綁定的語言擴展。但是有特殊變數,起到寄存器綁定的作用。特殊變數以"gl_"開頭;
  2. 輸入輸出變數都是作為全局變數進行聲明,而非作為函數的參數聲明;
  3. 類型名稱不同。HLSL當中的數據類型名稱以基本形的擴展形式出現,如float3;而GLSL當中則以vec3這種擴展的形式出現。

////////////////////////////////////////////////////////////////////////////////n// Filename: color.vsn////////////////////////////////////////////////////////////////////////////////nn#version 400nn/////////////////////n// INPUT VARIABLES //n/////////////////////nin vec3 inputPosition;nin vec3 inputColor;nn//////////////////////n// OUTPUT VARIABLES //n//////////////////////nout vec3 color;nn///////////////////////n// UNIFORM VARIABLES //n///////////////////////nuniform mat4 worldMatrix;nuniform mat4 viewMatrix;nuniform mat4 projectionMatrix;nn////////////////////////////////////////////////////////////////////////////////n// Vertex Shadern////////////////////////////////////////////////////////////////////////////////nvoid main(void)n{nt// Calculate the position of the vertex against the world, view, and projection matrices.ntgl_Position = worldMatrix * vec4(inputPosition, 1.0f);ntgl_Position = viewMatrix * gl_Position;ntgl_Position = projectionMatrix * gl_Position;nnt// Store the input color for the pixel shader to use.ntcolor = inputColor;n}n

這個shader實現的就是把頂點的坐標乘以MVP矩陣,使其投射到2維視口坐標當中;另外原樣拷貝輸出色彩屬性。

////////////////////////////////////////////////////////////////////////////////n// Filename: color.psn////////////////////////////////////////////////////////////////////////////////n#version 400nnn/////////////////////n// INPUT VARIABLES //n/////////////////////nin vec3 color;nnn//////////////////////n// OUTPUT VARIABLES //n//////////////////////nout vec4 outputColor;nnn////////////////////////////////////////////////////////////////////////////////n// Pixel Shadern////////////////////////////////////////////////////////////////////////////////nvoid main(void)n{ntoutputColor = vec4(color, 1.0f);n}n

這個PS Shader實現的也是色彩的原樣輸出。因為我們這個例子當中frame buffer的格式是RGBA,而頂點色只有RGB三個分量,所以添加了一個A通道,值為1.0,意思是完全不透明。

(-- EOF --)

參考引用:

  1. Load OpenGL Functions
  2. ARB assembly language

本作品採用知識共享署名 4.0 國際許可協議進行許可。


推薦閱讀:

GMS 中文教程特別篇 #2:GML 編程實踐的經驗與建議
FC遊戲的偽多捲軸
非IT人士都能看懂的工程代碼重構方法:三步走
【《Real-Time Rendering 3rd》 提煉總結】(九) 第十章 · 遊戲開發中基於圖像的渲染技術總結

TAG:游戏开发 |