第三章 OpenGL绘制方式

第三章 OpenGL绘制方式

OpenGL图元

设置点的大小可以再顶点、细分、几何着色器的内置变量gl_PointSize来指定,在OpenGL中可以通过glPointSize(size)来指定,这两种方式都需要开启GL_PROGRAM_POINT_SIZE。

如果点的大小超过1个像素,那么该点中心所在的所有像素都会受到该点光照的影响。

在片元着色器中,可以使用gl_PointCoord来访问点精灵的纹理坐标。

线、条带与循环线

线段光栅化的规则称为diamond exit规则。线的宽度的设置只能在OpenGL中设置。

void glLineWidth(GLfloat width);

如果线段宽度大于1,那么将被在水平和垂直方向复制宽度的大小次数。如果线段为Y主序(即它的主要延伸方向是垂直方向的),那么复制过程是水平的,否则是垂直的。

三角形、条带与扇面

对于三角形共享边光栅化的要求:必须进行光照计算,但不能受到多于一个三角形光计算的影响。也就是共享边不会产生裂缝也不会重复绘制。

OpenGL图元标识

图元类型OpenGL枚举
GL_POINTS
线GL_LINES
条带线GL_LINE_STRIP
循环线GL_LINE_LOOP
独立三角形GL_TRIANGLES
三角形条带GL_TRIANGLE_STRIP
三角形扇面GL_TRIANGLE_FAN

将多边形渲染为点集,轮廓线或者实体

void glPolygonMode(GLenum face, GLenum mode);

控制多边形的正面与背面的绘制模式。face必须为GL_FRONT_AND_BACK, mode可以是GL_POINT,GL_LINE或则是GL_FILL。默认为GL_FILL。

多边形的反转和裁剪

void glFrontFace(GLenum mode);

设置多边形正面的判断方式。默认为GL_CCW,即默认多边形投影到窗口坐标系中,顶点按照逆时针方向为正面。如果设置为GL_CW则采用顺时针方向为正面。

void glCullFace(GLenum mode);

在转换到屏幕坐标渲染之前,需要裁剪哪一类多边形。mode可以是GL_FRONT, GL_BACK, GL_FRONT_AND_BACK。在使用裁剪之前需要使用 glEnable(GL_CULL_FACE) 来开启裁剪。

要判断多边形是正面还是背面可以通过多边形在窗口坐标系下的面积计算。

a = 1 2 ∑ i = 0 n − 1 x i y i ⊕ 1 − x i ⊕ 1 y i a = \frac{1}{2} \sum_{i=0}^{n-1}x_iy_{i⊕1}-x_{i⊕1}y_i a=21i=0n1xiyi1xi1yi

其中 x i x_i xi y i y_i yi表示第i个顶点的窗口坐标 x x x y y y i ⊕ 1 i⊕1 i1表示 ( i + 1 ) m o d n (i+1)mod\quad n (i+1)modn,如果当前正面设置为GL_CCW那么a>0那么该顶点所对应的多边形就位于正面,负责位于反面。如果是GL_CW则是相反的。

OpenGL缓存数据

创建于分配缓存

可用的缓存结合点为:

在这里插入图片描述

缓存的建立是通过glGenBuffers()生成一系列名称,再通过glBindBuffer()将名称绑定在上表中的目标来完成的。

向缓存中输入和输出数据

向缓存中传递数据的最简单方式就是在分配内存的时候读入数据:

void glBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);

为target绑定的缓存对象分配size大小的空间,如果data不是NULL,则用它来填充数据,usage用来向OpenGL端发出一个提示,指示数据的可能用途。

glBufferData是真正为对象分配空间,如果新的数据比当前的数据大或者小都会引起缓存对象空间的扩张或者是收缩。usage分为两部分:第一部分可分为STATIC, DYNAMIC或者STREAM,第二部分可以试试DRAW, READ或者COPY。

"分解"的标识符意义
STATIC数据内容只写入一次,然后多次使用
DYNAMIC数据内容会被多次写入和反复使用
STREAM数据只写入一次,然后不会被反复使用(增量式)
DRAW数据内容由应用程序负责写入,然后作为OpenGL绘制和图像命令的数据源
READ数据内容通过OpenGL反馈的数据写入,然后在应用程序中查询时返回这些数据源
COPY数据内容通过OpenGL反馈的数据写入,并且作为OpenGL绘制和图像命令的数据源

缓存的部分初始化

void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);

用新数据替换缓存对象中的部分数据。从target绑定的缓存对象offset偏移开始使用大小为size数据内容为data的数据进行更新。如果offset和size总和超出了缓存对象数据的范围,那么将产生一个错误。

将缓存对象数据清除为一个已知值

void glClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data);

void glClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLintptr size, GLenum format, GLenum type, const void* data);

format和type指定了data对应数据格式和类型。首先将数据转换为internalformat再填入缓存数据中。

