OpenGL ES之四——绘制点,线,三角形

概述

这是一个系列的Android平台下OpenGl ES介绍,从最基本的使用最终到VR图的展示的实现,属于基础篇。(后面针对VR视频会再有几篇文章,属于进阶篇)

OpenGL ES之一——概念扫盲
OpenGL ES之二——Android中的OpenGL ES概述
OpenGL ES之三——绘制纯色背景
OpenGL ES之四——绘制点,线,三角形
OpenGL ES之五——相机和投影,绘制等腰三角形
OpenGL ES之六——绘制矩形和圆形
OpenGL ES之七——着色器语言GLSL
OpenGL ES之八——GLES20类和Matrix类
OpenGL ES之九——相机和投影
OpenGL ES之十——纹理贴图(展示一张图片)
OpenGL ES之十一——绘制3D图形
OpenGL ES之十二——地球仪和VR图

本篇概述

上一篇中介绍了使用OpenGL ES的使用流程,完成了纯色背景的绘制。但是其中没有涉及具体几何形状所以并没有使用到顶点着色器和片段着色器这篇文章中将着重介绍

绘制步骤如下:

1.配置OpenGL ES环境:在AndroidManifest.xml文件中设置使用的OpenGL ES的版本:


    

2.GLSurfaceView作为展示View,图形的具体渲染工作都是在Render中完成。
3.实现GLSurfaceView的Render,在Render中完成点,线,三角形的绘制,具体行为有:

  • 书写顶点着色器和片段着色器文件;
  • 加载编译顶点着色器和片段着色器;
  • 确定需要绘制图形的坐标和颜色数据;
  • 创建program对象,连接顶点和片元着色器,链接program对象,将坐标数据颜色数据传入OpenGL ES程序中;
  • 设置视图窗口(viewport);
  • 使颜色缓冲区的内容显示到屏幕上。

所以本文重点是:了解顶点着色器和片段着色器

一 配置OpenGL ES环境

AndroidManifest.xml文件中设置使用的OpenGL ES的版本


    

二 GLSurfaceView作为展示View

布局文件




    

Activity

public class SimpleShapeActivity extends AppCompatActivity {
    private GLSurfaceView glSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_simple_shape);

        initView();
    }

    private void initView() {
        glSurfaceView = findViewById(R.id.simple_shape_gls);
        glSurfaceView.setEGLContextClientVersion(3);
        GLSurfaceView.Renderer renderer = new SimpleShapeRender();
        glSurfaceView.setRenderer(renderer);
    }
}

由于下面我们要绘制三个点,由三个点连成线或组成三角形所以这里要使用顶点着色器和片段着色器这里先不详细的介绍GLSL语言,只是介绍一下AndroidStudio中配置GLSL环境和简单的介绍涉及到的一些api,后面会单独介绍一些重要的特性和api

三 AndroidStudio中配置GLSL环境

想要在Android Studio中开发GLSL需要安装相应的插件,因为默认是不支持的,所以Android Studio中不支持关键字高亮和智能提示。下面看一下如何安装插件:

3.1如下图找到Settings点击进入设置界面

OpenGL ES之四——绘制点,线,三角形_第1张图片

3.2下载GLSL插件

如下图,进入Settings页面后找到Plugins选项,在上方输入框中搜索GLSL,从而找到相应插件。如果从未安装过,那么右侧的Update按钮处会是一个Install按钮。我们点击Install安装,最后点击ok完成即可。
OpenGL ES之四——绘制点,线,三角形_第2张图片

3.3生成一个GLSL文件

我们在开发时候一般将GLSL文件放在raw文件夹或者Assets文件夹中,这里放在raw中。在raw中新建一个GLSl文件如下,和生成一个其他文件一样,名称为GLSL Shader。这里分为顶点着色器,片段着色器,内容按照GLSL规则编写。
OpenGL ES之四——绘制点,线,三角形_第3张图片

四.顶点着色器,片段着色器程序,Render的实现

4.1顶点着色器,片段着色器文件

顶点着色器文件:vertex_simple_shade

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aColor;
out vec4 vColor;
void main() {
    gl_Position  = vPosition;
    gl_PointSize = 10.0;
    vColor = aColor;
}

第一行:着色器的版本3.0,OpenGL ES 2.0版本可以不写。
第二行:输入一个名为vPosition的4分量向量,layout (location = 0)表示这个变量的位置是顶点属性0。
第三行:输入一个名为aColor的4分量向量,layout (location = 1)表示这个变量的位置是顶点属性1。
第四行:输出一个名为vColor的4分量向量,后面输入到片段着色器中。
第五行:main函数
第六行:gl_Position赋值为vPosition
第七行:gl_PointSize 绘制点的直径10
第八行:将输入数据aColor拷贝到vColor的变量中。

gl_Position,gl_PointSize为Shader内置变量,分别为顶点位置,点的直径。

片段着色器文件fragment_simple_shade

#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 fragColor;
void main() {
    fragColor = vColor;
}

第一行:着色器的版本,OpenGL ES 2.0版本可以不写。
第二行:声明着色器中浮点变量的默认精度。
第三行: 声明一个输入名为vColor的4分向量,来自上面的顶点着色器。
第四行:着色器声明一个输出变量fragColor,这个是一个4分量的向量。
第五行:mian函数
第六行:表示将输入的颜色值数据拷贝到fragColor变量中,输出到颜色缓冲区。

4.2 确定需要绘制图形的坐标和颜色数据和为顶点位置及颜色申请本地内存

绘制图形的坐标和颜色数据

	//三个顶点的位置参数
    private float triangleCoords[] = {
            0.5f, 0.5f, 0.0f, // top
            -0.5f, -0.5f, 0.0f, // bottom left
            0.5f, -0.5f, 0.0f // bottom right
    };

    //三个顶点的颜色参数
    private float color[] = {
            1.0f, 0.0f, 0.0f, 1.0f,// top
            0.0f, 1.0f, 0.0f, 1.0f,// bottom left
            0.0f, 0.0f, 1.0f, 1.0f// bottom right
    };

为顶点位置及颜色申请本地内存

public SimpleShapeRender() {
        //顶点位置相关
        //分配本地内存空间,每个浮点型占4字节空间;将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
        vertexBuffer = ByteBuffer.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        vertexBuffer.put(triangleCoords);
        vertexBuffer.position(0);

        //顶点颜色相关
        colorBuffer = ByteBuffer.allocateDirect(color.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        colorBuffer.put(color);
        colorBuffer.position(0);
 }

4.3 加载编译顶点着色器和片段着色器

/**
     * 编译
     *
     * @param type       顶点着色器:GLES30.GL_VERTEX_SHADER
     *                   片段着色器:GLES30.GL_FRAGMENT_SHADER
     * @param shaderCode 着色器语言编写的着色器程序
     * @return
     */
    private static int compileShader(int type, String shaderCode) {
        //创建一个着色器
        final int shaderId = GLES30.glCreateShader(type);
        if (shaderId != 0) {
            GLES30.glShaderSource(shaderId, shaderCode);
            GLES30.glCompileShader(shaderId);
            //检测状态
            final int[] compileStatus = new int[1];
            GLES30.glGetShaderiv(shaderId, GLES30.GL_COMPILE_STATUS, compileStatus, 0);
            if (compileStatus[0] == 0) {
                String logInfo = GLES30.glGetShaderInfoLog(shaderId);
                System.err.println(logInfo);
                //创建失败
                GLES30.glDeleteShader(shaderId);
                return 0;
            }
            return shaderId;
        } else {
            //创建失败
            return 0;
        }
    }

4.4 创建program对象,连接顶点和片元着色器,链接program对象

	@Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //将背景设置为白色
        GLES20.glClearColor(1.0f,1.0f,1.0f,1.0f);

        //编译顶点着色程序
        String vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_simple_shade);
        int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
        //编译片段着色程序
        String fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_simple_shade);
        int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
        //连接程序
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //在OpenGLES环境中使用程序
        GLES30.glUseProgram(mProgram);
    }

4.5 设置视图窗口(viewport);

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

4.6完成绘制;

	@Override
    public void onDrawFrame(GL10 gl) {
        //把颜色缓冲区设置为我们预设的颜色
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

        //准备坐标数据
        GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
        //启用顶点位置句柄
        GLES30.glEnableVertexAttribArray(0);

        //准备颜色数据
        GLES30.glVertexAttribPointer(1, 4, GLES30.GL_FLOAT, false, 0, colorBuffer);
        //启用顶点颜色句柄
        GLES30.glEnableVertexAttribArray(1);

        //绘制三个点
        //GLES30.glDrawArrays(GLES30.GL_POINTS, 0, POSITION_COMPONENT_COUNT);

        //绘制三条线
        GLES30.glLineWidth(3);//设置线宽
        GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 0, POSITION_COMPONENT_COUNT);

        //绘制三角形
        //GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, POSITION_COMPONENT_COUNT);

        //禁止顶点数组的句柄
        GLES30.glDisableVertexAttribArray(0);
        GLES30.glDisableVertexAttribArray(1);
}

4.7下面是涉及到的完整类

