前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OpenGL ES for Android 绘制旋转的地球

OpenGL ES for Android 绘制旋转的地球

作者头像
老孟Flutter
发布2020-09-11 16:06:09
1.4K0
发布2020-09-11 16:06:09
举报
文章被收录于专栏:FlutterFlutter

一个

有态度

的程序员

No 图 No Code,上面旋转的地球是不是很酷炫,下面就让我们开始说说如何绘制旋转地球吧?绘制旋转地球需要3个步骤:

  1. 计算球体顶点数据。
  2. 地球纹理贴图。
  3. 通过MVP矩阵旋转地球。

计算球体顶点数据

我们知道OpenGL中最基本的图元是三角形,任何复杂的图形都可以分解为一个个的三角形,球体也不例外,假设球体上有“经纬度”,通过“经纬度”将球体分割为一个个的四边形,如下图:

在把这些四边形分割为2个三角形,所以绘制球体的关键是计算“经纬度”相交的点的坐标。

假设球体的中心在坐标的原点(方便计算),半径为radius,n个经度,m个纬度,计算顶点坐标、索引、纹理坐标方法如下:

代码语言:javascript
复制
fun generateSphere(radius: Float, rings: Int, sectors: Int) {
            val PI = Math.PI.toFloat()
            val PI_2 = (Math.PI / 2).toFloat()
            val R = 1f / rings.toFloat()
            val S = 1f / sectors.toFloat()
            var r: Short
            var s: Short
            var x: Float
            var y: Float
            var z: Float
            val numPoint = (rings + 1) * (sectors + 1)
            val vertexs = FloatArray(numPoint * 3)
            val texcoords = FloatArray(numPoint * 2)
            val indices = ShortArray(numPoint * 6)
            var t = 0
            var v = 0
            r = 0
            while (r < rings + 1) {
                s = 0
                while (s < sectors + 1) {
                    x =
                        (Math.cos((2f * PI * s.toFloat() * S).toDouble()) * Math.sin((PI * r.toFloat() * R).toDouble())).toFloat()
                    y = -Math.sin((-PI_2 + PI * r.toFloat() * R).toDouble()).toFloat()
                    z =
                        (Math.sin((2f * PI * s.toFloat() * S).toDouble()) * Math.sin((PI * r.toFloat() * R).toDouble())).toFloat()
                    texcoords[t++] = s * S
                    texcoords[t++] = r * R
                    vertexs[v++] = x * radius
                    vertexs[v++] = y * radius
                    vertexs[v++] = z * radius
                    s++
                }
                r++
            }
            var counter = 0
            val sectorsPlusOne = sectors + 1
            r = 0
            while (r < rings) {
                s = 0
                while (s < sectors) {
                    indices[counter++] = (r * sectorsPlusOne + s).toShort()       //(a)
                    indices[counter++] = ((r + 1) * sectorsPlusOne + s).toShort()    //(b)
                    indices[counter++] = (r * sectorsPlusOne + (s + 1)).toShort()  // (c)
                    indices[counter++] = (r * sectorsPlusOne + (s + 1)).toShort()  // (c)
                    indices[counter++] = ((r + 1) * sectorsPlusOne + s).toShort()    //(b)
                    indices[counter++] = ((r + 1) * sectorsPlusOne + (s + 1)).toShort()  // (d)
                    s++
                }
                r++
            }
            vertexBuffer = GLTools.array2Buffer(vertexs)
            texBuffer = GLTools.array2Buffer(texcoords)
            mIndicesBuffer = GLTools.array2Buffer(indices)
            indicesNum = indices.size
        }

这个顶点的数据计算需要有比较好的立体感。最难的顶点坐标和纹理坐标已经获取,下面开始介绍如何绘制地球。

顶点shader代码如下:

代码语言:javascript
复制
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
uniform mat4 mvpMatrix;
varying vec2 v_TexCoord;
void main()
{
    v_TexCoord = a_TexCoord;
    gl_Position = mvpMatrix * a_Position;
}

片段shader代码如下:

代码语言:javascript
复制
precision mediump float;
uniform sampler2D u_Texture;
varying vec2 v_TexCoord;
void main()
{
    gl_FragColor = texture2D(u_Texture, v_TexCoord);
}

