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,我们就完成了绘制模式的转变。
和之前绘制平面的代码对比一下,除了构造函数以外,是不是满满的套路。。
public SphereNoTexture(float radius, int rings, int sectors)我们给构造函数传入半径,纬度切分数,经度切分数,一般sectors应该是rings的两倍(当然不是也看不出明显的区别),我们按照经纬度切分出多个矩形,然后把矩形再划分成两个小的三角形(使用下标索引的方式)
为什么还要rings + 1呢(sector类似),因为我们的最后矩形的右边界还应该和第一个矩形的左边界重合。
除了上面所说的,球体的绘制还有很多需要注意的小细节,大家可以对着代码慢慢揣摩
因为我们还没有做纹理映射,方便起见,我们将球的颜色设置为白色。 如果我们已经设置好了MVP矩阵(第五章),那么我们就能看到这样子的一个球:
呵呵,这真的是一个球,不是个圆啊!!!算了,加点噪声好了,这样明显一些:
当然,如果用棋盘纹理来表示也是棒棒哒(虽然这不是重点):
Github项目地址 回到目录
