无法在 OpenGL ES 上写入 GL_RGBA32UI FBO 纹理
Can't write to GL_RGBA32UI FBO texture on OpenGL ES
我有两个 GL_RGBA32UI
FBO 纹理,用于存储每个纹素的粒子 positions/velocities 当前状态。
第一次我只填了一次这样的数据:
Gdx.gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_RGBA32UI, width, height, 0, GL30.GL_RGBA_INTEGER, GL20.GL_UNSIGNED_INT, buffer);
在每个渲染循环中,第二个是通过着色器写入的,而第一个用作纹理,第二个用作目标。我通过绘制一个从 [-1, -1]
到 [1, 1]
的四边形来做到这一点,同时将视口设置在 [0, 0]
和 [textureSize, textureSize]
之间。这样,在片段着色器中,每个纹素都有一个着色器 运行。在每个 运行 中,我读取第一个纹理作为输入,更新它并将其写出到第二个纹理。
然后我使用不同的着色器和网格将第二个 FBO 的纹理渲染到屏幕上,其中每个纹素将由网格中的一个顶点表示。这样我就可以从纹理中提取粒子位置并在顶点着色器中相应地设置 gl_Position
。
之后我切换第一个和第二个 FBO 并继续下一个渲染循环。这意味着这两个 FBO 被用作基于 GPU 的渲染数据存储。
这在桌面应用程序甚至 Android 模拟器中都可以正常工作。但是它在真实 Android 设备上失败了:特定循环的第二个 FBO 纹理仅在真实 Android 设备上更新后始终具有值 [0, 0, 0, 0]
。不过,仅从第一个缓冲区渲染数据时,它完全可以正常工作。
有什么想法吗?
我的更新着色器(获取第一个 FBO 的纹理并将其渲染到第二个)如下。
顶点着色器:
#version 300 es
precision mediump float;
in vec2 a_vertex;
out vec2 v_texCoords;
void main()
{
v_texCoords = a_vertex / 2.0 + 0.5;
gl_Position = vec4(a_vertex, 0, 1);
}
片段着色器:
#version 300 es
precision mediump float;
precision mediump usampler2D;
uniform usampler2D u_positionTexture;
uniform float u_delta;
in vec2 v_texCoords;
out uvec4 fragColor;
void main()
{
uvec4 position_raw = texture(u_positionTexture, v_texCoords);
vec2 position = vec2(
uintBitsToFloat(position_raw.x),
uintBitsToFloat(position_raw.y)
);
vec2 velocity = vec2(
uintBitsToFloat(position_raw.z),
uintBitsToFloat(position_raw.w)
);
// Usually I would alter position and velocity vector here and write it back
// like this:
// position += (velocity * u_delta);
//
// fragColor = uvec4(
// floatBitsToUint(position.x),
// floatBitsToUint(position.y),
// floatBitsToUint(velocity.x),
// floatBitsToUint(velocity.y));
// Even with this the output is 0 on all channels:
fragColor = uvec4(
floatBitsToUint(50.0),
floatBitsToUint(50.0),
floatBitsToUint(0.0),
floatBitsToUint(0.0));
// Even writing the input directly would not make the correct values appear in the texture pixels:
// fragColor = position_raw;
}
我如何更新纹理(从 fbo1 到 fbo2):
private void updatePositions(float delta) {
fbo2.begin();
updateShader.bind();
Gdx.gl20.glViewport(0, 0, textureSize, textureSize);
fbo1.getColorBufferTexture().bind(0);
updateShader.setUniformf("u_delta", delta);
updateShader.setUniformi("u_positionTexture", 0);
Gdx.gl20.glDisable(GL20.GL_BLEND);
Gdx.gl20.glBlendFunc(GL20.GL_ONE, GL20.GL_ZERO);
updateMesh.render(updateShader, GL20.GL_TRIANGLE_STRIP);
fbo2.end();
}
如果您正在读取每个组件 32 位的纹理,您需要一个 highp
采样器并且需要将结果存储在一个 highp
变量中。
目前您正在为 usample2D
指定 mediump
,默认的 int
精度也是 mediump
。对于整数,mediump
被指定为“至少”16 位,因此其中任何一个都可能导致您的 32 位值被截断。
请注意“至少”——一个实现以更高的精度存储它是合法的——所以你可能会发现在某些实现(比如模拟器)上“它恰好可以工作”,因为该实现选择使用更宽的类型。
我有两个 GL_RGBA32UI
FBO 纹理,用于存储每个纹素的粒子 positions/velocities 当前状态。
第一次我只填了一次这样的数据:
Gdx.gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_RGBA32UI, width, height, 0, GL30.GL_RGBA_INTEGER, GL20.GL_UNSIGNED_INT, buffer);
在每个渲染循环中,第二个是通过着色器写入的,而第一个用作纹理,第二个用作目标。我通过绘制一个从 [-1, -1]
到 [1, 1]
的四边形来做到这一点,同时将视口设置在 [0, 0]
和 [textureSize, textureSize]
之间。这样,在片段着色器中,每个纹素都有一个着色器 运行。在每个 运行 中,我读取第一个纹理作为输入,更新它并将其写出到第二个纹理。
然后我使用不同的着色器和网格将第二个 FBO 的纹理渲染到屏幕上,其中每个纹素将由网格中的一个顶点表示。这样我就可以从纹理中提取粒子位置并在顶点着色器中相应地设置 gl_Position
。
之后我切换第一个和第二个 FBO 并继续下一个渲染循环。这意味着这两个 FBO 被用作基于 GPU 的渲染数据存储。
这在桌面应用程序甚至 Android 模拟器中都可以正常工作。但是它在真实 Android 设备上失败了:特定循环的第二个 FBO 纹理仅在真实 Android 设备上更新后始终具有值 [0, 0, 0, 0]
。不过,仅从第一个缓冲区渲染数据时,它完全可以正常工作。
有什么想法吗?
我的更新着色器(获取第一个 FBO 的纹理并将其渲染到第二个)如下。
顶点着色器:
#version 300 es
precision mediump float;
in vec2 a_vertex;
out vec2 v_texCoords;
void main()
{
v_texCoords = a_vertex / 2.0 + 0.5;
gl_Position = vec4(a_vertex, 0, 1);
}
片段着色器:
#version 300 es
precision mediump float;
precision mediump usampler2D;
uniform usampler2D u_positionTexture;
uniform float u_delta;
in vec2 v_texCoords;
out uvec4 fragColor;
void main()
{
uvec4 position_raw = texture(u_positionTexture, v_texCoords);
vec2 position = vec2(
uintBitsToFloat(position_raw.x),
uintBitsToFloat(position_raw.y)
);
vec2 velocity = vec2(
uintBitsToFloat(position_raw.z),
uintBitsToFloat(position_raw.w)
);
// Usually I would alter position and velocity vector here and write it back
// like this:
// position += (velocity * u_delta);
//
// fragColor = uvec4(
// floatBitsToUint(position.x),
// floatBitsToUint(position.y),
// floatBitsToUint(velocity.x),
// floatBitsToUint(velocity.y));
// Even with this the output is 0 on all channels:
fragColor = uvec4(
floatBitsToUint(50.0),
floatBitsToUint(50.0),
floatBitsToUint(0.0),
floatBitsToUint(0.0));
// Even writing the input directly would not make the correct values appear in the texture pixels:
// fragColor = position_raw;
}
我如何更新纹理(从 fbo1 到 fbo2):
private void updatePositions(float delta) {
fbo2.begin();
updateShader.bind();
Gdx.gl20.glViewport(0, 0, textureSize, textureSize);
fbo1.getColorBufferTexture().bind(0);
updateShader.setUniformf("u_delta", delta);
updateShader.setUniformi("u_positionTexture", 0);
Gdx.gl20.glDisable(GL20.GL_BLEND);
Gdx.gl20.glBlendFunc(GL20.GL_ONE, GL20.GL_ZERO);
updateMesh.render(updateShader, GL20.GL_TRIANGLE_STRIP);
fbo2.end();
}
如果您正在读取每个组件 32 位的纹理,您需要一个 highp
采样器并且需要将结果存储在一个 highp
变量中。
目前您正在为 usample2D
指定 mediump
,默认的 int
精度也是 mediump
。对于整数,mediump
被指定为“至少”16 位,因此其中任何一个都可能导致您的 32 位值被截断。
请注意“至少”——一个实现以更高的精度存储它是合法的——所以你可能会发现在某些实现(比如模拟器)上“它恰好可以工作”,因为该实现选择使用更宽的类型。