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
代码
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 = 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
代码
class MyMatrixGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {
private var mRenderer = MyMatrixRenderer(context)
init {
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
代码
class MyMatrixRenderer(private val mContext: Context) : GLSurfaceView.Renderer {
var mDrawData: MatrixDrawData? = null
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
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) {
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
需要的绘制数据
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
private var mVAO = IntArray(1)
private var mVBO = IntArray(2)
private var mIBO = IntArray(1)
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
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)
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)
}
fun initVertexBuffer() {
GLES30.glGenVertexArrays(mVAO.size, mVAO, NO_OFFSET)
GLES30.glBindVertexArray(mVAO[0])
GLES30.glGenBuffers(mVBO.size, mVBO, NO_OFFSET)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[0])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
vertex.size * 4,
vertexBuffer,
GLES30.GL_STATIC_DRAW
)
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)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[1])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
textureCoords.size * 4,
textureBuffer,
GLES30.GL_STATIC_DRAW
)
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)
GLES30.glGenBuffers(mIBO.size, mIBO, NO_OFFSET)
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mIBO[0])
GLES30.glBufferData(
GLES30.GL_ELEMENT_ARRAY_BUFFER,
index.size * 2,
indexBuffer,
GLES30.GL_STATIC_DRAW
)
GLES30.glBindVertexArray(0)
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)
GLES30.glBindVertexArray(mVAO[0])
GLES30.glDrawElements(
GLES30.GL_TRIANGLES,
index.size,
GLES30.GL_UNSIGNED_SHORT,
NO_OFFSET
)
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)
}
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
Matrix.setLookAtM(
mViewMatrix, NO_OFFSET,
0f, 0f, near + radius,
0f, 0f, 0f,
0f, 1f, 0f
)
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
)
Matrix.multiplyMM(
mMVPMatrix,
NO_OFFSET,
mViewMatrix,
NO_OFFSET,
mModelMatrix,
NO_OFFSET
)
Matrix.multiplyMM(
mMVPMatrix,
NO_OFFSET,
mProjectionMatrix,
NO_OFFSET,
mMVPMatrix,
NO_OFFSET
)
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)
}
fun drawCurrentOutput() {
val state = saveGLState()
try {
GLES30.glUseProgram(mProgram)
enableTexture0(mProgram, mTextureID[0])
drawSomething(mProgram, mMVPMatrix)
disableTexture0()
} finally {
restoreGLState(state)
}
}
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])
}
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)
}
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
}
}
}
效果图