拷贝缓存对象数据

void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);

可以从GL_COPY_READ_BUFFER数据拷贝到GL_COPY_WRITE_BUFFER。

读取缓存内容

void glGetBufferSubData(GLenum target, GLintptr offset, GLintptr size, GLvoid* data);

可以在使用transform feed back和Pixel Buffer Object 中使用该函数获取缓存独享中的数据。

访问缓存内容

之前的操作都涉及到一次数据的拷贝操作,如果要直接对OpenGL的数据进行直接访问,可以使用:

void* glMapBuffer(GLenum target, GLenum access);

将target绑定的缓存对象整个数据区域映射到客户端的地址空间中,之后根据access设置的策略对数据进行访问,如果无法将数据映射出来,那么将产生一个错误并返回NULL。该函数返回一个指针指向target的缓存对象的数据所对应的内存,它只对应与这个缓存本身而不一定就是图形处理器用到的内存区域。

access可以的取值:

标识符意义
GL_READ_ONLY仅对OpenGL映射的内存执行读操作
GL_WRITE_ONLY仅对OpenGL映射的内存执行写操作
GL_READ_WRITE对OpenGL映射的内容同时进行读写操作

解除映射

GLboolean glUnmapBuffer(GLenum target);

异步和显示映射

为了避免glMapBuffer()可能造成的缓存映射问题(例如应用程序错误的指定了access参数,或者是总是用GL_READ_WRITE),glMapBufferRange()函数使用额外的标识符来更精确的设置访问模式。

void* glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);

access必须包含GL_MAP_READ_BIT或GL_MAP_WRITE_BIT中的一个或两个,除此之外还包括其他的标识:

在这里插入图片描述

GL_MAP_UNSYCRONIZED_BIT用于禁止OpenGL数据传输和使用时的自动同步。如果没有这个标志的话,OpenGL会在使用缓存对象之前完成任何正在执行的命令。这么做会导致性能上的损失,但如果可以确保之后的操作可以在真正修改缓存内容之前完成,例如调用glFlush或者使用同步对象,那么OpenGL不需要为此维护一个同步功能。

GL_MAP_FLASH_EXPLICITY_BIT标识符用来通知OpenGL它修改了哪些数据,然后在调用glUnmapBuffer。通知操作使用:

void glFlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr lenght);

可以对缓存对象中独立的或者是重叠的部分多次调用glFlushMappedBufferRange。

丢弃缓存数据

void glInvalidBufferData(GLuint buffer);
void glInvalidBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr lenght);

通知OpenGL,应用程序已经完成对缓存对象中给定范围内容的操作,因此可以根据实际情况丢弃数据。与glBufferData传递NULL不同,glnvalidBufferData不会导致内存的重新分配,因此能优化一些。glInvalidBufferSubData是唯一一个可以抛弃缓存对象的区域数据的方法。

顶点规范

深入讨论VertexAttribPointer

glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer);

size标识每一个顶点中需要更新元素的个数。
type标识每个元素的类型。可以的取值:

在这里插入图片描述

对于整数类型只能存储到缓存对象的内存中,OpenGL必须将这些数据转换成浮点数才能读取到浮点数的顶点属性中。可以通过normalized来控制是否归一化,如果为GL_FALSE,那么就直接强转成浮点数,例如4转成4.0。如果设置为GL_TRUE对于有符号数则使用公式:

f = c 2 b − 1 f = \frac{c}{2^b-1} f=2b1c

如果是无符号则使用公式:

f = 2 c + 1 2 b − 1 f = \frac{2c+1}{2^b-1} f=2b12c+1

其中c表示输入的整数分量,b表示数据类型的位数。

整数顶点属性

void glVertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer);

type必须使用整数类型的一种。

双精度顶点属性

void glVertexAttribLPointer(GLuint index, GLint size, GLenum type, GLsizei stride, cpmst GLvoid* pointer);

type必须是GL_DOUBLE

顶点属性的压缩数据格式

glVertexAttribPointer的size属性除了设置为1,2,3,4之外还可以使用GL_BGRA,type可以指定GL_INT_2_10_10_10_REV或者是GL_UNSIGNED_INT_2_10_10_10_REV。他们表示一个有四个分量的数据格式,前三个分量占据10个字节,最后一个占据2个字节。

在这里插入图片描述

静态顶点属性规范

void glVertexAttrib{1234}{fds}(GLuint index, TYPE values);
void glVertexAttrib{1234}{fds}v(GLuint index, const TYPE* values);
void glVertexAttrib4{bsifd us ui}v(GLuint index, const TYPE* values);

如果在没有启用某个顶点属性数组(没有使用glEnableVertexAttribArray),可以利用以上api对其指定一个默认值。

对应的归一化版本:

void glVertexAttrib4Nub(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w);
void glVertexAttribN{bsi ub ui}v(GLuint index, const TYPE* v);

