OpenGL ES通过缩小GLSurfaceView来解决纹理贴图变形的问题

一、概述

  在使用OpenGL ES做纹理贴图的时候,图片有小有大。默认情况下纹理是撑满整个屏幕的。

  这就导致大图会被压扁、小图会被拉伸。这种体验相当不好。

  解决此问题的其中一种方式是:通过缩小GLSurfaceView的宽或高来解决问题。ps:公式可以看做是固定的,直接使用即可。

  1.根据屏幕及图像的宽高分别计算出宽高比

 var bitmapScaleRadio = bitmapWidth.toFloat() / bitmapHeight.toFloat()//bitmap的宽高比
        var viewScaleRadio = mSurfaceWidth.toFloat() / mSurfaceHeight.toFloat()//视图的宽高比

  2.根据屏幕及图像宽高比来计算视口的实际使用宽高(即缩小或放大到多少才不会导致图像变形)

if (bitmapScaleRadio > viewScaleRadio) {//需要缩小
            mViewWidth = mSurfaceWidth
            mViewHeight = (mSurfaceWidth / bitmapScaleRadio).toInt()
        } else if (bitmapScaleRadio < viewScaleRadio) {
            mViewHeight = mSurfaceHeight
            mViewWidth = (mSurfaceHeight * bitmapScaleRadio).toInt()
        } else {
            mViewWidth = mSurfaceWidth
            mViewHeight = mSurfaceHeight
        }

  3.把计算结果交给glViewport

override fun onSurfaceChanged(width: Int, height: Int) {
        mSurfaceWidth = width
        mSurfaceHeight = height
        var mViewBean =
            calculateViewport(mSurfaceWidth, mSurfaceHeight, bitmapSrc.width, bitmapSrc.height)
        //设置最终视口的大小
        var mStartX = (mSurfaceWidth - mViewBean.mViewWidth) / 2
        var mStartY = (mSurfaceHeight - mViewBean.mViewHeight) / 2
        GLES30.glViewport(mStartX, mStartY, mViewBean.mViewWidth, mViewBean.mViewHeight)
    }

 

 

二、代码示例(完整代码)

  1.calculateViewPort方法

/**
     * 通过缩小视口,让视口尽量接近图片宽高,从而使图片不变形
     * @param mSurfaceWidth 屏幕的宽度
     * @param mSurfaceHeight 屏幕宽度
     * @param imageWidth 图片宽度
     * @param imageHeight 图片高度
     * @return 返回视口要缩小到指定值的宽高。也就是最终视口的宽高
     */
    fun calculateViewport(
        mSurfaceWidth: Int,
        mSurfaceHeight: Int,
        imageWidth: Int,
        imageHeight: Int
    ): ViewPortAttribute {
        var mViewWidth: Int = 0
        var mViewHeight: Int = 0
        var bitmapWidth = imageWidth
        var bitmapHeight = imageHeight
        var bitmapScaleRadio = bitmapWidth.toFloat() / bitmapHeight.toFloat()//bitmap的宽高比
        var viewScaleRadio = mSurfaceWidth.toFloat() / mSurfaceHeight.toFloat()//视图的宽高比
        if (bitmapScaleRadio > viewScaleRadio) {//需要缩小
            mViewWidth = mSurfaceWidth
            mViewHeight = (mSurfaceWidth / bitmapScaleRadio).toInt()
        } else if (bitmapScaleRadio < viewScaleRadio) {
            mViewHeight = mSurfaceHeight
            mViewWidth = (mSurfaceHeight * bitmapScaleRadio).toInt()
        } else {
            mViewWidth = mSurfaceWidth
            mViewHeight = mSurfaceHeight
        }
        return ViewPortAttribute(mViewWidth, mViewHeight)
    }

  2.ScaleViewTextureShader.kt,这是一个shader,将其放入GLSurfaceView的回调方法中即可运行

package com.yw.filter.shader

import android.content.Context
import android.graphics.Bitmap
import android.opengl.GLES30
import com.yw.filter.R
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import java.nio.IntBuffer

class ScaleViewTextureShader(var context: Context, var bitmapSrc: Bitmap) : BaseShader() {
    private var vertices = floatArrayOf(//在android上纹理坐标需要上下颠倒之后才能够正确显示,否则会出现镜像、颠倒等问题
        //     ---- 位置 ----    - 纹理坐标 -
        1.0f, 1.0f, 0.0f, 1.0f, 0.0f,   // 右上
        1.0f, -1.0f, 0.0f, 1.0f, 1.0f,   // 右下
        -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,   // 左下
        -1.0f, 1.0f, 0.0f, 0.0f, 0.0f    // 左上
    )
    private var indices = intArrayOf(
        0, 1, 3,//第一个三角形
        1, 2, 3 // 第二个三角形
    )
    private var vertexBuffer: FloatBuffer? = null
    private var indicesBuffer: IntBuffer? = null
    private var mSurfaceWidth: Int = 0
    private var mSurfaceHeight: Int = 0

    private var VAO = IntArray(1)
    var textureId: Int = 0
    override fun onSurfaceCreate() {
        //分配空间填充数据
        vertexBuffer = ByteBuffer
            .allocateDirect(vertices.size * 4)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
        vertexBuffer?.put(vertices)
        vertexBuffer?.position(0)
        indicesBuffer = ByteBuffer
            .allocateDirect(indices.size * 4)
            .order(ByteOrder.nativeOrder())
            .asIntBuffer()
        indicesBuffer?.put(indices)
        indicesBuffer?.position(0)

        //创建并绑定VAO
        GLES30.glGenVertexArrays(1, VAO, 0)
        GLES30.glBindVertexArray(VAO[0])
        //创建并绑定VBO
        var VBO = IntArray(1)
        GLES30.glGenBuffers(1, VBO, 0)
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, VBO[0])
        GLES30.glBufferData(
            GLES30.GL_ARRAY_BUFFER,
            vertices.size * 4,
            vertexBuffer,
            GLES30.GL_STATIC_DRAW
        )

        //创建EBO
        var EBO = IntArray(1)
        GLES30.glGenBuffers(1, EBO, 0)
        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, EBO[0])
        GLES30.glBufferData(
            GLES30.GL_ELEMENT_ARRAY_BUFFER,
            indices.size * 4,
            indicesBuffer,
            GLES30.GL_STATIC_DRAW
        )

        //告知显卡如何解析顶点数据
        GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 4 * 5, 0)
        GLES30.glEnableVertexAttribArray(0)
        //告知显卡如何解析纹理数据
        GLES30.glVertexAttribPointer(1, 2, GLES30.GL_FLOAT, false, 4 * 5, 4 * 3)
        GLES30.glEnableVertexAttribArray(1)

        //销毁
        GLES30.glBindVertexArray(0)
        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)

        textureId = getTextureId(bitmapSrc, GLES30.GL_RGBA)
        loadProgram(context, R.raw.texture_vert, R.raw.texture_frag)
    }

    override fun onDrawFrame() {
        GLES30.glClearColor(0.0f, 0.0f, 1.0f, 1.0f)
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        GLES30.glBindVertexArray(VAO[0])
        GLES30.glUseProgram(programId)
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId)
        var vTextureLocation = GLES30.glGetUniformLocation(programId, "vTexture")
        GLES30.glUniform1i(vTextureLocation, 0)
        GLES30.glDrawElements(GLES30.GL_TRIANGLES, 6, GLES30.GL_UNSIGNED_INT, 0)

        GLES30.glBindVertexArray(0)
    }

    override fun onSurfaceChanged(width: Int, height: Int) {
        mSurfaceWidth = width
        mSurfaceHeight = height
        var mViewBean =
            calculateViewport(mSurfaceWidth, mSurfaceHeight, bitmapSrc.width, bitmapSrc.height)
        //设置最终视口的大小
        var mStartX = (mSurfaceWidth - mViewBean.mViewWidth) / 2
        var mStartY = (mSurfaceHeight - mViewBean.mViewHeight) / 2
        GLES30.glViewport(mStartX, mStartY, mViewBean.mViewWidth, mViewBean.mViewHeight)
    }

}

 

posted on 2024-09-09 16:42  飘杨......  阅读(44)  评论(0编辑  收藏  举报