V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
begeekmyfriend
V2EX  ›  Android

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

  •  
  •   begeekmyfriend · 2016-10-24 18:08:12 +08:00 · 9500 次点击
    这是一个创建于 2982 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大家好,我是安卓直播端开源项目 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 基本满足性能问题。

    第 1 条附言  ·  2016-10-25 09:37:48 +08:00

    res/raw/default_vertex.glsl

    attribute vec4 position;
    attribute vec4 inputTextureCoordinate;
    
    varying vec2 textureCoordinate;
    
    uniform mat4 textureTransform;
    
    void main() {
    	textureCoordinate = (textureTransform * inputTextureCoordinate).xy;
    	gl_Position = position;
    }
    

    渲染代码在此

    7 条回复    2016-10-25 19:59:20 +08:00
    jukka
        1
    jukka  
       2016-10-25 01:50:39 +08:00
    - -# 你直接贴代码上来吧,还要去你的仓库里翻代码。

    你问题提供的信息太少了啊。
    vertex shader 怎么写的?
    你的顶点数据的坐标系是怎么设置的?(-1, 1) normalized 的么?
    begeekmyfriend
        2
    begeekmyfriend  
    OP
       2016-10-25 09:38:58 +08:00
    @jukka 附言里添加了信息,还需要啥尽管说
    jukka
        3
    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
        4
    begeekmyfriend  
    OP
       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
        5
    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
        6
    begeekmyfriend  
    OP
       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
        7
    jukka  
       2016-10-25 19:59:20 +08:00 via iPhone   ❤️ 1
    :) 这个 issue 可以关闭了(手动滑稽
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2850 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 14:48 · PVG 22:48 · LAX 06:48 · JFK 09:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.