渲染器SimpleShapeRender
public class SimpleShapeRender implements GLSurfaceView.Renderer {
    //一个Float占用4Byte
    private static final int BYTES_PER_FLOAT = 4;
    //三个顶点
    private static final int POSITION_COMPONENT_COUNT = 3;
    //顶点位置缓存
    private final FloatBuffer vertexBuffer;
    //顶点颜色缓存
    private final FloatBuffer colorBuffer;
    //渲染程序
    private int mProgram;

    //三个顶点的位置参数
    private float triangleCoords[] = {
            0.5f, 0.5f, 0.0f, // top
            -0.5f, -0.5f, 0.0f, // bottom left
            0.5f, -0.5f, 0.0f // bottom right
    };

    //三个顶点的颜色参数
    private float color[] = {
            1.0f, 0.0f, 0.0f, 1.0f,// top
            0.0f, 1.0f, 0.0f, 1.0f,// bottom left
            0.0f, 0.0f, 1.0f, 1.0f// bottom right
    };

    public SimpleShapeRender() {
        //顶点位置相关
        //分配本地内存空间,每个浮点型占4字节空间;将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
        vertexBuffer = ByteBuffer.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        vertexBuffer.put(triangleCoords);
        vertexBuffer.position(0);

        //顶点颜色相关
        colorBuffer = ByteBuffer.allocateDirect(color.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        colorBuffer.put(color);
        colorBuffer.position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //将背景设置为白色
        GLES20.glClearColor(1.0f,1.0f,1.0f,1.0f);

        //编译顶点着色程序
        String vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_simple_shade);
        int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
        //编译片段着色程序
        String fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_simple_shade);
        int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
        //连接程序
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //在OpenGLES环境中使用程序
        GLES30.glUseProgram(mProgram);
    }

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

    @Override
    public void onDrawFrame(GL10 gl) {
        //把颜色缓冲区设置为我们预设的颜色
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

        //准备坐标数据
        GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
        //启用顶点位置句柄
        GLES30.glEnableVertexAttribArray(0);

        //准备颜色数据
        GLES30.glVertexAttribPointer(1, 4, GLES30.GL_FLOAT, false, 0, colorBuffer);
        //启用顶点颜色句柄
        GLES30.glEnableVertexAttribArray(1);

        //绘制三个点
        //GLES30.glDrawArrays(GLES30.GL_POINTS, 0, POSITION_COMPONENT_COUNT);

        //绘制三条线
        GLES30.glLineWidth(3);//设置线宽
        GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 0, POSITION_COMPONENT_COUNT);

        //绘制三角形
        //GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, POSITION_COMPONENT_COUNT);

        //禁止顶点数组的句柄
        GLES30.glDisableVertexAttribArray(0);
        GLES30.glDisableVertexAttribArray(1);
    }
}
编译,链接工具类ShaderUtils
public class ShaderUtils {

    private static final String TAG = "ShaderUtils";

    /**
     * 编译顶点着色器
     * @param shaderCode
     */
    public static int compileVertexShader(String shaderCode) {
        return compileShader(GLES30.GL_VERTEX_SHADER, shaderCode);
    }

    /**
     * 编译片段着色器
     * @param shaderCode
     */
    public static int compileFragmentShader(String shaderCode) {
        return compileShader(GLES30.GL_FRAGMENT_SHADER, shaderCode);
    }

    /**
     * 编译
     * @param type       顶点着色器:GLES30.GL_VERTEX_SHADER
     *                   片段着色器:GLES30.GL_FRAGMENT_SHADER
     * @param shaderCode
     */
    private static int compileShader(int type, String shaderCode) {
        //创建一个着色器
        final int shaderId = GLES30.glCreateShader(type);
        if (shaderId != 0) {
            GLES30.glShaderSource(shaderId, shaderCode);
            GLES30.glCompileShader(shaderId);
            //检测状态
            final int[] compileStatus = new int[1];
            GLES30.glGetShaderiv(shaderId, GLES30.GL_COMPILE_STATUS, compileStatus, 0);
            if (compileStatus[0] == 0) {
                String logInfo = GLES30.glGetShaderInfoLog(shaderId);
                System.err.println(logInfo);
                //创建失败
                GLES30.glDeleteShader(shaderId);
                return 0;
            }
            return shaderId;
        } else {
            //创建失败
            return 0;
        }
    }

