案例运行(绘制一个三角形)的基本步骤
【可以先看看文末的代码,结合文章内容去看, 理解了整个流程之后再来看这个步骤,会容易很多】
- 用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变量
中输出一个位置
;这个变量
定义 传递到管线
下一个阶段的 `位置;
片段着色器
代码语言:javascript复制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;
}
}
代码语言:javascript复制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版)》