OpenglES 3.0基础知识

来源:互联网 发布:首页源码 编辑:程序博客网 时间:2024/05/01 09:23

一、Opengles特性

1.对Opengl的简化,比如OGL指定网格数据,可以用立即模式,可以显示列表,和顶点数组;但是Opengl es上只能用顶点数组方式传递几何数据
2.对Opengl兼容,尽量定义为一个精简的opengl子集,也能够在Opengl中运行。
3.为了降低耗电和提高shader性能,引入了类型精度限定符
4.确保实现图像质量的最小功能集,符合openglel图像质量,正确性和稳定性。

OES3.0兼容2.0,但是也有一些少量改变,OES3.0可以在帧缓冲区对象在各个 content之间共享,cube texture总是使用无缝过滤,有符号定点数转换为浮点数也有小修改。单OES3.0/2.0和OES1.0不兼容,因为有了可编程管线就可以放弃固定管线,因为同时兼容可编程和固定管线内存占用会大得多。在一个进程中支持多个Opengl es渲染上下文对象,在一个进程中跨opengl es渲染上下文对象共享对象(例如顶点或纹理缓冲区)。

OES渲染所用的窗口系统是EGL,只有IOS不支持EGL,EGL负责初始化显示器,创建渲染表面(帧缓冲区,颜色,深度,模板缓存区,以及他们的格式),创建OES实例(必须要链接到合适的渲染表面才能创建成功),OES可以和其它图形API或窗口的原生绘图命令之间同步渲染。OES还管理纹理贴图等资源。Apple有自己的实现叫EAGL。
OES程序的头文件和库:
#ifdef __APPLE__
#include <OpenGLES/ES3/gl.h>
#else
#include <GLES3/gl3.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#endif
基本状态管理,可以用glEnable(GLenum cap)开启; glDisable(GLenum cap)关闭;除了GL_DITHER抖动是默认开启的,其它都是默认关闭的,可以用glIsEnabled(GLenum cap)来查询。错误可以用GLenum glGetError()来查询。

二、OpenglES3.0新特性

OES1.0版本是基于固定管线的,OES2.0全面引入了顶点着色器和像素着色器渲染模式,OES3.0在2.0基础上添加了更丰富的最新图形学技术实现,例如:阴影贴图,体渲染(将三维场景数据直接生成二维纹理技术,体纹理就是3D texture),GPU粒子动画,几何形状实例化,纹理压缩和Gamma校正等新技术。

1.纹理处理上的改进:

1).sRGB的gamma校正纹理和帧缓存区:
存储时可以时gamma校验的纹理(在纹理缓冲区中),读取到着色器时候需要反gamma校正到线性空间,在线性空间进行光照等计算(线性空间计算图像质量更好),输出到帧换缓存区是转换为sRGB伽马校正空间。
2).2D纹理数组,OES2.0只有单个纹理,用2D纹理数组做帧动画更加方便,不用修改纹理坐标。
3).3D纹理,医学成像中直接执行三维数据文件进行体渲染。
4).深度纹理和阴影比较,深度纹理是从光源位置作为观察点,将深度值写入深度纹理值在[0,1]内,对现有物体的纹理使用纹理堆栈自动计算纹理贴图的(s,t,r,q)坐标,r值缩放到[0,1]内。再从正常视角渲染场景,当场景中物体的纹理r值小于深度纹理中的值时候渲染,大于深度纹理值时候写入0黑色。OES2.0也有深度纹理,3.0增加了阴影比较,可以对2个深度纹理进行线性插值,完成双线性过滤。
5).无缝cube texture,面之间无缝.
6).浮点纹理,拓展了支持的纹理格式,16浮点可以过滤,支持32浮点但是不能过滤。
7).ETC2/EAC纹理压缩,格式为:RGB888,RGB8888,以及单通道双通道的int unsign int数据。可以更好的利用纹理缓存,减少GPU内存占用。
8).整数纹理或其它纹理格式处理能力,对未规范化的整数(8,16,32)纹理,进行直接渲染或读取的能力。
9).非2次方纹理支持,OES2.0要求是2次方的纹理存储。
10).纹理LOD功能,可以手动设置应用的mipmap级别,和设置mipmap范围。
11).纹理调配,引入新的纹理对象状态,允许独立控制每个纹理通道(RGBA)在着色器中的映射。
12).不可变纹理,加载纹理数据之前手动指定纹理的格式和大小,这样纹理的格式不可变,OGLES预先执行所有的一致性检查,在绘制物体时候设置跳过一致性检查,可以提高性能。
13).最小尺寸增大,OES3.0最大纹理支持为2048.

