OpenGL ES入门教程(二)之绘制一个平面桌子

这篇具有很好参考价值的文章主要介绍了OpenGL ES入门教程(二)之绘制一个平面桌子。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

上一篇文章OpenGL ES入门教程(一)编写第一个OpenGL程序,我们创建了自己的第一个OpenGL程序,实现了绘制红色背景的Activity页面,算是OpenGL ES的hello world程序吧。本篇文章基于上一篇文章基础上讲解如何使用OpenGL绘制一张平面桌子,桌子由一个长方形构成,且长方形中间绘制一条线,长方形两头绘制两个点。文章中提到的示例代码我都共享到gitee上了,各位博友可以从我的gitee仓库OpenGL_ES_DEMO下载完整的项目代码,代码都有很详细的提交记录。

0. OpenGL绘制图形的整体框架概述

  1. 要绘制图形,就要有图形的位置坐标数据,OpenGL中称这些位置为顶点,因此,首先需要定义顶点数据
  2. OpenGL如何操作顶点数据呢?答案是通过着色器操作图形数据,因此,第二步我们要定义着色器
  3. 定义完着色器需要加载着色器以供OpenGL使用
  4. OpenGL编译着色器
  5. 将着色器链接为一个OpenGL程序对象
  6. 通过OpenGL程序对象将着色器与图形数据相关
  7. 以上操作算是使用OpenGL的初始化操作,步骤比较固定,编写完成后,就可以开心的绘制图形啦

绘制图形都是在OpenGL渲染器类中完成,所以,本篇文章的所有代码都是在上一篇文章中定义的AirHockeyRenderer类中编写。

1. 定义顶点

OpenGL包括三类基础图形,点,直线,三角形。其余的任何图形,都是由这三种基本图形组成。

因此如果我们想绘制一个前言中所述的平面桌子,可以由两个三角形组成一个长方形,并在长方形的中间绘制一条直线,两端绘制两个点,如下图所示:
opengl绘制三维桌子,OpenGL ES,OpenGL,OpenGL ES,绘制图形,着色器,片段着色器,顶点着色器,java
无论是x还是y坐标,OpenGL都会把屏幕映射到[-1,1]的范围内。 即屏幕的左边对应x轴的-1,右边对应+1;屏幕的底边对应y轴的-1,顶边对应+1;因此如果将上面的图形绘制到屏幕中间,需要的顶点坐标如下图所示:
opengl绘制三维桌子,OpenGL ES,OpenGL,OpenGL ES,绘制图形,着色器,片段着色器,顶点着色器,java
如上图所示坐标数据,我们将每个顶点(由x坐标和y坐标组成)的数据存储到数组中,上图主要包含的OpenGL基本图形是两个三角形,一条线,两个点,其中三角形的顶点我们统一按照逆时针方向进行存储,这一步数据准备,我可以将它放在渲染器类的构造函数中,最终定义的顶点数据如下:

private Context context;//后面我们加载着色器需要用到该上下文,因此也通过构造函数传进去
public AirHockeyRenderer(Context context)
    {
        this.context = context;

        float[] tableVerticesWithTriangles = {
                /**
                无论是x还是y坐标,OpenGL都会把屏幕映射到[-1,1]的范围内。
                即屏幕的左边对应x轴的-1,右边对应+1;
                屏幕的底边对应y轴的-1,顶边对应+1
                */
                // Triangle 1
                -0.5f, -0.5f,
                0.5f,  0.5f,
                -0.5f,  0.5f,

                // Triangle 2
                -0.5f, -0.5f,
                0.5f, -0.5f,
                0.5f,  0.5f,

                // Line 1
                -0.5f, 0f,
                0.5f, 0f,

                // Mallets
                0f, -0.25f,
                0f,  0.25f
        };
    }

OpenGL作为本地系统库直接运行在硬件上,无法直接读取java程序中定义的数据,因此我们需要把上面定义的顶点数据复制到本地内存中。具体实现代码如下:

