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) } }