OpenGL笔记(七)

引用链接Learn OpenGL 作者 Joey de Vries

一. Advanced Data

除了基本的往OpenGL的buffer里面填充的API:glBufferData,再介绍几个相关API。

  • glBufferSubData: 可以在特定类型buffer的特定位置,插入数据,可用于动态添加buffer数据,而不用重新分配空间。
    此函数需要在有具体Buffer内存时,才可使用,所以该函数要在glDataBuffer调用之后。
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data); // Range: [24, 24 + sizeof(data)]
  • glMapBuffer: 返回当前绑定的Buffer对应的内存的指针,通过此API可以直接根据data进行当前OpenGL上绑定的buffer的切换,可以直接传输数据,而不用再去建立一个buffer,再用glBindBuffer函数。
    glUnmapBuffer: 释放绑定的指针,数据转移成功返回GL_TRUE
void *ptr = glMapBuffer(GL_ARRAY_BUFFER,GL_WRITE_ONLY);

举个例子,通过指针直接实现对buffer中的数据转移,从而不用再重新建立一个buffer,绑定该buffer:

float data[] = {
  0.5f, 1.0f, -0.35f
  ...
};
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// get pointer
void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
// now copy data into memory
memcpy(ptr, data, sizeof(data));
// make sure to tell OpenGL we're done with the pointer
glUnmapBuffer(GL_ARRAY_BUFFER);

高级挖顶点数据的方法
之前都是在一个data数组里面按照1,2,3的顺序逐次挖数据,用的API是glVertexAttribPointer
而现在可以分别给数组,比如position array ,normal array,其实这种格式的数据可能更常用一些。

比如这样填充数据:

float positions[] = { ... };
float normals[] = { ... };
float tex[] = { ... };
// fill buffer
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), (void*)(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)));

复制Buffer
API如下:

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

Buffer类型可以填:

  • VERTEX_ARRAY_BUFFER
  • VERTEX_ELEMENT_ARRAY_BUFFER
    所以此API是对当前绑定的Buffer进行操作,但是如果读和写的buffer是同种类型,绑定的只能是一个Buffer,该怎么办呢?
    有两种方法:
  • 第一种,将其中一个Buffer暂时绑到另外一种BufferTarget类型上。
float vertexData[] = { ... };
glBindBuffer(GL_ARRAY_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));  
  • 第二种,OpenGL为了解决这种情况,额外提供了两个Glenum的类型,GL_COPY_READ_BUFFERGL_COPY_WRITE_BUFFER.
    这两个类型都是可以绑定的BufferTarget.
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));

二. Advanced GLSL

GLSL的内置变量

vertex shader 中的内置变量

  • gl_Position: 片元在Clip-space下的坐标。
  • gl_PointSize: 当点为Render ptimitives时,可以在vertex shader中直接进行更改,也可以用API glPositionSize()进行更改。
    PS: 默认在vertex shader中写gl_PointSize是禁用的,需要用glEnable(GL_PROGRAM_POINT_SIZE);进行开启。
    举个例子
    VertexShader中这么写:
void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);    
    gl_PointSize = gl_Position.z;    
}  

画的点变大了:
在这里插入图片描述

  • gl_VertexID:在VertexShader中使用,read-only,记录了正在绘制的顶点的索引,好像没啥卵用。
    当使用glDrawElements绘制时,gl_VertexID记录的是绘制该点的index
    当使用glDrawArrays时,gl_VertexId记录的是绘制的第几个顶点。

fragment shader 中的内置变量

  • gl_FragCoord: 点的屏幕坐标,其x和y坐标的坐标上限由glViewpoit函数指定,注意这个参数不是输出的默认颜色参数。
    举个例子,通过判断坐标,实现不同的颜色效果。
void main()
{             
    if(gl_FragCoord.x < 400)
        FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    else
        FragColor = vec4(0.0, 1.0, 0.0, 1.0);        
}  

在这里插入图片描述
通过此参数,可以同时使用两个fragmentShader,分别在屏幕两边进行绘制。

  • gl_FrontFacing: 当未开启深度测试时,可以通过此参数判断当前点在正面还是背面。
    PS:如果enable face culling,此参数将无意义。
    举个例子
#version 330 core
out vec4 FragColor;
  
in vec2 TexCoords;

