OpenglES2.0 for Android:来画个圆吧
2016-06-04 10:54
633 查看
OpenglES2.0 for Android:来画个圆吧
首先看看本节的流程:
计算圆的顶点坐标:
我们先要明白OpenglES中圆是怎么画的,前面我们已经知道三角形扇的绘制方式,我们的圆其实也可以看成以圆心为中心点的三角形扇,如下图所示:看到圆的内部是一个正多边形,当我们的正多边形的边数(或三角形的个数)足够多的话,我们肉眼看起来就变成了一个圆。
圆心坐标是很容易确定的,这里我们假设圆心坐标为(x , y ),然后设圆的半径为 r 接下来我们需要计算的就是周边的点的坐标,
我们很容易计算出来A点的坐标 :
A点的横坐标为: x + r * cos θ
A点的纵坐标为: y + r * sin θ
我们将圆分成 n 份的话,就可以得到 每一份的角度的值 ,即 θ 的值。通过一个for循环我们就可以很容易的得到所有点的坐标。
我们在工程的shape文件夹下新建一个类 Circle , 完成坐标的计算 ,当前代码如下 (Circle.java ):
package com.cumt.shape; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import android.content.Context; public class Circle { private Context context; private FloatBuffer vertexData; // 定义圆心坐标 private float x; private float y; // 半径 private float r; // 三角形分割的数量 private int count = 10; // 每个顶点包含的数据个数 ( x 和 y ) private static final int POSITION_COMPONENT_COUNT = 2; private static final int BYTES_PER_FLOAT = 4; public Circle(Context context) { this.context = context; x = 0f; y = 0f; r = 0.6f; initVertexData(); } private void initVertexData() { // 顶点的个数,我们分割count个三角形,有count+1个点,再加上圆心共有count+2个点 final int nodeCount = count + 2; float circleCoords[] = new float[nodeCount * POSITION_COMPONENT_COUNT]; // x y int offset = 0; circleCoords[offset++] = x;// 中心点 circleCoords[offset++] = y; for (int i = 0; i < count + 1; i++) { float angleInRadians = ((float) i / (float) count) * ((float) Math.PI * 2f); circleCoords[offset++] = x + r * (float)Math.sin(angleInRadians); circleCoords[offset++] = y + r * (float)Math.cos(angleInRadians); } // 为存放形状的坐标,初始化顶点字节缓冲 ByteBuffer bb = ByteBuffer.allocateDirect( // (坐标数 * 4)float占四字节 circleCoords.length * BYTES_PER_FLOAT); // 设用设备的本点字节序 bb.order(ByteOrder.nativeOrder()); // 从ByteBuffer创建一个浮点缓冲 vertexData = bb.asFloatBuffer(); // 把坐标们加入FloatBuffer中 vertexData.put(circleCoords); // 设置buffer,从第一个坐标开始读 vertexData.position(0); } }
圆的绘制:
有了圆的顶点坐标我们就可以来绘制圆了,这里我们依然适用前面的着色器,着色器代码的读取,编译,连接都和前面两节绘制三角形,矩形的道理一样,直接看代码(Circle.java):
package com.cumt.shape; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import com.cumt.openglestwo_test_one.R; import com.cumt.utils.LoggerConfig; import com.cumt.utils.ShaderHelper; import com.cumt.utils.TextResourceReader; import android.content.Context; import android.opengl.GLES20; public class Circle { private Context context; private FloatBuffer vertexData; // 定义圆心坐标 private float x; private float y; // 半径 private float r; // 三角形分割的数量 private int count = 40; // 每个顶点包含的数据个数 ( x 和 y ) private static final int POSITION_COMPONENT_COUNT = 2; private static final int BYTES_PER_FLOAT = 4; private static final String U_COLOR = "u_Color"; private static final String A_POSITION = "a_Position"; private int program; private int uColorLocation; private int aPositionLocation; public Circle(Context context) { this.context = context; x = 0f; y = 0f; r = 0.6f; initVertexData(); } private void initVertexData() { // 顶点的个数,我们分割count个三角形,有count+1个点,再加上圆心共有count+2个点 final int nodeCount = count + 2; float circleCoords[] = new float[nodeCount * POSITION_COMPONENT_COUNT]; // x y int offset = 0; circleCoords[offset++] = x;// 中心点 circleCoords[offset++] = y; for (int i = 0; i < count + 1; i++) { float angleInRadians = ((float) i / (float) count) * ((float) Math.PI * 2f); circleCoords[offset++] = x + r * (float)Math.sin(angleInRadians); circleCoords[offset++] = y + r * (float)Math.cos(angleInRadians); } // 为存放形状的坐标,初始化顶点字节缓冲 ByteBuffer bb = ByteBuffer.allocateDirect( // (坐标数 * 4)float占四字节 circleCoords.length * BYTES_PER_FLOAT); // 设用设备的本点字节序 bb.order(ByteOrder.nativeOrder()); // 从ByteBuffer创建一个浮点缓冲 vertexData = bb.asFloatBuffer(); // 把坐标们加入FloatBuffer中 vertexData.put(circleCoords); // 设置buffer,从第一个坐标开始读 vertexData.position(0); getProgram(); uColorLocation = GLES20.glGetUniformLocation(program, U_COLOR); aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION); GLES20.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, 0, vertexData); GLES20.glEnableVertexAttribArray(aPositionLocation); } private void getProgram(){ String vertexShaderSource = TextResourceReader .readTextFileFromResource(context, R.raw.simple_vertex_shader); String fragmentShaderSource = TextResourceReader .readTextFileFromResource(context, R.raw.simple_fragment_shader); int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource); int fragmentShader = ShaderHelper .compileFragmentShader(fragmentShaderSource); program = ShaderHelper.linkProgram(vertexShader, fragmentShader); if (LoggerConfig.ON) { ShaderHelper.validateProgram(program); } GLES20.glUseProgram(program); } public void draw(){ GLES20.glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, count +2); } }
接下来我们在MyRender类中new 一个Circle对象,调用其draw方法进行绘制,结果如下:
对的,你没有看错,它看起来一点都不圆,明明是个椭圆~~ 还记得上一节绘制的矩形吗,那个矩形根据我们定义的坐标应该是个正方形,但我们的图示显示并不是正方形,
而是个长方形,这是什么原因呢?
宽高比的问题
在Opengl中我们要渲染的一切物体都是要映射到X轴和Y轴上【-1 ,1】的范围内(对Z轴也是一样,我们目前为止还未讨论三维,所以不用管Z轴),这个范围内的坐标称为归一化设备坐标,这个归一化坐标独立于屏幕的尺寸或者形状。如此就导致一个问题,我们假定在归一化坐标下有一个铺满的圆,在手机上就会有下图中的问题:
【-1,1】对应的屏幕的像素高与像素宽并不同,图像在x轴就会显得扁平了(竖屏时),在横屏时图像在y轴就会显得扁平。
正交投影
我们可以使用正交投影来解决这个问题,把虚拟坐标变换回归一化设备坐标。我们使用一个正交投影矩阵,使用该矩阵与我们的顶点矩阵相乘,得到新的顶点坐标值,利用这个值来绘制就不会有上述的问题了。关于正交投影的知识网上很多,这里不再叙述。
在Android的android.opengl包的Matrix类中有一个 orthoM ( )方法,使用这个函数可以方便的生成一个投影矩阵,
这里说一下各参数的含义:
float[ ] m :目标数组,长度至少为16个元素,这样才足以存储正交矩阵 ;
int mOffset :结果矩阵起始的偏移值
float left :x轴的最小范围
float right :x轴的最大范围
float bottom :y轴的最小范围
float top :y轴的最大范围
float near :z轴的最小范围
float far :z轴的最大范围
绘制真正的圆
现在我们开始着手绘制真正的圆,首先我们修改顶点着色器的代码,引入这个投影矩阵,我们新建一个顶点着色器 (vertex_shader.glsl ):uniform mat4 u_Matrix; attribute vec4 a_Position; void main() { gl_Position = u_Matrix * a_Position; }
u_Matrix即我们的4X4的投影矩阵,下面我们来修改Circle.java的代码 ,共分为四步,见下面代码中的 步骤 (Circle.java 中的第一步 ~~第四步) :
package com.cumt.shape; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import com.cumt.openglestwo_test_one.R; import com.cumt.utils.LoggerConfig; import com.cumt.utils.ShaderHelper; import com.cumt.utils.TextResourceReader; import android.content.Context; import android.opengl.GLES20; import android.opengl.Matrix; public class Circle { private Context context; private FloatBuffer vertexData; // 定义圆心坐标 private float x; private float y; // 半径 private float r; // 三角形分割的数量 private int count = 10; // 每个顶点包含的数据个数 ( x 和 y ) private static final int POSITION_COMPONENT_COUNT = 2; private static final int BYTES_PER_FLOAT = 4; private static final String U_COLOR = "u_Color"; private static final String A_POSITION = "a_Position"; private int program; private int uColorLocation; private int aPositionLocation; /* * 第一步: 定义投影矩阵相关 */ private static final String U_MATRIX = "u_Matrix"; private final float[] projectionMatrix = new float[16]; private int uMatrixLocation; public Circle(Context context) { this.context = context; x = 0f; y = 0f; r = 0.6f; initVertexData(); } private void initVertexData() { // 顶点的个数,我们分割count个三角形,有count+1个点,再加上圆心共有count+2个点 final int nodeCount = count + 2; float circleCoords[] = new float[nodeCount * POSITION_COMPONENT_COUNT]; // x y int offset = 0; circleCoords[offset++] = x;// 中心点 circleCoords[offset++] = y; for (int i = 0; i < count + 1; i++) { float angleInRadians = ((float) i / (float) count) * ((float) Math.PI * 2f); circleCoords[offset++] = x + r * (float)Math.sin(angleInRadians); circleCoords[offset++] = y + r * (float)Math.cos(angleInRadians); } // 为存放形状的坐标,初始化顶点字节缓冲 ByteBuffer bb = ByteBuffer.allocateDirect( // (坐标数 * 4)float占四字节 circleCoords.length * BYTES_PER_FLOAT); // 设用设备的本点字节序 bb.order(ByteOrder.nativeOrder()); // 从ByteBuffer创建一个浮点缓冲 vertexData = bb.asFloatBuffer(); // 把坐标们加入FloatBuffer中 vertexData.put(circleCoords); // 设置buffer,从第一个坐标开始读 vertexData.position(0); getProgram(); uColorLocation = GLES20.glGetUniformLocation(program, U_COLOR); aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION); /* * 第二步: 获取顶点着色器中投影矩阵的location */ uMatrixLocation = GLES20.glGetUniformLocation(program, U_MATRIX); GLES20.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, 0, vertexData); GLES20.glEnableVertexAttribArray(aPositionLocation); } private void getProgram(){ String vertexShaderSource = TextResourceReader .readTextFileFromResource(context, R.raw.vertex_shader); String fragmentShaderSource = TextResourceReader .readTextFileFromResource(context, R.raw.simple_fragment_shader); int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource); int fragmentShader = ShaderHelper .compileFragmentShader(fragmentShaderSource); program = ShaderHelper.linkProgram(vertexShader, fragmentShader); if (LoggerConfig.ON) { ShaderHelper.validateProgram(program); } GLES20.glUseProgram(program); } /** * 第三步 : 根据屏幕的width 和 height 创建投影矩阵 * @param width * @param height */ public void projectionMatrix(int width,int height){ final float aspectRatio = width > height ? (float) width / (float) height : (float) height / (float) width; if(width > height){ Matrix.orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f); }else{ Matrix.orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f); } } public void draw(){ GLES20.glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f); /* * 第四步:传入投影矩阵 */ GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix,0); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, count +2); } }
注意此时传入的顶点着色器是vertex_shader.glsl 而不是我们原来的simple_vertex_shader.glsl 。然后在MyRender中使用 :
此时MyRender的代码如下 :
package com.cumt.render; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import com.cumt.shape.Circle; import android.content.Context; import android.opengl.GLSurfaceView.Renderer; import android.util.Log; import static android.opengl.GLES20.glClear; import static android.opengl.GLES20.glClearColor; import static android.opengl.GLES20.glViewport; import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT; public class MyRender implements Renderer { private Context context; public MyRender(Context context){ this.context = context; } Circle circle; public void onSurfaceCreated(GL10 gl, EGLConfig config) { Log.w("MyRender","onSurfaceCreated"); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); circle = new Circle(context); } public void onSurfaceChanged(GL10 gl, int width, int height) { Log.w("MyRender","onSurfaceChanged"); glViewport(0,0,width,height); //设置投影矩阵 circle.projectionMatrix(width, height); } public void onDrawFrame(GL10 gl) { Log.w("MyRender","onDrawFrame"); glClear(GL_COLOR_BUFFER_BIT); circle.draw(); } }
看到我们绘制出了一个正多边形,看下我们的Circle类 ,其中有个参数
private int count = 10;// 三角形分割的数量
我们只分割了10个 ,大家可以数一下,上面的正多边形正好是一个正10多边形,下面我们把这个值改为40,再运行一下:
哈 ,我们的圆终于画出来了~~ ,相信大家连画椭圆都会了。
相关文章推荐
- Android中注解详解,通过注解代替findViewById方法
- 如何学习android开发
- [Android]Uri、UriMatcher、ContentUris详解
- Android Context 是什么?
- AndroidManifest.xml清单配置文件的相关介绍一
- 根据时间按年、月、日、分组、排序
- Service小结
- Android获取屏幕尺寸的4种方法
- Android Studio 如何打JAR包,,怎么 提取 .arr文件:
- Android_RadioButton,CheckBox
- Android activity属性汇总
- Android自定义RatingBar(评分控件)
- Android布局控件之LinearLayout详解
- Android_EditText
- Android控件属性 (TextView 、EditText )
- android之文件存储和读取
- Android学习笔记之AndroidManifest.xml文件解析
- Android布局中Layout_weight的属性
- Activity 生命周期总结
- Glide加载图片流程(Part One)