如果顶点属性变量必须声明为整数,应该使用:

void glVertexAttribI{1234}{i ui}(GLuint index, TYPE values);
void glVertexAttribI{123}{i ui}v(GLuint index, const TYPE* values);
void glVertexAttribI4{bsi ub us ui}v(GLuint index, const TYPE* values);

双精度:

void glVertexAttribL{1234}(GLuint index, TYPE values);
void glVertexAttribL{1234}v(GLuint index, const TYPE* values);

OpenGL的绘制命令

分为索引形式和非索引形式,最基本的非索引形式:

void glDrawArrays(GLenum mode, GLint first, GLsizei count);

mode必须是GL_TRIANGLES, GL_LINE_LOOP, GL_LINES, GL_POINTS。

最基本的索引形式:

void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices);

indices中指定了元素数组缓存中偏移的位置,也就是索引数据开始的位置。
type必须是GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT或者GL_UNSIGNED_INT,表示索引数据的类型。mode与glDrawArrays的相同。

添加一个偏移量:

void glDrawElementBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);

对于第i个元素,实际取得是顶点属性数组中第indices[i]+basevertex。

void glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid* indices);

元素数组缓存中所包含的任何一个索引值(来自indices)都会落入start与end之间

以上两个函数的结合

void glDrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid* indices, GLindex basevertex);

间接绘制函数

void glDrawArraysIndirect(GLenum mode, const GLvoid* indirect)

绘制命令使用GL_DRAW_INDIRECT_BUFFER缓存中的数据,indirect记录间接绘制缓存中的偏移。

typedef struct DrawArrayIndirectCommand_t
{
    GLuint count;
    GLuint primCount;
    GLuint first;
    GLuint baseInstance;
}DrawArraysIndirectCommand;

结构体的所有域成员都会作为glDrawArraysIndirect的参数进行解析。

void glDrawElementsIndirect(GLenum mode, GLenum type, const GLvoid* indirect);

typedef struct DrawElementsIndirectCommand_t
{
    GLuint count;
    GLuint primCount;
    GLuint firstIndex;
    GLuint baseVertex;
    GLuint baseInstance;
}DrawElementsIndirectCommand;

两个结构体中的primCount指定了实例数。

Multi开头的绘制函数

void glMultiDrawArrays(GLenum mode, const GLint * first, const GLint * count, GLsizei primcount)
{
    GLsizei i;
    for (i = 0; i < primcount; i ++)
    {
        glDrawArrays(mode, first[i], count[i]);
    }
}

void glMultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const * indices, GLsizei primcount)
{
    GLsizei i;
    for (i = 0; i < primcount; i ++)
    {
        glDrawElements(mode, count[i], type, indices[i]);
    }
}

void glMultiDrawElementsBaseVertex(Glenum mode, const GLsizei * count, GLenum type, const GLvoid * const * indices, GLsizei primcount, const GLint * basevertex)
{
    GLsizei i;
    for (i = 0; i < primcount; i ++)
    {
        glDrawElements(mode, count[i], type, indices[i], basevertex[i]);
    }
}

void glMultiDrawArraysIndirect(GLenum mode, const void * indirect, GLsizei drawcount, GLsizei stride);
每次调用都会发出drawcount个独立的绘制命令,每个命令的参数与glDrawArraysIndirect()相同。每个DrawArraysIndirectCommand之间的间隔为stride个字节,如果是0的话将紧密排列。

void glMultiDrawElementsIndirect(GLenum mode, GLenum type, const void * indices, GLsizei drawcount, GLsizei stride);

重启图元

void glPrimitiveRestartIndex(GLuint index);

设置一个顶点数组元素的索引值,用来指定渲染过程中,从什么地方重新启动新的图元绘制。如果处理中的数组元素索引的工程中遇到了符合该索引值的数值,那么系统不会处理它对应的顶点数据,而是终止绘制,并且从下一个顶点重新开始渲染同一类型的图元集合。需要glEnable(GL_PRIMITIVE_RESTART),需要在glDrawElements()时使用。

多实例渲染

void glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primCount);

对于每一个图元mode, first和count对于每一个实例是相同的,每一个实例有不同的gl_InstanceID用来区分不同的实例。

void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei primCount);

void glDrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instanceCount, GLuint baseVertex);

多实例的顶点属性

void glVertexAttribDivisor(GLuint index, GLuint divisor);

指定第index个属性是如何分配值到每个实例的,如果divisor是0的话,那么多实例将被禁用,大于0的值表示每divisor个实例都会分配一个新的属性值。

void glDrawArrayInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount, GLuint baseInstance);

void glDrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLsizei instanceCount, GLuint baseInstance);

void glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid * indices, GLsizei instanceCount, GLuint baseVertex, GLuint baseInstane);

如果没使用多实例特性,顶点着色器中也会有gl_InstanceID,值为0

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值