OpenGL ES - glReadPixels

9
我正在使用glReadPixels拍摄屏幕截图,以实现两张图像之间的“交叉”效果。
在Marmalade SDK模拟器上,截图正常进行,“交叉”效果很好: enter image description here 然而,在iOS和Android设备上看起来是这样的-损坏的: enter image description here
(来源: eikona.info) 我总是将屏幕读取为RGBA 1字节/通道,因为文档中说它始终被接受。
以下是用于拍摄屏幕截图的代码:
uint8* Gfx::ScreenshotBuffer(int& deviceWidth, int& deviceHeight, int& dataLength) {

    /// width/height
    deviceWidth = IwGxGetDeviceWidth();
    deviceHeight = IwGxGetDeviceHeight();
    int rowLength = deviceWidth * 4; /// data always returned by GL as RGBA, 1 byte/each

    dataLength = rowLength * deviceHeight;

    // set the target framebuffer to read
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    uint8* buffer = new uint8[dataLength];
    glReadPixels(0, 0, deviceWidth, deviceHeight, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

    return buffer;
}

void Gfx::ScreenshotImage(CIwImage* img, uint8*& pbuffer) {

    int deviceWidth, deviceHeight, dataLength;

    pbuffer = ScreenshotBuffer(deviceWidth, deviceHeight, dataLength);
    img->SetFormat(CIwImage::ABGR_8888);
    img->SetWidth(deviceWidth);
    img->SetHeight(deviceHeight);
    img->SetBuffers(pbuffer, dataLength, 0, 0);
}

1
如果您删除了上下块,腐败现象是否会消失?只是倒置吗?还是既腐败又倒置? - sarnold
上面的评论现在已经不相关了,因为我重新编辑了我的消息以反映我的新发现。 - Bill Kotsias
2
在分配内存后,尝试将缓冲区清零。这样你就可以知道glReadPixels调用是否实际写入了任何内容。 - Tomas Andrle
@TomA:请注意,你的评论让Bill找到了正确的方向,他想把100分的赏金给你作为提示--请在接下来的18小时内尽快修改你的答案。 :) - sarnold
不,那并不是一个真正的答案,只是一个提示。我很高兴能够帮助。干杯! - Tomas Andrle
显示剩余2条评论
6个回答

5
最终,问题在于内存不足。"new uint8[dataLength];" 没有返回现有的指针,因此整个过程都出现了错误。
TomA,你清除缓冲区的想法实际上帮助我解决了问题。谢谢。

5

这是一个驱动程序的错误。就是这么简单。

驱动程序在视频内存中错误地获取了表面的pitch。您可以在上部线中清楚地看到这一点。此外,您在图像下部看到的垃圾是驱动程序“认为”图像存储的内存,但那里有不同的数据。可能是纹理/顶点数据。

很抱歉,我不知道如何修复这个问题。您可以尝试使用不同的表面格式或启用/禁用多重采样来解决这个问题。


我希望这是真的,因为iOS和Android都存在明显的腐败现象,所以问题一定出在我的身上... - Bill Kotsias
Bill,很可能是在Android和iOS上使用相同的SGX 3D芯片和ARM驱动程序。 - Nils Pipenbrinck

3
我所理解的情况是您试图在被覆盖的窗口上使用glReadPixels。如果视图区域被覆盖,则glReadPixels的结果是未定义的。
请参阅如何使用glDrawPixels()和glReadPixels()像素所有权问题
正如这里所说:

解决方案是创建一个离屏渲染缓冲(FBO)并将其用于渲染。

另一个选择是确保在使用glReadPixels时窗口没有被覆盖。

3

我不了解你使用的Android或SDK,但在iOS上,当我截屏时,必须将缓冲区大小设置为下一个POT纹理的大小,类似于这样:

int x = NextPot((int)screenSize.x*retina);
int y = NextPot((int)screenSize.y*retina);

void *buffer = malloc( x * y * 4 );

glReadPixels(0,0,x,y,GL_RGBA,GL_UNSIGNED_BYTE,buffer);

NextPot函数只是给出下一个POT尺寸,所以如果屏幕尺寸为320x480,则x,y将为512x512。

也许你看到的是缓冲区的环绕,因为它期望一个更大的缓冲区大小?

此外,这可能是在模拟器上工作而不是在设备上工作的原因,我的显卡没有POT尺寸限制,我得到了类似(奇怪的)结果。


1

我没有修复glReadPixels的解决方案。我的建议是您更改算法以避免需要从屏幕读取数据。

看一下此页面。这些人已经在Flash中完成了翻页效果。全部都是2D的,只用阴影渐变实现了幻觉。

我认为您可以采用类似的方法,但在3D方面更好一些。基本上,您必须将效果分成三个部分:面向正面的顶部页面(云),底部页面(女孩)和正面的背面。您必须单独绘制每个部分。您可以轻松地在同一屏幕上绘制面向正面的顶部页面和底部页面,只需使用与拆分线对齐的预设剪辑区域调用每个绘图代码即可。在绘制顶部和后面的页面部分之后,您可以在其上绘制灰色的背面部分,也与拆分线对齐。

采用这种方法,您失去的唯一事物是云图像开始弯曲的一点形变,当然我的方法不会产生任何形变。希望这不会减弱效果,我认为阴影更重要,因为它们可以给出深度效果并且可以隐藏这个小的不一致性。


1

我在安卓设备上使用glReadPixels轻松获取我的安卓游戏的截图。

我还不确定你的情况出了什么问题,需要更多信息。所以让我们开始:

  1. 我建议您不要指定PixelStore格式。我担心您在1字节对齐方面是否真正“使用它”/“知道它的作用”?看起来您得到了您指定的东西 - 一个额外的字节(请看您的图片,总会有一个额外的像素!)而不是完全打包的图像。所以尝试删除这个:

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1);

  2. 我不确定C代码,因为我只在Java中工作过,但这似乎是可能的关键点:

    // width/height deviceWidth = IwGxGetDeviceWidth(); deviceHeight = IwGxGetDeviceHeight();

您是否获取设备大小?您应该使用OpenGL表面的大小,像这样:

public void onSurfaceChanged(GL10 gl, int width, int height) {
int surfaceWidth = width;
int surfaceHeight = height;
}
  1. 你接下来要对捕获的图像做什么?你知道从OpenGL获取的内存块是RGBA格式,但所有非OpenGL图像操作都期望ARGB格式吗?例如,在你的代码中,你期望alpha是第一个位而不是最后一个:

    img->SetFormat(CIwImage::ABGR_8888);

  2. 如果1、2和3都没有帮助你,你可能想把捕获的屏幕保存到手机SD卡上以便以后检查。我有一个程序,可以将OpenGL RGBA块转换为普通位图,在PC上进行检查。我可以与你分享。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接