【写在前面】
本章主要内容:
1、OpenGL Texture(OpenGL纹理)
2、Vertex Array Object(顶点数组对象)
【正文开始】
相比上一章,本章并没有多少新的内容,所以我们直接进入关键的 MyRender 类中:
#ifndef MYRENDER_H
#define MYRENDER_H
#include "OpenGLRender.h"
#include <glm/matrix.hpp>
#include <glm/gtc/quaternion.hpp>
class MyRender : public OpenGLRender
{
public:
MyRender();
~MyRender();
void render(const glm::quat &rotation);
void resizeGL(int w, int h);
void initializeGL();
void initializeShader();
void initializeTexture();
void initializeCube();
private:
GLuint m_vao;
GLuint m_vbo;
GLuint m_ebo;
GLuint m_texture1, m_texture2;
GLuint m_program;
glm::mat4x4 m_projection;
};
#endif
这里将要讲的是顶点数组对象( Vertex Array Object, VAO ):
它可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个 VAO 中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的 VAO 就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的 VAO 就行了。刚刚设置的所有状态都将存储在 VAO 中。
【当我们使用OpenGL的核心模式(Core)时,必须使用顶点数组对象,否则OpenGL不会绘制任何东西】
一个 VAO 会存储以下内容:
具体怎么使用我们来看代码:
void MyRender::initializeCube()
{
VertexData vertices[] =
{
{ glm::vec3( -0.75f, -0.75f, 0.75f), glm::vec3(0.8f, 0.8f, 0.0f), glm::vec2( 0.0f, 0.0f) },
{ glm::vec3( 0.75f, -0.75f, 0.75f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2( 1.0f, 0.0f) },
{ glm::vec3(-0.375f, 0.75f, 0.375f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2(0.33f, 1.0f) },
{ glm::vec3( 0.375f, 0.75f, 0.375f), glm::vec3(0.8f, 0.0f, 0.0f), glm::vec2(0.66f, 1.0f) },
{ glm::vec3( 0.75f, -0.75f, 0.75f), glm::vec3(0.8f, 0.8f, 0.0f), glm::vec2( 0.0f, 0.0f) },
{ glm::vec3( 0.75f, -0.75f, -0.75f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2( 1.0f, 0.0f) },
{ glm::vec3(0.375f, 0.75f, 0.375f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2(0.33f, 1.0f) },
{ glm::vec3(0.375f, 0.75f, -0.375f), glm::vec3(0.8f, 0.0f, 0.0f), glm::vec2(0.66f, 1.0f) },
{ glm::vec3( 0.75f, -0.75f, -0.75f), glm::vec3(0.8f, 0.8f, 0.0f), glm::vec2( 0.0f, 0.0f) },
{ glm::vec3( -0.75f, -0.75f, -0.75f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2( 1.0f, 0.0f) },
{ glm::vec3( 0.375f, 0.75f, -0.375f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2(0.33f, 1.0f) },
{ glm::vec3(-0.375f, 0.75f, -0.375f), glm::vec3(0.8f, 0.0f, 0.0f), glm::vec2(0.66f, 1.0f) },
{ glm::vec3( -0.75f, -0.75f, -0.75f), glm::vec3(0.8f, 0.8f, 0.0f), glm::vec2( 0.0f, 0.0f) },
{ glm::vec3( -0.75f, -0.75f, 0.75f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2( 1.0f, 0.0f) },
{ glm::vec3(-0.375f, 0.75f, -0.375f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2(0.33f, 1.0f) },
{ glm::vec3(-0.375f, 0.75f, 0.375f), glm::vec3(0.8f, 0.0f, 0.0f), glm::vec2(0.66f, 1.0f) },
{ glm::vec3(-0.375f, 0.75f, 0.375f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2(0.0f, 0.0f) },
{ glm::vec3( 0.375f, 0.75f, 0.375f), glm::vec3(0.8f, 0.0f, 0.0f), glm::vec2(1.0f, 0.0f) },
{ glm::vec3(-0.375f, 0.75f, -0.375f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2(0.0f, 1.0f) },
{ glm::vec3( 0.375f, 0.75f, -0.375f), glm::vec3(0.8f, 0.0f, 0.0f), glm::vec2(1.0f, 1.0f) },
{ glm::vec3( 0.75f, -0.75f, 0.75f), glm::vec3(0.8f, 0.8f, 0.0f), glm::vec2(0.0f, 0.0f) },
{ glm::vec3(-0.75f, -0.75f, 0.75f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2(1.0f, 0.0f) },
{ glm::vec3( 0.75f, -0.75f, -0.75f), glm::vec3(0.8f, 0.8f, 0.0f), glm::vec2(0.0f, 1.0f) },
{ glm::vec3(-0.75f, -0.75f, -0.75f), glm::vec3(0.0f, 0.8f, 0.8f), glm::vec2(1.0f, 1.0f) }
};
GLushort indices[] =
{
0, 1, 2, 3, 3,
4, 4, 5, 6, 7, 7,
8, 8, 9, 10, 11, 11,
12, 12, 13, 14, 15, 15,
16, 16, 17, 18, 19, 19,
20, 20, 21, 22, 23
};
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
glGenBuffers(1, &m_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenBuffers(1, &m_ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
int location = 0;
glVertexAttribPointer(location, 3, GL_FLOAT, GL_TRUE, sizeof(VertexData), (void *)0);
glEnableVertexAttribArray(location);
glVertexAttribPointer(location + 1, 3, GL_FLOAT, GL_TRUE, sizeof(VertexData), (void *)(sizeof(glm::vec3)));
glEnableVertexAttribArray(location + 1);
glVertexAttribPointer(location + 2, 2, GL_FLOAT, GL_TRUE, sizeof(VertexData), (void *)(sizeof(glm::vec3) * 2));
glEnableVertexAttribArray(location + 2);
}
1、使用 glGenVertexArrays() 生成一个 VAO。
2、使用 glBindVertexArray() 绑定到当前激活的 VAO。
接着我们生成 VBO 和 EBO,分别将顶点和顶点索引缓存。
【索引缓存对象(Element Array Object, EBO)】,它的作用和 VBO 一样,不同的是它缓存的是顶点索引,使用 EBO,绘制命令为 glDrawElements()。
接下来我们需要生成纹理( Image 类是读取图像的 ):
void MyRender::initializeTexture()
{
glGenTextures(1, &m_texture1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_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);
Image image1("../container.jpg");
if (image1.data())
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image1.width(), image1.height(),
0, GL_RGB, GL_UNSIGNED_BYTE, image1.data());
glGenerateMipmap(GL_TEXTURE_2D);
}
else std::cout << "Open Image1 Error! ";
glGenTextures(1, &m_texture2);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_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);
Image image2("../face.jpg", true);
if (image2.data())
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image2.width(), image2.height(),
0, GL_RGB, GL_UNSIGNED_BYTE, image2.data());
glGenerateMipmap(GL_TEXTURE_2D);
}
else std::cout << "Open Image2 Error! ";
}
1、使用 glGenTextures() 生成一个纹理对象。
2、使用 glBindTexture() 将其绑定到当前激活的纹理目标上,目标可以是:GL_TEXTURE_1D( sampler1D )、GL_TEXTURE_1D_ARRAY( sampler1DArray )、GL_TEXTURE_2D( sampler2D )、GL_TEXTURE_2D_ARRAY( sampler2DArray )、GL_TEXTURE_MULTISAMPLE( sampler2DMS )、GL_TEXTURE_MULTISAMPLE( sampler2DMSArray )、GL_TEXTURE_3D( sampler3D )、GL_TEXTURE_CUBE( samplerCube )、GL_TEXTURE_ARRAY( samplerCubeArray )、GL_TEXTURE_RECTANGLE( samplerRect )、GL_TEXTURE_BUFFER( samplerBuffer ),其中括号里面的是片元着色器的采样器类型( 好多啊(ಥ _ ಥ) )。
3、使用 glTexImage2D() 为纹理分配可变的内存( 不可变的使用 glTexStorage2D() )。
4、使用 glGenerateMipmap() 生成层级映射,这步是可选的但推荐使用。
5、使用 glTexParameteri() 为纹理设置环绕方式和放大/缩小滤波器。
因为本章使用了两个纹理,所以在这里生成了两个。
然后,来看 render():
void MyRender::render(const glm::quat &rotation)
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//标准示例
glm::mat4 modelMatrix(1.0f);
modelMatrix = glm::translate(modelMatrix, glm::vec3(0.0f, 0.0f, -5.0f));
modelMatrix *= glm::toMat4(rotation);
glUseProgram(m_program);
GLuint mvp = glGetUniformLocation(m_program, "mvp");
glUniformMatrix4fv(mvp, 1, GL_FALSE, glm::value_ptr(m_projection * modelMatrix));
glBindVertexArray(m_vao);
glDrawElements(GL_TRIANGLE_STRIP, 34, GL_UNSIGNED_SHORT, nullptr);
}
render() 基本就很简单了,我们使用 glm::quat(四元数) 进行旋转,然后设置顶点着色器中的 mvp 矩阵,最后绑定 VAO 并发出绘制命令。
【四元数是表达一个旋转的一种方式】,相关的文章有很多。
接着是顶点着色器:
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color0;
layout (location = 2) in vec2 texCoord0;
uniform mat4 mvp;
out vec3 color;
out vec2 texCoord;
void main()
{
gl_Position = mvp * vec4(position, 1.0);
color = color0;
texCoord = texCoord0;
}
它的工作也很简单,将 position 变换后输出,color0 和 texCoord0 直接输出( 我们的 VertexData 包含一个顶点,一个颜色,一个2D纹理坐标 )。
【纹理坐标(Texture Coordinate)】,用来标明该从纹理图像的哪个部分采样(采集片段颜色)。之后在图形的其它片段上进行片段插值。
最后是片元着色器:
#version 330 core
in vec2 texCoord;
in vec3 color;
out vec4 fragColor;
uniform sampler2D texture1;
uniform sampler2D texture2;
void main(void)
{
vec4 tempColor = mix(texture2D(texture1, texCoord), texture2D(texture2, texCoord), 0.1);
//fragColor = tempColor * vec4(color, 1.0);
fragColor = tempColor;
}
因为我们使用了两个纹理,所以这里是两个 sampler2D 采样器。
【sampler*D是不透明类型,所以只能使用uniform来定义它】,其他不透明类型还有 image(图像)、atomic counter(原子计数器)。
texture1 的 location 默认为 0 ( 对应纹理单元 GL_TEXTURE0 ),texture2 为 1 ( 对应纹理单元 GL_TEXTURE1 )。
接着使用 mix() 将采样到的颜色进行混合,texture2D() 函数从给定的纹理和坐标进行采样。
注释掉的一行则使用了color,有兴趣的可以自己试试。
【结语】
好了,这一章就讲解完了。。不过我还是没有讲得太仔细。。所以可能需要你自己多试试才能明白,然后还有一些其他的东西,具体可以自己看代码了解。