创建program、取参数句柄并生成顶点数据

代码语言:javascript
复制
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
            GLES20.glClearColor(0F, 0F, 0F, 1F)
            createProgram()
            generateSphere(2F,75,150)
            //获取vPosition索引
            vPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "a_Position")
            texCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoord")
            mvpMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "mvpMatrix")
            textureLoc= GLES20.glGetUniformLocation(mProgramHandle, "u_Texture")
        }
private fun createProgram() {
            var vertexCode =
                AssetsUtils.readAssetsTxt(
                    context = context,
                    filePath = "glsl/sphere_vs.glsl"
                )
            var fragmentCode =
                AssetsUtils.readAssetsTxt(
                    context = context,
                    filePath = "glsl/sphere_fs.glsl"
                )
            mProgramHandle = GLTools.createAndLinkProgram(vertexCode, fragmentCode)
        }

sphere_vs.glsl 和 sphere_fs.glsl分别表示顶点shader和片段shader的文件,存放于assets/glsl目录下,readAssetsTxt为读取assets目录下文件的公用方法。generateSphere方式就是开始介绍的顶点数据生成的方法。

地球纹理贴图

地球纹理图片如下:

将地球图片转为纹理,代码如下:

代码语言:javascript
复制
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
            ...
            var bitmap =
                BitmapFactory.decodeResource(context.resources, R.drawable.earth)
            textureId = GLTools.loadTexture(bitmap)
        }

GLTools.loadTexture为封装的工具类方法,在OpenGL ES 绘制纹理文章中已经详细介绍,图片纹理的相关内容也可以参考此文章。

MVP矩阵

初始化MVP矩阵代码如下:

代码语言:javascript
复制


var modelMatrix = FloatArray(16)
var viewMatrix = FloatArray(16)
var projectionMatrix = FloatArray(16)
override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
            GLES20.glViewport(0, 0, width, height)
            Matrix.setIdentityM(viewMatrix, 0)
            Matrix.setLookAtM(
                viewMatrix, 0,
                0F, 5F, 10F,
                0F, 0F, 0F,
                0F, 1F, 0F
            )
            Matrix.setIdentityM(projectionMatrix, 0)
            val ratio = width.toFloat() / height
            //设置透视投影
            Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 20f)
        }

绘制并通过MVP矩阵旋转地球

代码语言:javascript
复制
override fun onDrawFrame(p0: GL10?) {
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)
            GLES20.glUseProgram(mProgramHandle)
            //设置顶点数据
            vertexBuffer.position(0)
            GLES20.glEnableVertexAttribArray(vPositionLoc)
            GLES20.glVertexAttribPointer(vPositionLoc, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer)
            //设置纹理顶点数据
            texBuffer.position(0)
            GLES20.glEnableVertexAttribArray(texCoordLoc)
            GLES20.glVertexAttribPointer(texCoordLoc, 2, GLES20.GL_FLOAT, false, 0, texBuffer)
            //设置纹理
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
            GLES20.glUniform1i(textureLoc, 0)
            updateMvpMatrix()
            GLES20.glUniformMatrix4fv(mvpMatrixLoc, 1, false, mMvpMatrix, 0)
            GLES20.glDrawElements(
                GLES20.GL_TRIANGLES,
                indicesNum,
                GLES20.GL_UNSIGNED_SHORT,
                mIndicesBuffer
            )
        }
var currentRotateDegree = 0F
fun updateMvpMatrix(){
            Matrix.setIdentityM(modelMatrix, 0)
            Matrix.rotateM(modelMatrix, 0, currentRotateDegree++, 0F, 1F, 0F)
            var mTempMvMatrix = FloatArray(16)
            Matrix.setIdentityM(mTempMvMatrix, 0)
            Matrix.multiplyMM(mTempMvMatrix, 0, viewMatrix, 0, modelMatrix, 0)
            Matrix.multiplyMM(mMvpMatrix, 0, projectionMatrix, 0, mTempMvMatrix, 0)
        }

到此地球的绘制就结束了,我们经常听说的天空穹、全景(VR)球体模式和地球的绘制基本一样,只不过是相机位置的不同而已。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老孟Flutter 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档