前置技术
c++动态链接和静态链接:
【底层】动态链接库(dll)是如何工作的?_哔哩哔哩_bilibili
预编译,编译,汇编,链接
预编译头文件:
为什么要使用预编译头_vs2019 为什么要预编译-CSDN博客
注意平台选择!
项目中,在Sandbox中使用YOTOEngine的.h文件就需要用预编译头文件来确定路径。
输出目录:
(如果要添加dll文件时,一定要指定输出目录!!!!!!!)
vs下的输出目录/输出文件/工作目录-总结_英文版的vs文件输出路径exe文件在哪-CSDN博客
配置静态库:
使用opengl静态库:
随便找个地方把lib和include给复制到一个文件夹下:
c/c++配置 附加包含目录:
也就是把.h文件包含进项目
链接器配置附加库目录:
也就是把lib添加进来
添加两个依赖项名称:
配置动态库:
使用opengl动态库:
Visual Studio 2019-编写C++动态链接库_哔哩哔哩_bilibili
把没用的lib都删掉,剩下这个:
配置输出目录:(一般在运行时找不到dll都是因为dll文件没有放到输出目录下面 )
把glfw3.dll放到输出目录下:
修改链接器的依赖项:
链接器库目录:指向lib所在文件夹:
附加包含目录:
预处理:
cmake:
软件构建: CMake 快速入门_哔哩哔哩_bilibili
CMake 保姆级教程【C/C++】_哔哩哔哩_bilibili
preake5:
premake5实例教程_premake5教程-CSDN博客
opengl:
OpenGL - LearnOpenGL CN (learnopengl-cn.github.io)
制作一个能渲染3d物体的程序:
OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描述OpenGL此刻应当如何运行。OpenGL的状态通常被称为OpenGL上下文(Context)。我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。
// 创建对象
unsigned int objectId = 0;
glGenObject(1, &objectId);
// 绑定对象至上下文
glBindObject(GL_WINDOW_TARGET, objectId);
// 设置当前绑定到 GL_WINDOW_TARGET 的对象的一些选项
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// 将上下文对象设回默认
glBindObject(GL_WINDOW_TARGET, 0);
这一小段代码展现了你以后使用OpenGL时常见的工作流。我们首先创建一个对象,然后用一个id保存它的引用(实际数据被储存在后台)。然后我们将对象绑定至上下文的目标位置(例子中窗口对象目标的位置被定义成GL_WINDOW_TARGET)。接下来我们设置窗口的选项。最后我们将目标位置的对象id设回0,解绑这个对象。设置的选项将被保存在objectId所引用的对象中,一旦我们重新绑定这个对象到GL_WINDOW_TARGET位置,这些选项就会重新生效。
配置glad:
因为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。所以任务就落在了开发者身上,开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用。
GLAD现在应该提供给你了一个zip压缩文件,包含两个头文件目录,和一个glad.c文件。将两个头文件目录(glad和KHR)复制到你的Include文件夹中(或者增加一个额外的项目指向这些目录),并添加glad.c文件到你的工程中。
把#include<glad>放到glfw之前记好了~
无法解析的外部符号的话,就是.c没加进来
最暴力的方法就是直接把.c拖到项目中。
先了解一下渲染管线:
我就不详细介绍了,既然来了肯定有所了解
使用opengl随便绘制一个图形:
#include <GLAD/include/glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
//视口回调
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
//输入回调
void processInput(GLFWwindow* window);
// 宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
//顶点着色器
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(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
int main()
{
//初始化
glfwInit();
//配置
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
//创建窗口
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;
}
//通知GLFW将我们窗口的上下文设置为当前线程的主上下文
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;
}
// 创建和编译shader(在创建时指定shader类型)
// vertex shader
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
//绑定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;
}
// fragment shader
unsigned int 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;
}
// 链接shader为Program
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
//检查是否链接错误
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
//链接后删除shader
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// 设置顶点数据(和缓冲区)并配置顶点属性
float vertices[] = {
0.5f, 0.5f, 0.0f, // top right
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f // top left
};
unsigned int indices[] = { // note that we start from 0!
0, 1, 3, // first Triangle
1, 2, 3 // second Triangle
};
//顶点缓冲,顶点数组,索引缓冲(也叫元素缓冲)
unsigned int VBO, VAO, IBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &IBO);
//首先绑定顶点数组对象
glBindVertexArray(VAO);
//然后绑定和设置顶点缓冲区
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//把之前定义的顶点数据复制到缓冲的内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//绑定索引缓冲
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//配置顶点的属性,顶点在数组中的布局
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//注意,这是允许的,调用glVertexAttribPointer将VBO注册为顶点属性的绑定顶点缓冲对象,因此之后我们可以安全地解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0);
//记住:当VAO处于活动状态时,不要解除绑定EBO,因为绑定的元素缓冲区对象存储在VAO中;保持EBO的上限。
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
//您可以在之后解除对VAO的绑定,这样其他VAO调用就不会意外地修改该VAO,但这种情况很少发生。
//修改其他vao需要调用glBindVertexArray,所以当不直接需要时,我们通常不会取消绑定vao(也不会取消绑定vbo)。
glBindVertexArray(0);
//test
float vertices2[] = {
0.8f, 0.8f, 0.0f, // top right
0.8f, -0.9f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f // top left
};
unsigned int indices2[] = { // note that we start from 0!
0, 1, 3, // first Triangle
1, 2, 3 // second Triangle
};
unsigned int VBO2, VAO2, IBO2;
glGenVertexArrays(1, &VAO2);
glGenBuffers(1, &VBO2);
glGenBuffers(1, &IBO2);
glBindVertexArray(VAO2);
glBindBuffer(GL_ARRAY_BUFFER, VBO2);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO2);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// 取消此调用的注释以绘制线框多边形。
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// render loop
while (!glfwWindowShouldClose(window))
{
// 输入
processInput(window);
// render
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 先调用shader
glUseProgram(shaderProgram);
glBindVertexArray(VAO); //因为我们只有一个VAO,所以没有必要每次都绑定它,但是我们这样做是为了让事情更有条理
//glDrawArrays和glDrawElements区别:
//首先,这两个函数的作用都是从一个数据数组中提取数据,然后渲染图元。
//区别在于:glDrawArrays 是直接绘制真实的顶点数据,而 glDrawElements 是按照指定的索引顺序取出真实数据再绘制。
//于是对于顶点存在共享的场景时,使用 glDrawElements 对于重复的顶点数据只需要传输一份数据,
//绘制时通过索引反复的获取其值,最终降低内存占用和内存带宽需求。
//glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawElements(GL_TRIANGLE_STRIP,2*3, GL_UNSIGNED_INT,0);
//glBindVertexArray(0);
glUseProgram(shaderProgram);
glBindVertexArray(VAO2);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
// glBindVertexArray(0); // no need to unbind it every time
//glfw:交换缓冲区和轮询IO事件(按键按/释放,鼠标移动等)
glfwSwapBuffers(window);
glfwPollEvents();
}
// optional: de-allocate all resources once they've outlived their purpose:
glDeleteVertexArrays(1, &VAO2);
glDeleteBuffers(1, &VBO2);
glDeleteBuffers(1, &IBO2);
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &IBO);
glDeleteProgram(shaderProgram);
// glfw:终止,清除之前分配的所有glfw资源。
glfwTerminate();
return 0;
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
大概结果就是这样:
计算机图形学OpenGL:模型加载Assimp_哔哩哔哩_bilibili
- 和材质和网格(Mesh)一样,所有的场景/模型数据都包含在Scene对象中。Scene对象也包含了场景根节点的引用。
- 场景的Root node(根节点)可能包含子节点(和其它的节点一样),它会有一系列指向场景对象中mMeshes数组中储存的网格数据的索引。Scene下的mMeshes数组储存了真正的Mesh对象,节点中的mMeshes数组保存的只是场景中网格数组的索引。
- 一个Mesh对象本身包含了渲染所需要的所有相关数据,像是顶点位置、法向量、纹理坐标、面(Face)和物体的材质。
- 一个网格包含了多个面。Face代表的是物体的渲染图元(Primitive)(三角形、方形、点)。一个面包含了组成图元的顶点的索引。由于顶点和索引是分开的,使用一个索引缓冲来渲染是非常简单的(见你好,三角形)。
- 最后,一个网格也包含了一个Material对象,它包含了一些函数能让我们获取物体的材质属性,比如说颜色和纹理贴图(比如漫反射和镜面光贴图)。
怎么配置我就不叭叭了,按照之间讲的配置就行
构建一个3D模型加载的封装:
//暂无
c++语法特色:
根据源码慢慢添加:
c++智能指针:
重载运算符:
架构分析:
先看文件结构:
我先把每个文件所包含的功能拆分:
接下来我用专业一点儿的UML类图来表示:
(先学一下类图)
[UML] 类图介绍 —— 程序员(灵魂画手)必备画图技能之一_类图怎么画-CSDN博客
该博客会持续更新,目前就是防止丢稿。
未来更新内容为:示例和原理讲解。