private Context context;
private static final int BYTES_PER_FLOAT = 4;
private final FloatBuffer vertexData;
public AirHockeyRenderer(Context context)
    {
        this.context = context;

        float[] tableVerticesWithTriangles = {
                /**
                无论是x还是y坐标,OpenGL都会把屏幕映射到[-1,1]的范围内。
                即屏幕的左边对应x轴的-1,右边对应+1;
                屏幕的底边对应y轴的-1,顶边对应+1
                */
                // Triangle 1
                -0.5f, -0.5f,
                0.5f,  0.5f,
                -0.5f,  0.5f,

                // Triangle 2
                -0.5f, -0.5f,
                0.5f, -0.5f,
                0.5f,  0.5f,

                // Line 1
                -0.5f, 0f,
                0.5f, 0f,

                // Mallets
                0f, -0.25f,
                0f,  0.25f
        };

       vertexData = ByteBuffer
  				//申请本地内存空间大小,单位为字节。tableVerticesWithTriangles中存储的是float类型数据,由32bit组成,即4个字节组成;
                .allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT)
                //本地内存空间的一种排序方式
                .order(ByteOrder.nativeOrder())
                //转换为我们需要的FloatBuffer类型
                .asFloatBuffer();
        //将tableVerticesWithTriangles中的数据拷贝到本地内存中
        vertexData.put(tableVerticesWithTriangles);
    }

2. 定义着色器

OpenGL绘制图形的流程称为OpenGL管道(pipeline)。上面我们定义了顶点数据,并将其拷贝到了本地内存,下面就是将本地内存中的图形顶点数据在OpenGL管道进行流动,并通过着色器告诉GPU如何绘制数据。

着色器是一种只能运行在GPU上的特殊类型程序。OpenGL着色器分为顶点着色器和片段着色器两种类型:

  • 顶点着色器(vertex shader)用于处理顶点(主要作用是确定位置);
  • 片段着色器用于处理,由点,线,三角形组成的片段(主要作用是告诉GPU每个片段的最终颜色)

着色器处理完成后(颜色生成),openGL将它们写入一块称为帧缓冲区(frame buffer)的内存块,然后,Android把这分帧缓冲区中的数据显示在屏幕上。

OpenGL管道(pipeline)流程如下:

  • 读取顶点数据 -》执行顶点着色器-》组装图元-》光栅化图元-》执行片段着色器-》写入帧缓冲器-》显示在屏幕上

其中,光栅化技术是指:移动设备的显示屏通过大量像素的堆积(红绿蓝三种颜色不同比例的混合,就足以创造出人眼可见范围内的颜色),在视觉上创造出巨量颜色范围的技术。而OpenGL光栅化就是把每个点,直线,三角形,分解成大量的小片段,通常情况下,一个片段直接映射到屏幕的一个像素。

OpenGL着色器的定义采用着色器特定的语言(语法结构类似C语言),着色器文件后缀名为glsl(OpenGL shader language)。我们在工程的res目录下新建一个raw文件夹,并在raw文件夹中创建顶点着色器文件simple_vertex_shader.glsl和片段着色器文件simple_fragment_shader.glsl,如下图所示:

opengl绘制三维桌子,OpenGL ES,OpenGL,OpenGL ES,绘制图形,着色器,片段着色器,顶点着色器,java
顶点着色器文件内容如下:

/*
attribute:定义顶点类型位置数据的特定标识
vec4:一种包含4个分量的向量数据类型(x,y,z,w)
     其中x,y,z代表顶点的三维位置坐标,w是一个特殊坐标,后面会讲解
a_Position:变量名称,该名称后面OpenGL的glGetAttribLocation方法要用到,
           如果修改后面就要一起修改
*/
attribute vec4 a_Position;     		

//和C语言类似,main函数是着色器的入口函数
void main()                    
{   //gl_Position :OpenGL特定的变量名,用于存储我们定义的顶点数据                           
    gl_Position = a_Position;
    //gl_PointSize:OpenGL特定的变量名,用于存储点的大小
    gl_PointSize = 10.0;
}  

