转载请以链接形式标明出处:
本文出自:103style的博客
OpenGL ES 3.0学习汇总
- OpenGL ES 3.0 学习记录汇总
目录
本文主要介绍:
- OpenGL ES支持的 图元 和 几何形状对象 的类型,以及它们的绘制方法。
- OpenGL ES 图形管线 顶点着色器 之后的 图元装配 阶段。
- OpenGL ES 图形管线的 光栅化 阶段。
图元
图元可以用 glDrawArrays
、glDrawElements
、glDrawRangeElements
、glDrawArraysInstanced
、glDrawElementsInstanced
命令绘制的几何形状对象。
OpenGL ES 3.0 可以绘制以下图元:
- 三角形
- 直线
- 点精灵
三角形
OpenGL ES 支持的三角形图元有 GL_TRIANGLES
、GL_TRIANGLE_STRIP
、GL_TRIANGLE_FAN
。
以下为对应的示例:
-
GL_TRIANGLES
:绘制一系列单独的三角形。
在上图中绘制了顶点为 (V0,V1,V2)、(V3,V4,V5) 两个三角形。
总共绘制了 n / 3 个三角形,n 为 glDraw*** API中 Count 指定的索引。 -
GL_TRIANGLE_STRIP
:绘制一系列相互连接的三角形。
在上图中绘制了顶点为 (V0,V1,V2)、(V2,V1,V3)注意顺序
、(V2,V3,V4) 三个三角形。
总共绘制了 n - 2 个三角形,n 为 glDraw*** API中 Count 指定的索引。 -
GL_TRIANGLE_FAN
:绘制一系列相连的三角形。
在上图中绘制了顶点为(V0,V1,V2)、(V0,V2,V3)、(V0,V3,V4) 三个三角形。
总共绘制了 n - 2 个三角形,n 为 glDraw*** API中 Count 指定的索引。
直线
OpenGL ES支持的直线图元有 GL_LINES
、GL_LINE_STRIP
、GL_LINE_LOOP
。
GL_LINES
:绘制一系列不相连的线段。
在上图中绘制了端点为 (V0,V1)、(V2,V3) 、(V4,V5) 的单独线段。
总共绘制了 n / 2 个线段,n 为 glDraw*** API中 Count 指定的索引。GL_LINE_STRIP
:绘制一系列相连的线段。
在上图中绘制了3条端点为 (V0,V1)、(V1,V2)、(V2,V3) 的线段。
总共绘制了 n - 1 个线段,n 为 glDraw*** API中 Count 指定的索引。GL_LINE_LOOP
:除了最后一条线段之外,和GL_LINE_STRIP
类似。
在上图中绘制了端点为(V0,V1、(V1,V2)、(V2,V4)、(V3,V4)和(V4,V0) 的线段。
总共绘制了 n 条线段,n 为 glDraw*** API中 Count 指定的索引。
线段的宽度可以用 glLineWidth
void glLineWidth(GLfloat width)
width : 指定线宽,以像素表示
width 受限与OpenGL ES 3.0 支持的线宽范围, 可通过以下代码查询:
GLfloat lineWidthRange[2];
glGetFloatv(GL_ALIASED_LINE_WIDTH_SIZE, lineWidthRange);
点精灵
OpenGL ES支持的点精灵图元是 GL_POINTS
。 点精灵对指定的每个顶点绘制。通常用于粒子效果当作点而非正方形绘制,从而实现高效渲染。
点精灵 是 指定位置和半径的屏幕对齐的正方形。
gl_PointSize
是用于顶点着色器中 输出点半径 的内建变量。同样也受到OpenGL ES 3.0实现所支持的非平滑点尺寸范围限制,可通过以下代码查询:
GLfloat pointSizeRange[2];
glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
OpenGL ES 3.0 将窗口的 左下 视为 原点区域, 而 点精灵 的原点为 左上。
gl_PointCoord
是 片段着色器 只为 点精灵 构建的 内建变量。用 mediump
声明为一个 vec2
变量。赋值范围为 [0,1]。
以下代码为 绘制一个带有纹理的点精灵:
#version 300 es
percision mediump float;
uniform sampler2D s_texSprite;
layout(location = 0) out vec4 outColor;
void main()
{
outColor = texture(s_texSprite, gl_PointCoord);
}
绘制图元
在上文中我们讲到 图元可以用 glDrawArrays
、glDrawElements
、glDrawRangeElements
、glDrawArraysInstanced
、glDrawElementsInstanced
命令绘制的几何形状对象。
glDrawArrays
是 用元素索引为 first
到 first + count - 1
的元素指定的顶点绘制mode对应的图元。
void glDrawArrays(Glenum mode,GLint first, GLsizei count)
mode : 三角行、直线、点精灵对应的7种模式
first : 绘制的第一个顶点的索引
count : 一共绘制的顶点数量
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices)
void glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices)
mode : 三角行、直线、点精灵对应的7种模式
start : 指定indices最小的数组索引
end : 指定indices最大 的数组索引
count : 指定要绘制的索引数量
type : GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT、GL_UNSIGNED_INT
indices : 指向元素索引储存位置的指针
绘制立方体,如果是一系列顺序元素索引描述的图元,用glDrawArrays
,否则用glDrawElements
。
-
glDrawArrays
#version 300 es #define VERTEX_POS_INDX 0 #define NUM_FACES 6 GLfloat vertices[] = {...}; // 顶点坐标 glEnableVertexAttribArray(VERTEX_POS_INDX); glVertexAttribPointer(VERTEX_POS_INDX, 3, GL_FLOAT, GL_FLASE, 0, vertices); for(int i = 0; i < NUM_FACES; i++){ glDrawArrays(GL_TRIANGLES, i * 4, 4); }
-
glDrawElements
#version 300 es #define VERTEX_POS_INDX 0 GLfloat vertices[] = {...}; // 顶点坐标 GLubyte indices[36] = {0, 1, 2, 0, 2, 3 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 1, 7, 1, 6, 7, 2, 1, 7, 5, 4, 7, 6, 5, 7, 3, 2, 7, 4, 3 }; glEnableVertexAttribArray(VERTEX_POS_INDX); glVertexAttribPointer(VERTEX_POS_INDX, 3, GL_FLOAT, GL_FLASE, 0, vertices); glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(GLubyte), GL_UNSIGNED_BYTE, indices);
图元重启
使用图元重启 可以 在一次绘图调用中渲染多个不相连的图元,这对调用绘图API的开销来说是有利的。
图元重启还有一个相对复杂的方法就是 生成退化三角形.
使用图元重启,可以 通过在索引列表中插入一个特殊索引来重启一个用于索引绘图调用(glDraw***
)的图元,这个 特殊索引 就是该索引类型的 最大可能索引。
例如:假设三个 三角形条带(GL_TRIANGLE_STRIP
)分别有元素索引(0,1,2,3)和(8,9,10,11),如果想用图元重启来一次调用绘制两个条带,索引类型为GL_UNSIGNED_BYTE
,则组合的索引列表为(1,2,3,4,255,8,9,10,11)。
可以通过如下代码 启用或者禁用 图元重启。
glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
...
glDisable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
驱动顶点
如果没有限定符,那么顶点着色器的输出值在图元中使用线性插值。但是使用 平面着色 时没有发生插值,所以片段着色器中只有一个顶点值可以用。
以下时驱动顶点选择的规则:
图元i的类型 | 驱动顶点 |
---|---|
GL_POINTS | i |
GL_LINES | 2i |
GL_LINE_LOOP | 如果i < n,为 n+1;如果i = n,为 1 |
GL_LINE_STRIP | i + 1 |
GL_TRIANGLES | 3i |
GL_TRIANGLE_STRIP | i + 2 |
GL_TRIANGEL_FAN | i + 2 |
几何形状实例化
几何形状实例化很高效,可以用一次API调用多次渲染具有不同属性的一个对象,在渲染大量类似对象时很有用,例如对人群的渲染。
几何形状实例化降低了向OpenGL ES引擎发送许多API调用的CPU处理开销。
可以使用以下命令,用几何形状实例化绘图调用渲染:
void glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount)
void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei instanceCount)
mode : 三角形、直线、点精灵对应的七种模式
first : 启用的点点数组中的起始顶点索引
count : 绘制的索引数量
type : 指定保存在indices中的元素索引类型(GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT,GL_UNSIGNED_INT)
indices : 元素索引储存位置的一个指针
instaceCount : 绘制的图元实例数量
可以使用两种方法访问每个实例的数据。
- 用如下命令指示OpenGL ES 对每个实例读取一次或者多次顶点属性:
默认情况下,如果没有指定void glVertexAttribDivisor(GLuint index,GLuint divisior) index : 通用顶点属性索引 dicisior : index位置的通用属性更新之间传递的实例数量
glVertexAttribDivisor
或者顶点属性的divisior = 0
,对每个顶点将读取一次顶点属性。如果divisior = 1
,则每个图元实例读取一次顶点属性。 - 使用内建输入变量
gl_InstanceID
作为顶带着色器中的缓冲区索引,以访问每个实例的数据。如果绘制API时,gl_InstanceID
将保存当前图元实例的索引。使用非实例化绘图调用时,gl_InstanceID
将返回0。
下面两个代码片说明如何用一次实例化绘图调用绘制多个几何形状,其中每个实例的颜色不同。
#include "esUtil.h"
#include
#include
#ifdef _WIN32
#define srandom srand
#define random rand
#endif // _WIN32
#define NUM_INSTANCES 100
#define POSITION_LOC 0
#define COLOR_LOC 1
#define MVP_LOC 2
typedef struct
{
//着色器程序
GLuint programObject;
//顶点缓冲区对象
GLuint positionVBO;
GLuint colorVBO;
GLuint mvpVBO;
GLuint indicesIBO;
//索引数量
int numIndices;
//旋转角度
GLfloat angle[NUM_INSTANCES];
} UserData;
int _Init(ESContext *esContext)
{
GLfloat *positions;
GLuint *indices;
UserData *userData = esContext->userData;
//顶点着色器源代码
const char vShaderStr[] = "#version 300 es \n"
"layout(location = 0) in vec4 a_position; \n"
"layout(location = 1) in vec4 a_color; \n"
"layout(location = 2) in mat4 a_mvpMatix; \n"
"out vec4 v_color; \n"
"void main() \n"
"{ \n"
" v_color = a_color; \n"
" gl_Postion = a_postion * a_mvpMatrix; \n"
"} \n";
const char fShaderStr[] = "#version 300 es \n"
"precision mediump float; \n"
"in vec4 v_color; \n"
"layout(location = 0) out vec4 outColor; \n"
"void main() \n"
"{ \n"
" outColot = v_color; \n"
"}";
//编译着色器代码并链接到着色器程序
userData->programObject = esLoadProgram(vShaderStr, fShaderStr);
//生成数组数据
userData->numIndices = esGenCube(0.1f, &positions, NULL, NULL, &indices);
//索引缓冲区对象
glGenBuffers(1, userData->indicesIBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->indicesIBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * userData->numIndices, indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
free(indices);
//位置缓冲区对象
glGenBuffers(1, userData->positionVBO);
glBindBuffer(GL_ARRAY_BUFFER, userData->positionVBO);
glBufferData(GL_ARRAY_BUFFER, 24 * sizeof(GLfloat) * 3, positions, GL_STATIC_DRAW);
free(positions);
//对每个实例生成随机颜色
{
GLubyte colors[NUM_INSTANCES][4];
int instace;
srand(0);
for (instace = 0; instace < NUM_INSTANCES; instace++)
{
colors[instace][0] = random() % 255;
colors[instace][1] = random() % 255;
colors[instace][2] = random() % 255;
colors[instace][3] = 0;
}
glGenBuffers(1, userData->colorVBO);
glBindBuffer(GL_ARRAY_BUFFER, userData->colorVBO);
glBufferData(GL_ARRAY_BUFFER, NUM_INSTANCES * 4, colors, GL_STATIC_DRAW);
}
//分配储存空间保存每个 变换矩阵 实例
{
int instace;
for (instace = 0; instace < NUM_INSTANCES; instace++)
{
userData->angle[instace] = (float)(random() % 32768) / 32767.0f * 360.0f;
}
glGenBuffers(1, userData->mvpVBO);
glBindBuffer(GL_ARRAY_BUFFER, userData->mvpVBO);
glBufferData(GL_ARRAY_BUFFER, NUM_INSTANCES * sizeof(ESMatrix), NULL, GL_DYNAMIC_DRAW);
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
return GL_TRUE;
}
//根据时间增量更新 变换矩阵
void Update(ESContext *esContext, float deltaTime)
{
UserData *userData = esContext->userData;
ESMatrix *matrixBuf;
ESMatrix perspective;
float aspect;
int instance = 0;
int numRows;
int numColumn;
//计算窗口的宽高比
aspect = (GLfloat)esContext->width / (GLfloat)esContext->height;
esMatrixLoadIdentity(&perspective);
esPerspective(&perspective, 60.0f, aspect, 1.0f, 20.0f);
glBindBuffer(GL_ARRAY_BUFFER, userData->mvpVBO);
matrixBuf = (ESMatrix *)glMapBufferRange(GL_ARRAY_BUFFER, 0, sizeof(ESMatrix) * NUM_INSTANCES, GL_MAP_WRITE_BIT);
numRows = (int)sqrtf(NUM_INSTANCES);
numColumn = numRows;
for (instance = 0; instance < NUM_INSTANCES; instance++)
{
ESMatrix modelview;
float translateX = ((float)(instance % numRows) / (float)numRows)*2.0f - 1.0f;
float translateY = ((float)(instance / numColumn) / (float)numColumn) * 2.0f - 1.0f;
esMatrixLoadIdentity(&modelview);
esTranslate(&modelview, translateX, translateY, -2.0f);
userData->angle[instance] += (deltaTime * 40.0f);
if (userData->angle[instance] >= 360.0f)
{
userData->angle[instance] -= 360.0f;
}
//旋转立方体
esRotate(&modelview, userData->angle[instance], 1.0, 0.0, 1.0);
esMatrixMultiply(&matrixBuf[instance], &modelview, &perspective);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
}
void Draw(ESContext *esContext)
{
UserData *userData = esContext->userData;
//设置窗口
glViewport(0, 0, esContext->width, esContext->height);
//清空颜色
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//链接着色器程序
glUseProgram(userData->programObject);
//加载顶点坐标
glBindBuffer(GL_ARRAY_BUFFER, userData->positionVBO);
glVertexAttribPointer(POSITION_LOC, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (const void *)NULL);
glEnableVertexAttribArray(POSITION_LOC);
//加载实例颜色缓冲区
glBindBuffer(GL_ARRAY_BUFFER, userData->colorVBO);
glVertexAttribPointer(COLOR_LOC, 4, GL_UNSIGNED_BYTE, GL_TRUE, 4 * sizeof(GLubyte), (const void *)NULL);
glEnableVertexAttribArray(COLOR_LOC);
glVertexAttribDivisor(COLOR_LOC, 1);
//加载实例 变换矩阵 缓冲区
glBindBuffer(GL_ARRAY_BUFFER, userData->mvpVBO);
//加载矩阵的每一行
glVertexAttribPointer(MVP_LOC + 0, 4, GL_FLOAT, GL_FALSE, sizeof(ESMatrix), (const void *)NULL);
glVertexAttribPointer(MVP_LOC + 1, 4, GL_FLOAT, GL_FALSE, sizeof(ESMatrix), (const void *)(sizeof(GLfloat) * 4));
glVertexAttribPointer(MVP_LOC + 1, 4, GL_FLOAT, GL_FALSE, sizeof(ESMatrix), (const void *)(sizeof(GLfloat) * 8));
glVertexAttribPointer(MVP_LOC + 1, 4, GL_FLOAT, GL_FALSE, sizeof(ESMatrix), (const void *)(sizeof(GLfloat) * 12));
glEnableVertexAttribArray(MVP_LOC + 0);
glEnableVertexAttribArray(MVP_LOC + 1);
glEnableVertexAttribArray(MVP_LOC + 2);
glEnableVertexAttribArray(MVP_LOC + 3);
glVertexAttribDivisor(MVP_LOC + 0, 1);
glVertexAttribDivisor(MVP_LOC + 1, 1);
glVertexAttribDivisor(MVP_LOC + 2, 1);
glVertexAttribDivisor(MVP_LOC + 3, 1);
//绑定索引缓冲数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->indicesIBO);
//绘制立方体
glDrawElementsInstanced(GL_TRIANGLES, userData->numIndices, GL_UNSIGNED_INT, (const void *)NULL, NUM_INSTANCES);
}
void Shutdown(ESContext *esContext)
{
UserData *userData = esContext->userData;
glDeleteBuffers(1, userData->positionVBO);
glDeleteBuffers(1, userData->colorVBO);
glDeleteBuffers(1, userData->mvpVBO);
glDeleteBuffers(1, userData->indicesIBO);
//删除着色器程序
glDeleteProgram(userData->programObject);
}
int esMain(ESContext *esContext)
{
esContext->userData = malloc(sizeof(UserData));
esCreateWindow(esContext, "Instancing", 640, 480, ES_WINDOW_RGB | ES_WINDOW_DEPTH);
if (!_Init(esContext))
{
return GL_FALSE;
}
esRegisterShutdownFunc(esContext, Shutdown);
esRegisterUpdateFunc(esContext, Update);
esRegisterDrawFunc(esContext, Draw);
return GL_TRUE;
}
运行参考 将 Instancing 设为启动项目,运行报错,看报错提示,笔者是提示 Init
函数名的问题,所以修改为 _Init
。
性能提示
应用程序应该用 尽可能大的图元尺寸 调用 glDrawElements
和 glDrawElementsInstanced
。
如果绘制带有 三角形条带(GL_TRIANGLE_STRIP
)或者 扇形(GL_TRIANGLE_FAN
)的网格,则可以启用 图元重启 将这些网格连接在一起,而不是单独调用glDrawElements
。
当无法用 图元重启 将网格连接在一起时,可以添加造成退化三角形的元素索引,代价时使用更多的索引。
退化三角形 是指 两个顶点或者更多顶点相同 的三角形。
为了连接不同网格而添加的 元素索引(或者退化三角形)
数量取决与每个网格是三角扇形
还是三角形条带
以及每个条带中定义的索引数量
。
三角形条带网格的索引数量很重要,因为我们必须保留从跨越连接起来的不同网格的条带的一个三角形到下一个三角形的弯曲顺序。
连接不同的三角形条带时,我们需要检查两个相互连接的条带的最后一个三角形和第一个三角形的顺序。
有以下两种情况需要处理:
- 第一个三角形条带的
奇数编号
的三角形 连接到 第二个三角形条带的第一个(因而是偶数编号的)三角形。 - 第一个三角形条带的
偶数编号
的三角形 连接到 第二个三角形条带的第一个(因而是偶数编号的)三角形。
下图为两种情况下的三角形条带。
对于上图 相反的顶点顺序,如果我们调用glDrawElements***
绘制两个条带,
组合的元素索引列表为(0,1,2,3,3,8,8,9,10,10),粗体的表示组合元素索引添加的新索引。
将绘制(0,1,2)、(2,1,3)、(2,3,3)、(3,3,8)、(3,8,8)、(8,8,9)、(8,9,10)、(10,9,11)这些三角形,粗体的表示退化三角形。
对于 相同顶点顺序,如果我们调用glDrawElements***
绘制两个条带,
组合的元素索引列表为(0,1,2,3,4,4,8,8,9,10,10),粗体的表示组合元素索引添加的新索引。
将绘制(0,1,2)、(2,1,3)、(2,3,4)、(4,3,4)、(4,4,4)、(4,4,8)、(4,8,8)、(8,8,9)、(8,9,10)、(10,9,11)这些三角形,粗体的表示退化三角形。
添加的新索引数量 和 生成退化三角形的数量 取决于 第一个三角形条带的 顶点数量。必须保留下一个连接条带的弯曲顺序。
图元装配
通过 glDraw***
提供的顶点由顶点着色器执行,顶点着色器变换的每个顶点包括描述顶点(x,y,z,w)值的顶点位置。图元类型和顶点确定将被渲染的单独图元,对于每个单独图元及对应的顶点将执行下图的操作。
下图展示了 图元装配阶段。
坐标系统
下图展示了顶点通过顶点着色器
和图元装配阶段
时的坐标系统。
顶点以物体
或者本地
坐标空间 输入到OpenGL ES,在顶点着色器执行后,顶点位置被认定为在裁剪坐标
空间内。
顶点位置从本地坐标系统到裁剪坐标的变换通过加载执行这一转换的对应矩阵来完成。
-
裁剪
避免处理可视景体之外的图元,图元被裁剪到裁剪空间。在裁剪空间定义的顶点坐标根据 视景体(或称 裁剪体)裁剪,裁剪体由6个裁剪平面定义。
裁剪操作:- 裁剪三角形——全部在内不裁剪,全部在外则抛弃,部分在内则根据裁剪平面裁剪三角形,裁剪之后生成新的顶点成为三角扇形的平面。
- 裁剪直线——全部在内不裁剪,全部在外则抛弃,部分在内裁剪之后生成新的顶点。
- 裁剪点精灵——如果点位置在
近
或者远裁剪平面
之外,或者点精灵的正方形在裁剪体之外,裁剪阶段则抛弃点精灵,否则将通过不做变化通过该阶段,点精灵将在其从裁剪体内部移动外部时裁剪,反之亦然。
在图元根据六个裁剪平面进行裁剪时,顶点坐标经历 透视分割,从而成为规范化的设备坐标,范围为
[-1.0,1.0]
. -
视口变换
视口是一个二维矩形窗口区域,是OpenGL ES 渲染操作最终显示的地方。void glViewport(GLint x, GLint y, GLsizei w, GLsizei h) x,y : 指定视口左下角的窗口坐标,以像素数表示 w,h : 指定视口的宽度和高度,值必须大于0
从规范化设备坐标转换到窗口坐标用如下变换:
深度范围值n和f可以用如下API调用设置:
void glDepthRangef(GLclampf n, GLclampf f) n,f 指定所需的深度范围。 n,f的默认值为 0.0 和 1.0,两值的范围为`[0.0 ,1.0]`。
光栅化
剔除
在三角新被光栅化之前,我们需要确定它们是正面(面向观看者)还是背面(背向观看者)。
剔除操作就是 抛弃背向观看者的三角形。
如何确定三角形的方向:看对应三角形一词的顶点方向是 顺时针(CW)还是逆时针(CCW)。
三角形的方向通过以窗口坐标表示的有符号的三角形的面积来计算。
API如下:
void glFronntFace(GLenum dir)
dir : 指定正面三角形的方向。 有效值为GL_CW、GL_CCW,默认值为GL_CCW
可以通过以下API确定要剔除的三角形
void glCullFace(GLenum mode)
mode : 指定要被剔除的三角形的面。 有效值为GL_FRONT,GL_BACK、GL_FRONT_AND_BACK,默认为GL_BACK。
最后,可以通过以下API确认剔除操作是否应该执行,如果GL_CULL_FACE
被启用,剔除则执行。
void glEnable(GLenum cap)
void glDisable(GLenum cap)
cap : 设置为GL_CULL_FACE,默认情况下剔除被禁用
所以,要剔除合适的三角形,需要:
- 调用
glEnable(GL_CULL_FACE)
启用剔除。 - 调用
glCullFace
设置相应的剔除面。 - 调用
glFrontFace
设置正面三角形方向。
多边形偏移
考虑到相互重叠的多边形的情况,你可能注意到伪像,如被称为 深度伪像 的是因为三角形 光栅化的精度有限 而发生的,这种精度可能影响到 逐片段操作 生成的深度值的精度,造成伪像。
以下展示了不使用多边形偏移绘制这两个共面多边形的代码。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//设置顶点属性状态
...
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDepthFunc(GL_LEQUAL);
//设置顶点属性状态
...
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
为了避免伪像,我们需要执行 深度测试
和 深度值写入深度缓冲区
之前,在计算出来的深度值上添加一个偏移量
。 所以 如果通过深度测试,原始的深度值
将被保存到 深度缓冲区 中,而不是 深度值+偏移
。
可通过如下API设置偏移量,也可以通过glEnable(GL_POLYGON_OFFSET_FILL)
和glDisable(GL_POLYGON_OFFSET_FILL)
启用或者禁用多边形偏移
void glPolygonOffset(GLfloat factor, GLfloat units)
计算偏移的公式是: 深度偏移 = m * 因数 + r * 单位数
m : 三角形的最大深度斜率 。
r : OpenGL ES 实现定义的常量。
以下是启用多边形偏移渲染三角形的代码
const float polygonOffsetFactor = -1.0f;
const float polygonOffsetUnits = -2.0f;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//加载顶点着色器
...
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDepthFunc(GL_LEQUAL);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(polygonOffsetFactor, polygonOffsetUnits);
glDrawArrays(GL_TRIANFLE_FAN,0, 4);
遮挡查询
用来查询对象来跟踪通过深度测试的任何片段或者样本。
遮挡查询分别在 GL_ANY_SAMPLES_PASSED
或GL_ANY_SAMPLES_PASSED_CONSERVATIVE
目标上用 glBeginQuery
和 glEndQuery
开始和结束。
void glBeginQuery(GLenum target, GLuint id)
void glEndQuery(GLenum target)
target : 指定查询对象的目标类型:
`GL_ANY_SAMPLES_PASSED` : 返回是否有样本通过深度测试的精度布尔状态
`GL_ANY_SAMPLES_PASSED_CONSERVATIVR` : 提供更好的性能,但是精确度较低
`GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN` : 在没有样本通过进度测试时返回 GL_TRUE
id : 指定查询对象的名称. 用 `glGenQueries` 创建,用 `glDeleteQueries` 删除。
小结
- 学习了OpenGL ES支持的图元类型
- 了解了如何用常规的非实例化和实例化绘图调用高效的绘制它们
- 在顶点上执行坐标转换的方法
- 光栅化相关的知识