【OpenGL ES】绘制圆形

1,017 阅读3分钟

1 前言

【OpenGL ES】绘制三角形 中介绍了绘制三角形的方法,【OpenGL ES】绘制正方形中介绍了绘制正方形的方法,本文将介绍绘制圆形的方法。

​ OpenGL 以点、线段、三角形为图元,没有提供绘制圆形的接口。要绘制圆形边框,必须通过割圆法逼近圆形;要绘制圆形的内部,必须通过三角形逼近圆形,如下图,是通过GL_TRIANGLE_FAN 模式逼近圆形。

img

​ 本文完整代码资源见→【OpenGL ES】绘制圆形

​ 项目目录如下:

img

2 案例

​ MainActivity.java

package com.zhyan8.circle;

import android.opengl.GLSurfaceView;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private GLSurfaceView mGlSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mGlSurfaceView = new MyGLSurfaceView(this);
        setContentView(mGlSurfaceView);
        mGlSurfaceView.setRenderer(new MyRender(this));
    }

    @Override
    protected void onResume() {
        super.onResume();
        mGlSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mGlSurfaceView.onPause();
    }
}

​ MyGLSurfaceView.java

package com.zhyan8.circle;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;

public class MyGLSurfaceView extends GLSurfaceView {
    public MyGLSurfaceView(Context context) {
        super(context);
        setEGLContextClientVersion(3);
    }

    public MyGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setEGLContextClientVersion(3);
    }
}

​ MyRender.java

package com.zhyan8.circle;

import android.content.Context;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyRender implements GLSurfaceView.Renderer {
    private static final int VERTEX_NUM = 45;
    private FloatBuffer vertexBuffer;
    private GLUtils mGLUtils;

    public MyRender(Context context) {
        mGLUtils = new GLUtils(context);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
        //设置背景颜色
        GLES30.glClearColor(0.1f, 0.2f, 0.3f, 0.4f);
        //编译着色器
        final int vertexShaderId = mGLUtils.compileShader(GLES30.GL_VERTEX_SHADER, R.raw.vertex_shader);
        final int fragmentShaderId = mGLUtils.compileShader(GLES30.GL_FRAGMENT_SHADER, R.raw.fragment_shader);
        //链接程序片段
        int programId = mGLUtils.linkProgram(vertexShaderId, fragmentShaderId);
        GLES30.glUseProgram(programId);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //设置视图窗口
        GLES30.glViewport(0, 0, width, height);
        getFloatBuffer(width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //将颜色缓冲区设置为预设的颜色
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
        GLES30.glLineWidth(5);
        //启用顶点的数组句柄
        GLES30.glEnableVertexAttribArray(0);
        //准备坐标数据
        GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
//        GLES30.glDrawArrays(GLES30.GL_POINTS, 0, VERTEX_NUM); //绘制圆形的顶点
//        GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 0, VERTEX_NUM); //绘制圆形的边
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, VERTEX_NUM); //绘制圆形的内部
        //禁止顶点数组句柄
        GLES30.glDisableVertexAttribArray(0);
    }

    private void getFloatBuffer(int width, int height) {
        float[] vertex = mGLUtils.getCircle(0, 0, 0.75f, VERTEX_NUM);
        float[] tempVertex = mGLUtils.adjustCoord(vertex, width, height);
        vertexBuffer = mGLUtils.getFloatBuffer(tempVertex);
    }
}

​ GLUtils.java

package com.zhyan8.circle;

import android.content.Context;
import android.opengl.GLES30;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Arrays;

public class GLUtils {
    private Context mContext;

    public GLUtils(Context context) {
        mContext = context;
    }

    //获取圆周上散点坐标
    public float[] getCircle(float centerX, float centerY, float radius, int num) {
        float unit = (float)(2 * Math.PI / num);
        float[] coords = new float[num * 3];
        for (int i = 0; i < num; i++) {
            coords[i * 3] = (float)(centerX + radius * Math.cos(unit * i));
            coords[i * 3 + 1] = (float)(centerY + radius * Math.sin(unit * i));
            coords[i * 3 + 2] = 0;
        }
        return coords;
    }

    //调整坐标
    public float[] adjustCoord(float[] coords, int width, int height) {
        float ratio = width > height ? (1.0f * height / width) : (1.0f * width / height);
        int start = width > height ? 0 : 1;
        float[] tempCoord =  Arrays.copyOf(coords, coords.length);
        int num = tempCoord.length / 3;
        for (int i = 0; i < num; i++) {
            tempCoord[start + i * 3] *= ratio;
        }
        return tempCoord;
    }

    public FloatBuffer getFloatBuffer(float[] floatArr) {
        FloatBuffer fb = ByteBuffer.allocateDirect(floatArr.length * Float.BYTES)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer();
        fb.put(floatArr);
        fb.position(0);
        return fb;
    }

    //通过代码片段编译着色器
    public int compileShader(int type, String shaderCode){
        int shader = GLES30.glCreateShader(type);
        GLES30.glShaderSource(shader, shaderCode);
        GLES30.glCompileShader(shader);
        return shader;
    }

    //通过外部资源编译着色器
    public int compileShader(int type, int shaderId){
        String shaderCode = readShaderFromResource(shaderId);
        return compileShader(type, shaderCode);
    }

    //链接到着色器
    public int linkProgram(int vertexShaderId, int fragmentShaderId) {
        final int programId = GLES30.glCreateProgram();
        //将顶点着色器加入到程序
        GLES30.glAttachShader(programId, vertexShaderId);
        //将片元着色器加入到程序
        GLES30.glAttachShader(programId, fragmentShaderId);
        //链接着色器程序
        GLES30.glLinkProgram(programId);
        return programId;
    }

    //从shader文件读出字符串
    private String readShaderFromResource(int shaderId) {
        InputStream is = mContext.getResources().openRawResource(shaderId);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String line;
        StringBuilder sb = new StringBuilder();
        try {
            while ((line = br.readLine()) != null) {
                sb.append(line);
                sb.append("\n");
            }
            br.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }
}

​ vertex_shader.glsl

#version 300 es
layout (location = 0) in vec4 vPosition;
void main() {
     gl_Position  = vPosition;
     gl_PointSize = 20.0; //顶点大小(GL_POINTS模式下才会生效)
}

​ 顶点着色器的作用:进行矩阵变换位置、根据光照公式计算顶点颜⾊⽣成 / 变换纹理坐标,并且把位置和纹理坐标发送到片元着色器。

​ 顶点着色器中,如果没有指定默认精度,则 int 和 float 的默认精度都为 highp。

​ fragment_shader.glsl

#version 300 es
precision mediump float; //声明float型变量的精度为mediump
out vec4 fragColor;
void main() {
     fragColor = vec4(0.2, 1.0, 1.0, 1.0); //顶点、边线、内部填充颜色
}

​ 片元着色器的作用:处理经光栅化阶段生成的每个片元,计算每个像素的颜色和透明度。

​ 在片元着色器中,浮点值没有默认的精度值,每个着色器必须声明一个默认的 float 精度。

运行结果:

​ 1)GL_POINTS 模式

img

​ 2)GL_LINE_LOOP 模式

img

​ 3)GL_TRIANGLE_FAN 模式

img

​ 声明:本文转自【OpenGL ES】绘制圆形