今天使用OpenGLES实现一个圆心是玫红色,向圆周渐变成蓝色的圆。
本篇博文的内容也是后续绘制3D图形的基础。
实现过程中,需要重点关注的点是:如何使用数学公式求得图形的顶点,以及加载颜色值。
废话不多说,开工吧!
Render类中需要关注的重点是:createCirclePositions()
这个函数中实现了圆形的顶点创建和颜色值加载
已知如下两个变量:
求圆的顶点坐标需要求得两种坐标:
圆心坐标比较简单:(0,0)
圆周上点的坐标:
知道如何求得圆的顶点坐标后,如下就是Render类的实现代码:
public class CircleRender implements GLSurfaceView.Renderer {
private final String TAG = CubeRender.class.getSimpleName();
private final Context mContext;
//圆形顶点位置
private float vertexData[];
//顶点的颜色
private float colorData[];
private FloatBuffer vertexBuffer;
private FloatBuffer colorBuffer;
//MVP矩阵
private float[] mMVPMatrix = new float[16];
//shader程序/渲染器
private int shaderProgram;
//返回属性变量的位置
//变换矩阵
private int uMatrixLocation;
//位置
private int aPositionLocation;
//颜色
private int aColorLocation;
private float ratio;
public CircleRender(Context context) {
mContext = context;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.v(TAG, "onSurfaceCreated()");
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
initGLES();
}
public void initGLES() {
Log.v(TAG, "initGLES!");
/************** 着色器程序/渲染器 **************/
//创建并连接 着色器程序
shaderProgram = ShaderUtils.createAndLinkProgram(mContext,
"circle_vertex_shader.glsl",
"circle_fragtment_shader.glsl");
if (shaderProgram == 0) {
Log.v(TAG, "create And Link ShaderProgram Fail!");
return;
}
//使用着色器源程序
glUseProgram(shaderProgram);
createCirclePositions(0.8f, 60);
/************** 着色器变量 **************/
//获取着色器中的变量
uMatrixLocation = glGetUniformLocation(shaderProgram, "u_Matrix");
aPositionLocation = glGetAttribLocation(shaderProgram, "vPosition");
aColorLocation = glGetAttribLocation(shaderProgram, "aColor");
//为顶点、颜色、索引数据配置内存
vertexBuffer = ShaderUtils.getFloatBuffer(vertexData);
colorBuffer = ShaderUtils.getFloatBuffer(colorData);
//启动深度测试
glEnable(GL_DEPTH_TEST);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.v(TAG, "onSurfaceChanged(): " + width + " x " + height);
glViewport(0, 0, width, height);
//计算宽高比
ratio = (float) width / height;
}
@Override
public void onDrawFrame(GL10 gl) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.95f, 0.95f, 0.95f, 0.95f);
mMVPMatrix = TransformUtils.getCircleMVPMatrix(ratio);
//将变换矩阵传入顶点渲染器
glUniformMatrix4fv(uMatrixLocation, 1, false, mMVPMatrix, 0);
//准备顶点坐标和颜色数据
glVertexAttribPointer(aPositionLocation, 3, GL_FLOAT, false, 0, vertexBuffer);
glVertexAttribPointer(aColorLocation, 4, GL_FLOAT, false, 0, colorBuffer);
//启用顶点位置和顶点颜色句柄
glEnableVertexAttribArray(aPositionLocation);
glEnableVertexAttribArray(aColorLocation);
//绘制
glDrawArrays(GL_TRIANGLE_FAN, 0, vertexData.length / 3);
//禁止顶点数组的句柄
glDisableVertexAttribArray(aPositionLocation);
glDisableVertexAttribArray(aColorLocation);
}
private void createCirclePositions(float radius, int n) {
ArrayList data = new ArrayList<>();
data.add(0.0f); //设置圆心坐标
data.add(0.0f);
data.add(0.0f);
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(0.0f);
}
float[] f = new float[data.size()];
for (int i = 0; i < f.length; i++) {
f[i] = data.get(i);
}
vertexData = f;
//处理各个顶点的颜色
colorData = new float[f.length * 4 / 3];
ArrayList temp0 = new ArrayList<>();
ArrayList temp2 = new ArrayList<>();
ArrayList temp1 = new ArrayList<>();
temp1.add(1.0f);
temp1.add(0.0f);
temp1.add(1.0f);
temp1.add(0.0f);
temp0.add(0.0f);
temp0.add(0.0f);
temp0.add(1.0f);
temp0.add(0.0f);
for (int i = 0; i < f.length / 3; i++) {
if (i == 0) {
temp2.addAll(temp1);
} else {
temp2.addAll(temp0);
}
}
for (int i = 0; i < temp2.size(); i++) {
colorData[i] = temp2.get(i);
}
}
}
与之前的一样,常规代码
/*
* 创建和链接着色器程序
* 参数:顶点着色器、片段着色器程序ResId
* 返回:成功创建、链接了顶点和片段着色器的着色器程序Id
*/
public static int createAndLinkProgram(Context context, String vertexShaderFN, String fragShaderFN) {
//创建着色器程序
int shaderProgram = glCreateProgram();
if (shaderProgram == 0) {
Log.e(TAG, "Failed to create shaderProgram ");
return 0;
}
//获取顶点着色器对象
int vertexShader = loadShader(GL_VERTEX_SHADER, loadShaderSource(context, vertexShaderFN));
if (0 == vertexShader) {
Log.e(TAG, "Failed to load vertexShader");
return 0;
}
//获取片段着色器对象
int fragmentShader = loadShader(GL_FRAGMENT_SHADER, loadShaderSource(context, fragShaderFN));
if (0 == fragmentShader) {
Log.e(TAG, "Failed to load fragmentShader");
return 0;
}
//绑定顶点着色器到着色器程序
glAttachShader(shaderProgram, vertexShader);
//绑定片段着色器到着色器程序
glAttachShader(shaderProgram, fragmentShader);
//链接着色器程序
glLinkProgram(shaderProgram);
//检查着色器链接状态
int[] linked = new int[1];
glGetProgramiv(shaderProgram, GL_LINK_STATUS, linked, 0);
if (linked[0] == 0) {
glDeleteProgram(shaderProgram);
Log.e(TAG, "Failed to link shaderProgram");
return 0;
}
return shaderProgram;
}
public static FloatBuffer getFloatBuffer(float[] array) {
//将顶点数据拷贝映射到 native 内存中,以便opengl能够访问
FloatBuffer buffer = ByteBuffer
.allocateDirect(array.length * BYTES_PER_FLOAT)//直接分配 native 内存,不会被gc
.order(ByteOrder.nativeOrder())//和本地平台保持一致的字节序(大/小头)
.asFloatBuffer();//将底层字节映射到FloatBuffer实例,方便使用
buffer.put(array)//将顶点拷贝到 native 内存中
.position(0);//每次 put position 都会 + 1,需要在绘制前重置为0
return buffer;
}
与以往不同的是,这次绘制需要求得mvp矩阵。
也就是model、view和project三个矩阵,最终再求得一个总的mvpMatrix矩阵
同时还需要设置投影方式,是透视投影还是正交投影。
相关理论知识可以参看官网的这一章:《坐标系统 - LearnOpenGL CN》
本篇博文不再复述
代码:
public static float[] getCircleMVPMatrix(float ratio) {
float[] modelMatrix = getIdentityMatrix(16, 0); //模型变换矩阵
float[] viewMatrix = getIdentityMatrix(16, 0); //观测变换矩阵/相机矩阵
float[] projectionMatrix = getIdentityMatrix(16, 0); //投影变换矩阵
//设置透视投影
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
//设置相机位置
Matrix.setLookAtM(viewMatrix, 0, 0, 0, 7.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
//计算变换矩阵
float[] mvpMatrix = new float[16];
Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
return mvpMatrix;
}
#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aColor;
uniform mat4 u_Matrix;
out vec4 vColor;
void main() {
gl_Position = u_Matrix * vPosition;
vColor = aColor;
}
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
in vec4 vColor;
out vec4 outColor;
void main(){
outColor = vColor;
}
Render、GLSurfaceView与Activity、Fragment等之间的实现逻辑请根据自己项目的实际情况去实现
这里只贴出Render在GLSurfaceView中设置的代码:
mGLSurfaceView = rootView.findViewById(R.id.Circle_GLSurfaceView);
//设置GLES版本
mGLSurfaceView.setEGLContextClientVersion(3);
//创建Render对象,并将其设置到GLSurfaceView
mCircleRender = new CircleRender(getActivity());
mGLSurfaceView.setRenderer(mCircleRender);
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
最终效果如下: