前言:
上一篇已经开始渲染窗口了,现在将准备好绘制一个图形了吗
着色器
说到着色器就要引入一张在哪里都能看见的图
简单了解一下就知道,这是一个数据到一张图的过程,这里就不细讲了,我们可以操作哪些部分呢,顶点着色器,几何着色器,片段着色器,其他部分其实是不可操作的部分(当然这话放在以后不够绝对),这些都不重要,我们今天主要需要使用的着色器是顶点着色器和片段着色器(因为GPU不包含顶点着色器和片段着色器,所以必须配置),至于其他的就然计算机自己渲染吧
坐标
在介绍着色器之前让我们先对坐标有一个简单的了解:在给坐标时,我们的范围在0-1,比如我们设置的window窗口实际大小是600*800,那么在第300*400的位置该如何用坐标表示呢,(300,400)?当然是错的,我们刚刚说过给坐标时的范围是0-1,这个时候我们会将600=1,800=1,对于300*400而言,都是0.5,所以坐标(0.5f,0.5f)就是表示300*400的位置了(后面的f表示是浮点类型的数据,如果你不知道可以补习一点c语言的基础哦),那么120*320用坐标如何表示呢?
答案是(0.2f,0.4f)
记录数据的对象
VAO顶点数组对象,VBO顶点缓冲对象,EBO元素缓冲对象(这些不是变量,是固定名称,不可以更改哦!)
着色器有自己的语言GLSL,在编译器中如果直接写入则会报错,因为无法识别(后面会用文件的形式封装),现在我们使用字符串的形式让该语言内容被放入变量中,通过使用变量使用它
温馨提示:如果你是看文档的,这个代码不是凭空出现的,是依据于上一讲的内容在不同部分增加的,当然,在这里面会写完整
三角形分析代码内容
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(0.5f, 0.5f, 0.7f, 1.0f);\n"
"}\n\0";
1>在main函数之前需要导入GLSL的两个着色器,vertexShaderSource为顶点着色器,fragmentShaderSource为片段着色器,现在解析一下代码的含义
#version 330 core:OpenGL的版本3.3
layout (location = 0) in vec3 aPos:在location = 0的位置是一个输入,vec3是一个变量类型,含有三个值的变量,aPos是一个变量,类似于aPos内部封装了三个vec类型的变量x,y,z
void main(){ gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);}:这里的main并不是你实际的那个main,因为刚刚有说,这个是将着色器语言的内容用字符串写,如果用一个变量存它的地址不就相当于存了里面的内容嘛,这里面又使用了一个值,让他被赋值为vec4类型的值
在片段着色器中,out表示输出,输出的颜色
float vertices[] = {
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f,
0.5f, 0.5f,0.0f
};
2>输入顶点的坐标,刚刚我们解释了坐标,这里为什么有三个呢,因为其实这个可以表示3D,所以是x,y,z,我们将z都给0,不就在同一个平面吗,(只需要保证在同一个平面上就是2D,你可以随意设置相同的三个值)
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(600, 800, "Triangle", NULL,NULL);
if (window == NULL) {
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
return -1;
}
3>简单复习一下,这个是前面讲到的用glfw写一个窗口,glad告诉显卡,这里就不过多的赘述
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
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;
}
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_COMPILE_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINK_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
这段代码看似很复杂,其实从三个unsigned地方断开就很好理解了,第一个是vertexshader是顶点着色器,第二个fragmentshader是片段着色器,我们会发现两个的内容及其相似,那么我们就拎出一个顶点着色器进行解说:
1.用一个名词(变量vertexshader)来使用,至于它是怎么和真正的着色器关联的,首先glCreaterShader创建了一个顶点着色器,创建了就有吗,当然不是,我们说过这需要着色器语言,所以我们需要将自己的着色器连接到真实的着色器的位置(GLSL语言写的内容里)
2.glShaderSource里面有四个参数,主要关心第一个和第三个,第一个就是那个名词,第三个就是取刚刚GLSL中那个指针的地址,这两个值是什么我想你们应该也能找到了
3.既然已经关联好了,那就需要用glcompileshader编译,现在这个名词就是正在的顶点着色器了
4.至于success部分,其实只是为了防止不成功后,知道错误的位置,这样可以更便于修改代码
片段着色器也是同理,如果你问可不要可以改名什么的,如果你理解了,其实在变量的地方都是可以的,只需要你前后保持一致即可,但是不要顶点着色器用片段着色器的名字,你要知道,代码可读性也很重要,几年后你忘记了,你也会用错的,这种低级错误我并不希望你们犯
第三部分则是把一切做成一个流程,刚刚也说过一个坐标到三角形,是经过这些流程的,那我们就要自己写一个流程,我们说过正常流程里(GPU中)是不会包含片段着色器和顶点着色器的,所以我们需要将它们绑定进创建的流程
1.glAttachShader绑定两个着色器
2.glLinkProgram将他们链接
4.又到了判断自己所做的一切是不是成功的阶段了,如果你问可不可以不要,那我只想说你完全上道有点远了
当绑定进了流程里,就可以不需要他们了,就好像编译好的可执行文件没有源文件也可以不断执行,当然你可以选择不删,多浪费一点资源而已,毕竟你的程序也没多大
unsigned int VBO,VAO;
glGenBuffers(1, &VBO);
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
前面只是对流程进行了处理,但是却没有对数据处理,这一段则是对数据进行处理,对于三角形我们只需要VAO和VBO,因为我们希望数据是在缓存好了被一次性运输的
1.VBO是Buffers,VAO是VertexArrays,前两个函数前面的是创建VAO和VBO的个数,因为我们现在的变量数组中只放了位置变量,所以一个缓存和一个记录就够了
2.创建好了则需要进行绑定,两个含有Bind的则是绑定
3.glBufferData(),主要是表示存储哪些数据,第一个参数是存储到哪,第二个是存储数据的长度,第三个是存取的值,最后一个是使用方式(保持就好)
4.glVertexAttribPointer()这个函数的参数主要是索引,几个一组,通用类型(是它便于移植的数据类型),标准(不变就好)总长度和偏移量,因为我们现在的值只要位置,所以不需要考虑偏移位置长度设置(虽然用词不一定准确,但是可以这么理解),你可能会说数据不是已经写入了吗,可以开始了,为什么又加了一步,因为你把数据一口气给它了,他怎么知道我们是三个一个坐标呢,如果画矩形需要四个一个坐标呢,所以我们需要告知,把这些数据作为索引0,3个一组,类型是float,总长度是3*sizeof(float),没有偏移量(后面可能会有更深入的了解)
5.使能索引0
6.解绑之前绑定的VAO和VBO,给它们重新绑定0即解绑
while (!glfwWindowShouldClose(window)) {
processInput(window);
glClearColor(0.6f, 0.2f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
然后这里就是前面说过的渲染窗口了,那我们的三角形是不是应该也在里面绘制呢,当然,我们要在绘制前告诉它我们的流程,glUseProgram(shaderProgram);
2.glBindVertexArray(VAO);再绑定一下数据,我们可以把VAO当作领导视察,任何活动开启了VAO再干活等于干了且被领导看见,但是没有开启你干了,没领导看见等于没有干,所以我们要绘制一定要领导看见,那如果你问前面不解绑不就好了,我觉得可以,但是VBO后面不需要再用,如果不解绑就浪费资源,所以看你自己啦
3.最后告知要绘制三角形,从第0个开始那,拿3个绘制,glDrawArrays(GL_TRIANGLES, 0, 3);
glfwTerminate();
glDeleteProgram(shaderProgram);
glDeleteBuffers(1,&VBO);
glDeleteVertexArrays(1, &VAO);
return 0;
}
最后也是一系列删除和节省资源的工作,到这里你觉得会报错吗?
当然会啦,你用了glfwSetFramebufferSizeCallback调整窗口的视口和processInput(window);等待按键的函数却没有创建它们,所以你还得加上
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
tips:如果在main后加前面记得声明
在上一讲忘记要引用的头文件了,这里补一下
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
因为用了打印输出cout,所以引入了标准输入输出流
最后效果如下:
标签:VAO,OpenGL,VBO,0.5,---,window,VS2022,GL,着色器 From: https://blog.csdn.net/weixin_54193640/article/details/143947211