OpenGL:渲染到大于屏幕尺寸的纹理和glViewport

4

我已经在解决这个问题一段时间了,现在需要向更广泛的社区寻求帮助。我阅读了许多关于此主题的StackOverflow问题,但尚未找到相关的解决方案。

我有一个成熟的Android OpenGL项目,它将渲染到纹理,然后将该纹理渲染到屏幕上。这种机制对我的应用程序非常重要,我对其有很多历史和信心。最近,我添加了新功能来内部截取渲染的屏幕截图;也就是说,我的应用程序能够将渲染的纹理保存为文件。这些图像传统上都是与显示器大小完全相同的。

现在,我想生成比屏幕尺寸更大的图像,以便生成的截图反映更大的图像大小,但在显示在屏幕上时也会缩放到屏幕尺寸。这应该是一个简单而容易的过程,然而我得到了意外的结果。生成的截图是正确的大小,但除了屏幕大小的区域之外,其他地方都是空的。例如,如果渲染的纹理和生成的截图打算是屏幕显示大小的4倍(每个维度X和Y的大小为屏幕大小的两倍),则截图图像文件将是预期的大小,但只有图像的左上象限被绘制了。在这个例子中,这是生成的截图结果。我的视口是768x887,生成的截图正确地为1536x1774,在截图中,唯一有颜色的区域是768x887。对于我们这里的目的,我用于渲染纹理的片段着色器是坐标映射到屏幕上的测试...

gl_FragColor = vec4(uv.x, 0.0, uv.y, 1.0);  // during render to texture

请注意,当我们在执行期间将相同的纹理绘制到屏幕上时,整个屏幕都会根据该着色器进行染色。为什么只有一个象限填充了截图,而不是整个屏幕?当这个纹理被绘制在屏幕上时,为什么它只显示屏幕大小的一部分,而不是带有三个空白象限的整个纹理?
我从GLSurfaceView.Renderer.onSurfaceChanged()中获取视口的原始大小,并将其存储到_viewportWidth和_viewportHeight中。当我创建帧缓冲纹理时,我传统上是直接从_viewportWidth和_viewportHeight创建它。现在,我有一个例子...
float quality = 2f;
_frameBufferWidth = (int)((float)_viewportWidth * quality);
_frameBufferHeight = (int)((float)_viewportHeight * quality);

我需要生成大小为_frameBufferWidth乘以_frameBufferHeight的帧缓冲区。

我调用了两次glViewport()。在第一次调用glBindframebuffer()渲染到纹理而不是屏幕后,进行相关错误处理后,我调用glViewport(0, 0, _frameBufferWidth, _frameBufferHeight),没有出现错误。当我稍后想要将这个纹理绘制到屏幕上时,我进行了第二个glBindframebuffer()调用,紧接着立即调用glViewport(0, 0, _viewportWidth, _viewportHeight)。我的想法是,原始的渲染到纹理正在进入一个_frameBufferWidth乘以_frameBufferHeight大小的图像中,当我们在屏幕上呈现它时,我们想要一个_viewportWidth乘以_viewportHeight的大小。

有什么想法我可能会错过什么吗?提前感谢。

编辑(2016年3月10日): 我刚尝试了quality=0.5f并且得到了不寻常的结果。我更愿意分享更多的图像来澄清这种情况,但我是一个新成员,只允许两个。当我们使用quality=0.5f绘制到屏幕时,根据上面的GLSL代码,屏幕的颜色是正确的:显示与上面链接的截图的768x887左上象限(对应于quality=2f)完全相同。然而,生成的quality=0.5f截图,颜色与屏幕不同。这个截图的大小为384x443,但仍然被呈现为768x887,并且只裁剪出了384x443的一部分。

尽管代码表明不同,但似乎我们总是渲染到一个_viewportWidth乘以_viewportHeight的区域,而不是预期的_frameBufferWidth乘以_frameBufferHeight区域。

我基本上为两个渲染通道都有一个全屏四边形,并且习惯于它可以正常工作。当我渲染到屏幕时,我会对我刚刚渲染的纹理进行采样:

gl_FragColor = texture2D(u_sampler, uv);  // during render to screen
访问我们渲染的纹理,在两个维度上都是[0,1]。因此,为了使屏幕显示任何内容,它必须进行纹理查找以获取其颜色信息。因此,屏幕上显示的明亮的红色和蓝色必须最初存在于帧缓冲区中,即使在正确大小的截图中缺失。

这听起来非常合理。您有其他设备可以测试吗?最好是来自不同供应商,具有不同GPU的设备? - Reto Koradi
嗨,Swifter。我正在使用OpenGL ES 2.0,它不使用glBlitFramebuffer。我正在使用一种可能简单/直接的组合,包括glBindFramebuffer、glFramebufferTexture2D和glTexImage2D。我会进一步考虑这个问题,并可能发布更具体的调用。感谢您的建议和帮助。 - patriot4code
似乎你的glViewport调用没有起作用。你是否通过调用getViewport进行了检查?另外请注意,glViewport不会设置帧缓冲视口,而是GL状态的一部分。你的操作顺序应该是:绑定离屏帧缓冲->设置扩大的视口->在离屏上绘制->绑定屏幕帧缓冲->设置屏幕视口->在屏幕上绘制。这是你的情况吗? - Swifter
嗨,Swifter。没错,那就是我的操作顺序。使用glGet*是一个很好的建议,我刚刚在每个glViewport调用之后尝试了一下。查询结果验证了视口内部被正确设置。 - patriot4code
@patriot4code:我认为你应该发布更多的代码。现在情况并不是很清楚。 - derhass
显示剩余2条评论
1个回答

2

我之前也遇到了同样的问题,就是在iOS上使用openGL ES时出现了这个问题。我尝试将渲染目标设置为一个4096*4096的帧缓冲区,但它总是呈现在左上角。

我找到的解决方案是添加

glViewport(0, 0, 4096, 4096)

在任何其他函数之前,例如

glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

在主渲染函数中,我后来可以使用以下方式进行渲染:

glViewport(0, 0, view.bounds.width*3, view.bounds.height*3);

由于glViewport会将标准化坐标转换为像素坐标,因此需要注意的是,在iOS上,视图的大小不是以像素为单位而是以点为单位,因此需要乘以3。您可以在Android上检查是否获得了实际的像素坐标。


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