最深的Mipmap级别不是所有纹理像素的平均值吗?

时间:2019-02-27 17:14:22

标签: c++ opengl mipmaps mesa

我试图获取附着在FBO上的纹理中绘制的所有纹理像素的平均值。纹理具有RGBA32F格式,因此在任何情况下精度损失都应最小。

对于平均值的实际计算,我认为可以通过glGenerateMipmap命令使用硬件生成的mipmap,然后获得最深的mipmap级别– 1×1。

当纹理具有2的幂次方时,这很好地工作。但是当它比这个像素还要低一个像素时,直到达到其他尺寸时,我得到的结果非常远非平均值。

例如参见以下测试程序:

#include <cmath>
#include <vector>
#include <string>
#include <iostream>
// glad.h is generated by the following command:
// glad --out-path=. --generator=c --omit-khrplatform --api="gl=3.3" --profile=core --extensions=
#include "glad/glad.h"
#include <GL/freeglut.h>
#include <glm/glm.hpp>
using glm::vec4;

GLuint vao, vbo;
GLuint texFBO;
GLuint program;
GLuint fbo;
int width=512, height=512;

void getMeanPixelValue(int texW, int texH)
{
    // Get average value of the rendered pixels as the value of the deepest mipmap level
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texFBO);
    glGenerateMipmap(GL_TEXTURE_2D);

    using namespace std;
    // Formula from the glspec, "Mipmapping" subsection in section 3.8.11 Texture Minification
    const auto totalMipmapLevels = 1+floor(log2(max(texW,texH)));
    const auto deepestLevel=totalMipmapLevels-1;

    // Sanity check
    int deepestMipmapLevelWidth=-1, deepestMipmapLevelHeight=-1;
    glGetTexLevelParameteriv(GL_TEXTURE_2D, deepestLevel, GL_TEXTURE_WIDTH, &deepestMipmapLevelWidth);
    glGetTexLevelParameteriv(GL_TEXTURE_2D, deepestLevel, GL_TEXTURE_HEIGHT, &deepestMipmapLevelHeight);
    assert(deepestMipmapLevelWidth==1);
    assert(deepestMipmapLevelHeight==1);

    vec4 pixel;
    glGetTexImage(GL_TEXTURE_2D, deepestLevel, GL_RGBA, GL_FLOAT, &pixel[0]);

    // Get average value in an actual summing loop over all the pixels
    std::vector<vec4> data(texW*texH);
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, data.data());
    vec4 avg(0,0,0,0);
    for(auto const& v : data)
        avg+=v;
    avg/=texW*texH;

    std::cerr << "Mipmap value: " << pixel[0] << ", " << pixel[1] << ", " << pixel[2] << ", " << pixel[3] << "\n";
    std::cerr << "True average: " << avg[0] << ", " << avg[1] << ", " << avg[2] << ", " << avg[3] << "\n";
}

GLuint makeShader(GLenum type, std::string const& srcStr)
{
    const auto shader=glCreateShader(type);
    const GLint srcLen=srcStr.size();
    const GLchar*const src=srcStr.c_str();
    glShaderSource(shader, 1, &src, &srcLen);
    glCompileShader(shader);
    GLint status=-1;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    assert(glGetError()==GL_NO_ERROR);
    assert(status);
    return shader;
}

void loadShaders()
{
    program=glCreateProgram();

    const auto vertexShader=makeShader(GL_VERTEX_SHADER, R"(
#version 330
in vec4 vertex;
void main() { gl_Position=vertex; }
)");
    glAttachShader(program, vertexShader);

    const auto fragmentShader=makeShader(GL_FRAGMENT_SHADER, R"(
#version 330
out vec4 color;
void main()
{

    color.r = gl_FragCoord.y<100 ? 1 : 0;
    color.g = gl_FragCoord.y<200 ? 1 : 0;
    color.b = gl_FragCoord.y<300 ? 1 : 0;
    color.a = gl_FragCoord.y<400 ? 1 : 0;
}
)");
    glAttachShader(program, fragmentShader);

    glLinkProgram(program);
    GLint status=0;
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    assert(glGetError()==GL_NO_ERROR);
    assert(status);

    glDetachShader(program, fragmentShader);
    glDeleteShader(fragmentShader);

    glDetachShader(program, vertexShader);
    glDeleteShader(vertexShader);
}

