LearnOpenGL 笔记(四)-高级OpenGL下

目录

八、高级数据

缓冲函数

分批顶点属性

复制缓冲

九、高级GLSL

GLSL的内建变量

片段着色器变量

接口块

Uniform缓冲对象

使用Uniform缓冲

十、实例化

实例化数组

十一、抗锯齿

多重采样抗锯齿(Multisample Anti-aliasing, MSAA)

OpenGL中的MSAA


八、高级数据

缓冲函数

OpenGL中的缓冲只是一个管理特定内存块的对象,在我们将它绑定到一个缓冲目标(Buffer Target)时,我们才赋予了其意义。

glBufferData函数:分配一块内存,并将数据添加到这块内存中,如果我们将它的data参数设置为NULL,那么这个函数将只会分配内存,但不进行填充。4

glBufferSubData:我们可以提供一个偏移量,指定从何处开始填充这个缓冲.缓冲需要有足够的已分配内存,所以对一个缓冲调用glBufferSubData之前必须要先调用glBufferData.

glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data); // 范围: [24, 24 + sizeof(data)]
//一个缓冲目标、一个偏移量、数据的大小和数据本身

glMapBuffer:请求缓冲内存的指针,直接将数据复制到缓冲当中。返回当前绑定缓冲的内存指针

float data[] = {
  0.5f, 1.0f, -0.35f
  ...
};
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// 获取指针
void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
// 复制数据到内存
memcpy(ptr, data, sizeof(data));
// 记得告诉OpenGL我们不再需要这个指针了
glUnmapBuffer(GL_ARRAY_BUFFER);
//使用glUnmapBuffer函数,告诉OpenGL我们已经完成指针操作之后,指针将会不再可用

分批顶点属性

glVertexAttribPointer:指定顶点数组缓冲内容的属性布局。

float positions[] = { ... };
float normals[] = { ... };
float tex[] = { ... };
// 填充缓冲
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);//合并为一个大的数组
//直接将属性数组作为一个整体传递给缓冲,而不需要事先处理它们


//更新顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);  
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions)));  
glVertexAttribPointer(
  2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));

复制缓冲

与其它的缓冲共享其中的数据,或者想要将缓冲的内容复制到另一个缓冲当中:

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

//readtarget和writetarget参数需要填入复制源和复制目标的缓冲目标
//不能同时将两个缓冲绑定到同一个缓冲目标上

OpenGL提供给我们另外两个缓冲目标,叫做GL_COPY_READ_BUFFER和GL_COPY_WRITE_BUFFER

float vertexData[] = { ... };
glBindBuffer(GL_COPY_READ_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
//readtarget中读取size大小的数据,并将其写入writetarget缓冲的writeoffset偏移量处

九、高级GLSL

GLSL的内建变量

GLSL还定义了另外几个以gl_为前缀的变量,它们能提供给我们更多的方式来读取/写入数据:顶点着色器的输出向量gl_Position,和片段着色器的gl_FragCoord。

顶点着色器变量:在屏幕上显示任何东西,在顶点着色器中设置gl_Position是必须的

gl_PointSize:每一个顶点都是一个图元,都会被渲染为一个点。通过OpenGL的glPointSize函数来设置渲染出来的点的大小.gl_Position和gl_PointSize都是输出变量

glEnable(GL_PROGRAM_POINT_SIZE);
//启用OpenGL的GL_PROGRAM_POINT_SIZE

//将点的大小设置为裁剪空间位置的z值
void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);    
    gl_PointSize = gl_Position.z;    
}

gl_VertexID:输入变量,我们只能对它进行读取,储存了正在绘制顶点的当前ID。

片段着色器变量

GLSL提供给我们两个有趣的输入变量:gl_FragCoord和gl_FrontFacing。

gl_FragCoord:gl_FragCoord的z分量等于对应片段的深度值,gl_FragCoord的x和y分量是片段的窗口空间(Window-space)坐标,其原点为窗口的左下角。使用glViewport设定了一个800x600的窗口了,所以片段窗口空间坐标的x分量将在0到800之间,y分量在0到600之间。

gl_FragCoord能让我们读取当前片段的窗口空间坐标,并获取它的深度值,但是它是一个只读(Read-only)变量。