uniform sampler2D frontTexture;
uniform sampler2D backTexture;

void main()
{             
    if(gl_FrontFacing)
        FragColor = texture(frontTexture, TexCoords);
    else
        FragColor = texture(backTexture, TexCoords);
}  

如果深入到立方体内部,可以看到这样的效果:
在这里插入图片描述

  • gl_FragDepth: 之前提到的gl_FragCoord可以得到点在屏幕上的x,y,z坐标,但是只读的,而gl_FragDepth可以直接对点的深度值进行写入。
    写入方式很简单:
gl_FragDepth = 0.0;// this fragment now has a depth value of 0.0

允许写入片元深度的缺点
只要FragmentShader进行了深度值的写入操作,OpenGL会自动关闭所有的early depth testing功能。
因为early Z是提前进行了深度处理,在运行FragmentShader之前,所以OpenGL不会允许再在后面用FragmentShader进行绘制时,再改变深度值。

为了解决此问题,OpenGL4.2版本开始允许在FragmentShader前面预宣告gl_FragDepth,这样有可能可以进行一部分片元的earlyZ技术

layout (depth_<condition>) out float gl_FragDepth;//top of fragmentshader

condition有图下四个参数:
在这里插入图片描述
比如当condition为greater,OpenGL假设只允许写入比原来片元深度值更大的值,在这种情况下,可以对输入的fragment shader 的值进行一一判断,当输入gl_FragDepth小于gl_FragCoord,z时,这些点OpenGL会忽略,不对其进行写入,所以对于这些点,可以进行earlyZ处理(我是这么理解的,可能有出入)。
举个例子:

#version 420 core // note the GLSL version!
out vec4 FragColor;
layout (depth_greater) out float gl_FragDepth;

void main()
{             
    FragColor = vec4(1.0);
    gl_FragDepth = gl_FragCoord.z + 0.1;
}  

三. Interface Blocks

类似于结构体,可以实现顶点着色器与片元着色器间数据的传递。

vertex shader中写法:

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

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out VS_OUT
{
    vec2 TexCoords;
} vs_out;

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

fragment shader中写法,同样也要定义结构体

#version 330 core
out vec4 FragColor;

in VS_OUT
{
    vec2 TexCoords;
} fs_in;

uniform sampler2D texture;

void main()
{             
    FragColor = texture(texture, fs_in.TexCoords);   
}

四. Uniform Buffer Objects

之前对于不同的Shader,相同的Uniform,比如model矩阵,要用同样的glUniformMatrix4f()输入好多次,为方便这个过程,OpenGL提供了Uniform Buffer Objects,可以作为全局uniform变量
OpenGL中buffer的生成方式都是一样的glGenBuffers()
把buffer绑定到UniformBuffer类型上GL_UNIFORM_BUFFER

Uniform Block
没什么稀奇的,就是一个Uniform类型的结构体而已,可以把那些公用的要用Uniform传进去参数存进去。
可以在vertex shader中定义一个如Matrices的UniformBlock,因为很多shader中的 projection和view矩阵是一样的。

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

layout (std140) uniform Matrices
{
    mat4 projection;
    mat4 view;
};

uniform mat4 model;

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

注意,每一个使用这个Uniform的shader都需要定义该Uniform block。

Uniform block layout

一共有三种uniform block layout

  • Shared layout(Default)
  • std140 layout 统一规则来对齐字长
  • packed layout 让编译器自己去调整,对齐字长(甚至可能不同shader的uniform block的数据的offset都不一样)

也就是上面代码的std140的名字,因为正常的VAO有0~15个layout,这些并不是给Uniform来填充的槽位,std140指的是UniformBlock 里的变量所存储的位置的标识而已,具体在C++代码中需要这样定义:

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

OpenGL默认用的shared layout 来应对这样的情况:由于uniform block内的数据类型不一样,在不同的硬件上的内存分配也不一样,所以不同硬件的步长不一样,其uniform block内的参数顺序也不一样(可以用glGetUniformIndices来获取)
std140是一种新的layout,通过给每一个参数指定offset,统一其数据对其的方式,来防止参数顺序混乱。
下图是最常用的一些layout rull, 规定数据类型对应的字节数,N代表着4个字节。
在这里插入图片描述
按照这种规则,对其后每个数据成员的对齐偏移量为:
在这里插入图片描述
可以看出value是第一个,偏移值为0,vector是第二个,偏移量本该是4,但是由于步长是16,基于对齐原则,其偏移量变为了16.
所以用了std140layout,能保证数据顺序不乱,从而可以用APIglBufferSubData了。

