在Qt/C++的OpenGL着色器对象中添加或切换多纹理的实现方案
问题
需要在OpenGL单个Shader中为同一物体的不同部分(如房屋的窗户、屋顶、墙面)切换不同纹理,而非混合多纹理。当前实现仅支持单张纹理,扩展性不足,基于Qt开发,现有代码及尝试过的无效方法如下:
当前Qt实现代码
void widgetClass::initializeGL() { float r, g, b, a = 1.0f; initializeOpenGLFunctions(); shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vertexShader.vsh"); shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fragmentShader.fsh"); shaderProgram.link(); vertices << QVector3D(-0.5, -0.5, 0.5) << QVector3D( 0.5, -0.5, 0.5) << QVector3D( 0.5, 0.5, 0.5) // Front << QVector3D( 0.5, 0.5, 0.5) << QVector3D(-0.5, 0.5, 0.5) << QVector3D(-0.5, -0.5, 0.5) << QVector3D( 0.5, -0.5, -0.5) << QVector3D(-0.5, -0.5, -0.5) << QVector3D(-0.5, 0.5, -0.5) // Back << QVector3D(-0.5, 0.5, -0.5) << QVector3D( 0.5, 0.5, -0.5) << QVector3D( 0.5, -0.5, -0.5) << QVector3D(-0.5, -0.5, -0.5) << QVector3D(-0.5, -0.5, 0.5) << QVector3D(-0.5, 0.5, 0.5) // Left << QVector3D(-0.5, 0.5, 0.5) << QVector3D(-0.5, 0.5, -0.5) << QVector3D(-0.5, -0.5, -0.5) << QVector3D( 0.5, -0.5, 0.5) << QVector3D( 0.5, -0.5, -0.5) << QVector3D( 0.5, 0.5, -0.5) // Right << QVector3D( 0.5, 0.5, -0.5) << QVector3D( 0.5, 0.5, 0.5) << QVector3D( 0.5, -0.5, 0.5) << QVector3D(-0.5, 0.5, 0.5) << QVector3D( 0.5, 0.5, 0.5) << QVector3D( 0.5, 0.5, -0.5) // Top << QVector3D( 0.5, 0.5, -0.5) << QVector3D(-0.5, 0.5, -0.5) << QVector3D(-0.5, 0.5, 0.5) << QVector3D(-0.5, -0.5, -0.5) << QVector3D( 0.5, -0.5, -0.5) << QVector3D( 0.5, -0.5, 0.5) // Bottom << QVector3D( 0.5, -0.5, 0.5) << QVector3D(-0.5, -0.5, 0.5) << QVector3D(-0.5, -0.5, -0.5); colors << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) // Front << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) // Back << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) // Left << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) // Right << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) // Top << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) // Bottom << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1); textureCoordinates << QVector2D(0, 0) << QVector2D(1, 0) << QVector2D(1, 1) // Front << QVector2D(1, 1) << QVector2D(0, 1) << QVector2D(0, 0) << QVector2D(0, 0) << QVector2D(1, 0) << QVector2D(1, 1) // Back << QVector2D(1, 1) << QVector2D(0, 1) << QVector2D(0, 0) << QVector2D(0, 0) << QVector2D(1, 0) << QVector2D(1, 1) // Left << QVector2D(1, 1) << QVector2D(0, 1) << QVector2D(0, 0) << QVector2D(0, 0) << QVector2D(1, 0) << QVector2D(1, 1) // Right << QVector2D(1, 1) << QVector2D(0, 1) << QVector2D(0, 0) << QVector2D(0, 0) << QVector2D(1, 0) << QVector2D(1, 1) // Top << QVector2D(1, 1) << QVector2D(0, 1) << QVector2D(0, 0) << QVector2D(0, 0) << QVector2D(1, 0) << QVector2D(1, 1) // Bottom << QVector2D(1, 1) << QVector2D(0, 1) << QVector2D(0, 0); texturesBank[0] = new QOpenGLTexture(QImage(QString(":/texture.bmp"))); texturesBank[0]->create(); uint test = texturesBank[0]->textureId(); texturesBank[0]->setMinificationFilter(QOpenGLTexture::LinearMipMapNearest); texturesBank[0]->setMagnificationFilter(QOpenGLTexture::Linear); texturesBank[0]->setWrapMode(QOpenGLTexture::Repeat); } void widgetClass::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); QMatrix4x4 mMatrix; QMatrix4x4 vMatrix; QMatrix4x4 cameraTransformation; cameraTransformation.rotate(alpha, 0, 1, 0); cameraTransformation.rotate(beta, 1, 0, 0); QVector3D cameraPosition = cameraTransformation * QVector3D(0, 0, distance); QVector3D cameraUpDirection = cameraTransformation * QVector3D(0, 1, 0); vMatrix.lookAt(cameraPosition, QVector3D(0, 0, 0), cameraUpDirection); texturesBank[0]->bind(); shaderProgram.bind(); shaderProgram.setUniformValue("mvpMatrix", pMatrix * vMatrix * mMatrix); shaderProgram.setAttributeArray("vertex", vertices.constData()); shaderProgram.enableAttributeArray("vertex"); shaderProgram.setAttributeArray("textureCoordinate", textureCoordinates.constData()); shaderProgram.enableAttributeArray("textureCoordinate"); shaderProgram.setAttributeArray("color", colors.constData()); shaderProgram.enableAttributeArray("color"); glDrawArrays(GL_TRIANGLES, 0, vertices.size()); shaderProgram.disableAttributeArray("vertex"); shaderProgram.disableAttributeArray("textureCoordinate"); shaderProgram.disableAttributeArray("color"); shaderProgram.release(); } void widgetClass::resizeGL(int w, int h) { pMatrix.setToIdentity(); pMatrix.perspective(60.0, (float) w / (float) h, 0.001, 1000); glViewport(0,0,w,h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); }
当前着色器代码
顶点着色器(vertexShader.vsh):
uniform mat4 mvpMatrix; in vec4 vertex; in vec2 textureCoordinate; in vec4 color; out vec2 varyingTextureCoordinate; out vec4 varyingColor; void main(void) { varyingColor = color; varyingTextureCoordinate = textureCoordinate; gl_Position = mvpMatrix * vertex; }
片元着色器(fragmentShader.fsh):
uniform sampler2D texture; in vec2 varyingTextureCoordinate; in vec4 varyingColor; out vec4 fragColor; void main(void) { fragColor = texture2D(texture, varyingTextureCoordinate); }
尝试过的无效方法
- 创建多个sampler2D并与0/1数组相乘:效率低且实现不专业
- 调整纹理坐标:项目扩展时会导致资源文件过大
- 创建纹理数组并修改绑定:未生效
解决方案
方法一:顶点级纹理ID属性(灵活可控,推荐)
核心思路:为每个顶点添加纹理ID属性,顶点着色器将ID传递到片元着色器,片元着色器根据ID选择对应纹理采样。
1. Qt代码修改
- 在
initializeGL中添加纹理ID数组,为不同面分配对应ID:
QVector<float> textureIds; // Front/Back用纹理0,Left/Right用纹理1,Top/Bottom用纹理2 textureIds << 0 << 0 << 0 << 0 << 0 << 0; // Front textureIds << 0 << 0 << 0 << 0 << 0 << 0; // Back textureIds << 1 << 1 << 1 << 1 << 1 << 1; // Left textureIds << 1 << 1 << 1 << 1 << 1 << 1; // Right textureIds << 2 << 2 << 2 << 2 << 2 << 2; // Top textureIds << 2 << 2 << 2 << 2 << 2 << 2; // Bottom // 加载多张纹理 texturesBank[0] = new QOpenGLTexture(QImage(QString(":/wall.bmp"))); texturesBank[0]->create(); texturesBank[0]->setMinificationFilter(QOpenGLTexture::LinearMipMapNearest); texturesBank[0]->setMagnificationFilter(QOpenGLTexture::Linear); texturesBank[0]->setWrapMode(QOpenGLTexture::Repeat); texturesBank[1] = new QOpenGLTexture(QImage(QString(":/window.bmp"))); texturesBank[1]->create(); texturesBank[1]->setMinificationFilter(QOpenGLTexture::LinearMipMapNearest); texturesBank[1]->setMagnificationFilter(QOpenGLTexture::Linear); texturesBank[1]->setWrapMode(QOpenGLTexture::Repeat); texturesBank[2] = new QOpenGLTexture(QImage(QString(":/roof.bmp"))); texturesBank[2]->create(); texturesBank[2]->setMinificationFilter(QOpenGLTexture::LinearMipMapNearest); texturesBank[2]->setMagnificationFilter(QOpenGLTexture::Linear); texturesBank[2]->setWrapMode(QOpenGLTexture::Repeat);
- 在
paintGL中绑定多个纹理到不同纹理单元,并启用纹理ID属性:
// 绑定纹理到不同单元 texturesBank[0]->bind(0); texturesBank[1]->bind(1); texturesBank[2]->bind(2); shaderProgram.bind(); // 设置sampler对应的纹理单元 shaderProgram.setUniformValue("texture0", 0); shaderProgram.setUniformValue("texture1", 1); shaderProgram.setUniformValue("texture2", 2); // 启用纹理ID属性 shaderProgram.setAttributeArray("textureId", textureIds.constData()); shaderProgram.enableAttributeArray("textureId"); // 原有绘制代码不变...
2. 着色器修改
顶点着色器:
uniform mat4 mvpMatrix; in vec4 vertex; in vec2 textureCoordinate; in vec4 color; in float textureId; // 新增纹理ID输入 out vec2 varyingTextureCoordinate; out vec4 varyingColor; out float varyingTextureId; // 传递到片元着色器 void main(void) { varyingColor = color; varyingTextureCoordinate = textureCoordinate; varyingTextureId = textureId; gl_Position = mvpMatrix * vertex; }
片元着色器:
uniform sampler2D texture0; uniform sampler2D texture1; uniform sampler2D texture2; in vec2 varyingTextureCoordinate; in vec4 varyingColor; in float varyingTextureId; out vec4 fragColor; void main(void) { if (varyingTextureId == 0.0) { fragColor = texture2D(texture0, varyingTextureCoordinate); } else if (varyingTextureId == 1.0) { fragColor = texture2D(texture1, varyingTextureCoordinate); } else if (varyingTextureId == 2.0) { fragColor = texture2D(texture2, varyingTextureCoordinate); } else { fragColor = varyingColor; // fallback颜色 } }
方法二:使用纹理数组(Texture Array)
核心思路:将多张纹理打包成一个纹理数组,通过额外的纹理坐标分量选择数组中的纹理层,适合纹理尺寸一致的场景。
1. Qt代码修改
// initializeGL中创建纹理数组 QVector<QImage> textureImages; textureImages << QImage(":/wall.bmp") << QImage(":/window.bmp") << QImage(":/roof.bmp"); QOpenGLTexture* textureArray = new QOpenGLTexture(QOpenGLTexture::Target2DArray); textureArray->setSize(textureImages[0].width(), textureImages[0].height(), textureImages.size()); textureArray->setFormat(QOpenGLTexture::RGBA8_UNorm); textureArray->allocateStorage(); // 逐个上传纹理到数组层 for (int i = 0; i < textureImages.size(); ++i) { textureArray->setData(i, 0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, textureImages[i].bits()); } textureArray->setMinificationFilter(QOpenGLTexture::LinearMipMapNearest); textureArray->setMagnificationFilter(QOpenGLTexture::Linear); textureArray->setWrapMode(QOpenGLTexture::Repeat);
2. 着色器修改
顶点着色器:
uniform mat4 mvpMatrix; in vec4 vertex; in vec2 textureCoordinate; in vec4 color; in float textureLayer; // 纹理层ID out vec3 varyingTextureCoord; // 合并坐标和层,变成vec3 out vec4 varyingColor; void main(void) { varyingColor = color; varyingTextureCoord = vec3(textureCoordinate, textureLayer); gl_Position = mvp
相关产品推荐
相关产品推荐