2.着色器改进:

新特性:
1).二进制shader文件支持,编译后不用再编译,编译链接后不用再编译链接。
2).OES2.0不一定有shader编译器,3.0要求存在。
3).非方矩阵支持。
4).全整数支持,之前是浮点数。
5).质心采样,可以用质心采样声明顶点着色器的输出变量,避免多重采样产生伪命像。
6).光栅化时平面平滑插值支持,2.0在光栅化时候都是 默认线性插值。
7).统一变量块,可以高效加载和在多个着色器程序间共享。
8).布局限定符,在顶点输入,着色器输出多目标渲染,统一变量块内都可以使用,显式绑定着色器源代码中的位置,而不需要调用API.
9).实例ID和顶点ID,顶点索引可以在顶点着色器中访问,如果使用实例渲染,还可以访问实例ID.
10).片段深度,片段着色器可以显示控制深度值,而不依赖深度插值。
11).更多的内建函数。
12).宽松的指令数量限制,可以支持变量为基础的循环和分支,
支持数组索引。

3.几何形状和图元装配

1).变换反馈,可以在缓冲区对象中获取顶点着色器输出,对顶点动画和物理学模拟很有作用。
2).布尔遮挡查询,避免渲染不必要的区域。
3).实例渲染,有效的渲染包含类似几何形状但是属性(变换矩阵,颜色和大小)不同的对象,这一功能在渲染大量类似对象时候很有用,例如人群。
4).图元重启,可以在已有图元中使用特殊索引值表示新图元的开始,避免2.0中插入索引,引入退化的三角形条带。
5).新顶点格式。

4.缓冲区对象

缓冲区对象主要有:
GL_ARRAY_BUFFER
GL_ELEMENT_ARRAY_BUFFER
GL_COPY_READ_BUFFER
GL_COPY_WRITE_BUFFER
GL_PIXEL_PACK_BUFFER
GL_PIXEL_UNPACK_BUFFER
GL_TRANSFORM_FEEDBACK_BUFFER
GL_UNIFORM_BUFFER

新特性:

1).统一变量缓冲区对象,可以存储绑定大的统一变量块,统一变量缓冲区对象可以用于减少统一变量值绑定到着色器的性能代价,避免2.0中的性能开销。
2).顶点数组对象VAO,应用程序可以一次API调用避免多次调用设置VBO的开销。
3).采样器对象,将采样器状态(纹理循环模式和过滤)与纹理对象分离,这为在纹理中共享采样器状态提供了更搞笑的方法。
4).同步对象,查询等待GPU上完成执行的机制,应用程序可以控制OES命令在同步对象返回后再提交操作进入操作队列。
5).像素缓存区对象异步操作,CPU可以异步操作像素和纹理传输,在传输期间CPU可以执行其他的任务。
6).缓冲区子区域映射,传统是整个缓冲区映射,现在可以对一个指定区域进行映射,供CPU访问。
7).缓冲区对象间拷贝,不需要CPU等待。

5.帧缓冲区

1).多重渲染目标(MRT)
片段着色器可以输出多个颜色,每个颜色关联到一个颜色缓冲区,MRT用于许多高级的算法,例如延迟着色。
2).引入多重采样渲染缓冲区
使应用程序渲染到具备多重采样抗锯齿功能的屏幕外帧缓冲区,该区不能绑定到纹理,但是可以解析为单采样纹理移动到新的帧缓冲区。
3).帧缓冲区失效机制,避免峰值时候还进行帧缓冲区内容恢复,DX中的后台颜色缓冲区discard机制。
4).新的混合方程式,允许帧缓冲区用max/min函数作为混合方程式。

三、顶点着色器和片段着色器处理阶段

