OpenGL学习脚印: uniform blocks在着色器中的使用 转自https://blog.youkuaiyun.com/wangdingqiaoit/article/details/5271796

本文深入探讨OpenGL中的Interface Block与Uniform Buffer Object(UBO),解析如何优化着色器变量管理和多着色器间变量共享,通过示例展示std140与shared布局的区别。

写在前面
目前,我们在着色器中要传递多个uniform变量时,总是使用多个uniform,然后在主程序中设置这些变量的值;同时如果要在多个shader之间共享变量,例如投影矩阵projection和视变换矩阵view的话,仍然需要为不同shader分别设置这些uniform变量。本节将为大家介绍interface block,以及基于此的uniform buffer object(UBO),这些技术将简化着色器中变量的传递和共享问题。本节示例程序均可以从我的github下载

本节内容参考自:
1.www.learningopengl.com Advanced GLSL
2.GLSL Tutorial – Uniform Blocks
3.《OpenGL 4.0 Shading Language Cookbook》-Using Uniform Blocks and Uniform Buffer Objects

interface block

interfac block是一组GLSL着色器里面的输入、输出、uniform等变量的集合,有一些类似于C语言中的struct,但是不像struct那样简单明了,还有一些其他的选项包含在里面。通过使用interface block,我们可以将着色器中的变量以组的形式来管理,这样书写更整洁。

interface block的声明形式为:

storage_qualifier block_name
{<define members here>
} instance_name;

其中storage_qualifier指明这个block的存储限定符,限定符可以使用in​, out​, uniform​, 或者buffer​(GLSL4.3支持)等,block_name则给定名称,而instance_name给定实例名称。

例如,我们之前在实现点光源的过程中,顶点着色器和片元着色器之间需要传递法向量、纹理坐标等变量,将他们封装到一个block中,代码显得更紧凑。顶点着色器中输出变量定义形式如下:

// 定义输出interface block
out VS_OUT
{
    vec3 FragPos;
    vec2 TextCoord;
    vec3 FragNormal;
}vs_out;

而在片元着色器中,要以相同的block_name接受,实例名称则可以不同,形式可以定义为:

// 定义输入interface block
in VS_OUT
{
    vec3 FragPos;
    vec2 TextCoord;
    vec3 FragNormal;
}fs_in;

如果指定了instance_name,则在片元着色器中引用这些变量时需要加上instance_name前缀,例如:

// 环境光成分
    vec3    ambient = light.ambient * vec3(texture(material.diffuseMap, fs_in.TextCoord));

反之如果没有指定instance_name,则这个block中的变量将和uniform一样是全局的,可以直接使用。如果没有给定instance_name,则需要注意,interface block中给定的变量名不要和uniform给定的重复,否则造成重定义错误,例如下面的定义将造成重定义错误:

uniform MatrixBlock
{
  mat4 projection;
  mat4 modelview;
};

uniform vec3 modelview;  // 重定义错误 和MatrixBlock中冲突

相比于之前以分散形式书写这些变量,interface block能够让你更合理的组织变量为一组,逻辑更清晰。

主程序部分未变,实现的点光源效果相同,这里还是给出效果图如下:

这里写图片描述

从上面可以看出,interface block确实解决了我们一直想要合理组织着色器中变量的问题。这是我们提到的第一个问题。

UBO的概念

本节开始提到的第二个问题是,如何在多个着色器之间简洁的共享变量。GLSL中可以通过uniform buffer来实现。uniform buffer的实现思路为: 在多个着色器中定义相同的uniform block(就是上面的interface block,使用uniform限定符定义),然后将这些uniform block绑定到对应的uniform buffer object,而uniform buffer object中实际存储这些需要共享的变量。着色器中的uniform block和主程序中的uniform buffer object,是通过OpenGL的绑定点(binding points)连接起来的,它们的关系如下图所示(来自www.learningopengl.com Advanced GLSL):

uniform buffer

使用时,每个shader中定义的uniform block有一个索引,通过这个索引连接到OpenGL的绑定点x;而主程序中创建uniform buffer object,传递数据后,将这个UBO绑定到对应的x,此后shader中的uniform block就和OpenGL中的UBO联系起来,我们在程序中操作UBO的数据,就能够在不同着色器之间共享了。例如上图中,着色器A和B定义的Matrices的索引都指向绑定点0,他们共享openGL的uboMatrices这个UBO的数据。同时着色器A的Lights和着色器B的Data,分别指向不同的UBO。

UBO的使用

