【问题标题】:Android OpenGL ES 2.0 - Textures not showing on some devicesAndroid OpenGL ES 2.0 - 某些设备上未显示纹理
【发布时间】:2014-12-15 17:32:35
【问题描述】:

猜猜这个,

我最近发布了一款适用于 Android 的游戏(play.google.com/store/apps/details?id=com.quackers,如果您想亲眼目睹这些问题),最初的反馈表明该游戏无法运行在某些设备上正确。从那以后,我得到了其中一款有问题的平板电脑(Samsung Galaxy Tab 2 7.0),结果发现它确实可以运行,只是渲染不正确。

经过几次挖掘,我发现这是一个纹理问题。纹理加载正常,但它们没有被渲染 - 不是在出现问题时你经常使用 OpenGL 得到的通常的黑色方块 - 什么都没有。

这是 OpenGL ES 2.0,做 SDL/C++/ndk 的事情。虽然网上也有类似的问题,但其中大部分涉及 ES 1.0 并考虑了不同的问题 - 纹理尺寸不是 2 的幂(例如 64x64、128x128、256x256 等)或一些不适用的古怪压缩内容。

我已经剥离了所有的渲染代码,回到了基础——渲染一个带纹理的正方形(以一种没有特别优化的方式)。

预循环代码:

 SDL_Init(SDL_INIT_VIDEO);
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);

SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);

SDL_DisplayMode mode;
SDL_GetDisplayMode(0,0, &mode);
_currentWidth = mode.w;
_currentHeight = mode.h;

SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

_screen = SDL_CreateWindow("window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, _currentWidth, _currentHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE);

SDL_GLContext context = SDL_GL_CreateContext(_screen);
SDL_GL_MakeCurrent(_screen, context); 

glViewport(0, 0, _currentWidth, _currentHeight);

//---
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
const char *vs_source =     "attribute highp vec2 coord2d; "
                            "attribute highp vec2 texcoord;"
                            "varying highp vec2 f_texcoord;"
                            "void main(void) { "
                                "gl_Position = vec4(coord2d, 0.0, 1.0); "
                                "f_texcoord = texcoord;"
                            "}";
glShaderSource(vs, 1, &vs_source, NULL);
glCompileShader(vs);

GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
const char *fs_source =     "varying highp vec2 f_texcoord;"
                            "uniform sampler2D texture;"
                            "void main(void) { "
                                "vec2 flipped_texcoord = vec2(f_texcoord.x, 1.0 - f_texcoord.y);"
                                "gl_FragColor = texture2D(texture, flipped_texcoord);"
                            "}";
glShaderSource(fs, 1, &fs_source, NULL);
glCompileShader(fs);

_program = glCreateProgram();
glAttachShader(_program, vs);
glAttachShader(_program, fs);
glLinkProgram(_program);

//---
GLuint vs2 = glCreateShader(GL_VERTEX_SHADER);
const char *vs_source2 =    "attribute vec2 coord2d; "
                            "void main(void) { "
                                "gl_Position = vec4(coord2d, 0.0, 1.0); "
                            "}";
glShaderSource(vs2, 1, &vs_source2, NULL);
glCompileShader(vs2);

GLuint fs2 = glCreateShader(GL_FRAGMENT_SHADER);
const char *fs_source2 =    "uniform lowp vec4 u_colour;"
                            "void main(void) { "
                                "gl_FragColor = u_colour;"
                            "}";
glShaderSource(fs2, 1, &fs_source2, NULL);
glCompileShader(fs2);

_flatProgram = glCreateProgram();
glAttachShader(_flatProgram, vs2);
glAttachShader(_flatProgram, fs2);
glLinkProgram(_flatProgram);

glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

//---------------------------------------

_screenRect.x = -1.0;
_screenRect.y = -1.0;
_screenRect.w = 2.0;
_screenRect.h = 2.0;

_superDuperFrameBuffer = 0;
_depthRenderBuffer = 0;

glGenTextures(1, &_screenTexture);
glBindTexture(GL_TEXTURE_2D, _screenTexture);
if(_currentWidth < SCREENWIDTH*2 || _currentHeight < SCREENHEIGHT*2) {
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
else {
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCREENWIDTH, SCREENHEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);

glGenRenderbuffers(1, &_depthRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, SCREENWIDTH, SCREENHEIGHT);
glBindRenderbuffer(GL_RENDERBUFFER, 0);

// create a framebuffer object
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glGenFramebuffers(1, &_superDuperFrameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _superDuperFrameBuffer);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _screenTexture, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);

glBindFramebuffer(GL_FRAMEBUFFER, 0);

_defaultFrameBuffer = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_defaultFrameBuffer);

glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glEnable(GL_BLEND);
glEnable(GL_TEXTURE_2D);

SDL_Surface* testSurface = IMG_Load("graphics/bg_01_0.png");

uint32_t rmask;
uint32_t gmask;
uint32_t bmask;
uint32_t amask;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
    rmask = 0xff000000;
    gmask = 0x00ff0000;
    bmask = 0x0000ff00;
    amask = 0x000000ff;
#else
    rmask = 0x000000ff;
    gmask = 0x0000ff00;
    bmask = 0x00ff0000;
    amask = 0xff000000;
#endif 

SDL_Surface *tempSurface = SDL_CreateRGBSurface(0, testSurface->w, testSurface->h, 32, rmask, gmask, bmask, amask);

SDL_SetSurfaceBlendMode(tempSurface, SDL_BLENDMODE_BLEND);
SDL_BlitSurface(testSurface, NULL, tempSurface, NULL);
testSurface = tempSurface;

SDL_FreeSurface(tempSurface);


GLint uniformTexture = glGetUniformLocation(_program, "texture");

_testTexture = 0;
glGenTextures(1, &_testTexture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _testTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glUniform1i(uniformTexture, /*GL_TEXTURE*/0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, testSurface->pixels);

_vboTest = 0;
_vbo_cube_texcoords = 0;
glGenBuffers(1, &_vboTest);
glGenBuffers(1, &_vbo_cube_texcoords);

循环:

...

_quadColour[0] = 0.0f;
_quadColour[1] = 255.0f;
_quadColour[2] = 0.0f;
_quadColour[3] = 1.0f;
drawSquare(0, 0, 20, 20);


glViewport(0, 0, SCREENWIDTH, SCREENHEIGHT);


GLfloat x1 = 0, x2 = 8, y1 = 0, y2 = 8;

glUseProgram(_program);

GLint attributeCoord2d = glGetAttribLocation(_program, "coord2d");
GLint attributeTexcoord = glGetAttribLocation(_program, "texcoord");


glEnableVertexAttribArray(attributeTexcoord);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _testTexture);

GLfloat cube_texcoords[] = {
    0.0, 1.0,
    0.0, 0.0,
    1.0, 0.0,
    1.0, 1.0,
};
glBindBuffer(GL_ARRAY_BUFFER, _vbo_cube_texcoords);
glVertexAttribPointer(attributeTexcoord, 2, GL_FLOAT, GL_FALSE, 0, 0);
glBufferData(GL_ARRAY_BUFFER, sizeof(cube_texcoords), cube_texcoords, GL_STATIC_DRAW);

glEnableVertexAttribArray(attributeCoord2d);

GLfloat triangle_vertices[] = {
    x1, y2,
    x1, y1,
    x2, y1,
    x2, y2,
};
glBindBuffer(GL_ARRAY_BUFFER, _vboTest);
glVertexAttribPointer(attributeCoord2d, 2, GL_FLOAT, GL_FALSE, 0, 0);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangle_vertices), triangle_vertices, GL_STATIC_DRAW);

glDrawArrays(GL_TRIANGLE_FAN, 0, 4);


SDL_GL_SwapWindow(_screen);

...

显然,我添加了一些东西只是为了测试目的,比如将纹理转换为 RGBA 等等。它会绘制一个绿色的小方块,然后是一个带纹理的方块。

代码可能很乱,但重点是 - 两个不同的结果:

Galaxy Tab 2 7.0 (bork) http://i.imgur.com/ht6LvFV.png

Nexus 7(正确) http://i.imgur.com/p4acmIq.png

我该如何解决这个问题?

