秉承面向对象思想,我将learnopengl自纹理往前的代码进行归纳整理,如图所示组织代码:
我个人的观点,一个类就应该只完成自己的事(面向对象单一职能原则) ,比如程序入口main.cpp就要尽量展线逻辑思路,而具体的内容要放到各个类来进行(设计shader,纹理,几何体等等),下边下边就来看看具体代码:
main.cpp
#include "OpenGLWindow.h"
#include "Geometry.h"
#include "Texture.h"
#include "Shader.h"
#include "Render.h"
int main()
{
//显示窗口,绘图设备类
OpenGLWindow device;
//几何类,定义几何信息
Geometry Geo;
//编译shader
Shader ourShader("../Shader/shader.vs", "../Shader/shader.fs");
//纹理
Texture texture("../Texture/container.jpg","../Texture/awesomeface.png", ourShader);
//渲染循环或游戏循环
while (device.WindowShouldClose())
{
//对设备的输入信息处理
device.processInput();
//渲染
unsigned int tex[] = { texture.texture1,texture.texture2};
Render::DoRender(tex,sizeof(tex)/sizeof(unsigned int), Geo.VAO, ourShader);
//交换缓冲&监听消息
device.SwapBuffers();
device.PollEvents();
}
device.Terminate();
return 0;
}
按照main.cpp从上到下的调用顺序,查阅代码:
OpenGLWindow.h
#ifndef OPENGL_WINDOW_H
#define OPENGL_WINDOW_H
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
class OpenGLWindow
{
public:
OpenGLWindow();
void processInput();
void SwapBuffers();
bool WindowShouldClose();
void PollEvents();
void Terminate();
public:
GLFWwindow* window;
//定义显示窗体尺寸
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
};
#endif
OpenGLWindow.cpp
#include "OpenGLWindow.h"
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
glViewport(0, 0, width, height);
}
OpenGLWindow::OpenGLWindow()
{
//初始化创建窗体的函数:glfw=gl Frame Window
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//Apple用户使用
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
//使用规定尺寸创建显示窗体
window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
}
//创建gl上下文,类似于DX的device
glfwMakeContextCurrent(window);
//设置窗体大小改变的回调函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//使用glad加载OpenGL所有的函数指针
//在这一步的加持下,我们使用gl开头的函数就好似DX中的cmdList->向命令列表加命令并提交给命令队列
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
}
}
Geometry.h
#ifndef GEOMETRY_H
#define GEOMETRY_H
#include <glad/glad.h>; // 包含glad来获取所有的必须OpenGL头文件
class Geometry
{
public:
Geometry();
~Geometry();
public:
unsigned int VBO, VAO, EBO;
};
#endif
Geometry.cpp
#include "Geometry.h"
//几何数据,这里是一个三个顶点z都为0的平面三角形
float vertices[] = {
// ---- 位置 ---- ---- 颜色 ---- - 纹理坐标 -
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上
};
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
Geometry::Geometry()
{
//申请VAO存储空间,传出首地址,通过绑定,将这个地址作为存顶点数据的地址(可能会有多组顶点数据,所以要显式指定绑定)
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
//申请VBO存储空间,传出首地址,并建立与顶点缓冲的映射,相当于把数据拷贝到GPU
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//通过以上映射,将数据放到顶点缓冲就相当于放到了VBO中(GPU能访问)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//定义顶点属性,相当于DX的顶点布局,是顶点数据的解析说明,并启用之:
//layout=寄存器槽,一个顶点属性的维度,数据类型,是否初始化,步长,偏移值
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(0 * sizeof(float)));
glEnableVertexAttribArray(0);
//定义第二个并启用之
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
//定义第三个并启用之
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
//对应上方glBindBuffer(GL_ARRAY_BUFFER, VBO);使用0表示结束,数据传输完成(有开就有关)
glBindBuffer(GL_ARRAY_BUFFER, 0);
//同上,对应glBindVertexArray(VAO);有开就有关
glBindVertexArray(0);
}
Geometry::~Geometry()
{
//资源释放
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
}
Texture.h
#ifndef TEXTURE_H
#define TEXTURE_H
#include <glad/glad.h>; // 包含glad来获取所有的必须OpenGL头文件
#include "stb_image.h"
#include"Shader.h"
#include <iostream>
class Texture
{
public:
Texture(char const *filename);
Texture(char const *filename1,char const *filename2,Shader shader);
public:
unsigned int texture;
unsigned int texture1, texture2;
int width, height, nrChannels;
};
#endif
Texture.cpp
#include "Texture.h"
Texture::Texture(char const * filename)
{
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//加载纹理图片
int width, height, nrChannels;
unsigned char *data = stbi_load(filename, &width, &height, &nrChannels, 0);
if (data)
{
//向绑定好的纹理存储空间传数据
//格式,Mipmap层级,纹理通道格式,宽,高,兼容性遗留0,原图通道格式,原图数据类型,数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
//生成Mipmap
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
//释放原图数据资源
stbi_image_free(data);
}
Texture::Texture(char const * filename1, char const * filename2,Shader shader)
{
//纹理1
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//加载纹理图片
int width, height, nrChannels;
stbi_set_flip_vertically_on_load(true);
unsigned char *data1 = stbi_load(filename1, &width, &height, &nrChannels, 0);
if (data1)
{
//向绑定好的纹理存储空间传数据
//格式,Mipmap层级,纹理通道格式,宽,高,兼容性遗留0,原图通道格式,原图数据类型,数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data1);
//生成Mipmap
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
//释放原图数据资源
stbi_image_free(data1);
//纹理2
glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture2);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
unsigned char *data2 = stbi_load(filename2, &width, &height, &nrChannels, 0);
if (data2)
{
//向绑定好的纹理存储空间传数据
//格式,Mipmap层级,纹理通道格式,宽,高,兼容性遗留0,原图通道格式,原图数据类型,数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data2);
//生成Mipmap
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
//释放原图数据资源
stbi_image_free(data2);
//设置纹理采样器uniform,用一个整数表示其位置
shader.use();
shader.setInt("texture1", 0);
shader.setInt("texture2", 1);
}
Shader.h 与 Shader.cpp与上一篇着色器一致。
Render.h
#ifndef RENDER_H
#define RENDER_H
#include <glad/glad.h>
#include "Shader.h"
class Render
{
public:
static void DoRender(unsigned int texture[],int size, unsigned int VAO, Shader shader);
};
#endif
Render.cpp
#include "Render.h"
void Render::DoRender(unsigned int texture[],int size, unsigned int VAO, Shader shader)
{
//开始渲染,先使用指定颜色清空RTV
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//绑定纹理数据
//默认激活0号纹理,如果多个纹理要显式激活
for (size_t i = 0; i < size; i++)
{
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, texture[i]);
}
//使用Shader类
shader.use();
shader.setFloat4("ourColor", 1.0f, 1.0f, 1.0f, 1.0f);
//VAO是包围在VBO外层的,此处绑定VAO,相当于驱动程序使用存在绑定好的内存VBO的数据
//上方关闭了(使用0),此处重新打开(读),把数据取出来
//这也给我们提供了多个VBO的切换启示,就是使用不同的VAO包围不同的VBO,然后通过开关指定启动
//很像DX中向输入装配阶段传数据的IASetXXX函数
glBindVertexArray(VAO);
//DrawCall
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
还有一个头文件及其cpp叫做stb_image,这是参考learnopengl的说明直接从网上弄下来的,可以直接按照文档的指导自己复制粘贴即可。
All is OK!之后我们的代码编写就将更加的规整,因为需要什么对象,需要做啥,就去修改对应的文件,不必到处去找,到处去加,而main.cpp维护着我们清晰的渲染逻辑:状态机模型。