Android音视频开发入门(七)

通过OpenGL纹理绘制显示一张图片

        • 任务目标
        • 纹理介绍
        • 原理
        • 通过纹理绘制显示图片
          • 1. 设置顶点坐标和纹理坐标
          • 2. 创建着色器
          • 3. 声明FloatBuffer存放坐标数据
          • 4. 创建执行程序
          • 5. 计算变换矩阵
          • 6. 创建纹理
          • 7. 显示图片
        • 参考资料

任务目标

学习 Android 平台 OpenGL ES API,学习纹理绘制,能够使用 OpenGL 显示一张图片

纹理介绍

纹理就是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节。我们可以在图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点。除了图像以外,纹理也可以被用来存储大量的数据,这些数据可以发送到着色器上。

原理

纹理坐标在x轴和y轴上,范围为0.0-1.0之间(这儿以2D纹理为例),使用纹理坐标获取纹理颜色叫做采样。纹理坐标起始与(0.0, 0.0),为左上角,左下角坐标(0.0,1.0),右上角坐标(1.0, 0),右下角坐标(1.0,1.0)。如下图:
Android音视频开发入门(七)_第1张图片
我看有的文章介绍纹理坐标(0,0)是左下角,我试过这样定义,结果是图片显示不正确。关于坐标问题这儿还是有点不太明确,暂时认为跟Android坐标(0,0)点一样。
顶点坐标(0.0,0.0)为屏幕中心点。如下图:
Android音视频开发入门(七)_第2张图片
纹理坐标与顶点坐标应对应传入,否则图片显示可能不正确。

通过纹理绘制显示图片

1. 设置顶点坐标和纹理坐标

顶点坐标

private static final float sPicVertex[] = {
            -1f, 1f,//左上角
            -1f, -1f,//左下角
            1f, 1f,//右上角
            1f, -1f//右下角
    };

相对应的纹理坐标

private static final float sPicCoords[] = {
            0, 0,
            0, 1,
            1, 0,
            1, 1
    };
2. 创建着色器

顶点着色器

private final String mVertexShaderCode =
            "attribute vec4 vPosition;" +
                    "attribute vec2 vCoordinate;" +
                    "uniform mat4 vMatrix;" +
                    "varying vec2 aCoordinate;" +
                    "void main() {" +
                    "   gl_Position = vMatrix * vPosition;" +
                    "   aCoordinate = vCoordinate;" +
                    "}";

其中变量vec2就是纹理坐标,并将这个坐标传给片段着色器

private final String mFragmentShaderCode =
            "precision mediump float;" +
                    "uniform sampler2D vTexture;" +
                    "varying vec2 aCoordinate;" +
                    "void main() {" +
                    "   gl_FragColor = texture2D(vTexture, aCoordinate);" +
                    "}";
3. 声明FloatBuffer存放坐标数据
	private FloatBuffer mVertexBuffer;
    private FloatBuffer mIndexBuffer;
    
	ByteBuffer vertexBuffer = ByteBuffer.allocateDirect(sPicVertex.length * 4);
    vertexBuffer.order(ByteOrder.nativeOrder());
    mVertexBuffer = vertexBuffer.asFloatBuffer();
    mVertexBuffer.put(sPicVertex);
    mVertexBuffer.position(0);
    ByteBuffer coordinateBuffer = ByteBuffer.allocateDirect(sPicCoords.length * 4);
    coordinateBuffer.order(ByteOrder.nativeOrder());
    mIndexBuffer = coordinateBuffer.asFloatBuffer();
    mIndexBuffer.put(sPicCoords);
    mIndexBuffer.position(0);
4. 创建执行程序
	int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, mVertexShaderCode);
    int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, mFragmentShaderCode);

    mProgram = GLES20.glCreateProgram();
    GLES20.glAttachShader(mProgram, vertexShader);
    GLES20.glAttachShader(mProgram, fragmentShader);
    GLES20.glLinkProgram(mProgram);
5. 计算变换矩阵
		try {
            mBitmap = BitmapFactory.decodeStream(App.instance().getResources().getAssets().open("human.jpeg"));
            int width = mBitmap.getWidth();
            int height = mBitmap.getHeight();
            mBitmapRatio = (float) width / height;
        } catch (IOException e) {
            e.printStackTrace();
        }
		float ratio = (float) width / height;

        if (width > height) {
            if (mBitmapRatio > ratio) {
                Matrix.orthoM(mProjectionMatrix, 0, -ratio * mBitmapRatio, ratio * mBitmapRatio,
                        -1, 1, 3, 7);
            } else {
                Matrix.orthoM(mProjectionMatrix, 0, -ratio / mBitmapRatio, ratio / mBitmapRatio,
                        -1, 1, 3, 7);
            }
        } else {
            if (mBitmapRatio > ratio) {
                Matrix.orthoM(mProjectionMatrix, 0, -1, 1,
                        -1 / ratio * mBitmapRatio, 1 / ratio * mBitmapRatio, 3, 7);
            } else {
                Matrix.orthoM(mProjectionMatrix, 0, -1, 1,
                        -mBitmapRatio / ratio, mBitmapRatio / ratio, 3, 7);
            }
        }
        //设置相机位置
        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7, 0, 0, 0, 0, 1, 0);
        //计算变换矩阵
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
6. 创建纹理
private int createTexture() {
        int[] texture = new int[1];
        if (mBitmap != null && !mBitmap.isRecycled()) {
            //生成纹理
            GLES20.glGenTextures(1, texture, 0);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
            //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
            //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
            //设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
            //设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
            //根据以上指定的参数,生成一个2D纹理
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0);
            return texture[0];
        }
        return 0;
    }
7. 显示图片
	@Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        GLES20.glUseProgram(mProgram);
        GLES20.glUniformMatrix4fv(mMatrixHandle, 1, false, mMVPMatrix, 0);
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        GLES20.glEnableVertexAttribArray(mCoordinateHandle);
        GLES20.glUniform1i(mTextureHandle, 0);
        int textureId = createTexture();
        //传入顶点坐标
        GLES20.glVertexAttribPointer(mPositionHandle, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
        //传入纹理坐标
        GLES20.glVertexAttribPointer(mCoordinateHandle, 2, GLES20.GL_FLOAT, false, 0, mIndexBuffer);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    }

最后显示如下图
Android音视频开发入门(七)_第3张图片
至此通过纹理绘制显示一张图片就完成了,代码已上传GitHub。

参考资料

OpenGL ES 纹理贴图

你可能感兴趣的:(Android音视频入门)