OpenGL ES 的问题,为何图像仅显示在手机屏幕右上第一象限?

2016-10-24 18:08:12 +08:00
 begeekmyfriend

大家好,我是安卓直播端开源项目 yasea 作者,目前可以通过 OpenGL ES 2.0 将来自摄像头的图像进行滤镜渲染,基本功能已经实现: https://github.com/begeekmyfriend/yasea/tree/gpuimage

然而是对于磨皮,原来使用 glVertexAttribPointer 直接传递顶点数组的效率太差,导致输出帧率下降。幸好 GLES 2.0 支持 VBO ,我将代码改进如下,基本满足要求: https://github.com/begeekmyfriend/yasea/tree/gpuimage-vbo

问题是 VBO 替代后,渲染图像仅出现在屏幕右上第一象限中,大小为全屏四分之一。百思不得其解,我并没有改变顶点数组和纹理数组,也没有改变 glViewport 。请大家帮忙。

P.S.出于兼容性的原因,目前不考虑 GLES 3.0 ,而且 2.0 里的 VBO 基本满足性能问题。

9014 次点击
所在节点    Android
7 条回复
jukka
2016-10-25 01:50:39 +08:00
- -# 你直接贴代码上来吧,还要去你的仓库里翻代码。

你问题提供的信息太少了啊。
vertex shader 怎么写的?
你的顶点数据的坐标系是怎么设置的?(-1, 1) normalized 的么?
begeekmyfriend
2016-10-25 09:38:58 +08:00
@jukka 附言里添加了信息,还需要啥尽管说
jukka
2016-10-25 10:09:43 +08:00
@begeekmyfriend 运行的截图可以贴么,

大致看了下代码,
mGLCubeBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.CUBE.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mGLCubeBuffer.put(TextureRotationUtil.CUBE).position(0);

可以先检查一下这些数值。 mGLCubeBuffer
begeekmyfriend
2016-10-25 10:51:37 +08:00
@jukka 数据源 Cube 应该没有错,因为没有改动过,就是说——

GLES20.glVertexAttribPointer(mGLAttribPosition, 2, GLES20.GL_FLOAT, false, 4 * 2, mGLCubeBuffer)是全屏的。
GLES20.glVertexAttribPointer(mGLAttribPosition, 2, GLES20.GL_FLOAT, false, 4 * 2, 0)就变成右上角 1/4 了。

所以我想看看 default_vertex.glsl 的数据有何区别,比如用 vec4 还是 vec2 ( vec4 是否在 VBO 里面哪些分量变成 0 了导致全屏左下角对应到屏幕中心去了)。可惜无法调试,只能一步一步试了。

另外 V2EX 上我也不知道怎么上图。
jukka
2016-10-25 11:21:43 +08:00
稍微仔细看了一下你的代码。错误应该是在在这里。

GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mGLCubeBuffers[0]);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mGLTextureBuffers[0]);

你可以把 TextureBuffers 的数据改成(-1, -1) 你的画面位置应该就对了。
或者简单的条换一下这两个函数的调用顺序也可以。

OpenGL Client 接口是一个状态机。
OpenGL Client 接口是一个状态机。
OpenGL Client 接口是一个状态机。

在 issue 一个 drawcall 之前只能 bind 一个 buffer 。
你这样写相当于只有一个 buffer 被 bind 了。

以下是修改建议,

在 OpenGL ES 上,最好使用 packed 的 vertex 。
可以参考以下文档。
https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html#//apple_ref/doc/uid/TP40008793-CH107-SW1

也就是说只使用一个 vbo ,而不是给每一个 buffer 都建立一个 vbo 。

举个例子,

struct vertex {
GLfloat pos[2];
GLfloat uv[2];
}

glVertexAttribPointer(loc_pos, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), offsetof(struct vertex, pos)); glVertexAttribPointer(loc_uv, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), offsetof(struct vertex, uv));

offsetof(struct vertex, uv) -> 的值应该是 8
begeekmyfriend
2016-10-25 11:54:42 +08:00
果然你说的对!我查了一下《 OpenGL ES 3.0 编程指南》,它的写法是

```java
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mGLCubeBuffers[0]);
GLES20.glEnableVertexAttribArray(mGLAttribPosition);
GLES20.glVertexAttribPointer(mGLAttribPosition, 4, GLES20.GL_FLOAT, false, 4 * 4, 0);

GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mGLTextureBuffers[0]);
GLES20.glEnableVertexAttribArray(mGLAttribTextureCoordinate);
GLES20.glVertexAttribPointer(mGLAttribTextureCoordinate, 4, GLES20.GL_FLOAT, false, 4 * 4, 0);
```

也就是说,我错误的写法相当于只有一个 buffer 被 bind 了。

另外,除了你所说的 packed 顶点属性,还有一种是每个属性一个 VBO 的写法。文中这样解释:

每个顶点属性可以顺序方式读取,最有可能造成高效内存访问模式。缺点在于如果顶点属性的一个自己需要修改,将造成缓冲区跨距更新。当顶点属性缓冲区为 VBO 形式,需要重新加载整个缓冲区,可以将动态的顶点属性保存在单独缓冲区避免这种效率低下的情况。

当然我的顶点属性是静态的,可以考虑用你的方式修改。

不管怎样,原因找到了,非常感谢您!
jukka
2016-10-25 19:59:20 +08:00
:) 这个 issue 可以关闭了(手动滑稽

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/315098

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX