OpenGLES:绘制一个颜色渐变的圆

一.概述

今天使用OpenGLES实现一个圆心是玫红色,向圆周渐变成蓝色的圆

本篇博文的内容也是后续绘制3D图形的基础。

实现过程中,需要重点关注的点是:如何使用数学公式求得图形的顶点,以及加载颜色值。

废话不多说,开工吧!

二.Render类

Render类中需要关注的重点是:createCirclePositions()

这个函数中实现了圆形的顶点创建和颜色值加载

已知如下两个变量:

  • 圆半径:R;
  • 圆周的点与X轴的夹角:θ

求圆的顶点坐标需要求得两种坐标:

  • 圆心的顶点坐标
  • 圆周的顶点坐标

圆心坐标比较简单:(0,0)

圆周上点的坐标:

  • x = R * cos(θ)
  • y = R * sin(θ)

知道如何求得圆的顶点坐标后,如下就是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<Float> 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<Float> temp0 = new ArrayList<>();
        ArrayList<Float> temp2 = new ArrayList<>();
        ArrayList<Float> 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);
        }
    }
}

三.ShaderUtils相关函数:

与之前的一样,常规代码

3.1 createAndLinkProgram()

    /*
     * 创建和链接着色器程序
     * 参数:顶点着色器、片段着色器程序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;
    }

3.2 getFloatBuffer()

    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;
    }

四.TransformUtils相关函数

与以往不同的是,这次绘制需要求得mvp矩阵

也就是model、viewproject三个矩阵,最终再求得一个总的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;
    }

五.着色器代码

5.1 circle_vertex_shader.glsl

#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;
}

5.2 circle_fragtment_shader.glsl

#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;

in vec4 vColor;

out vec4 outColor;

void main(){
    outColor = vColor;
}

六.UI实现

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);

七.最终效果

最终效果如下:

Guess you like

Origin blog.csdn.net/geyichongchujianghu/article/details/133247572
Recommended