Разве самый глубокий уровень mipmap не является средним для всех текселей? - PullRequest
0 голосов
/ 27 февраля 2019

Я пытался получить среднее значение всех текселей, которые я нарисовал в текстуре, прикрепленной к FBO.Текстура имеет формат RGBA32F, поэтому потеря точности должна быть минимальной в любом случае.

Для фактического вычисления среднего значения я решил использовать аппаратную генерацию mipmaps с помощью команды glGenerateMipmap, а затем получить самую глубокуюУровень mipmap - 1 × 1.

Это хорошо работает, когда текстура имеет степень двойки.Но когда это даже на один пиксель ниже этого, и до какого-то другого размера, я получаю результаты очень далеко от среднего.

См., Например, следующую тестовую программу:

#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 не являются просто неточными средними значениями - они даже не близки к соответствующим средним, кроме размера степени двух!

Это на 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?

...