片段着色器内容如下:

//OpenGL定义float数据类型的精度(lowp;mediump;highp),就像java代码中浮点型选择float类型还是double类型。
//精度是以性能为代价的,这里选择mediump
precision mediump float; 

/*
uniform:定义片段颜色的一种特殊标识
vec4:一种包含4个分量的向量数据类型(r,g,b,a),分别代码红,绿,蓝,透明度。
     其中rgba的取值范围是0-1,rgba色彩不了解的可以去其它文章了解一下。
u_Color:变量名称,该名称后面OpenGL的glGetUniformLocation方法要用到,
                 如果修改后面就要一起修改
*/      	 								
uniform vec4 u_Color;
          	   								
//和C语言类似,main函数是着色器的入口函数
void main()                    		
{                              	
    gl_FragColor = u_Color;
}

3. 加载着色器

加载着色器其实非常简单就是通过Java IO流的方式将着色器文件中的内容读取为一个字符,以供OpenGL后面编译着色器使用。我以前写过一篇详细的Java IO流文章,如果有兴趣的博友可以移步去看看Java IO流最全详解

为了复用代码,我们定义一个TextResourceReader类,并在类中实现一个静态方法readTextFileFromResource,专用于加载着色器,具体实现代码如下:

public class TextResourceReader {
    public static String readTextFileFromResource(Context context, int resourceId) {
        StringBuilder body = new StringBuilder();
        try {
            InputStream inputStream = context.getResources().openRawResource(resourceId);
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String  newLine;
            while ((newLine = bufferedReader.readLine()) != null)
            {
                body.append(newLine);
                body.append("\n");
            }
        }
        catch (IOException e)
        {
            throw new RuntimeException("Could not open resource: " + resourceId, e);
        }
        catch (Resources.NotFoundException e)
        {
            throw new RuntimeException("Resource not found: " + resourceId, e);
        }
        return body.toString();
    }
}

因为加载着色器算是OpenGL绘图的初始化操作,我们可以在surface创建的时候调用一次该逻辑,即在渲染器类重写的onSurfaceCreated方法中调用加载着色器的逻辑,具体代码如下所示:

	@Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        //加载着色器
        String vertexShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_vertex_shader);
        String fragmentShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_fragment_shader);
    }

4. 编译着色器

编译着色器和将着色器链接到OpenGL程序对象都是比较固定的渲染步骤,为了重复利用它们,我们定义一个ShaderHelper类,专门用于编译着色器,将着色器链接为OpenGL程序对象和验证OpenGL程序对象的有效性。

编译着色的实现流程比较固定,我们只需要会用就行了,不必死记硬背,具体实现代码如下:

     /**
     * Compiles a shader, returning the OpenGL object ID.
     * 1. glCreateShader 创建着色器对象 0代表失败,检查创建状态
     * 2. glShaderSource 向着色器对象中上传着色器源码
     * 3. glCompileShader 着色器对象编译源码
     * 4. glGetShaderiv 获取编译状态,若编译失败,则删除着色器对象id,否则返回着色器对象id
     * 5. glGetShaderInfoLog 获取编译结果的详细信息
     * 6. 如果编译失败,glDeleteShader删除渲染器对象id
     * 7. 编译成功,返回渲染器对象id
     * @param type 着色器类型:顶点着色器 GL_VERTEX_SHADER,片段着色器 GL_FRAGMENT_SHADER
     * @param shaderCode 加载的着色器代码
     * @return
     */
    private static int compileShader(int type, String shaderCode) {
        // Create a new shader object.
        final int shaderObjectId = glCreateShader(type);

        if (shaderObjectId == 0) {
            if (LoggerConfig.ON) {
                Log.w(TAG, "Could not create new shader.");
            }

            return 0;
        }

        // Pass in the shader source.
        glShaderSource(shaderObjectId, shaderCode);

        // Compile the shader.
        glCompileShader(shaderObjectId);

        // Get the compilation status.
        final int[] compileStatus = new int[1];
        glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);

        if (LoggerConfig.ON) {
            // Print the shader info log to the Android log output.
            Log.v(TAG, "Results of compiling source:" + "\n" + shaderCode + "\n:"
                    + glGetShaderInfoLog(shaderObjectId));
        }

        // Verify the compile status.
        if (compileStatus[0] == 0) {
            // If it failed, delete the shader object.
            glDeleteShader(shaderObjectId);

            if (LoggerConfig.ON) {
                Log.w(TAG, "Compilation of shader failed.");
            }

            return 0;
        }

        // Return the shader object ID.
        return shaderObjectId;
    }
 
    /**
     * Loads and compiles a vertex shader, returning the OpenGL object ID.
     */
    public static int compileVertexShader(String shaderCode) {
        return compileShader(GL_VERTEX_SHADER, shaderCode);
    }

    /**
     * Loads and compiles a fragment shader, returning the OpenGL object ID.
     */
    public static int compileFragmentShader(String shaderCode) {
        return compileShader(GL_FRAGMENT_SHADER, shaderCode);
    }