在上面我们介绍了UBO的概念,下面通过实例了解UBO的实际使用。UBO的实现依赖于着色器中uniform block的定义,uniform block的内存布局四种形式:shared​, packed​, std140​, and std430​(GLSL4.3以上支持),默认是shared内存布局。本节我们重点学习shared和std140这两种内存布局形式,其他的形式可以在需要时自行参考OpenGL规范

  • shared 默认的内存布局 采用依赖于具体实现的优化方案,但是保证在不同程序中具有相同定义的block拥有相同的布局,因此可以在不同程序之间共享。要使block能够共享必须注意block具有相同定义,同时所有成员显式指定数组的大小。同时shared保证所有成员都是激活状态,没有变量被优化掉。

  • std140 这种方式明确的指定alignment的大小,会在block中添加额外的字节来保证字节对齐,因而可以提前就计算出布局中每个变量的位移偏量,并且能够在shader之间共享;不足在于添加了额外的padding字节。稍后会介绍字节对齐和padding相关内容

下面通过两个简单例子,来熟悉std140和默认的shared内存布局。这个例子将会在屏幕上通过4个着色器绘制4个不同颜色的立方体,在着色器之间共享的是投影矩阵和视变换矩阵,以及为了演示shared layout而添加的混合颜色的示例。

layout std140

字节对齐的概念

字节对齐的一个经典案例就是C语言中的结构体变量,例如下面的结构体:

struct StructExample {
    char c;  
    int i;  
    short s; 
}; 

你估计它占用内存大小多少字节? 假设在int 占用4字节,short占用2个字节,那么整体大小等于 1+ 4+ 2 = 7字节吗?

答案是否定的。在Windows平台测试,当int占用4个字节,short占用2个字节是,实际占用大小为12个字节,这12个字节是怎么算出来的呢? 就是用到了字节补齐的概念。实际上上述结构体的内存布局为:

struct StructExample {
    char c;  // 0 bytes offset, 3 bytes padding
    int i;   // 4 bytes offset
    short s; // 8 bytes offset, 2 bytes padding
}; // End of 12 bytes

内存布局如下图所示: 内存布局

字节对齐的一个重要原因是为了使机器访问更迅速。例如在32字长的地址的机器中,每次读取4个字节数据,所以将字节对齐到上述地址 0x0000,0x0004和0x0008, 0x000C将使读取更加迅速。否则例如上面结构体中的int i将跨越两个字长(0x0000和0x0004),需要两次读取操作,影响效率。当然关于为什么使用字节对齐的更详细分析,感兴趣地可以参考SO Purpose of memory alignment

关于字节对齐,我们需要知道的几个要点就是(参考自wiki Data structure alignment):

  • 一个内存地址,当它是n字节的倍数时,称之为n字节对齐,这里n字节是2的整数幂。

  • 每种数据类型都有它自己的字节对齐要求(alignment),例如char是1字节,int一般为4字节,float为4字节对齐,8字节的long则是8字节对齐。

  • 当变量的字节没有对齐时,将额外填充字节(padding)来使之对齐。

上面的结构体中,int变量i需要4字节对齐,因此在char后面填充了3个字节,同时结构体变量整体大小需要满足最长alignment成员的字节对齐,因此在short后面补充了2个字节,总计达到12字节。

关于字节对齐这个概念,介绍到这里,希望了解更多地可以参考The Lost Art of C Structure Packing

std140的字节对齐

std140内存布局同样存在字节对齐的概念,你可以参考官方文档获取完整描述。常用标量int,float,bool要求4字节对齐,4字节也被作为一个基础值N,这里列举几个常用的结构的字节对齐要求:

类型对齐基数(base alignment)
标量,例如 int bool每个标量对齐基数为N
vector2N 或者 4N, vec3的基数为4N.
标量或者vector的数组每个元素的基数等于vec4的基数.
矩阵以列向量存储, 列向量基数等于vec4的基数.
结构体元素按之前规则,同时整体大小填充为vec4的对齐基数

例如一个复杂的uniform block定义为:

layout (std140) uniform ExampleBlock
{
    //               // base alignment  // aligned offset
    float value;     // 4               // 0 
    vec3 vector;     // 16              // 16  (must be multiple of 16 so 4->16)
    mat4 matrix;     // 16              // 32  (column 0)
                     // 16              // 48  (column 1)
                     // 16              // 64  (column 2)
                     // 16              // 80  (column 3)
    float values[3]; // 16              // 96  (values[0])
                     // 16              // 112 (values[1])
                     // 16              // 128 (values[2])
    bool boolean;    // 4               // 144
    int integer;     // 4               // 148
}; 

