接上一篇,对代码进行一个简单的说明。
首先我们依然是加入OpenGL的头文件。这里需要特别注意的是Clang缺省是大小写敏感的,所以必须如下书写:
#include <GL/gl.h>
前面提到过,OpenGL是在显卡的驱动程序里面实现的。而显卡的驱动程序因厂商不同而不同。而且,同一个厂商也会有许多不同版本的驱动程序。因此,我们的程序不应该也不可能直接与驱动程序进行链接。因此,OpenGL的API是一种2进制的API,需要在运行的时候动态地去查找这些API所在的位置(也就是这些API在内存上的地址)
我们这次的程序看起来很长,但实际上一大半都是在进行这个工作而已。如果使用Glew等OpenGL Loader(也就是加载工具),那么这些代码都可以省去。但是,在使用方便的工具之前,了解它们实际上都在做些什么,是很有意思的。
///////////// // DEFINES // ///////////// #define WGL_DRAW_TO_WINDOW_ARB 0x2001 #define WGL_ACCELERATION_ARB 0x2003 #define WGL_SWAP_METHOD_ARB 0x2007 #define WGL_SUPPORT_OPENGL_ARB 0x2010 #define WGL_DOUBLE_BUFFER_ARB 0x2011 #define WGL_PIXEL_TYPE_ARB 0x2013 #define WGL_COLOR_BITS_ARB 0x2014 #define WGL_DEPTH_BITS_ARB 0x2022 #define WGL_STENCIL_BITS_ARB 0x2023 #define WGL_FULL_ACCELERATION_ARB 0x2027 #define WGL_SWAP_EXCHANGE_ARB 0x2028 #define WGL_TYPE_RGBA_ARB 0x202B #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 #define GL_ARRAY_BUFFER 0x8892 #define GL_STATIC_DRAW 0x88E4 #define GL_FRAGMENT_SHADER 0x8B30 #define GL_VERTEX_SHADER 0x8B31 #define GL_COMPILE_STATUS 0x8B81 #define GL_LINK_STATUS 0x8B82 #define GL_INFO_LOG_LENGTH 0x8B84 #define GL_TEXTURE0 0x84C0 #define GL_BGRA 0x80E1 #define GL_ELEMENT_ARRAY_BUFFER 0x8893
这是定义了一些识别子。这个定义是OpenGL规范事先规定好的,只是一个规定,并没有什么道理。显卡厂商按照这个规定写驱动,我们也必须按照这个规定写程序,两边才能对接起来。
WGL是微软提供的OpenGL的兼容版本。
下面的是OpenGL的API的类型定义。这些定义,同样的,是OpenGL规范的规定。
////////////// // TYPEDEFS // ////////////// typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC, HGLRC hShareContext, const int *attribList); typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval); typedef void (APIENTRY * PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); typedef void (APIENTRY * PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); typedef void (APIENTRY * PFNGLBINDVERTEXARRAYPROC) (GLuint array); typedef void (APIENTRY * PFNGLBUFFERDATAPROC) (GLenum target, ptrdiff_t size, const GLvoid *data, GLenum usage); typedef void (APIENTRY * PFNGLCOMPILESHADERPROC) (GLuint shader); typedef GLuint(APIENTRY * PFNGLCREATEPROGRAMPROC) (void); typedef GLuint(APIENTRY * PFNGLCREATESHADERPROC) (GLenum type); typedef void (APIENTRY * PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); typedef void (APIENTRY * PFNGLDELETEPROGRAMPROC) (GLuint program); typedef void (APIENTRY * PFNGLDELETESHADERPROC) (GLuint shader); typedef void (APIENTRY * PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); typedef void (APIENTRY * PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); typedef void (APIENTRY * PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); typedef void (APIENTRY * PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); typedef void (APIENTRY * PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); typedef GLint(APIENTRY * PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const char *name); typedef void (APIENTRY * PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, char *infoLog); typedef void (APIENTRY * PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); typedef void (APIENTRY * PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, char *infoLog); typedef void (APIENTRY * PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); typedef void (APIENTRY * PFNGLLINKPROGRAMPROC) (GLuint program); typedef void (APIENTRY * PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const char* *string, const GLint *length); typedef void (APIENTRY * PFNGLUSEPROGRAMPROC) (GLuint program); typedef void (APIENTRY * PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); typedef void (APIENTRY * PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const char *name); typedef GLint(APIENTRY * PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const char *name); typedef void (APIENTRY * PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRY * PFNGLACTIVETEXTUREPROC) (GLenum texture); typedef void (APIENTRY * PFNGLUNIFORM1IPROC) (GLint location, GLint v0); typedef void (APIENTRY * PFNGLGENERATEMIPMAPPROC) (GLenum target); typedef void (APIENTRY * PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); typedef void (APIENTRY * PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRY * PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value);
然后我们用这些函数指针类型定义我们自己的函数指针,用来存储OpenGL API的调用(跳转)地址:
PFNGLATTACHSHADERPROC glAttachShader; PFNGLBINDBUFFERPROC glBindBuffer; PFNGLBINDVERTEXARRAYPROC glBindVertexArray; PFNGLBUFFERDATAPROC glBufferData; PFNGLCOMPILESHADERPROC glCompileShader; PFNGLCREATEPROGRAMPROC glCreateProgram; PFNGLCREATESHADERPROC glCreateShader; PFNGLDELETEBUFFERSPROC glDeleteBuffers; PFNGLDELETEPROGRAMPROC glDeleteProgram; PFNGLDELETESHADERPROC glDeleteShader; PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays; PFNGLDETACHSHADERPROC glDetachShader; PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; PFNGLGENBUFFERSPROC glGenBuffers; PFNGLGENVERTEXARRAYSPROC glGenVertexArrays; PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation; PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; PFNGLGETPROGRAMIVPROC glGetProgramiv; PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; PFNGLGETSHADERIVPROC glGetShaderiv; PFNGLLINKPROGRAMPROC glLinkProgram; PFNGLSHADERSOURCEPROC glShaderSource; PFNGLUSEPROGRAMPROC glUseProgram; PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation; PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv; PFNGLACTIVETEXTUREPROC glActiveTexture; PFNGLUNIFORM1IPROC glUniform1i; PFNGLGENERATEMIPMAPPROC glGenerateMipmap; PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray; PFNGLUNIFORM3FVPROC glUniform3fv; PFNGLUNIFORM4FVPROC glUniform4fv; PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;
OpenGL的API分为核心API,扩展API,以及平台API。核心API是那些成熟的,已经正式纳入某个版本的OpenGL规范的API。扩展API是那些厂商拓展的,还没有正式纳入某个版本的OpenGL的API。平台API则是那些和平台密切相关的,比如生成上下文,改变分辨率,改变刷新率的API。上面以wgl开头的,为平台API。
随着显卡的发展和OpenGL的版本升级,这些API都是在动态改变的。[*1]
typedef struct VertexType { VectorType position; VectorType color; } VertexType;
这是定义了一个顶点所包含的数据(正式名称称为属性)的个数和类型。这个在前面的文章当中就已经用到过了。顶点数据结构对于渲染的性能指标有着很大的影响。因为在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"; const char PS_SHADER_SOURCE_FILE[] = "color.ps";
这里定义了VS Shader和PS Shader的程序文件名。在我们之前的DX的例子里,我们是在离线情况下先将Shader编译成二进制,再在运行时读入到GPU当中去的。这里我们演示了另外一种方法,直接读取Shader的源代码,然后在运行时内进行编译,之后再设置到GPU当中去。这种方法实质上允许我们在执行的时候动态生成Shader代码给自己使用。就类似Java/Javascript的JIT编译。我们现在有很多网游是支持“热更新”的,这种“热更新”往往就是采用了这种手段。当然,不仅仅是Shader,还包括游戏本身的逻辑,UI等(比如Lua语言)。
但是这种方法从安全角度来看是有巨大的隐患的。它意味着程序可以执行任何代码,包括未知的代码。因此,在一些成熟的平台,比如PS4平台上,这种功能是被禁止掉的。也就是说,只能使用离线编译好的代码。
float g_worldMatrix[16]; float g_viewMatrix[16]; float g_projectionMatrix[16];
这个就是所谓的MVP矩阵,是实现3D空间到2D空间的投影,摄像机语言,以及动画的关键。之前的文章我们渲染的都是静态的图像;而这里我们加入了这个MVP矩阵,我们就可以渲染动画了。
接下来都是一些辅助性函数(子过程),我们先跳过它们,看我们的主函数(WinMain)
首先我们可以看到的一个大的改变是我们创建了两次Window。
// fill in the struct with the needed information wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = DefWindowProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)COLOR_WINDOW; wc.lpszClassName = _T("Temporary"); // register the window class RegisterClassEx(&wc); // create the temporary window for OpenGL extension setup. hWnd = CreateWindowEx(WS_EX_APPWINDOW, _T("Temporary"), // name of the window class _T("Temporary"), // title of the window WS_OVERLAPPEDWINDOW, // window style 0, // x-position of the window 0, // y-position of the window 640, // width of the window 480, // height of the window NULL, // we have no parent window, NULL NULL, // we aren't using menus, NULL hInstance, // application handle NULL); // used with multiple windows, NULL // Don't show the window. ShowWindow(hWnd, SW_HIDE); InitializeExtensions(hWnd); DestroyWindow(hWnd); hWnd = NULL; // clear out the window class for use ZeroMemory(&wc, sizeof(WNDCLASSEX)); // fill in the struct with the needed information wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)COLOR_WINDOW; wc.lpszClassName = _T("Hello, Engine!"); // register the window class RegisterClassEx(&wc); // create the window and use the result as the handle hWnd = CreateWindowEx(WS_EX_APPWINDOW, _T("Hello, Engine!"), // name of the window class _T("Hello, Engine!"), // title of the window WS_OVERLAPPEDWINDOW, // window style 300, // x-position of the window 300, // y-position of the window 960, // width of the window 540, // height of the window NULL, // we have no parent window, NULL NULL, // we aren't using menus, NULL hInstance, // application handle NULL); // used with multiple windows, NULL InitializeOpenGL(hWnd, 960, 540, SCREEN_DEPTH, SCREEN_NEAR, true);
这是因为在Windows当中,获取OpenGL API入口的API,它本身也是一个OpenGL扩展函数。OpenGL的API必须在创建了一个Draw Context(DC)的情况下,才可以使用。因此我们先创建了一个纯粹以加载DC为目的的临时窗口,将OpenGL API的入口地址全部加载到我们事先定义好的函数指针之中。
这里需要注意的有几个点:
- 注册的windows class (wc)的wc.style,必须包含CS_OWNDC。这个标志强迫Windows为使用该windows class的每一个窗口创建一个单独的DC。如果没有这个标志,那么DC会在所有使用这个windows class的窗口之间共用,那么这是一个很危险的事情。(也就是某个窗口改变了frame buffer的格式会导致其它同一个windows class的窗口的frame buffer的格式也改变)
- 由于在调用wgl系的函数之前,要先确定frame buffer的pixel format,也就是frame buffer的格式,我们在第一次创建窗口之后,InitializeExtensions里面,指定了缺省的pixel format。(因为这个时候我们OpenGL API的函数入口都没有找到,没有办法知道显卡所支持的pixel format)然而不幸的是,这个SetPixelFormat对于每一个窗口,只能执行一次。因此,在我们完成InitializeExtensions之后,获得了OpenGL API的入口,想要将Frame buffer指定为我们喜欢的格式的时候,我们必须关闭当前这个(临时)窗口,重新创建一个窗口(也就是重新创建DC)
- 相同的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. glBindAttribLocation(g_shaderProgram, 0, "inputPosition"); glBindAttribLocation(g_shaderProgram, 1, "inputColor");
这其实就是告诉GPU,怎么去看我们输入的顶点数据。(否则,从GPU来看输入就是一块内存区域,一连串的byte,不知道每个顶点的数据从哪里开始,从哪里结束,各是什么意思)
然后是初始化我们的顶点数据和索引数据。索引数据并不是必须的。比如我们前一篇就没有用到索引数据。但是在一个实际的游戏当中,物体都是比较复杂的,每个顶点被多个三角面所用。所以,相对于带有许多属性的顶点数据来说,索引数据要轻量得多。与其让顶点数据不断重复出现(因为每3个顶点才能组成一个三角形,对于相邻的三角形,它们有1-2个顶点是共用的,这1-2个顶点就会重复出现),不如让索引重复出现。(顶点-索引机制就是先告诉GPU所有不同的顶点的数据,然后告诉GPU把哪些点连起来形成一个面,很像我们小时候玩的按数字连点画图游戏)
所以对于正方体来说,它一共有8个不同的顶点;6个面,每个面用一条对角线切成两个三角形的话,一共是12个三角形;每个三角形需要3个索引来描述,一共也就是36个索引。
VertexType vertices[] = { // Position: x y z Color:r g b {{ 1.0f, 1.0f, 1.0f }, { 1.0f, 0.0f, 0.0f }}, {{ 1.0f, 1.0f, -1.0f }, { 0.0f, 1.0f, 0.0f }}, {{ -1.0f, 1.0f, -1.0f }, { 0.0f, 0.0f, 1.0f }}, {{ -1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 0.0f }}, {{ 1.0f, -1.0f, 1.0f }, { 1.0f, 0.0f, 1.0f }}, {{ 1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f, 1.0f }}, {{ -1.0f, -1.0f, -1.0f }, { 0.5f, 1.0f, 0.5f }}, {{ -1.0f, -1.0f, 1.0f }, { 1.0f, 0.5f, 1.0f }}, }; 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 };
索引的排列有一定技巧。在当代的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 model rotateAngle += PI / 120; float rotationMatrixY[16]; float rotationMatrixZ[16]; MatrixRotationY(rotationMatrixY, rotateAngle); MatrixRotationZ(rotationMatrixZ, rotateAngle); MatrixMultiply(g_worldMatrix, rotationMatrixZ, rotationMatrixY); // Generate the view matrix based on the camera's position. CalculateCameraPosition(); // Set the color shader as the current shader program and set the matrices that it will use for rendering. glUseProgram(g_shaderProgram); SetShaderParameters(g_worldMatrix, g_viewMatrix, g_projectionMatrix);
我们这里使用了一个局部的静态变量,rotateAngle,来存储一个角度因变量(自变量是时间)。然后我们根据这个角度因变量计算出Y轴和Z轴的旋转矩阵。这两个矩阵相乘,就是我们的M矩阵。它实现了我们模型的动画。注意OpenGL是右手坐标系,这个和DX是不一样的。
同时我们根据镜头的当前姿态,计算出View矩阵。这个矩阵决定了我们观察的位置。
而P矩阵是我们提前根据视口的状态以及摄像机的FOV计算好的。在摄像机FOV不变化,屏幕分辨率不变化,视口不变化的情况下,它是不变化的。
在SetShaderParameter当中,我们查找到已经位于内存(而且是GPU可见的内存)当中的Shader的MVP变量的地址(也就是占位符的地址),把这些计算的结果覆盖上去。
最后我们调用绘图指令,命令GPU开始一帧的绘制:
// Render the vertex buffer using the index buffer. glDrawElements(GL_TRIANGLES, g_indexCount, GL_UNSIGNED_SHORT, 0);
所以我们可以看到,其实一个标准的(基本)绘制流程就是CPU先设置好绘图的上下文(frame buffer,显卡的状态等),然后决定画面当中绘制哪些物体,将这些物体的顶点以及索引数据调入内存的一片区域,将这个区域暴露给GPU并绑定在GPU的特定寄存器上;计算每个物体的MVP,将结果更新到绘制该物体的Shader当中;签发绘制指令,让GPU完成物体的绘制工作。
最后我们再来看一下Shader。OpenGL的Shader是用一种称为GLSL的语言书写的。基本语法等依然是来自于C/C++语言。与DX的HLSL相比,大致上有以下区别:
- 没有特殊寄存器绑定的语言扩展。但是有特殊变量,起到寄存器绑定的作用。特殊变量以”gl_”开头;
- 输入输出变量都是作为全局变量进行声明,而非作为函数的参数声明;
- 类型名称不同。HLSL当中的数据类型名称以基本形的扩展形式出现,如float3;而GLSL当中则以vec3这种扩展的形式出现。
//////////////////////////////////////////////////////////////////////////////// // Filename: color.vs //////////////////////////////////////////////////////////////////////////////// #version 400 ///////////////////// // INPUT VARIABLES // ///////////////////// in vec3 inputPosition; in vec3 inputColor; ////////////////////// // OUTPUT VARIABLES // ////////////////////// out vec3 color; /////////////////////// // UNIFORM VARIABLES // /////////////////////// uniform mat4 worldMatrix; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; //////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// void main(void) { // Calculate the position of the vertex against the world, view, and projection matrices. gl_Position = worldMatrix * vec4(inputPosition, 1.0f); gl_Position = viewMatrix * gl_Position; gl_Position = projectionMatrix * gl_Position; // Store the input color for the pixel shader to use. color = inputColor; }
这个shader实现的就是把顶点的坐标乘以MVP矩阵,使其投射到2维视口坐标当中;另外原样拷贝输出色彩属性。
//////////////////////////////////////////////////////////////////////////////// // Filename: color.ps //////////////////////////////////////////////////////////////////////////////// #version 400 ///////////////////// // INPUT VARIABLES // ///////////////////// in vec3 color; ////////////////////// // OUTPUT VARIABLES // ////////////////////// out vec4 outputColor; //////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// void main(void) { outputColor = vec4(color, 1.0f); }
这个PS Shader实现的也是色彩的原样输出。因为我们这个例子当中frame buffer的格式是RGBA,而顶点色只有RGB三个分量,所以添加了一个A通道,值为1.0,意思是完全不透明。
(– EOF –)
参考引用:
本作品采用知识共享署名 4.0 国际许可协议进行许可。