因为编译着色器算是OpenGL绘图的初始化操作,我们可以在surface创建的时候调用一次该逻辑,即在渲染器类重写的onSurfaceCreated方法中调用编译着色器的逻辑,具体代码如下所示:

	@Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        //加载着色器
        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);
    }

5. 将着色器链接为OpenGL程序对象

将着色器链接为OpenGL程序对象的实现流程也比较固定,我们只需要会用就行了,不必死记硬背,具体实现代码如下:

 	/**
     *
     * Links a vertex shader and a fragment shader together into an OpenGL
     * program. Returns the OpenGL program object ID, or 0 if linking failed.
     *
     * 1. glCreateProgram 创建OpenGL链接程序对象,获取对象id, 对象id为0代表创建失败
     * 2. glAttachShader 附上着色器
     * 3. glLinkProgram 链接程序,把着色器联合起来
     * 4. glGetProgramiv 获取链接状态,若成功则返回链接对象id,否则glDeleteProgram删除链接对象
     *
     * @param vertexShaderId 顶点着色器对象id
     * @param fragmentShaderId 片段着色器对象id
     * @return
     */
    public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
        // Create a new program object.
        final int programObjectId = glCreateProgram();

        if (programObjectId == 0) {
            if (LoggerConfig.ON) {
                Log.w(TAG, "Could not create new program");
            }

            return 0;
        }

        // Attach the vertex shader to the program.
        glAttachShader(programObjectId, vertexShaderId);
        // Attach the fragment shader to the program.
        glAttachShader(programObjectId, fragmentShaderId);

        // Link the two shaders together into a program.
        glLinkProgram(programObjectId);

        // Get the link status.
        final int[] linkStatus = new int[1];
        glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);

        if (LoggerConfig.ON) {
            // Print the program info log to the Android log output.
            Log.v(TAG, "Results of linking program:\n"
                    + glGetProgramInfoLog(programObjectId));
        }

        // Verify the link status.
        if (linkStatus[0] == 0) {
            // If it failed, delete the program object.
            glDeleteProgram(programObjectId);
            if (LoggerConfig.ON) {
                Log.w(TAG, "Linking of program failed.");
            }
            return 0;
        }

        // Return the program object ID.
        return programObjectId;
    }

     /**
     * Validates an OpenGL program. Should only be called when developing the application.
     * 1. glValidateProgram验证链接到OpenGL程序对象的有效性
     * 2. glGetProgramiv获取OpenGL程序对象有效性的状态,如果返回0代表无效,否则代表链接OpenGL程序对象成功
     * @param programObjectId
     * @return
     */
    public static boolean validateProgram(int programObjectId) {
        glValidateProgram(programObjectId);

        final int[] validateStatus = new int[1];
        glGetProgramiv(programObjectId, GL_VALIDATE_STATUS, validateStatus, 0);
        Log.v(TAG, "Results of validating program: " + validateStatus[0]
                + "\nLog:" + glGetProgramInfoLog(programObjectId));

        return validateStatus[0] != 0;
    }

