OpenGL ES 3.0 | 围绕HelloTriangle实战案例 展开 渲染流程分析
时间:2022-07-23
本文章向大家介绍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版)》
- 使用 React Native 重写大型 Ionic 应用后,我们想分享一下这八个经验
- 基于OWin的Web服务器Katana发布版本3
- 【工具推荐】图像界的魔术师 ImageMagick
- 使用Metrics.NET 构建 ASP.NET MVC 应用程序的性能指标
- 如何设计完善的构建系统,为日常开发提速一倍
- 两年 100 期技术周报后,我收获了这四点
- 如何为技术博客设计一个推荐系统(中):基于 Google 搜索的半自动推荐
- 我是如何为技术博客设计一个推荐系统(上):统计与评分加权
- c#处理空白字符
- 后台优化:使用应用性能管理工具
- Disruptor-NET和内存栅栏
- 我们是如何将 Cordova 应用嵌入到 React Native 中
- ADO.NET的弹性连接控制[ADO.NET idle connection resiliency]
- ASP.Net MVC 5 in Xamarin Studio 5.2
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Qt音视频开发34-Onvif时间设置
- 网络工程师提高篇 | 路由重发布你了解多少?从原理到配置,瑞哥带你学习一波!
- 短视频APP开发,简单计时功能
- LeetCode | 94.二叉树的中序遍历
- Druid 的整合
- LeetCode | 104.二叉树的最大深度
- Flutter 目录结构和项目资源
- iOS音视频接入- TRTC互动直播
- 【一天一大 lee】查找常用字符 (难度:简单) - Day20201014
- 金九银十准备换场地?对标腾讯T3的Android高级工程师面试大纲及时雨来了
- 【一天一大 lee】两两交换链表中的节点 (难度:中等) - Day20201013
- 【一天一大 lee】二叉搜索树的最小绝对差 (难度:简单) - Day20201012
- 有奖互动 | 腾讯云开发者社区 3 周年庆,我过生日,送你们礼物 ~
- 【一天一大 lee】分割等和子集 (难度:中等) - Day20201011
- 【一天一大 lee】寻找两个正序数组的中位数 (难度:困难) - Day20201003