在国内的服务器上建设国外网站,挖金矿游戏网站建设,网店美工与视觉设计,注册新公司需要准备的材料本人刚学OpenGL不久且自学#xff0c;文中定有代码、术语等错误#xff0c;欢迎指正 我写的项目地址#xff1a;https://github.com/liujianjie/LearnOpenGLProject
LearnOpenGL中文官网#xff1a;https://learnopengl-cn.github.io/ 文章目录坐标系统概述局部空间世界空… 本人刚学OpenGL不久且自学文中定有代码、术语等错误欢迎指正 我写的项目地址https://github.com/liujianjie/LearnOpenGLProject
LearnOpenGL中文官网https://learnopengl-cn.github.io/ 文章目录坐标系统概述局部空间世界空间观察空间裁剪空间正交投影透视投影把他们组合到一起进入3D例子1例子2更加3D例子3箱子派对坐标系统 标准化设备坐标介绍 每个顶点的xyz坐标都在**-1.0到1.0**之间 OpenGL希望在每次顶点着色器运行后我们可见的所有顶点都为标准化设备坐标 我们通常会自己设定一个坐标的范围之后再在顶点着色器中将这些坐标变换为标准化设备坐标 将标准化设备坐标传入光栅器(Rasterizer)将它们变换为屏幕上的二维坐标或像素。 多个坐标系统 为什么存在 将坐标变换为标准化设备坐标接着再转化为屏幕坐标的过程通常是分步进行的所以物体的顶点在最终转化为屏幕坐标之前还会被变换到多个坐标系统 多个坐标系统的优点优点 在这些特定的坐标系统中一些操作或运算更加方便和容易
概述 局部坐标是对象相对于局部原点的坐标也是物体起始的坐标局部坐标通过Model矩阵变换为世界空间坐标这些坐标相对于世界的全局原点它们会和其它物体一起相对于世界的原点进行摆放。观察空间坐标通过view矩阵将世界空间转换到观察空间使得每个坐标都是从摄像机或者说观察者的角度进行观察的。裁剪坐标通过投影矩阵从观察空间到裁剪空间裁剪坐标会被处理透视除法至-1.0到1.0的范围内并判断哪些顶点将会出现在屏幕上。视口变换将-1.0到1.0范围的裁剪坐标标准化设备坐标变换到由glViewport函数所定义的坐标范围内。
局部空间 简介 局部空间是指物体所在的坐标空间
世界空间 简介 是指顶点相对于游戏世界的坐标 如何从局部到世界坐标 该变换是由模型矩阵(Model Matrix)实现的。
观察空间 简介 摄像机的视角所观察到的空间是由观察矩阵(Model Matrix)从世界到观察坐标。 view(观察)矩阵 由一系列的位移和旋转的组合来完成平移/旋转场景从而使得特定的对象被变换到摄像机的前方。 这些组合在一起的变换通常存储在一个观察矩阵(View Matrix)里
裁剪空间 简介 OpenGL自动执行范围内的保留范围外的裁掉当裁剪后剩下的可见的片段就是裁剪空间 如何进入裁剪空间 由投影矩阵将观察空间变换到裁剪空间 投影矩阵分为 正交投影透视投影 透视除法 什么时候执行 一旦所有顶点被变换到裁剪空间OpenGL自动会透视除法在每一个顶点着色器运行的最后被自动执行 它如何做 将位置向量的xyz分量分别除以向量的齐次w分量 结果 透视除法执行后才将裁剪坐标系变换到标准化设备坐标系 小结工作流程自己捋的很大概率有误 设置-1000到1000范围投影矩阵会将在这个范围内的坐标从观察空间变换到裁剪空间然后OpenGL自动执行透视除法转换为标准化设备坐标的范围(-1.0, 1.0)。 所有的坐标先转换为(-1, 1)之间然后不在-1.0到1.0的范围会被裁剪掉OpenGL自动执行裁剪。 在标准化设备坐标系之后 执行第一张图所说的视口变换 最终的坐标(标准化设备坐标系)将会被映射到屏幕空间中使用glViewport中的设定并被变换成片段。 即视口变换将标准化设备坐标系到屏幕坐标
正交投影 图示 glm创建 glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);前两个参数指定了平截头体的左右坐标 第三和第四参数指定了平截头体的底部和顶部 通过这四个参数我们定义了近平面和远平面的大小 第五和第六个参数则定义了近平面和远平面的距离 缺点 这个投影没有将透视(Perspective)考虑进去所有的物体仿佛都保持原有大小。 因为这个投影不改变每个向量的w分量都保持1透视除法是用x,y,z除以w分量w1自然不会变。
透视投影 图示 简介 近大远小 透视投影矩阵如何工作 这个投影矩阵将给定的平截头体范围映射到裁剪空间 除此之外还修改了每个顶点坐标的w值 从而使得离观察者越远的顶点坐标w分量越大。 透视除法所做 将位置向量的xyz分量分别除以向量的齐次w分量因为远的顶点坐标w分量大所以距离观察者越远顶点坐标就会越小 什么时候执行透视除法 上面有讲由projection投影矩阵变换到裁剪空间后OpenGL要求所有可见的坐标都落在-1.0到1.0范围内作为顶点着色器最后的输出。因此一旦坐标在裁剪空间内之后就会在裁剪空间坐标上执行透视除法透视除法执行后便是标准化设备坐标-1.0,1.0范围。 glm创建 glm::mat4 proj glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);第一个参数定义了fov的值它表示的是视野 第二个参数设置了宽高比由视口的宽除以高所得 第三和第四个参数设置了平截头体的近和远平面 说明第三个参数 当 near 值设置太大时如10.0fOpenGL会将靠近摄像机的坐标**在0.0f和10.0f之间**都裁剪掉 透视投影与正交投影对比
把他们组合到一起 注意顺序 写代码顺序 v projection * view * model * local 读顺序 需从右往左阅读矩阵 再重复了一次裁剪空间这点的内容 OpenGL将会自动进行透视除法和裁剪操作 重要的顺序 顶点着色器的输出要求所有的顶点都在裁剪空间内这正是我们刚才使用变换矩阵所做的。OpenGL然后对裁剪坐标自动执行透视除法从而将它们变换到标准化设备坐标视口变换OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标
进入3D
观察矩阵 将摄像机向后移动和将整个场景向前移动是一样的。这正是观察矩阵所做的我们以相反于摄像机移动的方向移动整个场景因为我们想要往后移动所以场景需要沿着z轴负方向平移来实现它会给我们一种我们在往后移动的感觉。opengl右手坐标系
例子1 代码 glsl 顶点着色器 #version 330 core
layout (location 0) in vec3 aPos;
layout (location 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{// 注意乘法要从右向左读gl_Position projection * view * model * vec4(aPos, 1.0);TexCoord aTexCoord;
}在顶点着色器上进行坐标系转换从局部空间到裁剪空间 代码顺序是 project*view*model*local 解读顺序是 相反的需从右往左读将顶点local经过model矩阵到世界空间再经过view矩阵到观察空间再经过project到裁剪空间。 再次重复 到裁剪空间后经过透视除法到标准化设备坐标系再经过视口变换到屏幕坐标这两个是opengl自动执行的 片段着色器 #version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{FragColor mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}cpp Shader ourShader(assest/shader/1入门/1.8.transform.vs, assest/shader/1入门/1.8.transform.fs);// 顶点数据
float vertices[] {// positions // texture coords0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
unsigned int indices[] {0, 1, 3, // first triangle1, 2, 3 // second triangle
};
// 顶点数组
unsigned int VBO, VAO, EBO;
glGenVertexArrays(1, VAO);
glGenBuffers(1, VBO);
glGenBuffers(1, EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);// 加载纹理
unsigned int texture1, texture2;
// texture 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); // tell stb_image.h to flip loaded textures on the y-axis.
unsigned char* data stbi_load(FileSystem::getPath(assest/textures/container.jpg).c_str(), width, height, nrChannels, 0);
if (data)
{glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);
}
else
{std::cout Failed to load texture std::endl;
}
stbi_image_free(data);
// texture 2 和texture1一样
.......
// 告诉OpenGL两个采样器对应哪个纹理单元
ourShader.use();
ourShader.setInt(texture1, 0);
ourShader.setInt(texture2, 1);// render loop
while (!glfwWindowShouldClose(window))
{// inputprocessInput(window);// renderglClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // also clear the depth buffer now!// bind textures on corresponding texture unitsglActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, texture1);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, texture2);// 此节重点在这///// 构造矩阵变换glm::mat4 model glm::mat4(1.0f); // make sure to initialize matrix to identity matrix firstglm::mat4 view glm::mat4(1.0f);glm::mat4 projection glm::mat4(1.0f);model glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));// 注意我们将矩阵向我们要进行移动场景的反方向移动。view glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));// 透视投影矩阵projection glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);// 设置uniformourShader.use();unsigned int modelLoc glGetUniformLocation(ourShader.ID, model);unsigned int viewLoc glGetUniformLocation(ourShader.ID, view);// 3种不同的方式发送数据给ShaderglUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));glUniformMatrix4fv(viewLoc, 1, GL_FALSE, view[0][0]);ourShader.setMat4(projection, projection);// 渲染boxglBindVertexArray(VAO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);重点代码 // 将物体绕着x轴旋转-55.0度
model glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
// 注意我们将矩阵向我们要进行移动场景的反方向移动。
view glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
// 透视投影矩阵
projection glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);效果 经过model矩阵实现的旋转透视投影可看到的效果 稍微向后倾斜至地板方向。离我们有一些距离。有透视效果顶点越远变得越小。
例子2更加3D 代码改变 与上一个例子相比的改变 改变了顶点数据顶点数据是一个箱子的36个顶点去除了索引缓冲与索引绘制model矩阵不再是绕着x旋转-55度而是绕着y轴旋转度数time运行时间作为度数 代码 float vertices[] {-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 1.0f, 0.0f,0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f, 0.5f, -0.5f, 1.0f, 1.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.5f, 0.5f, 0.5f, 1.0f, 1.0f,-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f,0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f, -0.5f, -0.5f, 0.0f, 1.0f,0.5f, -0.5f, -0.5f, 0.0f, 1.0f,0.5f, -0.5f, 0.5f, 0.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,0.5f, -0.5f, -0.5f, 1.0f, 1.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f,-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
model glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.0f, 1.0f, 0.0f));效果 效果奇怪奇怪的地方在于没有进行深度测试导致后渲染的片段覆盖前渲染的片段从而箱子的背面覆盖前面。 如何修复 由于OpenGL默认不进行深度测试需要设置开启深度测试开启后opengl自动完成 只需在渲染代码前中加一句 glEnable(GL_DEPTH_TEST);深度测试工作原理 OpenGL存储它的所有深度信息于一个Z缓冲又称深度缓冲GLFW会自动为你生成这样一个缓冲就像它也有一个颜色缓冲来存储输出图像的颜色深度值存储在每个片段里面作为片段的z值当片段想要输出它的颜色时OpenGL会将它的深度值和z缓冲进行比较如果当前的片段在其它片段之后它将会被丢弃否则将会覆盖 修复效果
例子3箱子派对 实现思路 定义10个箱子的出生点 循环10次 每次model为单位矩阵再将model矩阵进行平移到出生点再可以加上旋转效果代码顺序 代码 float vertices[] {-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,......
};
// 出生点-初始位置
glm::vec3 cubePositions[] {glm::vec3(0.0f, 0.0f, 0.0f),glm::vec3(2.0f, 5.0f, -15.0f),glm::vec3(-1.5f, -2.2f, -2.5f),glm::vec3(-3.8f, -2.0f, -12.3f),glm::vec3(2.4f, -0.4f, -3.5f),glm::vec3(-1.7f, 3.0f, -7.5f),glm::vec3(1.3f, -2.0f, -2.5f),glm::vec3(1.5f, 2.0f, -2.5f),glm::vec3(1.5f, 0.2f, -1.5f),glm::vec3(-1.3f, 1.0f, -1.5f)
};
// render box
glBindVertexArray(VAO);
for (unsigned int i 0; i 10; i)
{glm::mat4 model glm::mat4(1.0f);model glm::translate(model, cubePositions[i]); // 先平移float angle 20.0f * i * (float)glfwGetTime();model glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));// 再旋转ourShader.setMat4(model, model);glDrawArrays(GL_TRIANGLES, 0, 36);
}注意model矩阵写代码的顺序是先平移再旋转但是解读的话是先旋转再平移请看本专栏中变换-矩阵的组合。 效果