因为将着色器链接为OpenGL程序对象算是OpenGL绘图的初始化操作,我们可以在surface创建的时候调用一次该逻辑,即在渲染器类重写的onSurfaceCreated方法中实现链接OpenGL程序对象的逻辑,具体代码如下所示:

    private int program;
	@Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        //加载着色器
        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);
        //将着色器链接到OpenGL程序
        program = ShaderHelper.linkProgram(vertexShader, fragmentShader);
        //打印OpenGL程序对象的有效性信息
        if (LoggerConfig.ON) {
            ShaderHelper.validateProgram(program);
        }
    }

6. 将着色器需要的数据与拷贝到本地的数组相关联

至此,我们已经完成了所要绘制图形的顶点数据定义,着色器定义、加载、编译,以及OpenGL程序对象获取。下面我们需要通过OpenGL程序对象获取着色器中定义的属性,并将着色器需要的数据与我们拷贝到本地的数据相关联,继而完成OpenGL绘图的所有前置操作。因为这一步仍然属于OpenGL绘图的初始化或者前置工作,我们可以在surface创建的时候调用一次该逻辑,即在渲染器类重写的onSurfaceCreated方法中调用实现该逻辑,具体实现代码如下:

    //这个字符串一定要和片段着色器中定义的属性名一致
    private static final String U_COLOR = "u_Color";
    private int uColorLocation;
    //这个字符串一定要和顶点着色器中定义的属性名一致
    private static final String A_POSITION = "a_Position";
    private int aPositionLocation;
    //每个顶点由两个浮点数组成:x,y
    private static final int POSITION_COMPONENT_COUNT = 2;
	@Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        
        //加载着色器
        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);
        
        //将着色器链接到OpenGL程序
        program = ShaderHelper.linkProgram(vertexShader, fragmentShader);
        //打印OpenGL程序对象的有效性信息
        if (LoggerConfig.ON) {
            ShaderHelper.validateProgram(program);
        }
        
        /*
        将着色器需要的数据与拷贝到本地的数组相关联
        */
        //告诉OpenGL在绘制任何东西到屏幕上时候,使用这里定义程序
        glUseProgram(program);
        //获取片段着色器中uniform的颜色属性
        uColorLocation = glGetUniformLocation(program, U_COLOR);
        //获取顶点着色器中(attribute)位置属性
        aPositionLocation = glGetAttribLocation(program, A_POSITION);
        //告诉OpenGL从vertexData中读取a_Position的数据
        vertexData.position(0);//将缓冲区数据中的指针指向第一个数据,即从第一个数据开始读
        /*
        将着色器中的位置属性与本地顶点数据相关联。
        aPositionLocation:着色器中定义的位置属性
        POSITION_COMPONENT_COUNT:每次从本地数组中读取两个数据(即x,y代表一个顶点坐标)
        GL_FLOAT:OpenGL采用的数据类型,因为我们定义的是浮点数数组,所以采用GL_FLOAT
        vertexData:要关联的本地数据列表
        */
        glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT,
                false, 0, vertexData);
        //使能顶点数组
        glEnableVertexAttribArray(aPositionLocation);
    }

glVertexAttribPointer方法所需参数的详细说明如下图所示:
opengl绘制三维桌子,OpenGL ES,OpenGL,OpenGL ES,绘制图形,着色器,片段着色器,顶点着色器,java

7. 在屏幕上绘制图形

至此,我们已经完成了使用OpenGL绘制图形的所有必要的前置步骤,下面就是绘制图形了,绘制图形的逻辑在渲染类中重写的onDrawFrame方法中实现。