顶点着色器输入的输入来自CPU或GPU中的VBO/VAO数据(都可以用顶点数组提供的每个顶点的数据, 程序设置的uniform变量和samplerXD,samplerXD是特殊的统一变量),进行顶点变换和光照处理最新的可以处理纹理;输出到裁剪4D空间,图元装配裁剪和背面剔除或曲面细分Shader, 透视除法NDC坐标系屏幕变换,光栅化插值为离散的像素位置/uv/颜色等片段。在像素级别对光栅化后离散的片段(顶点着色器输出后插值变量,uniform和samplerXD变量)进行像素颜色组合,最主要应用纹理,光照镜面反射辅助颜色融合和雾融合,输出一般是一个颜色值(如果是多重渲染目标则为每个目标输出一个颜色值),抗锯齿一般是硬件多重采样;经过像素归属测试(像素位置是不是当前OES所有被其它窗口遮挡了则不是)/ssisor test(裁剪区域外则丢弃)/alpha test/stencil test/depth test ,颜色blend融合(与之前的draw call),掩码写入后台缓存区,抖动,glSwapbuffer显示出来。
注意:alpha test和glLogicOp被OES在3.0中删除了,因为alpha test可以在片段着色器中做,glLogicOp很少被使用。
OES提供了一个接口,可以从帧缓存区读回数据。

着色器基础:Shader 源码可以共用 Shader Object对象实现的功能,故需要链接。
Shader Program链接后是动态的可执行指令集,执行中可以更新如果有效那么会马上看到效果(shader object detach 后再编译后attach),删除也不是马上的到不再执行才删除。
OGLES2.0/3.0的着色器的程序对象,只能关联一个顶点着色器程序和一个片段着色器程序不多也不少(和桌面版的OpenGL不一样,unity的着色器也是一样的,只不过提供了些默认的实现例如surface shader)。其它的glCreateShader,glShaderSource,glCompileShader;glCreateProgram,
glAttachShader,glLinkProgram,glUseProgram,顶点数据的传递函数和Opengl桌面版一样。
编译前可以查询是否有编译过的代码(避免使用shader时候每次从脚本编译)glGetProgramBinary (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary);查询后保存到文件系统或直接使用用:glProgramBinary (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length);使用后还需要检查下程序状态glGetProgramiv(programObject, GL_LINK_STATUS, &linked);,如果没成功,那么重新编译脚本。

Shader语法中的的变量类型初始化,作用域,运算符,语句,函数,预处理和GLSL相同,见:

http://blog.csdn.net/blues1021/article/details/54866403

四、Opengles程序框架逻辑实例

1.窗口和OES context实例初始化

1)需要获取显示设备句柄.
esContext->eglDisplay = eglGetDisplay( esContext->eglNativeDisplay );
2)配置帧缓冲区参数(颜色,深度,stencil,多重采样buffer)。
EGLint numConfigs = 0;
EGLint attribList[] =
{
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5,
EGL_ALPHA_SIZE, ( flags & ES_WINDOW_ALPHA ) ? 8 : EGL_DONT_CARE,
EGL_DEPTH_SIZE, ( flags & ES_WINDOW_DEPTH ) ? 8 : EGL_DONT_CARE,
EGL_STENCIL_SIZE, ( flags & ES_WINDOW_STENCIL ) ? 8 : EGL_DONT_CARE,
EGL_SAMPLE_BUFFERS, ( flags & ES_WINDOW_MULTISAMPLE ) ? 1 : 0,
// if EGL_KHR_create_context extension is supported, then we will use
// EGL_OPENGL_ES3_BIT_KHR instead of EGL_OPENGL_ES2_BIT in the attribute list
EGL_RENDERABLE_TYPE, GetContextRenderableType ( esContext->eglDisplay ),
EGL_NONE
};
// Choose config
if ( !eglChooseConfig ( esContext->eglDisplay, attribList, &config, 1, &numConfigs ) )
{
return GL_FALSE;
}
3)创建表面,也就是帧缓冲区。
esContext->eglSurface = eglCreateWindowSurface ( esContext->eglDisplay, config,
esContext->eglNativeWindow, NULL );
4)获取OES context实例,也就是一个OES实例。
esContext->eglContext = eglCreateContext ( esContext->eglDisplay, config,
EGL_NO_CONTEXT, contextAttribs );
5)设置该OES context为当前活动的实例。
if ( !eglMakeCurrent( esContext->eglDisplay, esContext->eglSurface,
esContext->eglSurface, esContext->eglContext ) )
{
return GL_FALSE;
}

2.渲染Shader VAO/VBO初始化

