Я пытался получить среднее значение всех текселей, которые я нарисовал в текстуре, прикрепленной к 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?