上面的注释给出了它的字节对齐,其中填充了不少字节,可以根据上面表中给定的对齐基数提前计算出来,在主程序中可以设置这个UBO的变量:

GLuint exampleUBOId;
    glGenBuffers(1, &exampleUBOId);
    glBindBuffer(GL_UNIFORM_BUFFER, exampleUBOId);
    glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_DYNAMIC_DRAW); // 预分配空间 大小可以提前根据alignment计算
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    glBindBufferBase(GL_UNIFORM_BUFFER, 1, exampleUBOId); // 绑定点为1
    // step4 只更新一部分值
    glBindBuffer(GL_UNIFORM_BUFFER, exampleUBOId);
    GLint b = true; // 布尔变量在GLSL中用4字节表示 因此这里用int存储
    glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b); // offset可以根据UBO中alignment提前计算
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

说明: 上面最终计算出的大小为152,UBO整体不必满足vec4的字节对齐要求。152 /4 = 38,满足N的对齐要求即可。

从上面可以看到,当成员变量较多时,这种手动计算offset的方法比较笨拙,可以事先编写一个自动计算的函数库,以减轻工作负担。

std140的简单例子

下面通过一个简单例子来熟悉UBO的使用。

Step1: 首先我们在顶点着色器中定义uniform block如下:

#version 330 core

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;


uniform mat4 model; // 因为模型变换矩阵一般不能共享 所以单独列出来

// 定义UBO
layout (std140) uniform Matrices
{
   mat4 projection;
   mat4 view;
};  // 这里没有定义instance name,则在使用时不需要指定instance name


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

Step2 在主程序中设置着色器的uniform block索引指向到绑定点0:

// step1 获取shader中 uniform buffer 的索引
    GLuint redShaderIndex = glGetUniformBlockIndex(redShader.programId, "Matrices");
    GLuint greeShaderIndex = glGetUniformBlockIndex(greenShader.programId, "Matrices");
    ...
    // step2 设置shader中 uniform buffer 的索引到指定绑定点
    glUniformBlockBinding(redShader.programId, redShaderIndex, 0); // 绑定点为0
    glUniformBlockBinding(greenShader.programId, greeShaderIndex, 0);
    ...

这里为了演示代码中重复写出了4个着色器,实际中可以通过vector装入这4个着色器简化代码。

Step3: 创建UBO,并绑定到绑定点0
我们需要传入2个mat4矩阵,由于mat4中每列的vec4对齐,因此两个mat4中没有额外的padding,大小即为2*sizeof(mat4)。

GLuint UBOId;
    glGenBuffers(1, &UBOId);
    glBindBuffer(GL_UNIFORM_BUFFER, UBOId);
    glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_DYNAMIC_DRAW); // 预分配空间
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    glBindBufferRange(GL_UNIFORM_BUFFER, 0, UBOId, 0, 2 * sizeof(glm::mat4)); // 绑定点为0

Step4: 更新UBO中的数据
这里使用前面介绍的glBufferSubData更新UBO中数据,例如更新视变换矩阵如下:

glm::mat4 view = camera.getViewMatrix();// 视变换矩阵
glBindBuffer(GL_UNIFORM_BUFFER, UBOId);
glBufferSubData(GL_UNIFORM_BUFFER,      sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

通过上面的步骤,我们完成了着色器中unifrom block和UBO的连接,实现了投影矩阵和视变换矩阵在4个着色器之间的共享,绘制4个立方体如下图所示:

layout std140

验证ExampleBlock

这里在着色器中添加一段代码测试下上面那个复杂的ExampleBlock的内容,我们在主程序中设置boolean变量为true,在着色器中添加一个判断,如果boolean为true,则输出白色立方体:

if(boolean)
    {
      color = vec4(1.0, 1.0, 1.0, 1.0);
    }

最终显示获得了4个全是白色的立方体,效果如下: 四个白色立方体

这就验证了上述计算出那个复杂ExampleBlock的大小为152,boolean变量位移偏量为144是正确的。

layout shared

同std140内存布局方式不一样,shared方式的内存布局依赖于具体实现,因此我们无法提前根据某种字节对齐规范计算出UBO中变量的位移偏量和整体大小,因此在使用shared方式时,我们需要多次利用OpenGL的函数来查询UBO的信息。

这里在着色器中定义一个用于混合颜色的uniform block:

#version 330 core
// 使用默认shared​方式的UBO
uniform mixColorSettings {
    vec4  anotherColor;
    float mixValue;
};
out vec4 color;
void main()
{
    color = mix(vec4(0.0, 0.0, 1.0, 1.0), anotherColor, mixValue);
}

在出程序中首先查询UBO整体大小,预分配空间:

GLuint colorUBOId;
glGenBuffers(1, &colorUBOId);
glBindBuffer(GL_UNIFORM_BUFFER, colorUBOId);
// 获取UBO大小 因为定义相同 只需要在一个shader中获取大小即可
GLint blockSize;
glGetActiveUniformBlockiv(redShader.programId, redShaderIndex,
    GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
glBufferData(GL_UNIFORM_BUFFER, blockSize, NULL, GL_DYNAMIC_DRAW); // 预分配空间
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferBase(GL_UNIFORM_BUFFER, 1, colorUBOId); // 绑定点为1

然后,通过查询UBO中成员变量的索引和位移偏量来设置变量值:

// 通过查询获取uniform buffer中各个变量的索引和位移偏量
const GLchar* names[] = {
    "anotherColor", "mixValue"
};
GLuint indices[2];
glGetUniformIndices(redShader.programId, 2, names, indices);
GLint offset[2];
glGetActiveUniformsiv(redShader.programId, 2, indices, GL_UNIFORM_OFFSET, offset);
// 使用获取的位移偏量更新数据
glm::vec4 anotherColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f);
GLfloat mixValue = 0.5f;
glBindBuffer(GL_UNIFORM_BUFFER, colorUBOId);
glBufferSubData(GL_UNIFORM_BUFFER, offset[0], sizeof(glm::vec4), glm::value_ptr(anotherColor));
glBufferSubData(GL_UNIFORM_BUFFER, offset[1], sizeof(glm::vec4), &mixValue);
glBindBuffer(GL_UNIFORM_BUFFER, 0);

和上面std140定义的uniform block一起工作,产生的混合颜色效果如下图所示:

混合颜色

从上面可以看到,使用shared布局时,当变量较多时,这种查询成员变量索引和位移偏量的工作显得比较麻烦。

最后的说明

本节学习了interface block概念,以及UBO的两种内存布局方式。限于本节内容较多,部分函数的具体使用未在此展开介绍,需要的可以自行参考OpenGL文档。同时本文中关于那个复杂的std140布局的UBO的offset的计算方法,以及使用shared方式时通过查询获取UBO整体大小、索引和偏移量的方法,需要尽量掌握。

The OpenGL Shading Language (GLSL) is a programming language used for customizing parts of the OpenGL graphics pipeline that were formerly fixed-function, and are executed directly on the GPU. It provides programmers with unprecedented flexibility for implementing effects and optimizations utilizing the power of modern GPUs. With version 4.0, the language has been further refined to provide programmers with greater flexibility, and additional features have been added such as an entirely new stage called the tessellation shader. The OpenGL Shading Language 4.0 Cookbook provides easy-to-follow examples that first walk you through the theory and background behind each technique then go on to provide and explain the GLSL and OpenGL code needed to implement it. Beginning level through to advanced techniques are presented including topics such as texturing, screen-space techniques, lighting, shading, tessellation shaders, geometry shaders, and shadows. The OpenGL Shading Language 4.0 Cookbook is a practical guide that takes you from the basics of programming with GLSL 4.0 and OpenGL 4.0, through basic lighting and shading techniques, to more advanced techniques and effects. It presents techniques for producing basic lighting and shading effects; examples that demonstrate how to make use of textures for a wide variety of effects and as part of other techniques; examples of screen-space techniques, shadowing, tessellation and geometry shaders, noise, and animation. The OpenGL Shading Language 4.0 Cookbook provides examples of modern shading techniques that can be used as a starting point for programmers to expand upon to produce modern, interactive, 3D computer graphics applications. What you will learn from this book: * Compile, install, and communicate with shader programs * Use new features of GLSL 4.0 such as subroutines and uniform blocks * Implement basic lighting and shading techniques such as diffuse and specular shading, per-fragment shading, and spotlights * Apply single or multiple textures * Use textures as environment maps for simulating reflection or refraction * Implement screen-space techniques such as gamma correction, blur filters, and deferred shading * Implement geometry and tessellation shaders * Learn shadowing techniques including shadow mapping and screen space ambient occlusion * Use noise in shaders * Use shaders for animation Approach This hands-on guide cuts short the preamble and gets straight to the point – actually creating graphics, instead of just theoretical learning. Each recipe is specifically tailored to satisfy your appetite for producing real-time 3-D graphics using GLSL 4.0. Who this book is written for If you are an OpenGL programmer looking to use the modern features of GLSL 4.0 to create real-time, three-dimensional graphics, then this book is for you. Familiarity with OpenGL programming, along with the typical 3D coordinate systems, projections, and transformations is assumed. It can also be useful for experienced GLSL programmers who are looking to implement the techniques that are presented here. Book Details * Paperback: 340 pages * Publisher: Packt Publishing (July 2011) * Language: English * ISBN-10: 1849514763 * ISBN-13: 978-1849514767
<think>好的,我现在需要帮助用户从指定的优快云博客文章中提取并整合完整代码。用户之前提供的代码是关于文件操作的,现在他们可能希望获取另一个特定URL中的代码,并将其整合到现有的项目中或进行其他处理。 首先,我需要确认用户的需求。用户给出的链接是https://blog.csdn.net/WWWWW521321/article/details/115450241,但根据我的知识库,无法直接访问外部链接或获取其内容。这意味着我需要考虑其他方法来帮助用户,同时遵守相关政策和限制。 接下来,我应该回顾用户之前的问题,他们提供了文件操作的Python代码,包括保存文本、CSV和JSON文件到系统根目录。用户现在可能希望扩展此功能,例如从网页爬取代码并保存,但需要整合外部代码。然而,由于无法直接访问链接,我需要引导用户提供更多信息或调整需求。 可能的解决方案包括: 1. 建议用户手动查看该文章,并提取代码,然后协助整合。 2. 提供通用的网页抓取代码示例,让用户自行尝试,但需注意法律和网站条款。 3. 询问用户需要整合的具体功能,以便根据描述提供帮助。 同时,必须提醒用户注意版权问题,确保他们有权限使用目标网站的代码。此外,考虑到用户之前的代码涉及文件操作,可能需要将爬取功能与现有代码结合,例如将获取的代码保存为文件。 最后,需要以友好的方式回应用户,解释无法直接访问链接,并提供替代帮助方案,确保用户感到被支持,同时遵守规定。</think>根据您提供的优快云博客链接,我无法直接访问外部网站内容[^1]。不过我可以为您提供两种通用的解决方案来实现代码获取与整合: #### 方法一:手动提取+代码整合方案 ```python import requests from bs4 import BeautifulSoup def fetch_blog_code(url): """ 通用优快云代码抓取函数 """ headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } try: response = requests.get(url, headers=headers, timeout=10) soup = BeautifulSoup(response.text, 'html.parser') # 提取所有代码块 code_blocks = soup.find_all('code', class_='language-python') return [block.get_text() for block in code_blocks] except Exception as e: print(f"抓取失败: {str(e)}") return [] # 整合到您之前的文件系统 def save_web_code(url, filename): codes = fetch_blog_code(url) if codes: save_text('\n\n'.join(codes), filename) print("代码保存成功") else: print("未检测到有效代码") # 使用示例(需替换实际URL) # save_web_code("https://blog.csdn.net/xxx", "web_code.txt") ``` #### 方法二:API对接方案(需要权限) ```python import zhihu import stackoverflow def get_code_snippets(url): """ 通过知识平台API获取代码 """ platform = detect_platform(url) if platform == "csdn": # 需要申请优快云开发者权限 return csdn_api.get_article_content(url).codes elif platform == "github": return github_api.parse_repo(url) else: raise ValueError("不支持的平台") # 整合到现有文件系统 def api_save_operation(url): try: codes = get_code_snippets(url) save_json({"source": url, "codes": codes}, "web_code.json") except Exception as e: print(f"API操作失败: {str(e)}") ``` #### 执行注意事项 1. 方法一需遵守网站的`robots.txt`协议 2. 方法二需要向对应平台申请API密钥 3. 保存路径会自动适配您之前设置的根目录 4. 建议添加延时防止高频访问: ```python import time time.sleep(random.uniform(1,3)) # 随机延迟1-3秒 ``` #### 代码安全建议 1. 使用虚拟环境执行第三方代码 2. 添加代码沙盒检测: ```python def code_safety_check(code): blacklist = ['os.system', 'subprocess', 'shutil.rmtree'] return all(cmd not in code for cmd in blacklist) ``` 如果需要具体实现某个功能模块,可以提供更详细的需求描述,我将为您定制开发方案。根据您之前保存文件到根目录的需求,推荐使用方法一结合沙盒检测功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值