OpenGL ES 2.0:如何以较低的分辨率渲染到 frameBuffer 以进行环境映射?
OpenGL ES 2.0: How to render to a frameBuffer in a lower resolution for environment mapping?
我正在尝试使用 OpenGL ES 2.0 在 Android 上实现动态环境反射。
为此,我将相机设置在反射对象的位置,并在 6 个不同方向(每个轴两个)渲染到屏幕外渲染缓冲区以构建立方体贴图,但这非常慢,所以我的想法是使立方体贴图具有较低的分辨率以加快速度。我认为这应该很简单,但我不明白我的观察。
我想查看这 6 次渲染的结果以检查结果是否符合预期,因此我在渲染下一次之前将它们作为 png 文件导出到磁盘。我用 1024x1024 的帧缓冲区渲染一次,用 256x256 渲染一次。但是,当我查看导出的文件时,我可以看到 256.png 只包含较大文件的一小部分内容。我希望它们具有相同的内容(视场,如果您愿意)但分辨率不同 ("bigger pixels"),但事实并非如此。
我有静态常量 REFLECTION_TEX_WIDTH 和 REFLECTION_TEX_HEIGHT 来设置创建的纹理和渲染缓冲区的宽度和高度,我将这些常量用于创建和导出。但是导出的文件从来没有像我预期的那样覆盖那么多区域。当我将这些尺寸设置得非常大时,比如每个 2000,渲染区域似乎覆盖了大约 1080x1550 像素,文件的其余部分仍然是黑色。有人能告诉我这是怎么回事吗?
我不确定问题出在我对framebuffer如何工作的理解上,或者渲染是否正确,但问题是在我的文件导出中引入的...那些文件导出方法是从互联网上复制的,我不是很懂。
我想渲染相同的 area/FOV 但分辨率较低。是不是要求太多了?
然后是一些代码。
初始化:
// create 6 textures for the dynamic environment reflection
final int skyboxFaces=6;
final int[] textureId=new int[1];
GLES20.glGenTextures(1, textureId, 0);
skyboxTexture=textureId[0];
ShaderFactory.checkGLError("initRendering::createSkyboxTextures");
GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, skyboxTexture);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
for(int i=0; i < skyboxFaces; i++)
{
GLES20.glTexImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GLES20.GL_RGBA,
REFLECTION_TEX_WIDTH, REFLECTION_TEX_HEIGHT, 0, GLES20.GL_RGBA,
GLES20.GL_UNSIGNED_BYTE, null);
ShaderFactory.checkGLError("initRendering::setSkyboxTexture(" + i + ")");
}
// create renderbuffer and bind 16-bit depth buffer
renderBuffers=new int[1];
GLES20.glGenRenderbuffers(1, renderBuffers, 0);
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderBuffers[0]);
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16,
REFLECTION_TEX_WIDTH, REFLECTION_TEX_HEIGHT);
ShaderFactory.checkGLError("initRendering::createRenderbuffer");
frameBuffers=new int[1];
GLES20.glGenFramebuffers(1, frameBuffers, 0);
// GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
// GLES20.GL_RENDERBUFFER, frameBuffers[0]);
ShaderFactory.checkGLError("initRendering::createFrameBuffer");
然后在渲染循环中我执行以下操作:
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[0]);
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderBuffers[0]);
// assign the cubemap texture to the framebuffer
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + bufferId, skyboxTexture, 0);
// assign the depth renderbuffer to the framebuffer
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
GLES20.GL_RENDERBUFFER, frameBuffers[0]);
// clear the current framebuffer (color and depth)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
为了将结果导出为文件,我这样做了:
public void savePNG(final int x, final int y, final int w, final int h, final String name)
{
final Bitmap bmp=savePixels(x, y, w, h);
try
{
final File file=new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), name);
final File parent=file.getParentFile();
// create parent directories if necessary
if(null != parent && !parent.isDirectory())
parent.mkdirs();
// delete existing file to avoid mixing old data with new
if(file.exists())
file.delete();
final FileOutputStream fos=new FileOutputStream(file);
bmp.compress(CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
}
catch(final FileNotFoundException e)
{
// TODO Auto-generated catch block
LOG.error("problem " + e);
}
catch(final IOException e)
{
// TODO Auto-generated catch block
LOG.error("problem " + e);
}
}
// TODO: magic imported code
public Bitmap savePixels(final int x, final int y, final int w, final int h)
{
final int b[]=new int[w * (y + h)];
final int bt[]=new int[w * h];
final IntBuffer ib=IntBuffer.wrap(b);
ib.position(0);
GLES20.glReadPixels(x, 0, w, y + h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);
for(int i=0, k=0; i < h; i++, k++)
{
// OpenGL bitmap is incompatible with Android bitmap and needs some correction.
for(int j=0; j < w; j++)
{
final int pix=b[i * w + j];
final int pb=(pix >> 16) & 0xff;
final int pr=(pix << 16) & 0x00ff0000;
final int pix1=(pix & 0xff00ff00) | pr | pb;
bt[(h - k - 1) * w + j]=pix1;
}
}
final Bitmap sb=Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
return sb;
}
您似乎没有在渲染到 FBO 之前设置视口。在设置 FBO 渲染期间,添加此调用:
glViewport(0, 0, REFLECTION_TEX_WIDTH, REFLECTION_TEX_HEIGHT);
你可以把它放在你有 glClear()
的地方。在完成 FBO 渲染之后,在渲染到默认帧缓冲区之前,不要忘记将其设置回默认帧缓冲区的大小。
视口大小是全局状态,默认为默认帧缓冲区的初始大小。因此,无论何时您使用大小不同于默认绘图表面的 FBO,都需要相应地设置视口。
我正在尝试使用 OpenGL ES 2.0 在 Android 上实现动态环境反射。
为此,我将相机设置在反射对象的位置,并在 6 个不同方向(每个轴两个)渲染到屏幕外渲染缓冲区以构建立方体贴图,但这非常慢,所以我的想法是使立方体贴图具有较低的分辨率以加快速度。我认为这应该很简单,但我不明白我的观察。
我想查看这 6 次渲染的结果以检查结果是否符合预期,因此我在渲染下一次之前将它们作为 png 文件导出到磁盘。我用 1024x1024 的帧缓冲区渲染一次,用 256x256 渲染一次。但是,当我查看导出的文件时,我可以看到 256.png 只包含较大文件的一小部分内容。我希望它们具有相同的内容(视场,如果您愿意)但分辨率不同 ("bigger pixels"),但事实并非如此。
我有静态常量 REFLECTION_TEX_WIDTH 和 REFLECTION_TEX_HEIGHT 来设置创建的纹理和渲染缓冲区的宽度和高度,我将这些常量用于创建和导出。但是导出的文件从来没有像我预期的那样覆盖那么多区域。当我将这些尺寸设置得非常大时,比如每个 2000,渲染区域似乎覆盖了大约 1080x1550 像素,文件的其余部分仍然是黑色。有人能告诉我这是怎么回事吗?
我不确定问题出在我对framebuffer如何工作的理解上,或者渲染是否正确,但问题是在我的文件导出中引入的...那些文件导出方法是从互联网上复制的,我不是很懂。
我想渲染相同的 area/FOV 但分辨率较低。是不是要求太多了?
然后是一些代码。 初始化:
// create 6 textures for the dynamic environment reflection
final int skyboxFaces=6;
final int[] textureId=new int[1];
GLES20.glGenTextures(1, textureId, 0);
skyboxTexture=textureId[0];
ShaderFactory.checkGLError("initRendering::createSkyboxTextures");
GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, skyboxTexture);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
for(int i=0; i < skyboxFaces; i++)
{
GLES20.glTexImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GLES20.GL_RGBA,
REFLECTION_TEX_WIDTH, REFLECTION_TEX_HEIGHT, 0, GLES20.GL_RGBA,
GLES20.GL_UNSIGNED_BYTE, null);
ShaderFactory.checkGLError("initRendering::setSkyboxTexture(" + i + ")");
}
// create renderbuffer and bind 16-bit depth buffer
renderBuffers=new int[1];
GLES20.glGenRenderbuffers(1, renderBuffers, 0);
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderBuffers[0]);
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16,
REFLECTION_TEX_WIDTH, REFLECTION_TEX_HEIGHT);
ShaderFactory.checkGLError("initRendering::createRenderbuffer");
frameBuffers=new int[1];
GLES20.glGenFramebuffers(1, frameBuffers, 0);
// GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
// GLES20.GL_RENDERBUFFER, frameBuffers[0]);
ShaderFactory.checkGLError("initRendering::createFrameBuffer");
然后在渲染循环中我执行以下操作:
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[0]);
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderBuffers[0]);
// assign the cubemap texture to the framebuffer
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + bufferId, skyboxTexture, 0);
// assign the depth renderbuffer to the framebuffer
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
GLES20.GL_RENDERBUFFER, frameBuffers[0]);
// clear the current framebuffer (color and depth)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
为了将结果导出为文件,我这样做了:
public void savePNG(final int x, final int y, final int w, final int h, final String name)
{
final Bitmap bmp=savePixels(x, y, w, h);
try
{
final File file=new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), name);
final File parent=file.getParentFile();
// create parent directories if necessary
if(null != parent && !parent.isDirectory())
parent.mkdirs();
// delete existing file to avoid mixing old data with new
if(file.exists())
file.delete();
final FileOutputStream fos=new FileOutputStream(file);
bmp.compress(CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
}
catch(final FileNotFoundException e)
{
// TODO Auto-generated catch block
LOG.error("problem " + e);
}
catch(final IOException e)
{
// TODO Auto-generated catch block
LOG.error("problem " + e);
}
}
// TODO: magic imported code
public Bitmap savePixels(final int x, final int y, final int w, final int h)
{
final int b[]=new int[w * (y + h)];
final int bt[]=new int[w * h];
final IntBuffer ib=IntBuffer.wrap(b);
ib.position(0);
GLES20.glReadPixels(x, 0, w, y + h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);
for(int i=0, k=0; i < h; i++, k++)
{
// OpenGL bitmap is incompatible with Android bitmap and needs some correction.
for(int j=0; j < w; j++)
{
final int pix=b[i * w + j];
final int pb=(pix >> 16) & 0xff;
final int pr=(pix << 16) & 0x00ff0000;
final int pix1=(pix & 0xff00ff00) | pr | pb;
bt[(h - k - 1) * w + j]=pix1;
}
}
final Bitmap sb=Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
return sb;
}
您似乎没有在渲染到 FBO 之前设置视口。在设置 FBO 渲染期间,添加此调用:
glViewport(0, 0, REFLECTION_TEX_WIDTH, REFLECTION_TEX_HEIGHT);
你可以把它放在你有 glClear()
的地方。在完成 FBO 渲染之后,在渲染到默认帧缓冲区之前,不要忘记将其设置回默认帧缓冲区的大小。
视口大小是全局状态,默认为默认帧缓冲区的初始大小。因此,无论何时您使用大小不同于默认绘图表面的 FBO,都需要相应地设置视口。