XML
文件
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.myapplication.MyMatrixGLSurfaceView
android:id="@+id/glSurfaceView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4" />
<!-- 旋转控制按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="旋转:"
android:textSize="16sp" />
<Button
android:id="@+id/rotateXBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="X轴" />
<Button
android:id="@+id/rotateYBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Y轴" />
<Button
android:id="@+id/rotateZBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Z轴" />
</LinearLayout>
<!-- 缩放控制按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="缩放:"
android:textSize="16sp" />
<Button
android:id="@+id/scaleXBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="X轴" />
<Button
android:id="@+id/scaleYBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Y轴" />
</LinearLayout>
<!-- 位移控制按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="位移:"
android:textSize="16sp" />
<Button
android:id="@+id/translateXPosBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="X轴" />
<Button
android:id="@+id/translateYPosBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Y轴" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="正交投影:" />
<Button
android:id="@+id/resetBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="重置" />
</LinearLayout>
</LinearLayout>
Activity
代码
kotlin
class MainActivity5 : AppCompatActivity(), View.OnClickListener {
companion object {
// 变换增量
private const val ROTATION_DELTA = 10.0f
private const val SCALE_DELTA = 0.25f
private const val TRANSLATE_DELTA = 0.25f
}
private lateinit var glSurfaceView: MyMatrixGLSurfaceView
// 变换参数
private var rotationX = 0f
private var rotationY = 0f
private var rotationZ = 0f
private var scaleX = 1.0f
private var scaleY = 1.0f
private var translateX = 0f
private var translateY = 0f
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main5)
// 初始化GLSurfaceView
glSurfaceView = findViewById(R.id.glSurfaceView)
// 初始化所有按钮并设置点击监听器
findViewById<Button>(R.id.rotateXBtn).setOnClickListener(this)
findViewById<Button>(R.id.rotateYBtn).setOnClickListener(this)
findViewById<Button>(R.id.rotateZBtn).setOnClickListener(this)
findViewById<Button>(R.id.scaleXBtn).setOnClickListener(this)
findViewById<Button>(R.id.scaleYBtn).setOnClickListener(this)
findViewById<Button>(R.id.translateXPosBtn).setOnClickListener(this)
findViewById<Button>(R.id.translateYPosBtn).setOnClickListener(this)
findViewById<Button>(R.id.resetBtn).setOnClickListener(this)
}
override fun onClick(v: View) {
when (v.id) {
// 处理旋转
R.id.rotateXBtn -> {
rotationX += ROTATION_DELTA
glSurfaceView.updateRotation(rotationX, AXIS.X)
}
R.id.rotateYBtn -> {
rotationY += ROTATION_DELTA
glSurfaceView.updateRotation(rotationY, AXIS.Y)
}
R.id.rotateZBtn -> {
rotationZ += ROTATION_DELTA
glSurfaceView.updateRotation(rotationZ, AXIS.Z)
}
// 处理缩放
R.id.scaleXBtn -> {
scaleX += SCALE_DELTA
glSurfaceView.updateScale(scaleX, AXIS.X)
}
R.id.scaleYBtn -> {
scaleY += SCALE_DELTA
glSurfaceView.updateScale(scaleY, AXIS.Y)
}
// 处理位移
R.id.translateXPosBtn -> {
translateX += TRANSLATE_DELTA
glSurfaceView.updateTranslate(translateX, AXIS.X)
}
R.id.translateYPosBtn -> {
translateY += TRANSLATE_DELTA
glSurfaceView.updateTranslate(translateY, AXIS.Y)
}
// 重置所有变换
R.id.resetBtn -> {
resetTransformations()
}
}
// 更新渲染
glSurfaceView.requestRender()
}
private fun resetTransformations() {
// 重置所有变换参数
rotationX = 0f
rotationY = 0f
rotationZ = 0f
scaleX = 1.0f
scaleY = 1.0f
translateX = 0f
translateY = 0f
glSurfaceView?.reset()
}
}
enum class AXIS {
X,
Y,
Z
}
GLSurfaceView
代码
kotlin
class MyMatrixGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {
private var mRenderer = MyMatrixRenderer(context)
init {
// 设置 OpenGL ES 3.0 版本
setEGLContextClientVersion(3)
setRenderer(mRenderer)
// 设置渲染模式, 仅在需要重新绘制时才进行渲染,以节省资源
renderMode = RENDERMODE_WHEN_DIRTY
}
fun updateRotation(angle: Float, axis: AXIS) {
mRenderer?.mDrawData?.apply {
resetMatrix()
computeRotateModelMatrix(angle, axis)
computeMVPMatrix(width, height)
drawCurrentOutput()
}
}
fun updateScale(scale: Float, axis: AXIS) {
mRenderer?.mDrawData?.apply {
resetMatrix()
computeScaleModelMatrix(scale, axis)
computeMVPMatrix(width, height)
drawCurrentOutput()
}
}
fun updateTranslate(translate: Float, axis: AXIS) {
mRenderer?.mDrawData?.apply {
resetMatrix()
computeTranslateModelMatrix(translate, axis)
computeMVPMatrix(width, height)
drawCurrentOutput()
}
}
fun reset() {
mRenderer?.mDrawData?.apply {
resetMatrix()
computeMVPMatrix(width, height)
drawCurrentOutput()
}
}
}
GLSurfaceView.Renderer
代码
kotlin
class MyMatrixRenderer(private val mContext: Context) : GLSurfaceView.Renderer {
var mDrawData: MatrixDrawData? = null
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 当 Surface 创建时调用, 进行 OpenGL ES 环境的初始化操作, 设置清屏颜色为青蓝色 (Red=0, Green=0.5, Blue=0.5, Alpha=1)
GLES30.glClearColor(0.0f, 0.5f, 0.5f, 1.0f)
mDrawData = MatrixDrawData().apply {
initTexture0(mContext, R.drawable.picture)
initShader()
initVertexBuffer()
}
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 当 Surface 尺寸发生变化时调用,例如设备的屏幕方向发生改变, 设置视口为新的尺寸,视口是指渲染区域的大小
GLES30.glViewport(0, 0, width, height)
mDrawData?.resetMatrix()
mDrawData?.computeMVPMatrix(width, height)
}
override fun onDrawFrame(gl: GL10?) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
mDrawData?.drawCurrentOutput()
}
}
GLSurfaceView.Renderer
需要的绘制数据
kotlin
class MatrixDrawData {
private var NO_OFFSET = 0
private val VERTEX_POS_DATA_SIZE = 3
private val TEXTURE_POS_DATA_SIZE = 2
private var mProgram: Int = -1
// VAO(Vertex Array Object), 顶点数组对象, 用于存储VBO
private var mVAO = IntArray(1)
// VBO(Vertex Buffer Object), 顶点缓冲对象,用于存储顶点数据和纹理数据
private var mVBO = IntArray(2)
// IBO(Index Buffer Object), 索引缓冲对象,用于存储顶点索引数据
private var mIBO = IntArray(1)
// 纹理ID
var mTextureID = IntArray(1)
// 有上下翻转的变换矩阵
private var mMVPMatrix = FloatArray(16)
// 投影矩阵
private val mProjectionMatrix = FloatArray(16)
// 观察矩阵
private val mViewMatrix = FloatArray(16)
// 模型矩阵
private val mModelMatrix = FloatArray(16)
// 视口比例
private var mViewPortRatio = 1f
// 准备顶点坐标,分配直接内存
// OpenGL ES坐标系:原点在中心,X轴向右为正,Y轴向上为正,Z轴向外为正
val vertex = floatArrayOf(
-1.0f, 1.0f, 0.0f, // 左上
-1.0f, -1.0f, 0.0f, // 左下
1.0f, 1.0f, 0.0f, // 右上
1.0f, -1.0f, 0.0f, // 右下
)
val vertexBuffer = ByteBuffer.allocateDirect(vertex.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertex)
.position(NO_OFFSET)
// 准备纹理坐标,分配直接内存
// 纹理坐标系:原点在左下角,X轴向右为正,Y轴向上为正
val textureCoords = floatArrayOf(
0.0f, 1.0f, // 左上
0.0f, 0.0f, // 左下
1.0f, 1.0f, // 右上
1.0f, 0.0f, // 右下
)
val textureBuffer = ByteBuffer.allocateDirect(textureCoords.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureCoords)
.position(NO_OFFSET)
// 索引坐标,分配直接内存
val index = shortArrayOf(
0, 1, 2, // 第一个三角形
1, 3, 2, // 第二个三角形
)
val indexBuffer = ByteBuffer.allocateDirect(index.size * 2)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(index)
.position(NO_OFFSET)
// 初始化着色器程序
fun initShader() {
val vertexShaderCode = """#version 300 es
uniform mat4 uMVPMatrix; // 变换矩阵
in vec4 aPosition; // 顶点坐标
in vec2 aTexCoord; // 纹理坐标
out vec2 vTexCoord;
void main() {
// 输出顶点坐标和纹理坐标到片段着色器
gl_Position = uMVPMatrix * aPosition;
vTexCoord = aTexCoord;
}""".trimIndent()
val fragmentShaderCode = """#version 300 es
precision mediump float;
uniform sampler2D uTexture_0;
in vec2 vTexCoord;
out vec4 fragColor;
void main() {
fragColor = texture(uTexture_0, vTexCoord);
}""".trimIndent()
// 加载顶点着色器和片段着色器, 并创建着色器程序
val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader =
LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
mProgram = GLES30.glCreateProgram()
GLES30.glAttachShader(mProgram, vertexShader)
GLES30.glAttachShader(mProgram, fragmentShader)
GLES30.glLinkProgram(mProgram)
// 删除着色器对象
GLES30.glDeleteShader(vertexShader)
GLES30.glDeleteShader(fragmentShader)
}
// 创建VAO, VBO, IBO
fun initVertexBuffer() {
// 绑定VAO
GLES30.glGenVertexArrays(mVAO.size, mVAO, NO_OFFSET)
GLES30.glBindVertexArray(mVAO[0])
// 绑定VBO
GLES30.glGenBuffers(mVBO.size, mVBO, NO_OFFSET)
// 绑定顶点缓冲区数据到VBO[0]
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[0])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
vertex.size * 4,
vertexBuffer,
GLES30.GL_STATIC_DRAW
)
// 解析顶点缓冲区数据到VBO[0]
val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")
GLES30.glEnableVertexAttribArray(positionHandle)
GLES30.glVertexAttribPointer(
positionHandle,
VERTEX_POS_DATA_SIZE,
GLES30.GL_FLOAT,
false,
0,
NO_OFFSET
)
// 解绑顶点缓冲区
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
// 绑定纹理缓冲区数据到VBO[1]
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[1])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
textureCoords.size * 4,
textureBuffer,
GLES30.GL_STATIC_DRAW
)
// 解析纹理缓冲区数据到VBO[1]
val textureHandle = GLES30.glGetAttribLocation(mProgram, "aTexCoord")
GLES30.glEnableVertexAttribArray(textureHandle)
GLES30.glVertexAttribPointer(
textureHandle,
TEXTURE_POS_DATA_SIZE,
GLES30.GL_FLOAT,
false,
0,
NO_OFFSET
)
// 解绑纹理缓冲区
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
// 绑定IBO
GLES30.glGenBuffers(mIBO.size, mIBO, NO_OFFSET)
// 绑定索引缓冲区数据到IBO[0]
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mIBO[0])
GLES30.glBufferData(
GLES30.GL_ELEMENT_ARRAY_BUFFER,
index.size * 2,
indexBuffer,
GLES30.GL_STATIC_DRAW
)
// 解绑VAO
GLES30.glBindVertexArray(0)
// 解绑IBO
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)
}
// 使用着色器程序绘制图形
fun drawSomething(program: Int, mvpMatrix: FloatArray) {
// 解析变换矩阵
val matrixHandle = GLES30.glGetUniformLocation(program, "uMVPMatrix")
GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mvpMatrix, NO_OFFSET)
// 绑定VAO
GLES30.glBindVertexArray(mVAO[0])
// 绘制图形
GLES30.glDrawElements(
GLES30.GL_TRIANGLES,
index.size,
GLES30.GL_UNSIGNED_SHORT,
NO_OFFSET
)
// 解绑VAO
GLES30.glBindVertexArray(0)
}
fun resetMatrix() {
Matrix.setIdentityM(mModelMatrix, NO_OFFSET)
Matrix.setIdentityM(mViewMatrix, NO_OFFSET)
Matrix.setIdentityM(mProjectionMatrix, NO_OFFSET)
Matrix.setIdentityM(mMVPMatrix, NO_OFFSET)
}
// 计算GLSurfaceView变换矩阵
fun computeMVPMatrix(width: Int, height: Int) {
val isLandscape = width > height
mViewPortRatio = if (isLandscape) width.toFloat() / height else height.toFloat() / width
// 计算包围图片的球半径
val radius = sqrt(1f + mViewPortRatio * mViewPortRatio)
val near = 0.1f
val far = near + 2 * radius
// 视图矩阵View Matrix
Matrix.setLookAtM(
mViewMatrix, NO_OFFSET,
0f, 0f, near + radius, // 相机位置
0f, 0f, 0f, // 看向原点
0f, 1f, 0f // 上方向
)
// 投影矩阵Projection Matrix
Matrix.orthoM(
mProjectionMatrix, NO_OFFSET,
if (isLandscape) -mViewPortRatio else -1f, // 左边界
if (isLandscape) mViewPortRatio else 1f, // 右边界
if (isLandscape) -1f else -mViewPortRatio, // 下边界
if (isLandscape) 1f else mViewPortRatio, // 上边界
near, // 近平面
far // 远平面
)
// 最终变换矩阵,第一次变换,模型矩阵 x 视图矩阵 = Model x View, 但是OpenGL ES矩阵乘法是右乘,所以是View x Model
Matrix.multiplyMM(
mMVPMatrix,
NO_OFFSET,
mViewMatrix,
NO_OFFSET,
mModelMatrix,
NO_OFFSET
)
// 最终变换矩阵,第二次变换,模型矩阵 x 视图矩阵 x 投影矩阵 = Model x View x Projection, 但是OpenGL ES矩阵乘法是右乘,所以是Projection x View x Model
Matrix.multiplyMM(
mMVPMatrix,
NO_OFFSET,
mProjectionMatrix,
NO_OFFSET,
mMVPMatrix,
NO_OFFSET
)
// 纹理坐标系为(0, 0), (1, 0), (1, 1), (0, 1)的正方形逆时针坐标系,从Bitmap生成纹理,即像素拷贝到纹理坐标系
// 变换矩阵需要加上一个y方向的翻转, x方向和z方向不改变
Matrix.scaleM(
mMVPMatrix,
NO_OFFSET,
1f,
-1f,
1f,
)
}
fun computeRotateModelMatrix(angleX: Float, axis: AXIS) {
when (axis) {
AXIS.X -> Matrix.rotateM(mModelMatrix, NO_OFFSET, angleX, 1f, 0f, 0f)
AXIS.Y -> Matrix.rotateM(mModelMatrix, NO_OFFSET, angleX, 0f, 1f, 0f)
AXIS.Z -> Matrix.rotateM(mModelMatrix, NO_OFFSET, angleX, 0f, 0f, 1f)
}
}
fun computeScaleModelMatrix(scale: Float, axis: AXIS) {
when (axis) {
AXIS.X -> Matrix.scaleM(mModelMatrix, NO_OFFSET, scale, 1f, 1f)
AXIS.Y -> Matrix.scaleM(mModelMatrix, NO_OFFSET, 1f, scale, 1f)
else -> {}
}
}
fun computeTranslateModelMatrix(translate: Float, axis: AXIS) {
when (axis) {
AXIS.X -> Matrix.translateM(mModelMatrix, NO_OFFSET, translate, 0f, 0f)
AXIS.Y -> Matrix.translateM(mModelMatrix, NO_OFFSET, 0f, translate, 0f)
else -> {}
}
}
// 加载纹理
fun loadTexture(context: Context, resourceId: Int): Int {
val textureId = IntArray(1)
// 生成纹理
GLES30.glGenTextures(1, textureId, 0)
// 绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[0])
// 设置纹理参数
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MIN_FILTER,
GLES30.GL_LINEAR
) // 纹理缩小时使用线性插值
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MAG_FILTER,
GLES30.GL_LINEAR
) // 纹理放大时使用线性插值
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_S,
GLES30.GL_CLAMP_TO_EDGE
) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_T,
GLES30.GL_CLAMP_TO_EDGE
) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
// 加载图片
val options = BitmapFactory.Options().apply {
inScaled = false // 不进行缩放
}
val bitmap = BitmapFactory.decodeResource(context.resources, resourceId, options)
// 将图片数据加载到纹理中
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0)
// 释放资源
bitmap.recycle()
// 解绑纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
Log.e(
"yang",
"loadTexture: 纹理加载成功 bitmap.width:${bitmap.width} bitmap.height:${bitmap.height}"
)
return textureId[0]
}
fun enableTexture0(program: Int, id: Int) {
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)
val textureSampleHandle = GLES30.glGetUniformLocation(program, "uTexture_0")
if (textureSampleHandle != -1) {
GLES30.glUniform1i(textureSampleHandle, 0)
}
}
fun disableTexture0() {
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
}
fun initTexture0(context: Context, resourceId: Int) {
mTextureID[0] = loadTexture(context, resourceId)
}
// GLSurfaceView实时绘制
fun drawCurrentOutput() {
val state = saveGLState()
try {
GLES30.glUseProgram(mProgram)
enableTexture0(mProgram, mTextureID[0])
drawSomething(mProgram, mMVPMatrix)
disableTexture0()
} finally {
restoreGLState(state)
}
}
// 保存OpenGL状态
private fun saveGLState(): GLState {
val viewport = IntArray(4)
val program = IntArray(1)
val framebuffer = IntArray(1)
GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewport, 0)
GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, program, 0)
GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, framebuffer, 0)
return GLState(viewport, program[0], framebuffer[0])
}
// 恢复OpenGL状态
private fun restoreGLState(state: GLState) {
GLES30.glViewport(
state.viewport[0],
state.viewport[1],
state.viewport[2],
state.viewport[3]
)
GLES30.glUseProgram(state.program)
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, state.framebuffer)
}
// OpenGL状态数据类
data class GLState(
val viewport: IntArray,
val program: Int,
val framebuffer: Int
)
object LoadShaderUtil {
// 创建着色器对象
fun loadShader(type: Int, source: String): Int {
val shader = GLES30.glCreateShader(type)
GLES30.glShaderSource(shader, source)
GLES30.glCompileShader(shader)
return shader
}
}
}
效果图
