上一篇文章中,我们通过去写一个三角形,了解了OpenGL ES大概怎么用,这篇文章我们会去尝试构建更多的图形。由于之前的代码,我们已经构建好了部分基础类,所以我们就按照图形一个一个去讲解。
由于我们知道,OpenGL ES相比于OpenGL,舍弃了很多基本图形,只保留了三角形的绘制,那我们如何去画一个正方形呢?其实很简单,把一个正方形拆解成两个三角形就可以了
接下来我们仿照上一篇写三角形的代码,来创建我们的正方形类Square
public class Square extends Shape{
static float triangleCoords[] = {
-0.5f, 0.5f, 0.0f, // top left
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f, // bottom right
0.5f, 0.5f, 0.0f // top right
};
static short index[] = {
0, 1, 2, 0, 2, 3
};
//顶点个数
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
//顶点之间的偏移量
private final int vertexStride = COORDS_PER_VERTEX * 4; // 每个顶点四个字节
private int mMatrixHandler;
//设置颜色,依次为红绿蓝和透明通道
float color[] = {1.0f, 0.6f, 0.3f, 1.0f};
@Override
public void init() {
ByteBuffer bb = ByteBuffer.allocateDirect(
triangleCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(triangleCoords);
vertexBuffer.position(0);
ByteBuffer cc = ByteBuffer.allocateDirect(index.length * 2);
cc.order(ByteOrder.nativeOrder());
indexBuffer = cc.asShortBuffer();
indexBuffer.put(index);
indexBuffer.position(0);
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
//创建一个空的OpenGLES程序
mProgram = GLES20.glCreateProgram();
//将顶点着色器加入到程序
GLES20.glAttachShader(mProgram, vertexShader);
//将片元着色器加入到程序中
GLES20.glAttachShader(mProgram, fragmentShader);
//连接到着色器程序
GLES20.glLinkProgram(mProgram);
}
@Override
public void onDraw(float[] mMVPMatrix) {
//将程序加入到OpenGLES2.0环境
GLES20.glUseProgram(mProgram);
//获取变换矩阵vMatrix成员句柄
mMatrixHandler = GLES20.glGetUniformLocation(mProgram, "vMatrix");
//指定vMatrix的值
GLES20.glUniformMatrix4fv(mMatrixHandler, 1, false, mMVPMatrix, 0);
//获取顶点着色器的vPosition成员句柄
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
//启用三角形顶点的句柄
GLES20.glEnableVertexAttribArray(mPositionHandle);
//准备三角形的坐标数据
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
//获取片元着色器的vColor成员的句柄
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
//设置绘制三角形的颜色
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
//绘制三角形
// GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);
//索引法绘制正方形
GLES20.glDrawElements(GLES20.GL_TRIANGLES, index.length, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"void main() {" +
" gl_Position = vMatrix*vPosition;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
}
发现比三角形好像有点不一样,我们一个一个来看。
首先在代码中多了个index[ ]。这个是用来存放顶点索引的。我们把一个正方形分成了两个三角形,按照上一篇我们的绘制方法,肯定有顶点既属于这个三角形,又属于那个三角形。为了能够较少这种顶点重复写法,于是就有了索引。
我们之前绘制三角形所用的方法是GLES20.glDrawArrays,这是顶点法,根据我们传入的点去一个一个的绘制。而我们在正方形绘制时,使用的是GLES20.glDrawElements方法,称之为索引法,根据我们传入的索引序列,去顶点序列中寻找对应的顶点,然后去绘制图形。索引法的优势就是相比顶点法减少很多重复顶点占用空间。
圆形的构建就稍微复杂点了,我们可以把圆和正多边形放在一起去考虑,如果正多边形的边无限的去增加,那不就是圆了吗,盗用别人的图说明下:
既然是圆,我们就需要知道圆上的X和Y的坐标,这就需要我们用到三角函数。在我们代码中有Math.sin和Math.cos。
Math.sin(x) x 的正玄值。返回值在 -1.0 到 1.0 之间;X 都是指的“弧度”而非“角度”,弧度的计算公式为: 2PI/360角度
同理Math.cos也是一样的,那这就好办了,我们写个方法,来创建圆形的X和Y的坐标,由于我们现阶段只做平面的图形,暂不考虑Z。
private float[] createPositions() {
ArrayList<Float> data = new ArrayList<>();
//设置圆心坐标
data.add(0.0f);
data.add(0.0f);
data.add(height);
float angDegSpan = 360f / n;
for (float i = 0; i < 360 + angDegSpan; i += angDegSpan) {
data.add((float) (radius * Math.sin(i * Math.PI / 180f)));
data.add((float) (radius * Math.cos(i * Math.PI / 180f)));
data.add(height);
}
float[] f = new float[data.size()];
for (int i = 0; i < f.length; i++) {
f[i] = data.get(i);
}
return f;
}
其中height为Z轴的数值,可能觉得奇怪了,不是和Z无关吗,此处是为了后续为立体图形拓展的时候使用,先留个伏笔。变量n为切割分数,此处我们让n = 360,这样画出来就像个圆了。
其他的方法和正方形的绘制都差不多,就是在具体绘制的时候,调用的方法需要注意,如下:
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, shapePos.length / 3);
我们发现绘制方法又用回了顶点法,但三角形引用顶点法时,参数GLES20.GL_TRIANGLE_FAN不一样。这里我们解读下绘制方法的三个参数。
GLES20.glDrawArrays的第一个参数表示绘制方式,第二个参数表示偏移量,第三个参数表示顶点个数。接下来我们来看看都有哪些绘制方式
这里可以看到,在我们构建圆形的时候,采用的是将顶点作为扇面绘制。绘制出来的效果是:
之前写的都是些平面图形,现在我们可以去操纵Z的值去绘制一些立体的几何图形了。同样,我们先去构建顶点坐标和坐标索引
final float cubePositions[] = {
-1.0f, 1.0f, 1.0f, //正面左上0
-1.0f, -1.0f, 1.0f, //正面左下1
1.0f, -1.0f, 1.0f, //正面右下2
1.0f, 1.0f, 1.0f, //正面右上3
-1.0f, 1.0f, -1.0f, //反面左上4
-1.0f, -1.0f, -1.0f, //反面左下5
1.0f, -1.0f, -1.0f, //反面右下6
1.0f, 1.0f, -1.0f, //反面右上7
};
final short index[] = {
6, 7, 4, 6, 4, 5, //后面
6, 3, 7, 6, 2, 3, //右面
6, 5, 1, 6, 1, 2, //下面
0, 3, 2, 0, 2, 1, //正面
0, 1, 5, 0, 5, 4, //左面
0, 7, 3, 0, 4, 7, //上面
};
同时这次我们换点不一样的东西,我们让我们的顶点,每一个顶点都去拥有不一样的颜色。想实现我们的需求,就要先定义一个保存顶点颜色的数组:
float color[] = {
1f, 0f, 0f, 1f,
0f, 1f, 0f, 1f,
0f, 0f, 1f, 1f,
0f, 1f, 0f, 1f,
1f, 0f, 0f, 1f,
0f, 1f, 0f, 1f,
0f, 0f, 1f, 1f,
0.5f, 0.5f, 0.5f, 1f,
};
颜色是我随机写的,估计成品可能会有点辣眼睛。既然我们让每个顶点都拥有自己的颜色,那么我们原来的顶点着色器和片元着色器的代码可能就不满足了,那我们修改一下:
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"varying vec4 vColor;" +
"attribute vec4 aColor;" +
"void main() {" +
" gl_Position = vMatrix*vPosition;" +
" vColor=aColor;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"varying vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
可以看到,我们在顶点着色器代码中,多了varying vec4 vColor参数,此参数的目的就是将我们根据顶点获取的颜色传递给片元着色器去上色。既然我们着色器的代码发生了变动,那么我们在绘制具体图形的地方代码也应该进行相应的更改:
@Override
public void onDraw(float[] mMVPMatrix) {
//将程序加入到OpenGLES2.0环境
GLES20.glUseProgram(mProgram);
//获取变换矩阵vMatrix成员句柄
mMatrixHandler = GLES20.glGetUniformLocation(mProgram, "vMatrix");
//指定vMatrix的值
GLES20.glUniformMatrix4fv(mMatrixHandler, 1, false, mMVPMatrix, 0);
//获取顶点着色器的vPosition成员句柄
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
//启用三角形顶点的句柄
GLES20.glEnableVertexAttribArray(mPositionHandle);
//准备三角形的坐标数据
GLES20.glVertexAttribPointer(mPositionHandle, 3,
GLES20.GL_FLOAT, false,
0, vertexBuffer);
//获取片元着色器的vColor成员的句柄
mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
//设置绘制三角形的颜色
// GLES20.glUniform4fv(mColorHandle, 2, color, 0);
GLES20.glEnableVertexAttribArray(mColorHandle);
GLES20.glVertexAttribPointer(mColorHandle, 4,
GLES20.GL_FLOAT, false,
0, colorBuffer);
//索引法绘制正方体
GLES20.glDrawElements(GLES20.GL_TRIANGLES, index.length, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
还有一个需要注意的地方,在我们开启绘制的时候,需要开启深度缓冲GLES20.glEnable(GLES20.GL_DEPTH_TEST),同时在绘制前也要对应的去清除深度缓冲GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT)。那么何为深度缓冲
在计算机图形学中,深度缓冲是在三维图形中处理图像深度坐标的过程,这个过程通常在硬件中完成,它也可以在软件中完成,它是可见性问题的一个解决方法。可见性问题是确定渲染场景中哪部分可见、哪部分不可见的问题。画家算法是另外一种常用的方法,尽管效率较低,但是也可以处理透明场景元素。深度缓冲也称为Z缓冲。
通俗点的理解,我们的计算机在画图形的时候是有一定的步骤的,当画完近处的物体,去画远端的物体。在没有开启深度缓冲的情况下,就会对近处的物体产生一定的遮挡,虽然我们设置了不同的Z值。但是这种效果肯定不是我们所希望的。我们开启了深度缓冲,我们在缓存中就会保存对应像素的深度值(可以理解为Z值),这样在绘制的时候,就不会有相互遮挡的感觉了。
之所以把立方体和平面图形放在一起,是因为立方体的绘制方法和正方形大同小异,代码层面更容易理解。在下一篇博客中,我们就要画点不一样的立体图形了。
Android OpenGL ES2.0从放弃到入门(四)—— 绘制圆柱体和球体
关于OpenGL代码全部在一个项目中,托管在Github上——OpenGL4Android
欢迎转载,转载请保留文章出处。有梦想的咸鱼9527的博客https://blog.csdn.net/weixin_39560693/article/details/95513995