gl_FrontFacing:bool,告诉我们当前片段是属于正向面的一部分还是背向面的一部分。如果当前片段是正向面的一部分那么就是true,否则就是false

gl_FragDepth:用它来在着色器内设置片段的深度值。如果着色器没有写入值到gl_FragDepth,它会自动取用gl_FragCoord.z的值。

gl_FragDepth = 0.0; // 这个片段现在的深度值为 0.0

//直接写入一个0.0到1.0之间的float值到输出变量

只要我们在片段着色器中对gl_FragDepth进行写入,OpenGL就会禁用所有的提前深度测试(Early Depth Testing)。OpenGL无法在片段着色器运行之前得知片段将拥有的深度值,因为片段着色器可能会完全修改这个深度值。

在片段着色器的顶部使用深度条件(Depth Condition)重新声明gl_FragDepth变量:

layout (depth_<condition>) out float gl_FragDepth;

接口块

接口块的声明和struct的声明有点相像,不同的是,现在根据它是一个输入还是输出块(Block),使用in或out关键字来定义的。只要两个接口块的名字一样,它们对应的输入和输出将会匹配起来。

out VS_OUT
{
    vec2 TexCoords;
} vs_out;

//片段着色器,中定义一个输入接口块
in VS_OUT
{
    vec2 TexCoords;
} fs_in;

Uniform缓冲对象

它允许我们定义一系列在多个着色器中相同的全局Uniform变量。当使用Uniform缓冲对象的时候,我们只需要设置相关的uniform一次

Uniform缓冲对象仍是一个缓冲,我们可以使用glGenBuffers来创建它,将它绑定到GL_UNIFORM_BUFFER缓冲目标,并将所有相关的uniform数据存入缓冲。

#version 330 core
layout (location = 0) in vec3 aPos;

layout (std140) uniform Matrices//设置了Uniform块布局
//将projection和view矩阵存储到所谓的Uniform块(Uniform Block)中
{
    mat4 projection;
    mat4 view;
};

uniform mat4 model;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

Uniform块布局:Uniform块的内容是储存在一个缓冲对象中的,它实际上只是一块预留内存。

GLSL会使用一个叫做共享(Shared)布局的Uniform内存布局,共享是因为一旦硬件定义了偏移量,它们在多个程序中是共享并一致的。虽然共享布局给了我们很多节省空间的优化,但是我们需要查询每个uniform变量的偏移量,这会产生非常多的工作量。通常的做法是,不使用共享布局,而是使用std140布局。

layout (std140) uniform ExampleBlock
{
    float value;//4
    vec3  vector;//16
    mat4  matrix;//16-16-16-16
    float values[3];//16-16-16
    bool  boolean;//4
    int   integer;//4
};

使用Uniform缓冲

//调用glGenBuffers,创建一个Uniform缓冲对象
unsigned int uboExampleBlock;
glGenBuffers(1, &uboExampleBlock);
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // 分配152字节的内存
//绑定到GL_UNIFORM_BUFFER目标,并调用glBufferData,分配足够的内存

glBindBuffer(GL_UNIFORM_BUFFER, 0);


//将Uniform块绑定到一个特定的绑定点中,我们需要调用glUniformBlockBinding函数

unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");   
//接受一个程序对象和Uniform块的名称

glUniformBlockBinding(shaderA.ID, lights_index, 2);
//第一个参数是一个程序对象,之后是一个Uniform块索引和链接到的绑定点


//绑定Uniform缓冲对象到相同的绑定点上
//一个目标,一个绑定点索引和一个Uniform缓冲对象
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock); 
// 或glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);

从OpenGL 4.2版本起,你也可以添加一个布局标识符,显式地将Uniform块的绑定点储存在着色器中:

layout(std140, binding = 2) uniform Lights { ... };

 Uniform缓冲对象比起独立的uniform有很多好处。第一,一次设置很多uniform会比一个一个设置多个uniform要快很多。第二,比起在多个着色器中修改同样的uniform,在Uniform缓冲中修改一次会更容易一些。

十、实例化

如果我们需要渲染大量物体时,代码:

for(unsigned int i = 0; i < amount_of_models_to_draw; i++)
{
    DoSomePreparations(); // 绑定VAO,绑定纹理,设置uniform等
    glDrawArrays(GL_TRIANGLES, 0, amount_of_vertices);
}

