我试图获取附着在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实现中的错误?