08 How I Deal with Shaders 在之前的着色器编写中,对于每个字符串都要加引号和换行符,这是非常不方便的。
因此我们需要从文件读取shader。
一般来说会把vs和fs分成两个文件。但作者认为两个文件也很不方便,而是把vs和fs合并在一起(shaderlab就是这样做的)。并且在文件中对两部分进行区别。
在项目文件夹中新建resource文件夹,包含shaders文件夹,(以后也可能添加纹理、模型等资源),创建一个后缀为shader的文件
将着色器的代码粘贴到文件中,并分别加上指定vs和fs编译的宏(可以用替换的功能去掉引号和换行符)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #shader vertex #version 330 core layout (location = 0 ) in vec4 position;void main () { gl_Position = position; }; #shader fragment #version 330 core layout (location = 0 ) out vec4 color;void main () { color = vec4 (1.0 ,0.0 ,0.0 ,1.0 ); };
然后我们要做的是把这个文件转换成std字符串。读文件采用fstream设置文件流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <fstream> #include <string> #include <sstream> struct ShaderProgramSource { std::string VertexSource; std::string FrgmentSource; }; static ShaderProgramSource ParseShader (const std::string& filepath) { std::ifstream stream (filepath) ; enum class ShaderType { NONE = -1 , VERTEX = 0 , FRAGMENT = 1 }; std::string line; std::stringstream ss[2 ]; ShaderType type = ShaderType::NONE; while (getline (stream, line)) { if (line.find ("#shader" )!=std::string::npos){ if (line.find ("vertex" )!=std::string::npos){ type = ShaderType::VERTEX; } else if (line.find ("fragment" )!=std::string::npos) { type = ShaderType::FRAGMENT; } } else { ss[(int )type] <<line<<"\n" ; } } return {ss[0 ].str (),ss[1 ].str ()}; }
1 2 ShaderProgramSource source= ParseShader ("res/shaders/Basic.shader" );
将解析的着色器代码输出出来,一切正常
那么我们实现了从一个文件里读取vs和fs,这部分修改就完成了
09 Index Buffers
我们已经画好了一个三角形。我们如果想画一个正方形呢?实际上,也是由三角形组成的。
1 2 3 4 5 6 7 8 9 float positions[12 ] = { -0.5f , -0.5f , 0.5f , -0.5f , 0.5f , 0.5f , 0.5f , 0.5f , -0.5f , 0.5f , -0.5f , -0.5f };
相应地,顶点缓冲区大小也增大了,绘制方法也需要改成6个顶点
1 glDrawArrays (GL_TRIANGLES, 0 , 6 );
我们成功地 绘制出了想要的正方形/长方形
但是实际上顶点数组发生了大量重复,这也浪费了内存,尤其是顶点储存属性很多时。
因此我们使用索引缓冲Index Buffer,使顶点能够重复使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 float positions[12 ] = { -0.5f , -0.5f , 0.5f , -0.5f , 0.5f , 0.5f , -0.5f , 0.5f }; unsigned int indices[] = { 0 ,1 ,2 , 2 ,3 ,0 }; unsigned int buffer;glGenBuffers (1 , &buffer);glBindBuffer (GL_ARRAY_BUFFER, buffer);glBufferData (GL_ARRAY_BUFFER, sizeof (positions), positions, GL_STATIC_DRAW);glVertexAttribPointer (0 , 2 , GL_FLOAT, GL_FALSE, 2 * sizeof (float ),0 );glEnableVertexAttribArray (0 );unsigned int ibo;glGenBuffers (1 , &ibo);glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, ibo);glBufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices, GL_STATIC_DRAW);glDrawElements (GL_TRIANGLES, 6 , GL_UNSIGNED_INT, nullptr );
也非常顺利
10 Dealing with Errors 这里讨论的是Opengl提供的检查错误的方法,不会用到外部工具
主要有两种方法
glGetError
使用glGetError的工作流程是首先在opengl的每个函数调用前,==在循环中调用==,直到清除所有错误。
glMessageCallBack是opengl4.3添加的功能,允许我们指定一个指向Opengl函数的指针,比glGetError更方便。这一集教程中只关注glGetError。
1 2 3 4 5 6 7 8 9 10 11 12 13 static void GLClearError () { while (glGetError ()!=GL_NO_ERROR); } static void GLCheckError () { while (GLenum error = glGetError ()) { std::cout<<"[OpenGL Error] (" << error <<")" <<std::endl; } } ...... GLClearError ();glDrawElements (GL_TRIANGLES, 6 , GL_INT, nullptr );GLCheckError ();
控制台打印出1280的错误
在glew.h中 ,会发现各种宏的定义都是16进制的
因此我们把1280转换成16进制,如果懒得搜的话,在断点 中定位到变量,右键可以选择16进制显示,这里结果是0x00000500,
在glew.h中搜索,我们可以找到是GL_INVALID_ENUM的错误,也就是说,在我们检查的glDrawElements函数中,传递了一个无效的枚举类型参数。也就是GL_INT
那么,最终的问题是,对于每个函数都做这样的处理太过麻烦,也会污染代码。所以我们最好知道错误发生在哪里 。
在这里我们已经设置了断点,当然我们在堆栈中可以找到具体的出现问题的函数。我们也可以用ASSERT来做这件事,如果条件不成立,通常发送一个消息到控制台,或是停止程序的执行 。这相当于用代码来设置断点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #define ASSERT(x) if (!(x)) __debugbreak(); static void GLClearError () { while (glGetError ()!=GL_NO_ERROR); } static bool GLLogCall () { while (GLenum error = glGetError ()) { std::cout<<"[OpenGL Error] (" << error <<")" <<std::endl; return false ; } return true ; } ... GLClearError ();glDrawElements (GL_TRIANGLES, 6 , GL_INT, nullptr );ASSERT (GLLogCall ());
运行后自动触发了断点
我们还可以再设置一宏命令来简化这个过程。
1 2 3 4 5 6 #define GLCall(x) GLClearError();\ x;\ ASSERT(GLLogCall()) ... GLCALL (glDrawElements (GL_TRIANGLES, 6 , GL_INT, nullptr ));
非常好用。
还要解决的问题是,我们的错误消息并不能指定实际错误发生在哪个文件或行上,甚至函数名。
1 2 3 4 5 6 7 8 9 10 11 12 #define GLCall(x) GLClearError();\ x;\ ASSERT(GLLogCall(#x, __FILE__, __LINE__)) ... static bool GLLogCall (const char * function, const char * file, int line) { while (GLenum error = glGetError ()) { std::cout << "[OpenGL Error] (" << error << "): " << function << " " << file<<" : " <<line<<std::endl; return false ; } return true ; }
更好用了。
接下来我们要做的就是把每个opengl方法用GLCall包装起来。
有一些情况会为GLCall添加if、while等作用域,但是这样就无法使用于赋值的构造函数语句等,因为这会让创建的变量无法被调用,因为超出了作用域(相当于封装在了一个{}scope中)
1 2 GLCall (unsigned int program = glCreaateProgram ());
Uniform是一种从CPU获取数据的方式。
在交互时着色器变量有可能需要更新, 因此C++的变量通过Uniform传递到着色器。
我们在每次绘制前设置uniform
1 2 3 4 5 6 7 8 9 10 11 12 #shader fragment #version 330 core layout (location = 0 ) out vec4 color;uniform vec4 u_Color; void main () { color = u_Color; };
1 2 3 4 5 6 7 8 9 glUseProgram (shader);int location = glGetUniformLocation (shader, "u_Color" );ASSERT (location != -1 );glUniform4f (location, 0.2f , 0.3f , 0.8f , 1.0f );
通过Uniform来对变量进行变化控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int location = glGetUniformLocation (shader, "u_Color" );ASSERT (location != -1 );glUniform4f (location, 0.2f , 0.3f , 0.8f , 1.0f );float r = 0.0f ;float increment = 0.05f ;while (!glfwWindowShouldClose (window)){ glUniform4f (location, r, 0.3f , 0.8f , 1.0f ); GLCall (glDrawElements (GL_TRIANGLES, 6 , GL_UNSIGNED_INT, nullptr )); if (r>1.0f ) increment = -0.05f ; else if (r<0.0f ) increment = 0.05f ; r += increment; ..... }
(有可能会出现变化太快的问题,可以把双缓冲交换间隔设置为1。虽然我好像没有这个问题)
1 2 3 4 ... glfwMakeContextCurrent (window);glfwSwapInterval (1 );...
12 Vertex Arrays
绑定vertex buffer
指定vertex layout
绑定index buffer
1 2 3 4 glUseProgram (0 );glBindBuffer (GL_ARRAY_BUFFER, 0 );glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0 );
在每一帧重新绑定,结果是一样的。事实上由于每一帧绘制数据可能发生变化,正需要在这里进行绑定。
1 2 3 4 5 6 7 8 9 glUseProgram (shader);glBindBuffer (GL_ARRAY_BUFFER, buffer);glVertexAttribPointer (0 , 2 , GL_FLOAT, GL_FALSE, 2 * sizeof (float ), 0 );glEnableVertexAttribArray (0 );glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, ibo);glUniform4f (location, r, 0.3f , 0.8f , 1.0f );GLCall (glDrawElements (GL_TRIANGLES, 6 , GL_UNSIGNED_INT, nullptr ));
我们需要正确使用顶点数组,为每个几何体设置不同的顶点数组对象,然后只需要在drawcall前进行绑定。这就是VAO。
绘制过程
绑定着色器
绑定顶点缓冲区
设置顶点布局
绑定index缓冲区
drawcall
-》使用VAO
绑定着色器
绑定顶点数组
绑定index缓冲
drawcall
使用core profile
1 2 3 4 5 glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 3 );glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3 );glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
1 2 3 4 unsigned int vao; glGenVertexArrays(1, &vao); glBindVertexArray(vao);
在这些都绑定好后,由于Opengl的状态机属性,我们可以解除绑定。然后在drawcall前再绑定vao(把vertex buffer链接到VAO),就可以正常绘制。
绑定VAO
绑定顶点缓冲
指定顶点属性指针,这时将会把顶点缓冲和VAO绑定在一起
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 unsigned int vao;glGenVertexArrays (1 , &vao);glBindVertexArray (vao);unsigned int buffer;glGenBuffers (1 , &buffer);glBindBuffer (GL_ARRAY_BUFFER, buffer);glBufferData (GL_ARRAY_BUFFER, sizeof (positions), positions, GL_STATIC_DRAW);glVertexAttribPointer (0 , 2 , GL_FLOAT, GL_FALSE, 2 * sizeof (float ), 0 );glEnableVertexAttribArray (0 );unsigned int ibo;glGenBuffers (1 , &ibo);glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, ibo);glBufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices, GL_STATIC_DRAW);..... glBindVertexArray (0 );glBindBuffer (GL_ARRAY_BUFFER, 0 );glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0 );while (!glfwWindowShouldClose (window)){ glUseProgram (shader); glBindVertexArray (vao); glUniform4f (location, r, 0.3f , 0.8f , 1.0f ); GLCall (glDrawElements (GL_TRIANGLES, 6 , GL_UNSIGNED_INT, nullptr )); }
原来的做法:
每次需要绘制时,绑定顶点缓冲区,指定顶点属性指针,绑定顶点索引缓冲,然后渲染。
VAO的方法:
为每个几何对象创建VAO,绑定顶点缓冲区,指定顶点属性指针,绑定顶点索引缓冲,解绑
在渲染该对象前绑定对应的VAO
LearnOpengl对此操作的描述
1 2 3 4 5 6 7 8 9 10 11 glBindBuffer (GL_ARRAY_BUFFER, 0 ); glBindVertexArray (0 );
在之后我们可以用class来做这件事,会方便很多。
每次绑定vertex buffer和VAO的方式哪种更快呢?
实际上在过去使用一个VAO,每次绑定其他东西更快。
NVIDIA提出不建议使用VAO
但其实在不同环境下也可能出现不同的结果
如果一定特别需要压榨性能,可以看情况使用
13 Abstracting Opengl into Class 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 #include <GL/glew.h> #include <GLFW/glfw3.h> #include <iostream> #include <fstream> #include <string> #include <sstream> #define ASSERT(x) if (!(x)) __debugbreak(); #define GLCall(x) GLClearError();\ x;\ ASSERT(GLLogCall(#x, __FILE__, __LINE__)) static void GLClearError () { while (glGetError () != GL_NO_ERROR); } static bool GLLogCall (const char * function, const char * file, int line) { while (GLenum error = glGetError ()) { std::cout << "[OpenGL Error] (" << error << "): " << function << " " << file << " : " << line << std::endl; return false ; } return true ; } struct ShaderProgramSource { std::string VertexSource; std::string FrgmentSource; }; static ShaderProgramSource ParseShader (const std::string& filepath) { std::ifstream stream (filepath) ; enum class ShaderType { NONE = -1 , VERTEX = 0 , FRAGMENT = 1 }; std::string line; std::stringstream ss[2 ]; ShaderType type = ShaderType::NONE; while (getline (stream, line)) { if (line.find ("#shader" ) != std::string::npos) { if (line.find ("vertex" ) != std::string::npos) { type = ShaderType::VERTEX; } else if (line.find ("fragment" ) != std::string::npos) { type = ShaderType::FRAGMENT; } } else { ss[(int )type] << line << "\n" ; } } return { ss[0 ].str (),ss[1 ].str () }; } static unsigned int ComplieShader (unsigned int type, const std::string& source) { unsigned int id = glCreateShader (type); const char * src = source.c_str (); glShaderSource (id, 1 , &src, nullptr ); glCompileShader (id); int result; glGetShaderiv (id, GL_COMPILE_STATUS, &result); if (!result) { int length; glGetShaderiv (id, GL_INFO_LOG_LENGTH, &length); char * message = (char *)alloca (length * sizeof (char )); glGetShaderInfoLog (id, length, &length, message); std::cout << "Fail to complie " << (type == GL_VERTEX_SHADER ? "vertex " : "fragment " ) << "shader!" << std::endl; std::cout << message << std::endl; glDeleteShader (id); return 0 ; } return id; } static unsigned int CreateShader (const std::string& vertexShader, const std::string& fragmentShader) { unsigned int program = glCreateProgram (); unsigned int vs = ComplieShader (GL_VERTEX_SHADER, vertexShader); unsigned int fs = ComplieShader (GL_FRAGMENT_SHADER, fragmentShader); glAttachShader (program, vs); glAttachShader (program, fs); glLinkProgram (program); glValidateProgram (program); glDeleteShader (vs); glDeleteShader (fs); return program; } int main (void ) { GLFWwindow* window; if (!glfwInit ()) return -1 ; glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 3 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3 ); glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); window = glfwCreateWindow (640 , 480 , "Hello World" , NULL , NULL ); if (!window) { glfwTerminate (); return -1 ; } glfwMakeContextCurrent (window); glfwSwapInterval (1 ); if (glewInit () != GLEW_OK) std::cout << "GlewInit fail!" << std::endl; float positions[12 ] = { -0.5f , -0.5f , 0.5f , -0.5f , 0.5f , 0.5f , -0.5f , 0.5f }; unsigned int indices[] = { 0 ,1 ,2 , 2 ,3 ,0 }; unsigned int vao; glGenVertexArrays (1 , &vao); glBindVertexArray (vao); unsigned int buffer; glGenBuffers (1 , &buffer); glBindBuffer (GL_ARRAY_BUFFER, buffer); glBufferData (GL_ARRAY_BUFFER, sizeof (positions), positions, GL_STATIC_DRAW); glVertexAttribPointer (0 , 2 , GL_FLOAT, GL_FALSE, 2 * sizeof (float ), 0 ); glEnableVertexAttribArray (0 ); unsigned int ibo; glGenBuffers (1 , &ibo); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, ibo); glBufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices, GL_STATIC_DRAW); ShaderProgramSource source = ParseShader ("res/shaders/Basic.shader" ); unsigned int shader = CreateShader (source.VertexSource, source.FrgmentSource); int location = glGetUniformLocation (shader, "u_Color" ); ASSERT (location != -1 ); float r = 0.0f ; float increment = 0.05f ; glBindVertexArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, 0 ); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0 ); while (!glfwWindowShouldClose (window)) { glUseProgram (shader); glBindVertexArray (vao); glUniform4f (location, r, 0.3f , 0.8f , 1.0f ); GLCall (glDrawElements (GL_TRIANGLES, 6 , GL_UNSIGNED_INT, nullptr )); if (r > 1.0f ) increment = -0.05f ; else if (r < 0.0f ) increment = 0.05f ; r += increment; glfwSwapBuffers (window); glfwPollEvents (); } glfwTerminate (); return 0 ; }
到这里先放一放整体的代码,然后我们将要进行大的修改。
Renderer 首先创建Renderer.h 和cpp文件。并且把错误处理的部分挪到头文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #pragma once #include <GL/glew.h> #define ASSERT(x) if (!(x)) __debugbreak(); #define GLCall(x) GLClearError();\ x;\ ASSERT(GLLogCall(#x, __FILE__, __LINE__)) void GLClearError () ;bool GLLogCall (const char * function, const char * file, int line) ;#include "Renderer.h" #include <iostream> void GLClearError () { while (glGetError () != GL_NO_ERROR); } bool GLLogCall (const char * function, const char * file, int line) { while (GLenum error = glGetError ()) { std::cout << "[OpenGL Error] (" << error << "): " << function << " " << file << " : " << line << std::endl; return false ; } return true ; }
VertexBuffer 我们再创建VertexBuffer的头文件和cpp文件,用来定义顶点缓冲区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #pragma once class VertexBuffer { private : unsigned int m_RendererID; public : VertexBuffer (const void * data, unsigned int size); ~VertexBuffer (); void Bind () const ; void Unbind () const ; };
我们可以右键类名后选择“快速操作和重构”,然后创建函数定义,这样IDE自动在cpp中为我们创建了各个方法的定义框架(但实际上我操作失败了,提示说所选的文本不包含任何函数签名,我只能全选下面的方法,然后再右键快速操作)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include "VertexBuffer.h" #include "Renderer.h" VertexBuffer::VertexBuffer (const void * data, unsigned int size) { glGenBuffers (1 , &m_RendererID); glBindBuffer (GL_ARRAY_BUFFER, m_RendererID); glBufferData (GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); } VertexBuffer::~VertexBuffer () { glDeleteBuffers (1 , &m_RendererID); } void VertexBuffer::Bind () const { glBindBuffer (GL_ARRAY_BUFFER, m_RendererID); } void VertexBuffer::Unbind () const { glBindBuffer (GL_ARRAY_BUFFER, 0 ); }
IndexBuffer 对indexBuffer也可以用同样的做法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #pragma once class IndexBuffer { private : unsigned int m_RendererID; unsigned int m_Count; public : IndexBuffer (const unsigned int * data, unsigned int count); ~IndexBuffer (); void Bind () const ; void Unbind () const ; inline unsigned int GetCount () { return m_Count; } }; #include "IndexBuffer.h" #include "Renderer.h" IndexBuffer::IndexBuffer (const unsigned int * data, unsigned int count) : m_Count (count) { ASSERT (sizeof (unsigned int ) == sizeof (GLuint)); glGenBuffers (1 , &m_RendererID); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, m_RendererID); glBufferData (GL_ELEMENT_ARRAY_BUFFER, count * sizeof (unsigned int ), data, GL_STATIC_DRAW); } IndexBuffer::~IndexBuffer () { glDeleteBuffers (1 , &m_RendererID); } void IndexBuffer::Bind () const { glBindBuffer (GL_ARRAY_BUFFER, m_RendererID); } void IndexBuffer::Unbind () const { glBindBuffer (GL_ARRAY_BUFFER, 0 ); }
Application 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 VertexBuffer vb (positions, 4 *2 *sizeof (float )) ; glVertexAttribPointer (0 , 2 , GL_FLOAT, GL_FALSE, 2 * sizeof (float ), 0 ); glEnableVertexAttribArray (0 ); IndexBuffer ib (indices, 6 ) ;
14 Buffer Layout Abstraction 我们希望用如下的结构来组织vao
1 2 3 4 5 6 7 8 9 VertexArray va; VertexBuffer vb (positions, 4 *2 *sizeof (float )) ;va.AddBuffer (vb); BufferLayout layout; layout.Push <float >(3 ); va.AddLayout (layout); .... va.Bind ();
VertexArray 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #pragma once #include "VertexBuffer.h" #include "VertexBufferLayout.h" class VertexArray {private : unsigned int m_RendererID; public : VertexArray (); ~VertexArray (); void AddBuffer (const VertexBuffer& vb, const VertexBufferLayout& layout) ; void Bind () const ; void Unbind () const ; }; #include "VertexArray.h" #include "Renderer.h" VertexArray::VertexArray () { glGenVertexArrays (1 , &m_RendererID); } VertexArray::~VertexArray () { glDeleteVertexArrays (1 , &m_RendererID); } void VertexArray::AddBuffer (const VertexBuffer& vb, const VertexBufferLayout& layout) { Bind (); vb.Bind (); const auto & elements = layout.GetElements (); unsigned int offset = 0 ; for (unsigned int i = 0 ; i < elements.size ();i++) { const auto & element = elements[i]; glVertexAttribPointer (i, element.count,element.type, element.normalized, layout.GetStride (), (const void *) offset); glEnableVertexAttribArray (i); offset += element.count * VertexBufferElement::GetSizeOfType (element.type); } } void VertexArray::Bind () const { glBindVertexArray (m_RendererID); } void VertexArray::Unbind () const { glBindVertexArray (0 ); }
VertexArrayLayout 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #pragma once #include <vector> #include "Renderer.h" struct VertexBufferElement { unsigned int type; unsigned int count; unsigned char normalized; static unsigned int GetSizeOfType (unsigned int type) { switch (type) { case GL_FLOAT: return 4 ; case GL_UNSIGNED_INT: return 4 ; case GL_UNSIGNED_BYTE: return 1 ; } ASSERT (false ); return 0 ; } }; class VertexBufferLayout { private : std::vector<VertexBufferElement> m_Elements; unsigned int m_Stride; public : VertexBufferLayout () : m_Stride (0 ) {}; template <typename T> void Push (unsigned int count) { static_assert (false ); } template <> void Push <float >(unsigned int count) { m_Elements.push_back ({ GL_FLOAT, count, GL_FALSE}); m_Stride += VertexBufferElement::GetSizeOfType (GL_FLOAT) * count; } template <> void Push <unsigned int >(unsigned int count) { m_Elements.push_back ({ GL_UNSIGNED_INT, count, GL_FALSE }); m_Stride += VertexBufferElement::GetSizeOfType (GL_UNSIGNED_INT) * count; } template <> void Push <unsigned char >(unsigned int count) { m_Elements.push_back ({ GL_UNSIGNED_BYTE, count, GL_TRUE }); m_Stride += VertexBufferElement::GetSizeOfType (GL_UNSIGNED_BYTE) * count; } inline const std::vector<VertexBufferElement> GetElements () const { return m_Elements; } inline unsigned int GetStride () const { return m_Stride; } };
作者在Bind和Unbind的地方其实处理得不是很舒服,受之前学长的影响,个人喜欢在添加完顶点属性后直接解绑,也就是AddBuffer函数之后,把va和vb解绑直接做掉。并且elementarray也就是IBO(EBO)Learnopengl中说过是绑定在VAO中的,可以不管。
15 Shader Abstraction 游戏和引擎中,通常有一种自定义着色语言,然后编译成每种api或平台适合的语言。并且是可控和可扩展的(着色器动态创建)。
我们在这里shader abstraction要完成的:
能够使用文件和字符串编译shader
绑定和取消绑定
设置uniform
Shader shader.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #pragma once #include <string> #include <unordered_map> struct ShaderProgramSource { std::string VertexSource; std::string FrgmentSource; }; class Shader { private : std::string m_FilePath; unsigned int m_RendererID; std::unordered_map<std::string, int > m_UniformLocationCache; public : Shader (const std::string& filepath); ~Shader (); void Bind () const ; void Unbind () const ; void SetUniform4f (const std::string& name, float v0, float v1, float v2, float v3) ; private : int GetUniformLocation (const std::string& name) ; unsigned int CompileShader (unsigned int type, const std::string& source) ; unsigned int CreateShader (const std::string& vertexShader, const std::string& fragmentShader) ; ShaderProgramSource ParseShader (const std::string& filepath) ; };
Shader.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 #include <iostream> #include <fstream> #include <sstream> #include "Renderer.h" #include "Shader.h" Shader::Shader (const std::string& filepath) : m_FilePath (filepath), m_RendererID (0 ) { ShaderProgramSource source = ParseShader (filepath); m_RendererID = CreateShader (source.VertexSource, source.FrgmentSource); } Shader::~Shader () { glDeleteProgram (m_RendererID); } unsigned int Shader::CompileShader (unsigned int type, const std::string& source) { unsigned int id = glCreateShader (type); const char * src = source.c_str (); glShaderSource (id, 1 , &src, nullptr ); glCompileShader (id); int result; glGetShaderiv (id, GL_COMPILE_STATUS, &result); if (!result) { int length; glGetShaderiv (id, GL_INFO_LOG_LENGTH, &length); char * message = (char *)alloca (length * sizeof (char )); glGetShaderInfoLog (id, length, &length, message); std::cout << "Fail to complie " << (type == GL_VERTEX_SHADER ? "vertex " : "fragment " ) << "shader!" << std::endl; std::cout << message << std::endl; glDeleteShader (id); return 0 ; } return id; } ShaderProgramSource Shader::ParseShader (const std::string& filepath) { std::ifstream stream (filepath) ; enum class ShaderType { NONE = -1 , VERTEX = 0 , FRAGMENT = 1 }; std::string line; std::stringstream ss[2 ]; ShaderType type = ShaderType::NONE; while (getline (stream, line)) { if (line.find ("#shader" ) != std::string::npos) { if (line.find ("vertex" ) != std::string::npos) { type = ShaderType::VERTEX; } else if (line.find ("fragment" ) != std::string::npos) { type = ShaderType::FRAGMENT; } } else { ss[(int )type] << line << "\n" ; } } return { ss[0 ].str (),ss[1 ].str () }; } unsigned int Shader::CreateShader (const std::string& vertexShader, const std::string& fragmentShader) { unsigned int program = glCreateProgram (); unsigned int vs = CompileShader (GL_VERTEX_SHADER, vertexShader); unsigned int fs = CompileShader (GL_FRAGMENT_SHADER, fragmentShader); glAttachShader (program, vs); glAttachShader (program, fs); glLinkProgram (program); glValidateProgram (program); glDeleteShader (vs); glDeleteShader (fs); return program; } void Shader::Bind () const { glUseProgram (m_RendererID); } void Shader::Unbind () const { glUseProgram (0 ); } void Shader::SetUniform4f (const std::string& name, float v0, float v1, float v2, float v3) { glUniform4f (GetUniformLocation (name), v0, v1, v2, v3); } int Shader::GetUniformLocation (const std::string& name) { if (m_UniformLocationCache.find (name) != m_UniformLocationCache.end ()) { return m_UniformLocationCache[name]; } int location = glGetUniformLocation (m_RendererID, name.c_str ()); if (location == -1 ) std::cout << "Warning: uniform '" << name << "' dosen't exist!" << std::endl; m_UniformLocationCache[name] = location; return location; }