如果像这样绘制模型的大量实例(Instance),你很快就会因为绘制调用过多而达到性能瓶颈。使用glDrawArrays或glDrawElements函数告诉GPU去绘制你的顶点数据会消耗更多的性能。(即便渲染顶点非常快,命令GPU去渲染却未必。)

实例化这项技术能够让我们使用一个渲染调用来绘制多个物体,来节省每次绘制物体时CPU -> GPU的通信,它只需要一次即可。只需要将glDrawArrays和glDrawElements的渲染调用分别改为glDrawArraysInstanced和glDrawElementsInstanced就可以。

实例化版本需要一个额外的参数,叫做实例数量(Instance Count),它能够设置我们需要渲染的实例个数。

glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);

gl_InstanceID:在使用实例化渲染调用时,gl_InstanceID会从0开始,在每个实例被渲染时递增1.

实例化数组

如果超过最大能够发送至着色器的uniform数据大小上限,则选择实例化数组。它被定义为一个顶点属性(能够让我们储存更多的数据),仅在顶点着色器渲染一个新的实例时才会更新。

#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aOffset;

out vec3 fColor;

void main()
{
    gl_Position = vec4(aPos + aOffset, 0.0, 1.0);
    fColor = aColor;
}

//存在顶点缓冲对象中,并且配置它的属性指针
unsigned int instanceVBO;
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

//设置它的顶点属性指针,并启用顶点属性
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);   
glVertexAttribDivisor(2, 1);
//更新顶点属性的内容至新一组数据
//第一个参数是需要的顶点属性,第二个参数是属性除数(Attribute Divisor)。
//除数是0:告诉OpenGL我们需要在顶点着色器的每次迭代时更新顶点属性
//1:告诉OpenGL我们希望在渲染一个新实例的时候更新顶点属性
//2:希望每2个实例更新一次属性

十一、抗锯齿

 这种现象被称之为走样(Aliasing)。有很多种抗锯齿(Anti-aliasing,也被称为反走样)的技术能够帮助我们缓解这种现象,从而产生更平滑的边缘。

超采样抗锯齿(Super Sample Anti-aliasing, SSAA):它会使用比正常分辨率更高的分辨率(即超采样)来渲染场景,当图像输出在帧缓冲中更新时,分辨率会被下采样(Downsample)至正常的分辨率。它也会带来很大的性能开销。

多重采样抗锯齿(Multisample Anti-aliasing, MSAA)

OpenGL光栅器的工作方式:

光栅器是位于最终处理过的顶点之后到片段着色器之前所经过的所有的算法与过程的总和。光栅器会将一个图元的所有顶点作为输入,并将它转换为一系列的片段。

 一个屏幕像素的网格,每个像素的中心包含有一个采样点(Sample Point),它会被用来决定这个三角形是否遮盖了某个像素。图中红色的采样点被三角形所遮盖,在每一个遮住的像素处都会生成一个片段。

多重采样所做的正是将单一的采样点变为多个采样点,采样点的数量可以是任意的,更多的采样点能带来更精确的遮盖率。

 MSAA真正的工作方式是,无论三角形遮盖了多少个子采样点,(每个图元中)每个像素只运行一次片段着色器。当颜色缓冲的子样本被图元的所有颜色填满时,所有的这些颜色将会在每个像素内部平均化。

每个像素包含4个子采样点,蓝色的采样点被三角形所遮盖,而灰色的则没有。而在三角形的边缘,并不是所有的子采样点都被遮盖,根据被遮盖的子样本的数量,最终的像素颜色将由三角形的颜色与其它子样本中所储存的颜色来决定。

OpenGL中的MSAA

要使用一个能在每个像素中存储大于1个颜色值的颜色缓冲(因为多重采样需要我们为每个采样点都储存一个颜色)。多重采样缓冲(Multisample Buffer)。

glfwWindowHint(GLFW_SAMPLES, 4);
//现在再调用glfwCreateWindow创建渲染窗口时,每个屏幕坐标就会使用一个包含4个子采样点的颜色缓冲了

//调用glEnable并启用GL_MULTISAMPLE,来启用多重采样
glEnable(GL_MULTISAMPLE);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值