[OpenGL]从零开始写一个Android平台下的全景视频播放器——3.2 使用OpenGL ES 2.0绘制一个球

    xiaoxiao2021-03-31  45

    Github项目地址

    为了方便没有准备好梯子的同学,我把项目在上打包下载,不过更新会慢一些

    回到目录

    如何用OpenGL绘制一个球呢?其实方法很多,网上一搜就能搜到一大把。

    坐标系

    要想知道怎么画,当然要把坐标系先搞清楚啦,上图:

    这是世界坐标(看懂这张图需要一定想象力),Y轴是和重力方向相反的,X轴和水平面齐平,我们(相机)站在Z轴上,观察点是在球内的。 这是世界坐标(3D)对应的纹理坐标(2D),不过我们现在还不需要考虑这个,等第五章再来讨论纹理映射的事。

    原理

    绘制球的原则其实和绘制平面并没有什么区别,那么球的坐标要如何表示呢?我们知道三角形是OpenGL的基本形状,所以我们就将球面划分成许多个三角形,当我们划分的个数足够多时,整个球看起来就比较圆润了,像这样:

    最后一张图是用矩形表示的终极形状(是不是很像一个地球仪),我们还需要把矩阵切分成两个三角形进行绘制。

    坐标推导公式就不上了,估计也没人看,直接上代码吧。

    下面是一张百度上爬来的图(坐标系标的不符合我们的要求,凑合着看吧):

    题外话

    在贴出球的代码之前,我们先将之前绘制平面视频的代码块提取出来,像这样:

    package com.martin.ads.panoramaopengltutorial; import android.opengl.GLES20; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import static com.martin.ads.panoramaopengltutorial.ShaderUtils.checkGlError; public class Plain { private final FloatBuffer mVerticesBuffer; private FloatBuffer mTexCoordinateBuffer; private final float[] vertexData = { 1f,-1f,0f, -1f,-1f,0f, 1f,1f,0f, -1f,1f,0f }; private final float[] textureVertexData = { 1f,0f, 0f,0f, 1f,1f, 0f,1f }; public Plain() { mVerticesBuffer = ByteBuffer.allocateDirect(vertexData.length * 4) .order(ByteOrder.nativeOrder()) .asFloatBuffer() .put(vertexData); mVerticesBuffer.position(0); mTexCoordinateBuffer = ByteBuffer.allocateDirect(textureVertexData.length * 4) .order(ByteOrder.nativeOrder()) .asFloatBuffer() .put(textureVertexData); mTexCoordinateBuffer.position(0); } public void uploadVerticesBuffer(int positionHandle){ FloatBuffer vertexBuffer = getVerticesBuffer(); if (vertexBuffer == null) return; vertexBuffer.position(0); GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer); checkGlError("glVertexAttribPointer maPosition"); GLES20.glEnableVertexAttribArray(positionHandle); checkGlError("glEnableVertexAttribArray maPositionHandle"); } public void uploadTexCoordinateBuffer(int textureCoordinateHandle){ FloatBuffer textureBuffer = getTexCoordinateBuffer(); if (textureBuffer == null) return; textureBuffer.position(0); GLES20.glVertexAttribPointer(textureCoordinateHandle, 2, GLES20.GL_FLOAT, false, 0, textureBuffer); checkGlError("glVertexAttribPointer maTextureHandle"); GLES20.glEnableVertexAttribArray(textureCoordinateHandle); checkGlError("glEnableVertexAttribArray maTextureHandle"); } public FloatBuffer getVerticesBuffer() { return mVerticesBuffer; } public FloatBuffer getTexCoordinateBuffer() { return mTexCoordinateBuffer; } public void draw() { GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); } }

    为什么要这样做呢?因为就像之前说的,绘制平面和绘制球体并没有本质的区别,所以我们完全可以抽象出一个类,只要把Plain换成Sphere,我们就完成了绘制模式的转变。

    球体绘制

    package com.martin.ads.panoramaopengltutorial; import android.opengl.GLES20; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer; import static com.martin.ads.panoramaopengltutorial.ShaderUtils.checkGlError; public class SphereNoTexture { private static final int sPositionDataSize = 3; private FloatBuffer mVerticesBuffer; private ShortBuffer indexBuffer; private int mNumIndices; public SphereNoTexture(float radius, int rings, int sectors) { final float PI = (float) Math.PI; final float PI_2 = (float) (Math.PI / 2); float R = 1f/(float)rings; float S = 1f/(float)sectors; short r, s; float x, y, z; int numPoint = (rings + 1) * (sectors + 1); float[] vertexs = new float[numPoint * 3]; short[] indices = new short[numPoint * 6]; //矩形的四个点 int t = 0, v = 0; for(r = 0; r < rings + 1; r++) { for(s = 0; s < sectors + 1; s++) { x = (float) (Math.cos(2*PI * s * S) * Math.sin( PI * r * R )); y = (float) Math.sin( -PI_2 + PI * r * R ); z = (float) (Math.sin(2*PI * s * S) * Math.sin( PI * r * R )); vertexs[v++] = x * radius; vertexs[v++] = y * radius; vertexs[v++] = z * radius; } } //球体绘制坐标索引,用于 glDrawElements int counter = 0; int sectorsPlusOne = sectors + 1; for(r = 0; r < rings; r++){ for(s = 0; s < sectors; s++) { indices[counter++] = (short) (r * sectorsPlusOne + s); //(a) indices[counter++] = (short) ((r+1) * sectorsPlusOne + (s)); //(b) indices[counter++] = (short) ((r) * sectorsPlusOne + (s+1)); // (c) indices[counter++] = (short) ((r) * sectorsPlusOne + (s+1)); // (c) indices[counter++] = (short) ((r+1) * sectorsPlusOne + (s)); //(b) indices[counter++] = (short) ((r+1) * sectorsPlusOne + (s+1)); // (d) } } // initialize vertex byte buffer for shape coordinates ByteBuffer bb = ByteBuffer.allocateDirect( // (# of coordinate values * 4 bytes per float) vertexs.length * 4); bb.order(ByteOrder.nativeOrder()); FloatBuffer vertexBuffer = bb.asFloatBuffer(); vertexBuffer.put(vertexs); vertexBuffer.position(0); // initialize byte buffer for the draw list ByteBuffer dlb = ByteBuffer.allocateDirect( // (# of coordinate values * 2 bytes per short) indices.length * 2); dlb.order(ByteOrder.nativeOrder()); indexBuffer = dlb.asShortBuffer(); indexBuffer.put(indices); indexBuffer.position(0); mVerticesBuffer=vertexBuffer; mNumIndices=indices.length; } public void uploadVerticesBuffer(int positionHandle){ FloatBuffer vertexBuffer = getVerticesBuffer(); if (vertexBuffer == null) return; vertexBuffer.position(0); GLES20.glVertexAttribPointer(positionHandle, sPositionDataSize, GLES20.GL_FLOAT, false, 0, vertexBuffer); checkGlError("glVertexAttribPointer maPosition"); GLES20.glEnableVertexAttribArray(positionHandle); checkGlError("glEnableVertexAttribArray maPositionHandle"); } public FloatBuffer getVerticesBuffer() { return mVerticesBuffer; } public void draw() { indexBuffer.position(0); GLES20.glDrawElements(GLES20.GL_TRIANGLES, mNumIndices, GLES20.GL_UNSIGNED_SHORT, indexBuffer); } }

    和之前绘制平面的代码对比一下,除了构造函数以外,是不是满满的套路。。

    public SphereNoTexture(float radius, int rings, int sectors)

    我们给构造函数传入半径,纬度切分数,经度切分数,一般sectors应该是rings的两倍(当然不是也看不出明显的区别),我们按照经纬度切分出多个矩形,然后把矩形再划分成两个小的三角形(使用下标索引的方式)

    为什么还要rings + 1呢(sector类似),因为我们的最后矩形的右边界还应该和第一个矩形的左边界重合。

    除了上面所说的,球体的绘制还有很多需要注意的小细节,大家可以对着代码慢慢揣摩

    因为我们还没有做纹理映射,方便起见,我们将球的颜色设置为白色。 如果我们已经设置好了MVP矩阵(第五章),那么我们就能看到这样子的一个球:

    呵呵,这真的是一个球,不是个圆啊!!!算了,加点噪声好了,这样明显一些:

    当然,如果用棋盘纹理来表示也是棒棒哒(虽然这不是重点):

    Github项目地址 回到目录

    转载请注明原文地址: https://ju.6miu.com/read-665265.html

    最新回复(0)