    /**
     * 链接小程序
     * @param vertexShaderId   顶点着色器
     * @param fragmentShaderId 片段着色器
     */
    public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
        //创建一个空的OpenGLES程序
        final int programId = GLES30.glCreateProgram();
        if (programId != 0) {
            //将顶点着色器加入到程序
            GLES30.glAttachShader(programId, vertexShaderId);
            //将片元着色器加入到程序中
            GLES30.glAttachShader(programId, fragmentShaderId);
            //链接着色器程序
            GLES30.glLinkProgram(programId);
            final int[] linkStatus = new int[1];

            GLES30.glGetProgramiv(programId, GLES30.GL_LINK_STATUS, linkStatus, 0);
            if (linkStatus[0] == 0) {
                String logInfo = GLES30.glGetProgramInfoLog(programId);
                System.err.println(logInfo);
                GLES30.glDeleteProgram(programId);
                return 0;
            }
            return programId;
        } else {
            //创建失败
            return 0;
        }
    }

    /**
     * 验证程序片段是否有效
     * @param programObjectId
     */
    public static boolean validProgram(int programObjectId) {
        GLES30.glValidateProgram(programObjectId);
        final int[] programStatus = new int[1];
        GLES30.glGetProgramiv(programObjectId, GLES30.GL_VALIDATE_STATUS, programStatus, 0);
        return programStatus[0] != 0;
    }

}
资源加载工具类
public class ResReadUtils {

    /**
     * 读取资源
     * @param resourceId
     */
    public static String readResource(int resourceId) {
        StringBuilder builder = new StringBuilder();
        try {
            InputStream inputStream = App.getInstance().getResources().openRawResource(resourceId);
            InputStreamReader streamReader = new InputStreamReader(inputStream);

            BufferedReader bufferedReader = new BufferedReader(streamReader);
            String textLine;
            while ((textLine = bufferedReader.readLine()) != null) {
                builder.append(textLine);
                builder.append("\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Resources.NotFoundException e) {
            e.printStackTrace();
        }
        return builder.toString();
    }

}

着重说明一个地方

在onDrawFrame中的glVertexAttribPointer和glEnableVertexAttribArray中参数比较不好理解,下面着重说一下。

1.具体使用场景
		 //准备坐标数据
        GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
        //启用顶点位置句柄
        GLES30.glEnableVertexAttribArray(0);

        //准备颜色数据
        GLES30.glVertexAttribPointer(1, 4, GLES30.GL_FLOAT, false, 0, colorBuffer);
        //启用顶点颜色句柄
        GLES30.glEnableVertexAttribArray(1);
2.glVertexAttribPointer
public static void glVertexAttribPointer(
        int indx,
        int size,
        int type,
        boolean normalized,
        int stride,
        java.nio.Buffer ptr
    ) 
  • index:顶点属性的索引.(这里我们的顶点位置和颜色向量在着色器中分别为0和1)layout (location = 0) in vec4 vPosition; layout (location = 1) in vec4 aColor;
  • size: 指定每个通用顶点属性的元素个数。必须是1、2、3、4。此外,glvertexattribpointer接受符号常量gl_bgra。初始值为4(也就是涉及颜色的时候必为4)。
  • type:属性的元素类型。(上面都是Float所以使用GLES30.GL_FLOAT);
  • normalized:转换的时候是否要经过规范化,true:是;false:直接转化;
  • stride:跨距,默认是0。(由于我们将顶点位置和颜色数据分别存放没写在一个数组中,所以使用默认值0)
  • ptr: 本地数据缓存(这里我们的是顶点的位置和颜色数据)。
    一层层的去查看我们发现最终实现都是c方法,这也和我们第一篇中说的OpenGL的核心代码都是写的。

4.8 绘制的点,线,三角形截图

OpenGL ES之四——绘制点,线,三角形_第4张图片

线

OpenGL ES之四——绘制点,线,三角形_第5张图片

三角

OpenGL ES之四——绘制点,线,三角形_第6张图片

4.9注意

我们会发现按照顶点数据如下

//三个顶点的位置参数
    private float triangleCoords[] = {
            0.5f, 0.5f, 0.0f, // top
            -0.5f, -0.5f, 0.0f, // bottom left
            0.5f, -0.5f, 0.0f // bottom right
    };

理应绘制成的是等腰直角三角形啊,但是现在怎么不是。这个和第一篇OpenGL ES文中提到的“使用的是虚拟坐标”有关。
同时我们的手机如果横过来的话,以三角形为例会变成如下的样子。
OpenGL ES之四——绘制点,线,三角形_第7张图片
我们发现形状和竖屏的时候完全不同了(如果是三个点,或者是绘制的三条线也会出项相应的变化,因为三个顶点的位置在不同屏幕状态下“发生了变化”)。要想解决上面的问题就要了解坐标系,投影等知识放在下一篇中详细介绍。

你可能感兴趣的:(OpenGL,ES)