OpenGL绘制基本图形的步骤如下

  1. 通过glUniform4f方法,更新片段着色器的颜色,该颜色将应用于下面它后面绘制的所有图形,直至再次更新颜色。
    glUniform4f方法的定义如下:
   public static native void glUniform4f(
        int location,//片段着色器的颜色属性
        float x, //rgb色彩的r分量
        float y, //rgb色彩的g分量
        float z, //rgb色彩的b分量
        float w  //rgb色彩的透明度分量
    );
  1. 通过glDrawArrays方法,绘制基本图形
    glDrawArrays方法的定义如下:
   public static native void glDrawArrays(
        int mode, //要绘制的基本图元类型:三角形 GL_TRIANGLES;直线 GL_LINES;点 GL_POINTS
        int first,//从本地顶点数组中读取数据的开始位置
        int count //一共读取多少个顶点,上面glVertexAttribPointer方法中我定义了一个顶点由2个数据组成
    );

因此,如果我们想绘制一个白色的三角形,实现代码如下:

//更新着色器中u_Color的值(白色)
glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
//因为三角形由三个顶点组成,因此我们从本地顶点数据列表中的第一个顶点开始,连续读取3个顶点
glDrawArrays(GL_TRIANGLES, 0, 3);

如果我们想要实现绘制上面提到的如下图形,具体实现代码如下:
opengl绘制三维桌子,OpenGL ES,OpenGL,OpenGL ES,绘制图形,着色器,片段着色器,顶点着色器,java

	@Override
    public void onDrawFrame(GL10 gl) {
        glClear(GL_COLOR_BUFFER_BIT);
        /*
        绘制桌子
         */
        //更新着色器中u_Color的值(白色)
        glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
        //参数1:绘制三角形;参数2:从顶点数组的开头开始读顶点;参数3:读取6个顶点(即绘制两个三角形)
        //之前glVertexAttribPointer告诉过OpenGL每个顶点的位置包含两个浮点分量,因此OpenGL会使用vertexData中如下12个浮点数绘制两个三角形
        /**
         // Triangle 1
         -0.5f, -0.5f,
         0.5f,  0.5f,
         -0.5f,  0.5f,

         // Triangle 2
         -0.5f, -0.5f,
         0.5f, -0.5f,
         0.5f,  0.5f,
         */
        glDrawArrays(GL_TRIANGLES, 0, 6);

        /*
        绘制分割线
         */
        //更新u_Color的值(红色)
        glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
        //参数1:绘制直线; 参数2:从顶点数组的第6个顶点之后(即第7个顶点)开始读取;参数3:读取两个顶点
        glDrawArrays(GL_LINES, 6, 2);

        /*
        绘制两个木槌
         */
        //更新u_Color的值(蓝色)
        glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
        glDrawArrays(GL_POINTS, 8, 1);
        //更新u_Color的值(绿色)
        glUniform4f(uColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
        glDrawArrays(GL_POINTS, 9, 1);
    }

至此,我们已经实现了使用OpenGL绘制一个平面桌子的全部代码,可以从我的gitee仓库OpenGL_ES_DEMO下载完整的项目代码,并执行git reset --hard f1a8e96f0e126814be1a4275459bd7be37c183c0切到文章目前所实现代码的节点,运行程序效果如下:
opengl绘制三维桌子,OpenGL ES,OpenGL,OpenGL ES,绘制图形,着色器,片段着色器,顶点着色器,java

8. 让桌子有边框的效果

为了更好的验证我们是否掌握了使用OpenGL灵活绘制图形的能力,可以在以上绘制图形的基础上,再在长方形内添加一个小的长方形,形成桌子边框的效果,各位博友如果能够独立实现这一功能,那么应该对OpenGL基本图形的绘制有了比较熟练的掌握。具体实现思路和代码如下:

  1. 既然要再加一个小长方形,第一步就需要定义它的顶点坐标,添加小长方形后的顶点坐标数组如下:
float[] tableVerticesWithTriangles = {
                /**
                无论是x还是y坐标,OpenGL都会把屏幕映射到[-1,1]的范围内。
                即屏幕的左边对应x轴的-1,右边对应+1;
                屏幕的底边对应y轴的-1,顶边对应+1
                */
                // Triangle 1
                -0.5f, -0.5f,
                0.5f,  0.5f,
                -0.5f,  0.5f,

                // Triangle 2
                -0.5f, -0.5f,
                0.5f, -0.5f,
                0.5f,  0.5f,

                // Triangle 3
                -0.4f, -0.4f,
                0.4f,  0.4f,
                -0.4f,  0.4f,

                // Triangle 4
                -0.4f, -0.4f,
                0.4f, -0.4f,
                0.4f,  0.4f,

                // Line 1
                -0.5f, 0f,
                0.5f, 0f,

                // Mallets
                0f, -0.25f,
                0f,  0.25f
        };
  1. 顶点坐标数据有了后,就可以开始使用OpenGL绘制图形了,添加一个小长方形后的绘制代码如下:
@Override
    public void onDrawFrame(GL10 gl) {
        glClear(GL_COLOR_BUFFER_BIT);
        /*
        绘制桌子
         */
        //更新着色器中u_Color的值(蓝色)
        glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
        //参数1:绘制三角形;参数2:从顶点数组的开头开始读顶点;参数3:读取6个顶点(即绘制两个三角形)
        //之前glVertexAttribPointer告诉过OpenGL每个顶点的位置包含两个浮点分量,因此OpenGL会使用vertexData中如下12个浮点数绘制两个三角形
        /**
         // Triangle 1
         -0.5f, -0.5f,
         0.5f,  0.5f,
         -0.5f,  0.5f,

         // Triangle 2
         -0.5f, -0.5f,
         0.5f, -0.5f,
         0.5f,  0.5f,
         */
        glDrawArrays(GL_TRIANGLES, 0, 6);

        /**
         * 绘制第二个内长方形,形成边框的效果
         */
        //更新着色器中u_Color的值(白色)
        glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
        glDrawArrays(GL_TRIANGLES, 6, 6);

        /*
        绘制分割线
         */
        //更新u_Color的值(红色)
        glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
        //参数1:绘制直线; 参数2:从顶点数组的第6个顶点之后(即第7个顶点)开始读取;参数3:读取两个顶点
        glDrawArrays(GL_LINES, 12, 2);

        /*
        绘制两个木槌
         */
        //更新u_Color的值(黑色)
        glUniform4f(uColorLocation, 0.0f, 0.0f, 0.0f, 1.0f);
        glDrawArrays(GL_POINTS, 14, 1);
        //更新u_Color的值(绿色)
        glUniform4f(uColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
        glDrawArrays(GL_POINTS, 15, 1);
    }

下载完整的项目代码后,执行git reset --hard bd8d6607592abcddd1547a5e36a01688772eda64切到实现带边框效果的桌面节点,运行程序效果如下:
opengl绘制三维桌子,OpenGL ES,OpenGL,OpenGL ES,绘制图形,着色器,片段着色器,顶点着色器,java文章来源地址https://www.toymoban.com/news/detail-796436.html

到了这里,关于OpenGL ES入门教程(二)之绘制一个平面桌子的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • OpenGL ES 2.0 for Android教程(三):编译着色器并绘制到屏幕

    文章传送门 OpenGL ES 2.0 for Android教程(一) OpenGL ES 2.0 for Android教程(二) OpenGL ES 2.0 for Android教程(四) OpenGL ES 2.0 for Android教程(五) OpenGL ES 2.0 for Android教程(六) OpenGL ES 2.0 for Android教程(七) OpenGL ES 2.0 for Android教程(八) OpenGL ES 2.0 for Android教程(九) 本章将继续我

    2023年04月26日
    浏览(59)
  • OpenCV 入门教程:寻找和绘制轮廓

    寻找和绘制轮廓是图像处理中常用的技术之一,用于识别、定位和分析图像中的目标区域。在 OpenCV 中,寻找和绘制轮廓可以通过边缘检测和形态学操作实现。本文将以寻找和绘制轮廓为中心,为你介绍使用 OpenCV 进行轮廓处理的基本步骤和实例。 😃#x

    2024年02月14日
    浏览(45)
  • ES教程:从入门到入土

    建议直接用docker。 docker启动参数说明: -d 后台启动 -p 9200:9200 将虚拟机9200端口映射到elasticsearch的9200端口(web通信默认使用9200端口) -p 9300:9300 将虚拟机9300端口映射到elasticsearch的9300端口(分布式情况下,各个节点之间通信默认使用9300端口) –name MyEs7 指定一个名字(MyEs

    2024年02月07日
    浏览(40)
  • 【OpenGL ES】三维图形绘制

    不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵! 颜色的简单搭配: 不红+不绿+不蓝 = 黑 红+绿+蓝 = 白 红+绿 = 黄 红+蓝 = 紫 绿+蓝 = 青蓝 投影主要分为 正交投影 和 透视投影 两种。 正交投影 没有近大远小的效果,是平行投影,投影

    2023年04月08日
    浏览(52)
  • 数据可视化神器!Matplotlib Python教程 | 从入门到精通绘制各种类型的图形和保存图形

    大家好,我是爱吃熊掌的鱼,今天我要给大家带来一篇有趣开朗的Matplotlib Python教程。Matplotlib是Python中最流行的数据可视化库之一,它可以帮助我们将数据转化为易于理解的图表和图形。无论你是初学者还是专业人士,Matplotlib都是一个非常有用的工具。让我们开始吧! 在开

    2023年04月21日
    浏览(47)
  • OpenGL ES 绘制一张图片

    GLSL 的修饰符与数据类型 GLSL 中变量的修饰符 const:修饰不可被外界改变的常量 attribute:修饰经常更改的变量,只可以在顶点着色器中使用 uniform:修饰不经常更改的变量,可用于顶点着色器和片段着色器 varying:修饰在顶点着色器计算,然后传递到片元着色器中使用的变量

    2024年02月06日
    浏览(40)
  • 掌握Linux指令和权限:一个入门教程

    语法格式 :ls [选项][目录或者文件] 功能 :对于目录,该命令列出该目录下的所有子目录与文件。对于文件,将列出文件名以及其他信息。 a 列出目录下的所有文件,包括以 . 开头的隐含文件。 -d 将目录象文件一样显示,而不是显示其下的文件。 如:ls –d 指定目录 -i 输出文

    2023年04月23日
    浏览(41)
  • 03.Three.js的入门教程(二)如何创建一个3D地球?

    前言:通过上节课 02.Three.js的入门课程(一),我们了解了Three.js的最小案例DEMO,熟悉了几个重要组成部分。这节课带领大家编写一个3D地球。 一、通过纹理图渲染一个地球 1.1. 创建一个纹理加载器对象TextureLoader,可以加载图片作为纹理贴图; 1.2.完整代码结构 二、小球标

    2024年02月04日
    浏览(69)
  • 【Python】pyqt5入门教程之第一个UI界面

    1.pyqt5工具安装 (1)使用pip工具安装PyQt5工具: (2)安装Qt Designer图形界面开发工具: 安装完成后所在路径 (3)安装QtDesigner 安装完成后所在路径 designer.exe路径 启动QtDesigner 2.第一个QT窗口程序 Pycharm配置 找到py文件,右键External Tool — QTDesigner—就可以启动 QTDesigner .ui文件转换

    2024年02月15日
    浏览(47)
  • LearnOpenGL - Android OpenGL ES 3.0 绘制三角形

    LearnOpenGL 笔记 - 入门 01 OpenGL LearnOpenGL 笔记 - 入门 02 创建窗口 LearnOpenGL 笔记 - 入门 03 你好,窗口 LearnOpenGL 笔记 - 入门 04 你好,三角形 OpenGL - 如何理解 VAO 与 VBO 之间的关系 经过一段时间 OpenGL 的学习,我们已经掌握了如何使用 glwf 在桌面端绘制简单图形。现在让我们把目光

    2024年02月12日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包