【问题讨论】:

  • 您是否在其他设备上尝试过由于 GPU 架构而获得了类似的结果? (不知道这是由于立即渲染还是延迟渲染)
  • 我不够富有,买不起无数的设备 :P 但到目前为止,它在其他所有方面都有效。我相信较旧的 Kindle HDXes 可能存在类似的故障 - 亚马逊只是报告“黑屏”。我还被告知它在 2012 Nexus 7 上失败了(但不是我拥有的 2013 年)。在几款 HTC 设备上取得了成功,所以它非常成功。
  • 在 Opengl ES 2 上仍然需要 2 纹理的幂。非 2 纹理的幂可能在某些设备上工作。
  • @RichardCritten ES 2.0 支持 NPOT 纹理,但有一些限制(没有 mipmapping,只有 CLAMP_TO_EDGE 环绕模式)。

标签: android opengl-es android-ndk opengl-es-2.0


【解决方案1】:

许多 GLES 2.0 设备仍需要二次幂纹理。来自 GLES 2.0 规范 (https://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.25.pdf) 的第 3.8.2 节:

"从片段着色器调用采样器将返回 (R; G;B;A) = (0; 0; 0; 1) 如果满足以下任一条件: ... 调用二维采样器,对应的纹理图像是非二次幂图像(如 Mipmapping 讨论中所述3.7.7 节的),并且纹理环绕模式不是 CLAMP_TO_EDGE,或者缩小过滤器既不是 NEAREST 也不是 LINEAR。"

假设 SCREENHEIGHT/SCREENWIDTH 是您设备的尺寸,则您违反了此限制。如果您的设备支持某些 NPOT 扩展,您可以忽略此限制,例如 GL_OES_texture_npot (https://www.khronos.org/registry/gles/extensions/OES/OES_texture_npot.txt),尽管根据我的经验,当 npot 纹理是帧缓冲区的颜色目标时,一些报告此扩展的设备仍将纹理采样为黑色。最好的做法是在 ES 2.0 中始终使用 POT 渲染目标。

【讨论】:

  • 贴出的代码中,换行方式CLAMP_TO_EDGE,缩小过滤器LINEAR。所以没有违反任何限制。
  • 我在 Galaxy Tab 2 P5110(Android 版本 4.2.2)上遇到了这个问题。我错误地编辑了一个 PNG,所以它的宽度为 226 而不是 256 用作纹理。 Galaxy II 手机和 Nexus 4 都使用位图没有问题,而 Tab 2 P5110 没有渲染。对 glGetError 的调用返回零。我检查了 decodeResource 踢出的位图,它报告没有 alpha、premultipled false 和 config ARGB_8888;
【解决方案2】:

此代码中有一些错误和可能的问题:

  • 如果 GLSL 编译器对错误检查非常严格,则不应编译您的片段着色器。由于您没有指定默认精度,并且浮点/向量/矩阵类型没有默认精度,因此所有声明都需要显式精度。此变量缺少它:

    vec2 flipped_texcoord = vec2(f_texcoord.x, 1.0 - f_texcoord.y);
    

    如果你想坚持highp,应该是:

    highp vec2 flipped_texcoord = vec2(f_texcoord.x, 1.0 - f_texcoord.y);
    
  • 这个调用有一个错误的参数:

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _screenTexture, 0);
    

    由于您要附加纹理,因此第三个参数必须是 GL_TEXTURE_2D(您需要使用 glFramebufferRenderbuffer 来附加渲染缓冲区):

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0);
    
  • 确保设备支持 OES_packed_depth_stencil 扩展,因为您在此处使用它:

    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, SCREENWIDTH, SCREENHEIGHT);
    
  • 这个代码序列没有多大意义:

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    
    _defaultFrameBuffer = 0;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_defaultFrameBuffer);
    

    你刚刚绑定了 framebuffer 0,所以这里当前的 framebuffer 绑定永远是 0。如果您担心默认帧缓冲区可能不是 0,则必须在第一次更改绑定之前查询该值。

  • 这不是 ES 2.0 中的有效调用:

    glEnable(GL_TEXTURE_2D);
    

    只有在固定功能的 OpenGL 中才需要启用纹理。一旦你使用着色器,它只会在着色器...使用纹理的任何时候使用纹理。无需显式启用任何内容。

【讨论】:

  • 其中一些是愚蠢的疏忽,或者是我戳东西看会发生什么的结果。但是,对片段着色器的更改似乎已经解决了一些问题,所以这是个好消息。
  • 是的,看起来问题是缺少“highp”。我渴望有一天我们都在使用常规的 OpenGL,并且设备不再那么挑剔。非常感谢,这意味着我可以在本周末之前获得补丁。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-27
  • 2012-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-08
相关资源
最近更新 更多