基于上一个 你好,三角形 的代码绘制修改点击蓝色字体查看
如果我们打算从一个着色器向另一个着色器发送数据,我们必须在发送方着色器中声明一个输出,在接收方着色器中声明一个类似的输入。当类型和名字都一样的时候,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)
顶点着色器
//顶点着色器的源代码硬编码
//用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器
const char* vertexShaderSource = "#version 330 core\n"
// 位置变量的属性位置值为0
//通过layout(location = 0)设定了输入变量的位置值(Location)
"layout (location = 0) in vec3 aPos;\n"
// 为片段着色器指定一个颜色输出
"out vec4 vertexColor;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0); \n"
// 注意我们如何把一个vec3作为vec4的构造器的参数
//或者" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
//由于我们的输入是一个3分量的向量,我们必须把它转换为4分量的
//同时把w分量设置为1.0f
" vertexColor = vec4(0.5, 0.0, 0.0, 1.0);\n"
// 把输出变量设置为暗红色
"}\0";
片段着色器
//片段着色器的源代码硬编码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
// 从顶点着色器传来的输入变量(名称相同、类型相同)
"in vec4 vertexColor;\n"
"void main()\n"
"{\n"
" FragColor = vertexColor;\n"
//" in vec4 vertexColor;\n"
"}\n\0";
采用uniform让三角形颜色随着时间发生改变
顶点着色器
//顶点着色器的源代码硬编码
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0); \n"
"}\0";
片段着色器
//片段着色器的源代码硬编码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"uniform vec4 ourColor;\n"
"void main()\n"
"{\n"
" FragColor = ourColor;\n"
"}\n\0";
在渲染部分的while (!glfwWindowShouldClose(window))代码部分加入更新uniform语句的代码,在这之前一定要记得激活着色器glUseProgram(shaderProgram);
//更新uniform颜色
//获取运行的秒数
float timeValue = glfwGetTime();
//使用sin函数让颜色在0.0到1.0之间改变
float greenValue = sin(timeValue) / 2.0f + 0.5f;
//查询uniform ourColor的位置值
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
实验结果:三角形逐渐由绿变黑再变回绿色
把颜色数据加入顶点数据中
float vertices[] = {
// 位置 // 颜色
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 顶部
};
这时就需要增加顶点着色器颜色变量的属性
const char* vertexShaderSource = "#version 330 core\n"
// 位置变量的属性位置值为 0
"layout (location = 0) in vec3 aPos;\n"
// 颜色变量的属性位置值为 1
"layout (location = 1) in vec3 aColor;\n"
"out vec3 ourColor;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0);\n"
// 将ourColor设置为我们从顶点数据那里得到的输入颜色
" ourColor = aColor;\n"
"}\0";
由于现在不用uniform来传递了,用ourcolor输出变量,做一下修改
片段着色器
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec3 ourColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(ourColor, 1.0f);\n"
"}\n\0";
我们增加了顶点属性,更新了VBO的内存(如下图),我就需要重新配置顶点属性指针
根据上图改写glVertexAttribPointer()函数
//位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
偏移量就是3 * sizeof(float),用字节来计算就是12字节
此外,还有新着色器类源代码可以对着色器做进一步优化,这里就不再赘述了。
彩色三角形完整代码
#include
#include
#include
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
//顶点着色器的源代码硬编码
const char* vertexShaderSource = "#version 330 core\n"
// 位置变量的属性位置值为 0
"layout (location = 0) in vec3 aPos;\n"
// 颜色变量的属性位置值为 1
"layout (location = 1) in vec3 aColor;\n"
"out vec3 ourColor;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0);\n"
// 将ourColor设置为我们从顶点数据那里得到的输入颜色
" ourColor = aColor;\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec3 ourColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(ourColor, 1.0f);\n"
"}\n\0";
/*在顶点着色器中声明了一个vertexColor变量作为vec4输出,
并在片段着色器中声明了一个类似的vertexColor
由于它们名字相同且类型相同
片段着色器中的vertexColor就和顶点着色器中的vertexColor链接了
*/
int main()
{
//glfw:初始化和配置
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
//glfw窗口创建
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
/*第一步 把顶点数据存储在显卡内存中*/
//将三角形的三个顶点的3D坐标以标准化设备坐标的形式定义为一个float数组
float vertices[] = {
// 位置 // 颜色
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
0.0f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 顶部
};
/*第二步 着色器程序:创建顶点着色器(Vertex Shader)和片段着色器(fragment shader)来处理数据*/
//1.vertex shader
unsigned int vertexShader; //创建着色器对象
vertexShader = glCreateShader(GL_VERTEX_SHADER); //把需要创建的着色器类型以参数形式提供给glCreateShader
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //把着色器源码附加到着色器对象上
glCompileShader(vertexShader); //编译顶点着色器对象
//检查调用glCompileShader后编译是否成功
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 2.fragment shader
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
//check for shader compile errors
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
//3.link shader
unsigned int shaderProgram;
shaderProgram = glCreateProgram(); //创建一个程序,并返回新创建程序对象的ID引用
glAttachShader(shaderProgram, vertexShader); //把之前编译的着色器附加到程序对象上
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram); //链接它们
//check for linking error
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
//glUseProgram(shaderProgram);//调用该函数激活程序对象
glDeleteShader(vertexShader);//删除着色器对象
glDeleteShader(fragmentShader);
/*第三步 告诉OpenGL怎么解释内存中的顶点数据以及处理顶点数据连接到顶点着色器属性上
创建VBO和VAO*/
unsigned int VBO;
unsigned int VAO; //创建顶点数组对象(Vertex Array Object, VAO)
glGenBuffers(1, &VBO); //创建顶点缓冲对象
glGenVertexArrays(1, &VAO); //生成顶点数组对象名称
glBindVertexArray(VAO); //绑定VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //把用户定义的数据复制到当前绑定缓冲的函数
//位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glUseProgram(shaderProgram);
// render loop 渲染循环
// 明确地关闭它之前不断绘制图像并能够接受用户输入
// 函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后游戏循环便结束了,之后为我们就可以关闭应用程序了。
// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
// input
// -----
processInput(window);
// render
//清除颜色缓冲
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//记得激活着色器
//glUseProgram(shaderProgram);
// draw our first triangle
glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
glDrawArrays(GL_TRIANGLES, 0, 3);
// glBindVertexArray(0); // no need to unbind it every time
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// WAP 缓冲区和循环 IO 事件(按下/释放键、移动鼠标等)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
// optional: de-allocate all resources once they've outlived their purpose:
// 可选:在资源超出用途后取消分配所有资源:
// ------------------------------------------------------------------------
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
// glfw: terminate, clearing all previously allocated GLFW resources.
// GLFW:终止,清除所有以前分配的 GLFW 资源。
// ------------------------------------------------------------------
glfwTerminate();
return 0;
}
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// 处理所有输入:查询GLFW是否按下/释放此帧的相关键并做出相应的反应
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// glfw:每当窗口大小发生变化(通过操作系统或用户调整大小)时,都会执行此回调函数
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// make sure the viewport matches the new window dimensions; note that width and
// height will be significantly larger than specified on retina displays.
//确保视口与新的窗口尺寸匹配;请注意,宽度和高度将明显大于 Retina 显示屏上指定的宽度和高度。
glViewport(0, 0, width, height);
//前两个参数控制窗口左下角的位置,第三个和第四个参数控制渲染窗口的宽度和高度(像素)
}