五. Using Uniform Buffers

既然是Buffer,创建的方式都是类似的

unsigned int uboExampleBlock;
glGenBuffers(1, &uboExampleBlock);
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // allocate 150 bytes of memory
glBindBuffer(GL_UNIFORM_BUFFER, 0);

由于不同的Shader可能用到同样的UBO,所以OpenGL将存在的UBO逐一绑定到索引上,shader要什么UBO,就去对应索引绑定的UBO上面获取,如图所示:
在这里插入图片描述
不同Shader可以贡献Uniform块,前提是shader里必须有其相关定义内容。
负责将UBO绑定到对应索引上的API,有两个

glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock); 
// or
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152); //只绑定UBO的一部分内容到对应index的点上,一个点可以有多个UBO绑定在上面

负责将Shader与UBO和索引号连起来的API

glUniformBlockBinding(shaderID, UBOindex, indexNumber);

通过API可以获取UBO的IndeglGetUniformBlockIndex(shaderid, string uboName)
具体例子:

unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");   
glUniformBlockBinding(shaderA.ID, lights_index, 2);

在shader中需要这么定义对应的ubo

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

下面介绍了一种改变UBO内数据的方法,注意GLSL中 bool是四个字节:

glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
int b = true; // bools in GLSL are represented as 4 bytes, so we store it in an integer
glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b); 
glBindBuffer(GL_UNIFORM_BUFFER, 0);

六. UBO应用实例

之前很多模型有Uniform model、view 和projection 三个矩阵需要传入,实际上这些模型的view和projection的参数是通用的。

所以在Shader中创建UBO块:

#version 330 core
layout(location = 0)in vec3 aPos;
layout(std140)uniform Matrices
{
	mat4 view;
	mat4 projection;
};
uniform mat4 model;
void main()
{
	gl_Position = projection * view * model * vec4(aPos, 1.0);
}

为了应用同样的UBO,创建了四个用于画正方体的shader,四个正方体的vertexShader,完全相同,只是传入的Model uniform不同,fragment shader则只添加了颜色区分。

先取得各个UBO在对应shader中的索引,再统统绑定到0索引接口上。

unsigned int uniformBlockIndexRed    = glGetUniformBlockIndex(shaderRed.ID, "Matrices");
unsigned int uniformBlockIndexGreen  = glGetUniformBlockIndex(shaderGreen.ID, "Matrices");
unsigned int uniformBlockIndexBlue   = glGetUniformBlockIndex(shaderBlue.ID, "Matrices");
unsigned int uniformBlockIndexYellow = glGetUniformBlockIndex(shaderYellow.ID, "Matrices");  
  
glUniformBlockBinding(shaderRed.ID,    uniformBlockIndexRed, 0);
glUniformBlockBinding(shaderGreen.ID,  uniformBlockIndexGreen, 0);
glUniformBlockBinding(shaderBlue.ID,   uniformBlockIndexBlue, 0);
glUniformBlockBinding(shaderYellow.ID, uniformBlockIndexYellow, 0);

再创建UBO,为UBO分配内存

unsigned int uboMatrices
glGenBuffers(1, &uboMatrices);
  
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
  
glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4));

然后把对应的view和projection的数据填充进去:

glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(projection));
glBindBuffer(GL_UNIFORM_BUFFER, 0);  

glm::mat4 view = camera.GetViewMatrix();	       
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
glBindBuffer(GL_UNIFORM_BUFFER, 0);  

最后分配不同的Model uniform,画出来就行了

glBindVertexArray(cubeVAO);
shaderRed.use();
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(-0.75f, 0.75f, 0.0f));	// move top-left
shaderRed.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);        
// ... draw Green Cube
// ... draw Blue Cube
// ... draw Yellow Cube	  

最后可以绘制出这样的效果:
在这里插入图片描述

posted @ 2019-11-05 13:06  弹吉他的小刘鸭  阅读(210)  评论(0编辑  收藏  举报