OpenGL笔记(七)

mac2024-04-11  30

引用链接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_BUFFERVERTEX_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_BUFFER和GL_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

最后可以绘制出这样的效果:

最新回复(0)