1)获取着色器对象(包括顶点和片段着色器)
GLuint vertexShader = glCreateShader ( GL_VERTEX_SHADER );
glShaderSource ( shader, 1, &shaderSrc, NULL );
glCompileShader ( shader );
2)将着色器对象链接为着色器程序
GLuint programObject = glCreateProgram ( );
glAttachShader ( programObject, vertexShader );
glAttachShader ( programObject, fragmentShader );
glLinkProgram ( programObject );
userData->programObject = programObject;

3.消息驱动的渲染绘制

窗口系统收到部分或全部画面无效发生更改,那么触发消息事件,进行该渲染。
esContext->drawFunc ( esContext );
例如Window上收到 WM_PAINT消息则进行绘制。
void ESUTIL_API esRegisterDrawFunc ( ESContext *esContext, void ( ESCALLBACK *drawFunc ) ( ESContext * ) )
{
esContext->drawFunc = drawFunc;
}
// 注册一次绘制事件
esRegisterDrawFunc ( esContext, Draw );
渲染过程是:
0)清理后台缓冲区(也可以其它帧缓存区,包括深度和stencil)设置视口的屏幕变换
glClear ( GL_COLOR_BUFFER_BIT );
glViewport ( 0, 0, esContext->width, esContext->height );
1)使用著色器程序
glUseProgram ( userData->programObject );
2)向着色器顶点属性传递数据(可以向VAO/VBO中传递,渲染初始化时传递一次即可)
glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
3)开启顶点属性数组索引
glEnableVertexAttribArray ( 0 );
4)绘制Draw call(一帧包含n个draw call)
glDrawArrays ( GL_TRIANGLES, 0, 3 );
6)提交渲染命令,GPU表面翻转进行一帧绘制
eglSwapBuffers ( esContext->eglDisplay, esContext->eglSurface );
例如:
void Draw ( ESContext *esContext )
{
UserData *userData = esContext->userData;
GLfloat vVertices[] = { 0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
// Set the viewport
glViewport ( 0, 0, esContext->width, esContext->height );
// Clear the color buffer
glClear ( GL_COLOR_BUFFER_BIT );
// Use the program object
glUseProgram ( userData->programObject );
// Load the vertex data
glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0, 3 );
eglSwapBuffers ( esContext->eglDisplay, esContext->eglSurface );
}

4.消息驱动的键盘鼠标等交互事件

窗口系统中发生键盘鼠标事件则触发消息进行回调,例如Windows系统上收到了WM_CHAR消息。
esContext->keyFunc ( esContext, ( unsigned char ) wParam,
( int ) point.x, ( int ) point.y );
// 需要注册一次键盘鼠标事件即可:
void ESUTIL_API esRegisterKeyFunc ( ESContext *esContext,
void ( ESCALLBACK *keyFunc ) ( ESContext *, unsigned char, int, int ) )
{
esContext->keyFunc = keyFunc;
}
void Shutdown ( ESContext *esContext )
{
UserData *userData = esContext->userData;
glDeleteProgram ( userData->programObject );
}

5.消息捕获分发和应用程序主循环

当没有窗口消息时候,那么进行窗口的主循环调用,通常是一个主update,进行动画特效物理声音等表现。
当收到窗口或应用程序退出消息时候,进行退出卸载逻辑。
int main ( int argc, char *argv[] )
{
ESContext esContext;
memset ( &esContext, 0, sizeof ( ESContext ) );
if ( esMain ( &esContext ) != GL_TRUE )
{
return 1;
}
WinLoop ( &esContext );
if ( esContext.shutdownFunc != NULL )
{
esContext.shutdownFunc ( &esContext );
}
if ( esContext.userData != NULL )
{
free ( esContext.userData );
}
return 0;
}
void WinLoop ( ESContext *esContext )
{
MSG msg = { 0 };
int done = 0;
DWORD lastTime = GetTickCount();
while ( !done )
{
int gotMsg = ( PeekMessage ( &msg, NULL, 0, 0, PM_REMOVE ) != 0 );
DWORD curTime = GetTickCount();
float deltaTime = ( float ) ( curTime - lastTime ) / 1000.0f;
lastTime = curTime;

if ( gotMsg )
{
if ( msg.message == WM_QUIT )
{
done = 1;
}
else
{
TranslateMessage ( &msg );
DispatchMessage ( &msg );
}
}
else
{
SendMessage ( esContext->eglNativeWindow, WM_PAINT, 0, 0 );
}
// Call update function if registered
if ( esContext->updateFunc != NULL )
{
esContext->updateFunc ( esContext, deltaTime );
}
}
}

0 0