OpenGL ES 3.0 | 围绕HelloTriangle实战案例 展开 渲染流程分析

案例运行(绘制一个三角形)的基本步骤

【可以先看看文末的代码,结合文章内容去看,
理解了整个流程之后再来看这个步骤,会容易很多】

  • 用EGL创建屏幕上的渲染表面(Android直接用一个GLSurfaceView
  • 加载顶点、片段着色器
  • 创建一个程序对象,
    连接顶点、片段着色器,
    并链接程序对象;
  • 设置视口;
  • 清除颜色缓冲区;
  • 渲染简单图元
  • 使颜色缓冲区的内容在EGL窗口表面(GLSurfaceView)中可见

着色器

  • 在OpenGL ES 3.0中,
    除非加载有效的顶点和片段着色器,否则不会绘制任何几何形状;

  • OpenGL ES 3.0程序必须至少有 一个顶点着色器 和 一个片段着色器;

  • 着色器示例代码:

String vShaderStr =
         "#version 300 es             \n"
         +   "in vec4 vPosition;           \n"
         + "void main()                  \n"
         + "{                            \n"
         + "   gl_Position = vPosition;  \n"
         + "}                            \n";

顶点着色器

  • 第一行:
    声明使用的着色器版本,
    #version 300 es 表示 OpenGL ES着色语言V3.00;

  • 这个顶点着色器声明一个输入属性数组——一个名为vPosition的4分量向量;
    Hello Triangle中的 Draw函数 将传入 要放在这个变量中的 每个顶点的位置。`

  • 着色器从它生命的main函数开始执行;

  • 实例着色器代码主题简单,
    vPosition输入属性 拷贝到 gl_Position的 特殊输出变量上;
    每个顶点着色器 必须在 gl_Position变量中输出一个位置
    这个变量 定义 传递到管线下一个阶段的 `位置;

片段着色器

String fShaderStr =
         "#version 300 es                               \n"
         + "precision mediump float;                        \n"
         + "out vec4 fragColor;                         \n"
         + "void main()                                  \n"
         + "{                                            \n"
         + "  fragColor = vec4 ( 0.0, 0.8, 1.0, 1.0 );  \n"
         + "}                                            \n";
  • 第一行同顶点着色器(#version 300 es);

  • precision mediump float;声明 着色器中 浮点变量默认精度

  • 片段着色器 声明 一个输出变量fragColor,这是一个4分量的向量,
    写入这个变量的值 将被 输出到 颜色缓冲区

  • 一般,
    游戏或者应用程序不会像这个例子一样内嵌着色器源字符串
    实际开发中,
    着色器从某种文本或者数据文件中加载,然后加载到API。

编译和加载着色器

  • 以上是定义着色器源代码,
    接着可以将着色器加载到OpenGL ES了;

  • 实例代码中,
    HelloTriangleRenderer.java的 LoadShader()负责 加载着色器源码、编译并检查其错误;

///
   // Create a shader object,
   // load the shader source, and
   // compile the shader,
   // finally,
   // return the shader`s id!
   // 【适用于 顶点着色器、片段着色器】
   //
   private int LoadShader ( int type, String shaderSrc )
   {
      int shader;
      int[] compiled = new int[1];

      // Create the shader object
      shader = GLES30.glCreateShader ( type );

      if ( shader == 0 )
      {
         return 0;
      }

      // Load the shader source
      // 加载 着色器代码
      GLES30.glShaderSource ( shader, shaderSrc );

      // Compile the shader
      // 编译 着色器代码
      GLES30.glCompileShader ( shader );

      // Check the compile status
      // 查看 着色器编译结果状态
      GLES30.glGetShaderiv ( shader, GLES30.GL_COMPILE_STATUS, compiled, 0 );

      //编译失败,则 报错 并 删除着色器实例
      if ( compiled[0] == 0 )
      {
         Log.e ( TAG, GLES30.glGetShaderInfoLog ( shader ) );
         GLES30.glDeleteShader ( shader );
         return 0;
      }

      //编译成功,则返回 着色器id
      return shader;
   }
  • GLES30.glCreateShader ( type );返回一个着色器对象,
    这是一个OpenGL ES 3.0对象,可用于连接到程序对象
    glCreateShader ( type )指定着色器类型并创建着色器对象;

  • GLES30.glShaderSource ( shader, shaderSrc );
    把 着色器源码 加载到 着色器对象;

  • GLES30.glCompileShader ( shader );
    编译着色器;

  • GLES30.glGetShaderiv ( shader, GLES30.GL_COMPILE_STATUS, compiled, 0 );
    查看 着色器编译结果状态;
    编译失败,则 报错(打印错误信息) 并 删除着色器实例;
    编译成功,则返回 着色器id,后续 用于连接到程序对象

创建一个程序对象并链接着色器

  • 应用程序 为顶点和片段着色器 创建了 着色器对象 之后,
    就需要 创建一个 程序对象;

  • 程序对象 可视为 最终链接的程序;

  • 不同的 着色器 编译为 一个 着色器对象之后,
    它们必须连接到 一个 程序对象 并一起链接,才能绘制图形;

///
   // Initialize the shader and program object
   // 初始化 着色器 和 渲染管线程序
   //
   public void onSurfaceCreated ( GL10 glUnused, EGLConfig config )
   {
      String vShaderStr =
         "#version 300 es             \n"
         +   "in vec4 vPosition;           \n"
         + "void main()                  \n"
         + "{                            \n"
         + "   gl_Position = vPosition;  \n"
         + "}                            \n";

      String fShaderStr =
         "#version 300 es                               \n"
         + "precision mediump float;                        \n"
         + "out vec4 fragColor;                         \n"
         + "void main()                                  \n"
         + "{                                            \n"
         + "  fragColor = vec4 ( 0.0, 0.8, 1.0, 1.0 );  \n"
         + "}                                            \n";

      int vertexShader;//加载好的 顶点着色器实例
      int fragmentShader;//加载好的 片段着色器实例
      int programObject;//自定义的渲染管线程序id
      int[] linked = new int[1];//存放链接成功 渲染管线程序id 的数组

      // Load the vertex/fragment shaders
      // 【调用 LoadShader() 】加载着色器实例
      vertexShader = LoadShader ( GLES30.GL_VERTEX_SHADER, vShaderStr );
      fragmentShader = LoadShader ( GLES30.GL_FRAGMENT_SHADER, fShaderStr );

      // Create the program object
      // 基于顶点着色器与片段着色器创建程序
      programObject = GLES30.glCreateProgram();

      //创建失败,就拜拜
      if ( programObject == 0 )
      {
         return;
      }

      //向程序中加入顶点着色器 片元着色器
      GLES30.glAttachShader ( programObject, vertexShader );
      GLES30.glAttachShader ( programObject, fragmentShader );

      // Bind vPosition to attribute 0
      GLES30.glBindAttribLocation ( programObject, 0, "vPosition" );

      // Link the program
      // 链接程序
      GLES30.glLinkProgram ( programObject );

      // Check the link status
      // 把链接成功的 渲染管线程序id 存入数组linked
      GLES30.glGetProgramiv ( programObject, GLES30.GL_LINK_STATUS, linked, 0 );

      //若链接失败则报错 并 删除程序
      if ( linked[0] == 0 )
      {
         Log.e ( TAG, "Error linking program:" );
         Log.e ( TAG, GLES30.glGetProgramInfoLog ( programObject ) );
         GLES30.glDeleteProgram ( programObject );
         return;
      }

      // Store the program object
      // 保存 链接成功的 渲染管线程序id 到 全局变量
      mProgramObject = programObject;

      //指定清除屏幕用的颜色
      GLES30.glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
   }

至此,便完成了
编译着色器、检查编译错误、
创建程序对象、连接着色器、链接程序并检查链接错误等流程;

  • 程序对象 成功链接之后,
    就可使用 程序对象 进行渲染了!
public void onDrawFrame ( GL10 glUnused )
   {
      // Set the viewport
      // viewport【窗口】
      GLES30.glViewport ( 0, 0, mWidth, mHeight );

      // Clear the color buffer
      GLES30.glClear ( GLES30.GL_COLOR_BUFFER_BIT );

      // Use the program object
      // 指定使用某套shader程序
      GLES30.glUseProgram ( mProgramObject );

      // Load the vertex data
      // vertex【顶点】
      // 将顶点位置数据【mVertices】传送进 渲染管线
      GLES30.glVertexAttribPointer ( 0, 3, GLES30.GL_FLOAT, false, 0, mVertices );
      //启用顶点位置数据
      GLES30.glEnableVertexAttribArray ( 0 );

//      GLES30.glDrawArrays ( GLES30.GL_TRIANGLES, 0, 3 );
//      绘制
      GLES30.glDrawArrays ( GLES30.GL_TRIANGLE_STRIP, 0, 3 );
   }
  • GLES30.glUseProgram ( mProgramObject );
    用于绑定程序对象,进行渲染;
    程序对象调用glUseProgram ();之后,
    所有后续的渲染 将用 链接到程序对象的 顶点着色器、片段着色器进行;

设置视口和清除颜色缓冲区

设置视口
  • onDrawFrame()方法用于绘制帧;
  • GLES30.glViewport ( 0, 0, mWidth, mHeight );
    通知OpenGL ES 用于绘制的2D渲染表面原点、宽度和高度

    在OpenGL ES 中,
    视口(Viewport) 定义所有 OpenGL ES 渲染操作 最终显示的 2D矩形

    视口 由 原点坐标(x,y)和宽度、高度 定义;
清除颜色缓冲区
  • 设置视口之后,需要清除屏幕;
  • 在OpenGL ES中,
    绘图中涉及多种缓冲区类型:颜色、深度、模板;
  • HelloTriangle案例中,
    只向颜色缓冲区中绘制图形;
  • 在每个帧的开始,
    GLES30.glClear ( GLES30.GL_COLOR_BUFFER_BIT );清除颜色缓冲区

    缓冲区将用GLES30.glClearColor();指定的颜色清除;

    onSurfaceCreated()的初始化代码中,
    我们已经用GLES30.glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
    指定清除屏幕用的颜色为( 1.0f, 1.0f, 1.0f, 0.0f )了,即白色,
    因此屏幕清为白色;

    清除颜色的设置,
    应该由应用程序在调用颜色缓冲区GLES30.glClear()之前设置;

加载几何形状和绘制图元

加载几何形状
  • 清除颜色缓冲区、设置视口加载程序对象之后,
    指定三角形的几何形状

    三角形的顶点由mVerticesData数组中的3个坐标(x,y,z)指定;
private final float[] mVerticesData =
           { 0.0f, 0.5f, 0.0f,
             -0.5f, -0.5f, 0.0f,
             0.5f, -0.5f, 0.0f };

...

public HelloTriangleRenderer ( Context context )
   {
      mVertices = ByteBuffer.allocateDirect ( mVerticesData.length * 4 )
                  .order ( ByteOrder.nativeOrder() ).asFloatBuffer();
      mVertices.put ( mVerticesData ).position ( 0 );
   }

...

GLES30.glVertexAttribPointer ( 0, 3, GLES30.GL_FLOAT, false, 0, mVertices );
GLES30.glEnableVertexAttribArray ( 0 );
GLES30.glDrawArrays ( GLES30.GL_TRIANGLE_STRIP, 0, 3 );
  • 顶点位置需要加载到GL,
    并连接到 顶点着色器源码中 声明的 vPosition属性;

    默认vPosition变量输入属性位置0绑定
    ——"layout(location = 0) in vec4 vPosition; \n"
    顶点着色器中的每个属性都有一个由无符号整数值唯一标志位置

    使用GLES30.glVertexAttribPointer ();
    顶点数据加载到 顶点变量值vPosition对应的输入属性位置 0上;
绘制图元
  • 通过GLES30.glDrawArrays ( GLES30.GL_TRIANGLE_STRIP, 0, 3 );
    真正告诉OpenGL ES 绘制的图元是什么;
    可选的图元有三角形、直线或者条带等;

显示后台缓冲区

  • 最终最终一步,
    将三角形绘制到帧缓冲区!

如何在屏幕上
真正显示帧缓冲区的内容
——双缓冲区

项目代码

  • 说了这么多,最后直接上代码吧;
    其实这个案例要在Android Studio中编辑并运行的话,流程也不复杂,
    OpenGL ES 在SDK中是有封装好的API的,直接可以调用了;
    不像OpenCV一样还需要进行外接库项目、配置各种环境;

  • 本案例的话,只需要两个文件就可以直接运行了:

    【当然,注意HelloTriangle 是一个Activity,需要在Manifest中注册】
// Book:      OpenGL(R) ES 3.0 Programming Guide, 2nd Edition
// Authors:   Dan Ginsburg, Budirijanto Purnomo, Dave Shreiner, Aaftab Munshi
// ISBN-10:   0-321-93388-5
// ISBN-13:   978-0-321-93388-1
// Publisher: Addison-Wesley Professional
// URLs:      http://www.opengles-book.com
//            http://my.safaribooksonline.com/book/animation-and-3d/9780133440133
//

// Hello_Triangle
//
//    This is a simple example that draws a single triangle with
//    a minimal vertex/fragment shader.  The purpose of this
//    example is to demonstrate the basic concepts of
//    OpenGL ES 3.0 rendering.

package com.lwp.openglorigintest;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.util.Log;

public class HelloTriangleRenderer implements GLSurfaceView.Renderer
{
   // Member variables
   private int mProgramObject;//自定义渲染管线程序id
   private int mWidth;
   private int mHeight;
   private FloatBuffer mVertices;
   private static String TAG = "HelloTriangleRenderer";

   private final float[] mVerticesData =
           { 0.0f, 0.5f, 0.0f,
             -0.5f, -0.5f, 0.0f,
             0.5f, -0.5f, 0.0f };

   ///
   // Constructor
   //
   public HelloTriangleRenderer ( Context context )
   {
      mVertices = ByteBuffer.allocateDirect ( mVerticesData.length * 4 )
                  .order ( ByteOrder.nativeOrder() ).asFloatBuffer();
      mVertices.put ( mVerticesData ).position ( 0 );
   }

   ///
   // Create a shader object,
   // load the shader source, and
   // compile the shader,
   // finally,
   // return the shader`s id!
   // 【适用于 顶点着色器、片段着色器】
   //
   private int LoadShader ( int type, String shaderSrc )
   {
      int shader;
      int[] compiled = new int[1];

      // Create the shader object
      shader = GLES30.glCreateShader ( type );

      if ( shader == 0 )
      {
         return 0;
      }

      // Load the shader source
      // 加载 着色器代码
      GLES30.glShaderSource ( shader, shaderSrc );

      // Compile the shader
      // 编译 着色器代码
      GLES30.glCompileShader ( shader );

      // Check the compile status
      // 查看 着色器编译结果状态
      GLES30.glGetShaderiv ( shader, GLES30.GL_COMPILE_STATUS, compiled, 0 );

      //编译失败,则 报错 并 删除着色器实例
      if ( compiled[0] == 0 )
      {
         Log.e ( TAG, GLES30.glGetShaderInfoLog ( shader ) );
         GLES30.glDeleteShader ( shader );
         return 0;
      }

      //编译成功,则返回 着色器id
      return shader;
   }

   ///
   // Initialize the shader and program object
   // 初始化 着色器 和 渲染管线程序
   //
   public void onSurfaceCreated ( GL10 glUnused, EGLConfig config )
   {
      String vShaderStr =
         "#version 300 es             \n"
         +   "in vec4 vPosition;           \n"
         + "void main()                  \n"
         + "{                            \n"
         + "   gl_Position = vPosition;  \n"
         + "}                            \n";

      String fShaderStr =
         "#version 300 es                               \n"
         + "precision mediump float;                        \n"
         + "out vec4 fragColor;                         \n"
         + "void main()                                  \n"
         + "{                                            \n"
         + "  fragColor = vec4 ( 0.0, 0.8, 1.0, 1.0 );  \n"
         + "}                                            \n";

      int vertexShader;//加载好的 顶点着色器实例
      int fragmentShader;//加载好的 片段着色器实例
      int programObject;//自定义的渲染管线程序id
      int[] linked = new int[1];//存放链接成功 渲染管线程序id 的数组

      // Load the vertex/fragment shaders
      // 【调用 LoadShader() 】加载着色器实例
      vertexShader = LoadShader ( GLES30.GL_VERTEX_SHADER, vShaderStr );
      fragmentShader = LoadShader ( GLES30.GL_FRAGMENT_SHADER, fShaderStr );

      // Create the program object
      // 基于顶点着色器与片段着色器创建程序
      programObject = GLES30.glCreateProgram();

      //创建失败,就拜拜
      if ( programObject == 0 )
      {
         return;
      }

      //向程序中加入顶点着色器 片元着色器
      GLES30.glAttachShader ( programObject, vertexShader );
      GLES30.glAttachShader ( programObject, fragmentShader );

      // Bind vPosition to attribute 0
      GLES30.glBindAttribLocation ( programObject, 0, "vPosition" );

      // Link the program
      // 链接程序
      GLES30.glLinkProgram ( programObject );

      // Check the link status
      // 把链接成功的 渲染管线程序id 存入数组linked
      GLES30.glGetProgramiv ( programObject, GLES30.GL_LINK_STATUS, linked, 0 );

      //若链接失败则报错 并 删除程序
      if ( linked[0] == 0 )
      {
         Log.e ( TAG, "Error linking program:" );
         Log.e ( TAG, GLES30.glGetProgramInfoLog ( programObject ) );
         GLES30.glDeleteProgram ( programObject );
         return;
      }

      // Store the program object
      // 保存 链接成功的 渲染管线程序id 到 全局变量
      mProgramObject = programObject;

      //指定清除屏幕用的颜色
      GLES30.glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
   }

   // /
   // Draw a triangle using the shader pair created in onSurfaceCreated()
   // 使用onSurfaceCreated()中创建好的 着色器对(一对顶点、片段着色器) 画一个三角形
   //
   public void onDrawFrame ( GL10 glUnused )
   {
      // Set the viewport
      // viewport【窗口】
      GLES30.glViewport ( 0, 0, mWidth/2, mHeight/2 );

      // Clear the color buffer
      GLES30.glClear ( GLES30.GL_COLOR_BUFFER_BIT );

      // Use the program object
      // 指定使用某套shader程序
      GLES30.glUseProgram ( mProgramObject );

      // Load the vertex data
      // vertex【顶点】
      // 将顶点位置数据【mVertices】传送进 渲染管线
      GLES30.glVertexAttribPointer ( 0, 3, GLES30.GL_FLOAT, false, 0, mVertices );
      //启用顶点位置数据
      GLES30.glEnableVertexAttribArray ( 0 );

//      GLES30.glDrawArrays ( GLES30.GL_TRIANGLES, 0, 3 );
//      绘制
      GLES30.glDrawArrays ( GLES30.GL_TRIANGLE_STRIP, 0, 3 );
   }

   // /
   // Handle surface changes
   //
   public void onSurfaceChanged ( GL10 glUnused, int width, int height )
   {
      mWidth = width;
      mHeight = height;
   }
}
package com.lwp.openglorigintest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;

public class HelloTriangle extends AppCompatActivity {

    private final int CONTEXT_CLIENT_VERSION = 3;

    @Override
    protected void onCreate ( Bundle savedInstanceState )
    {
        super.onCreate ( savedInstanceState );
        mGLSurfaceView = new GLSurfaceView ( this );

        if ( detectOpenGLES30() )
        {
            // Tell the surface view we want to create an OpenGL ES 3.0-compatible
            // context, and set an OpenGL ES 3.0-compatible renderer.
            mGLSurfaceView.setEGLContextClientVersion ( CONTEXT_CLIENT_VERSION );
            mGLSurfaceView.setRenderer ( new HelloTriangleRenderer ( this ) );
        }
        else
        {
            Log.e ( "HelloTriangle", "OpenGL ES 3.0 not supported on device.  Exiting..." );
            finish();

        }

        setContentView ( mGLSurfaceView );
    }

    private boolean detectOpenGLES30()
    {
        ActivityManager am =
                ( ActivityManager ) getSystemService ( Context.ACTIVITY_SERVICE );
        ConfigurationInfo info = am.getDeviceConfigurationInfo();
        return ( info.reqGlEsVersion >= 0x30000 );
    }

    @Override
    protected void onResume()
    {
        // Ideally a game should implement onResume() and onPause()
        // to take appropriate action when the activity looses focus
        super.onResume();
        mGLSurfaceView.onResume();
    }

    @Override
    protected void onPause()
    {
        // Ideally a game should implement onResume() and onPause()
        // to take appropriate action when the activity looses focus
        super.onPause();
        mGLSurfaceView.onPause();
    }

    private GLSurfaceView mGLSurfaceView;
}

然后就可以运行了:











参考自:

  • 《OPENGL ES 3.0编程指南(第2版)》
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌川江雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值