void setupBuffers()
{
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    const GLfloat vertices[]=
    {
        -1, -1,
         1, -1,
        -1,  1,
         1,  1,
    };
    glBufferData(GL_ARRAY_BUFFER, sizeof vertices, vertices, GL_STATIC_DRAW);
    constexpr GLuint attribIndex=0;
    constexpr int coordsPerVertex=2;
    glVertexAttribPointer(attribIndex, coordsPerVertex, GL_FLOAT, false, 0, 0);
    glEnableVertexAttribArray(attribIndex);
    glBindVertexArray(0);
}

void setupRenderTarget()
{
    glGenTextures(1, &texFBO);
    glGenFramebuffers(1,&fbo);
    glBindTexture(GL_TEXTURE_2D,texFBO);
    glBindTexture(GL_TEXTURE_2D,0);
}

bool init()
{
    if(!gladLoadGL())
    {
        std::cerr << "Failed to initialize GLAD\n";
        return false;
    }
    if(!GLAD_GL_VERSION_3_3)
    {
        std::cerr << "OpenGL 3.3 not supported\n";
        return false;
    }

    setupRenderTarget();
    loadShaders();
    setupBuffers();

    return true;
}

bool inited=false;
void reshape(int width, int height)
{
    ::width=width;
    ::height=height;
    std::cerr << "New size: " << width << "x" << height << "\n";

    if(!inited)
    {
        if(!(inited=init()))
            std::exit(1);
    }
    glViewport(0,0,width,height);

    glBindTexture(GL_TEXTURE_2D,texFBO);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA32F,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,nullptr);
    glBindTexture(GL_TEXTURE_2D,0);
    glBindFramebuffer(GL_FRAMEBUFFER,fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texFBO,0);
    const auto status=glCheckFramebufferStatus(GL_FRAMEBUFFER);
    assert(status==GL_FRAMEBUFFER_COMPLETE);
    glBindFramebuffer(GL_FRAMEBUFFER,0);
}

void display()
{
    if(!inited)
    {
        if(!(inited=init()))
            std::exit(1);
    }

    glBindFramebuffer(GL_FRAMEBUFFER,fbo);

    glUseProgram(program);

    glBindVertexArray(vao);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindVertexArray(0);

    getMeanPixelValue(width, height);

    // Show the texture on screen
    glBindFramebuffer(GL_READ_FRAMEBUFFER,fbo);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);
    glBlitFramebuffer(0,0,width,height,0,0,width,height,GL_COLOR_BUFFER_BIT,GL_NEAREST);

    glFinish();
}

int main(int argc, char** argv)
{
    glutInitContextVersion(3,3);
    glutInitContextProfile(GLUT_CORE_PROFILE);
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_RGB);

    glutInitWindowSize(width, height);
    glutCreateWindow("Test");
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);

    glutMainLoop();
}

垂直调整窗口大小时,得到以下输出:

New size: 512x512
Mipmap value: 0.195312, 0.390625, 0.585938, 0.78125
True average: 0.195312, 0.390625, 0.585938, 0.78125
New size: 512x511
Mipmap value: 0, 0, 1, 1
True average: 0.195695, 0.391389, 0.587084, 0.782779
New size: 512x479
Mipmap value: 0, 0.00123596, 1, 1
True average: 0.208768, 0.417537, 0.626305, 0.835073
New size: 512x453
Mipmap value: 0, 0.125, 1, 1
True average: 0.220751, 0.441501, 0.662252, 0.883002

上面的mipmap值不仅是不精确的平均值-除了2的幂次方,它们甚至没有接近相应的平均值!

这是在Kubuntu 18.04上,其中包含来自glxinfo的以下信息:

Vendor: Intel Open Source Technology Center (0x8086)
Device: Mesa DRI Intel(R) Haswell Server  (0x41a)
Version: 18.2.2

那么,这是怎么回事?最深的mipmap级别不是纹理中所有纹理像素的平均值吗?还是仅仅是OpenGL实现中的错误?

0 个答案:

没有答案