将S3TC/DXTn压缩纹理直接转换为QImage的技术咨询
Hey, I’ve tackled similar legacy texture conversion work before, so let’s walk through how to pull this off using Qt’s OpenGL tools—no temporary files or manual decompression required, exactly what you’re looking for.
Step 1: Ensure a Valid OpenGL Context
First, you need an active QOpenGLContext to work with. If you’re working within a QOpenGLWidget, you can handle this directly in its initializeGL or paintGL methods. For offline/batch processing (no UI), create an offscreen surface and bind the context:
QOpenGLContext context; context.setFormat(QSurfaceFormat::defaultFormat()); if (!context.create()) { // Handle context creation failure } QOffscreenSurface surface; surface.setFormat(context.format()); surface.create(); context.makeCurrent(&surface);
Step 2: Load Compressed Data into QOpenGLTexture
This is where setCompressedData shines—you just need to map your DXT format to the corresponding Qt OpenGL texture format, and pass in your raw pixel data with the correct dimensions:
// Assume you have these metadata values pre-defined int width = 512; int height = 512; const unsigned char* rawData = /* Your compressed pixel data pointer */; size_t dataSize = /* Size of the compressed data in bytes */; QOpenGLTexture::TextureFormat glFormat; // Map DXT format to OpenGL format switch (yourTextureFormat) { case DXT1: glFormat = QOpenGLTexture::RGB_S3TC_DXT1_EXT; break; case DXT3: glFormat = QOpenGLTexture::RGBA_S3TC_DXT3_EXT; break; case DXT5: glFormat = QOpenGLTexture::RGBA_S3TC_DXT5_EXT; break; default: // Handle invalid format } // Create and configure the texture QOpenGLTexture texture(QOpenGLTexture::Target2D); texture.setSize(width, height); texture.setFormat(glFormat); texture.allocateStorage(); texture.setCompressedData(dataSize, rawData); texture.setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); // Optional: adjust filtering
Step 3: Render Texture to Framebuffer Object (FBO)
Next, we’ll draw the texture to an FBO so we can extract it as a QImage. For this, you’ll need to render a full-screen quad. If you’re using a core OpenGL profile, you’ll need a simple shader program and vertex buffer. Here’s a minimal example:
First, define basic shaders (you can embed these as strings):
const char* vertexShaderSource = R"( #version 330 core layout (location = 0) in vec2 aPos; layout (location = 1) in vec2 aTexCoord; out vec2 TexCoord; void main() { gl_Position = vec4(aPos, 0.0, 1.0); TexCoord = aTexCoord; } )"; const char* fragmentShaderSource = R"( #version 330 core out vec4 FragColor; in vec2 TexCoord; uniform sampler2D texture1; void main() { FragColor = texture(texture1, TexCoord); } )";
Then set up the shader program, vertex data, and render:
// Create shader program QOpenGLShaderProgram shaderProgram; shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource); shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource); shaderProgram.link(); // Full-screen quad vertex data (positions + texture coords) float vertices[] = { // Positions // TexCoords 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f }; unsigned int indices[] = { 0, 1, 3, 1, 2, 3 }; // Create vertex buffer/array objects QOpenGLBuffer vbo(QOpenGLBuffer::VertexBuffer); QOpenGLBuffer ebo(QOpenGLBuffer::IndexBuffer); QOpenGLVertexArrayObject vao; vao.create(); vao.bind(); vbo.create(); vbo.bind(); vbo.allocate(vertices, sizeof(vertices)); ebo.create(); ebo.bind(); ebo.allocate(indices, sizeof(indices)); // Set vertex attribute pointers shaderProgram.bind(); shaderProgram.enableAttributeArray(0); shaderProgram.setAttributeBuffer(0, GL_FLOAT, 0, 2, sizeof(float)*4); shaderProgram.enableAttributeArray(1); shaderProgram.setAttributeBuffer(1, GL_FLOAT, sizeof(float)*2, 2, sizeof(float)*4); // Create FBO QOpenGLFramebufferObject fbo(width, height); if (!fbo.isValid()) { // Handle FBO creation failure } // Render to FBO fbo.bind(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); shaderProgram.bind(); texture.bind(0); shaderProgram.setUniformValue("texture1", 0); vao.bind(); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); // Clean up bindings vao.release(); texture.release(); shaderProgram.release(); fbo.release();
Step 4: Extract QImage from FBO
Once rendering is done, you can grab the image directly from the FBO. Make sure to wait for OpenGL to finish rendering first:
glFinish(); // Ensure all rendering commands are completed QImage resultImage = fbo.toImage(QImage::Format_RGBA8888);
Key Notes & Optimizations
- Extension Check: Verify that the OpenGL context supports S3TC compression with
context.hasExtension("GL_EXT_texture_compression_s3tc")—most modern GPUs do, but older ones might not. - Batch Processing: If you’re handling hundreds/thousands of textures, reuse the FBO, shader program, and VAO/VBO/EB objects. Just resize the FBO and update the texture data instead of creating new objects each time.
- Thread Safety: If processing in a background thread, make sure the OpenGL context is properly bound to that thread and the offscreen surface.
- Alternative for Compatibility Profile: If you’re stuck with an older OpenGL compatibility profile, you can skip the shader setup and use fixed-function pipeline calls (e.g.,
glBegin(GL_QUADS)with texture coordinates), but modern core profile is more reliable long-term.
内容的提问来源于stack exchange,提问作者user9088793

