4.QOpenGLWidget-对三角形进行纹理贴图、纹理叠加
在上章3.QOpenGLWidget-通过着色器来渲染渐变三角形,我们为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像。但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多的颜色。这将会产生很多额外开销。
- 除了图像以外,纹理也可以被用来储存大量的数据,这些数据可以发送到着色器上,比如传输大量RGB数据显示一幅画面
为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样(译注:采集片段颜色)。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。
纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角。
float texCoords[] = { 0.0f, 0.0f, // 左下角 1.0f, 0.0f, // 右下角 0.5f, 1.0f // 上中 };
void QOpenGLTexture::setWrapMode(CoordinateDirection direction, WrapMode mode);
//direction:坐标方向,纹理的坐标系统和xyz坐标系统一样,s对应x,t对应y,r对应z(3D纹理时才设置z)
//mode:纹理模式,Repeat(超出部分重复纹理)MirroredRepeat(超出部分镜像重复纹理)ClampToEdge(超出部分显示纹理临近的边缘颜色值)、
QOpenGLTexture放大缩小的过滤方式是通过 setMinMagFilters(Filter minificationFilter, Filter magnificationFilter)函数实现,比如:
m_texture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Nearest);
//参数1:设置缩小方式 ,参数2:设置放大方式
//设置缩小和放大的方式,缩小图片采用LinearMipMapNearest线性过滤,并使用多级渐远纹理邻近过滤,放大图片采用:Nearest邻近过滤
- Nearest : 邻近过滤,速度快,可能有锯齿,等同于opengl中的GL_NEAREST
- Linear : 线性过滤,将最接近的2*2个颜色,计算出一个插值,速度慢,画面好,等同于opengl中的GL_LINEAR
- //下面4个多级渐远纹理参数只能用在缩小方式参数1上面
- NearestMipMapNearest : 使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样,等同于GL_NEAREST_MIPMAP_NEAREST
- NearestMipMapLinear : 在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样,等同于GL_NEAREST_MIPMAP_LINEAR
- LinearMipMapNearest : 使用最邻近的多级渐远纹理级别,并使用线性插值进行采样,等同于GL_LINEAR_MIPMAP_NEAREST
- LinearMipMapLinear : 在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样,GL_LINEAR_MIPMAP_LINEAR
#include "myglwidget.h" #include <QtDebug> //GLSL3.0版本后,废弃了attribute关键字(以及varying关键字),属性变量统一用in/out作为前置关键字 #define GL_VERSION "#version 330 core\n" #define GLCHA(x) #@x //加单引号 #define GLSTR(x) #x //加双引号 #define GET_GLSTR(x) GL_VERSION#x const char *vsrc = GET_GLSTR( layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor; layout (location = 2) in vec2 aTexCoord; out vec3 ourColor; out vec2 TexCoord; void main() { gl_Position = vec4(aPos, 1.0); ourColor = aColor; TexCoord = aTexCoord; } ); const char *fsrc =GET_GLSTR( out vec4 FragColor; in vec3 ourColor; in vec2 TexCoord; uniform sampler2D ourTexture; void main() { FragColor = texture(ourTexture, TexCoord); } ); myGlWidget::myGlWidget(QWidget *parent):QOpenGLWidget(parent) { } void myGlWidget::paintGL() { // 绘制 // glViewport(0, 0, width(), height()); glClear(GL_COLOR_BUFFER_BIT); // 渲染Shader vao.bind(); //绑定激活vao m_texture->bind(); glDrawArrays(GL_TRIANGLES, 0, 3); //绘制3个定点,样式为三角形 m_texture->release(); vao.release(); //解绑 } void myGlWidget::initializeGL() { // 为当前环境初始化OpenGL函数 initializeOpenGLFunctions(); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); //设置背景色为白色 //初始化纹理对象 m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); m_texture->setData(QImage(":wall1")); m_texture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Nearest); //设置缩小和放大的方式,缩小图片采用LinearMipMapLinear线性过滤,并使用多级渐远纹理邻近过滤,放大图片采用:Nearest邻近过滤
m_texture->setWrapMode(QOpenGLTexture::DirectionS,QOpenGLTexture::Repeat); m_texture->setWrapMode(QOpenGLTexture::DirectionT,QOpenGLTexture::Repeat); //创建着色器程序 program = new QOpenGLShaderProgram; program->addShaderFromSourceCode(QOpenGLShader::Vertex,vsrc); program->addShaderFromSourceCode(QOpenGLShader::Fragment,fsrc); program->link(); program->bind();//激活Program对象 //初始化VBO,将顶点数据存储到buffer中,等待VAO激活后才能释放 float vertices[] = { // 位置 // 颜色 //纹理坐标 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 2.0f, 0.0f,// 右下 -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // 左下 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 2.0f // 顶部 }; vbo.create(); vbo.bind(); //绑定到当前的OpenGL上下文, vbo.allocate(vertices, sizeof(vertices)); vbo.setUsagePattern(QOpenGLBuffer::StaticDraw); //设置为一次修改,多次使用 //初始化VAO,设置顶点数据状态(顶点,法线,纹理坐标等) vao.create(); vao.bind(); // void setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0); program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 8 * sizeof(float)); //设置aPos顶点属性 program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 3, 8 * sizeof(float)); //设置aColor顶点颜色 program->setAttributeBuffer(2, GL_FLOAT, 6 * sizeof(float), 2, 8 * sizeof(float)); //设置纹理坐标 //offset:第一个数据的偏移量 //tupleSize:一个数据有多少个元素,比如位置为xyz,颜色为rgb,所以是3 //stride:步长,下个数据距离当前数据的之间距离,比如右下位置和左下位置之间间隔了:3个xyz值+3个rgb值,所以填入 6 * sizeof(float) program->enableAttributeArray(0); //使能aPos顶点属性 program->enableAttributeArray(1); //使能aColor顶点颜色 program->enableAttributeArray(2); //使能纹理坐标 //解绑所有对象 vao.release(); vbo.release(); } void myGlWidget::resizeEvent(QResizeEvent *e) { }
//纹理坐标 2.0f, 0.0f,// 右下 0.0f, 0.0f, // 左下 1.0f, 2.0f // 顶部
所以是超过了范围(0, 0)到(1, 1),假如我们绘制mode改为QOpenGLTexture::ClampToEdge,就可以看出其实三角形是大于图片的,修改代码如下:
m_texture->setWrapMode(QOpenGLTexture::DirectionS,QOpenGLTexture::ClampToEdge);
m_texture->setWrapMode(QOpenGLTexture::DirectionT,QOpenGLTexture::ClampToEdge);
显示界面如下所示:
在代码中,我们还保存了上章着色器颜色渲染相关代码,所以我们可以把得到的纹理颜色与顶点颜色混合,来获得更有趣的混合效果,修改fragment源码:
FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);
编译并运行,如下图所示:
texture()函数意义
其中的texture函数主要是来采样不同坐标的纹理颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。
texture函数会根据当前所在纹理坐标去获取对应的颜色。然后输出到FragColor 显示颜色.
3.纹理叠加
在上个源码实现中,我们在fragment源码中定义了一个uniform类型的ourTexture变量,但是我们却没有给它赋值就已经实现了纹理,这是因为默认激活的是GL_TEXTURE0,所以我们之前的操作都是针对GL_TEXTURE0(如果有一个纹理的话).
假如有多个纹理的话,我们就需要设置其纹理位置值(也称为一个纹理单元(Texture Unit))。然后再将对应的QOpenGLTexture绑定上.
设置如下所示:
program->setUniformValue("texture1", 0); //设置texture1为GL_TEXTURE0 m_texture->bind(); //将m_texture绑定在GL_TEXTURE0上 program->setUniformValue("texture2", 1); //设置texture2为GL_TEXTURE1 m_texture2->bind(1);//将m_texture2绑定在GL_TEXTURE1上 ....
OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。
修改fragment源码:
#version 330 core ... uniform sampler2D texture1; uniform sampler2D texture2; void main() { FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.7); }
mix函数作用是将前两个纹理参数进行融合,根据第三个参数值来进行线性插值,如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.7表示返回30%的第一个输入颜色和70%的第二个输入颜色。
然后再加入一个我的大学图片:
最终和砖墙叠加后的效果如下所示:
具体源码如下所示:
#include "myglwidget.h" #include <QtDebug> //GLSL3.0版本后,废弃了attribute关键字(以及varying关键字),属性变量统一用in/out作为前置关键字 #define GL_VERSION "#version 330 core\n" #define GLCHA(x) #@x //加单引号 #define GLSTR(x) #x //加双引号 #define GET_GLSTR(x) GL_VERSION#x const char *vsrc = GET_GLSTR( layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor; layout (location = 2) in vec2 aTexCoord; out vec3 ourColor; out vec2 TexCoord; void main() { gl_Position = vec4(aPos, 1.0); ourColor = aColor; TexCoord = aTexCoord; } ); const char *fsrc =GET_GLSTR( out vec4 FragColor; in vec3 ourColor; in vec2 TexCoord; uniform sampler2D texture1; uniform sampler2D texture2; void main() { FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.7); } ); myGlWidget::myGlWidget(QWidget *parent):QOpenGLWidget(parent) { } void myGlWidget::paintGL() { // 绘制 int w = width(); int h = height(); int side = qMin(w, h); glViewport((w - side) / 2, (h - side) / 2, side, side); glClear(GL_COLOR_BUFFER_BIT); // 渲染Shader vao.bind(); //绑定激活vao m_texture->bind(); program->setUniformValue("texture1", 0); m_texture->bind(); program->setUniformValue("texture2", 1); m_texture2->bind(1); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); //绘制3个定点,样式为三角形 m_texture->release(); m_texture2->release(); vao.release(); //解绑 } void myGlWidget::initializeGL() { // 为当前环境初始化OpenGL函数 initializeOpenGLFunctions(); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); //设置背景色为白色 //初始化纹理对象 m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); m_texture->setData(QImage(":wall")); //加载砖块图片 m_texture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Nearest); //设置缩小和放大的方式,缩小图片采用LinearMipMapLinear线性过滤,并使用多级渐远纹理邻近过滤,放大图片采用:Nearest邻近过滤 m_texture->setWrapMode(QOpenGLTexture::DirectionS,QOpenGLTexture::Repeat); m_texture->setWrapMode(QOpenGLTexture::DirectionT,QOpenGLTexture::Repeat); //初始化纹理对象 m_texture2 = new QOpenGLTexture(QOpenGLTexture::Target2D); m_texture2->setData(QImage(":my").mirrored()); //返回图片的镜像,设置为Y轴反向,因为在opengl的Y坐标中,0.0对应的是图片底部 m_texture2->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Nearest); //设置缩小和放大的方式,缩小图片采用LinearMipMapLinear线性过滤,并使用多级渐远纹理邻近过滤,放大图片采用:Nearest邻近过滤 m_texture2->setWrapMode(QOpenGLTexture::DirectionS,QOpenGLTexture::Repeat); m_texture2->setWrapMode(QOpenGLTexture::DirectionT,QOpenGLTexture::Repeat); //创建着色器程序 program = new QOpenGLShaderProgram; program->addShaderFromSourceCode(QOpenGLShader::Vertex,vsrc); program->addShaderFromSourceCode(QOpenGLShader::Fragment,fsrc); program->link(); program->bind();//激活Program对象 //初始化VBO,将顶点数据存储到buffer中,等待VAO激活后才能释放 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 // 左上 }; vbo.create(); vbo.bind(); //绑定到当前的OpenGL上下文, vbo.allocate(vertices, sizeof(vertices)); vbo.setUsagePattern(QOpenGLBuffer::StaticDraw); //设置为一次修改,多次使用 //初始化VAO,设置顶点数据状态(顶点,法线,纹理坐标等) vao.create(); vao.bind(); // void setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0); program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 8 * sizeof(float)); //设置aPos顶点属性 program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 3, 8 * sizeof(float)); //设置aColor顶点颜色 program->setAttributeBuffer(2, GL_FLOAT, 6 * sizeof(float), 2, 8 * sizeof(float)); //设置aColor顶点颜色 //offset:第一个数据的偏移量 //tupleSize:一个数据有多少个元素,比如位置为xyz,颜色为rgb,所以是3 //stride:步长,下个数据距离当前数据的之间距离,比如右下位置和左下位置之间间隔了:3个xyz值+3个rgb值,所以填入 6 * sizeof(float) program->enableAttributeArray(0); //使能aPos顶点属性 program->enableAttributeArray(1); //使能aColor顶点颜色 program->enableAttributeArray(2); //使能aColor顶点颜色 //解绑所有对象 vao.release(); vbo.release(); } void myGlWidget::resizeEvent(QResizeEvent *e) { }
人间有真情,人间有真爱。