超现代的OpenGL。 第一部分



大家好 稍微了解OpenGL主题的每个人都知道有很多关于该主题的文章和课程,但是许多文章和课程都不会影响现代API,其中有些通常谈论glBeginglEnd 。 从版本4开始,我将尝试介绍新API的一些细微差别。 链接到文章的第二部分

这次,我将尝试写一篇有趣且内容丰富的文章,而所发生的一切取决于好哈勃拉居民的决定。 请原谅我的语法不佳(我将感谢您的更正)。

如果您愿意,我将写有关优化OpenGL和减少DrawCalls的文章。

让我们开始吧!

本文的内容-现代OpenGL的功能
本文不做的事情-在OpenGL上渲染的现代方法

内容:
  • 直接状态访问
  • 除错
  • 单独的着色器对象
  • 纹理数组
  • 纹理视图
  • 索引和顶点的单个缓冲区
  • 镶嵌和计算着色
  • 路径渲染


DSA(直接状态访问)


直接状态访问-直接访问状态。 用于修改OpenGL对象而无需将其捕捉到上下文的工具。 这使您可以在本地上下文中更改对象的状态,而不会影响应用程序所有部分共享的全局状态。 这也使API更加面向对象,因为可以清楚地定义更改对象状态的函数。 这就是OpenGL Wiki告诉我们的。

众所周知,OpenGL是具有许多单选按钮的API- glActiveTextureglBindTexture等。

从这里我们有一些问题

  • 选择器和当前状态可以使状态发生更深的变化。
  • 可能需要绑定/更改活动单元以设置纹理过滤器
  • 状态管理成为问题,结果导致应用程序的复杂性增加
  • 状态未知会导致其他设置
  • 尝试保存/恢复状态可能会出现问题

Khronos小组为我们提供了什么?DSA有何帮助?

  • 添加直接与一个或多个对象一起使用的功能
  • 为指定的纹理对象而不是当前对象设置纹理过滤器。
  • 将纹理绑定到特定单元,而不是活动对象
  • 添加了大量新功能
  • 涵盖OpenGL 1.x之前的内容
  • 添加了额外的功能。

从理论上讲,DSA可以帮助将非渲染和状态更改操作的数量减少到零...但这是不准确的。

现在,我将简要介绍一些新功能,不再详细介绍参数,而是将链接留在Wiki上。

  • glCreateTextures替换了glGenTextures + glBindTexture (初始化)。
    那是:
    glGenTextures(1, &name); glBindTexture(GL_TEXTURE_2D, name); 
    它变成了:
     glCreateTextures(GL_TEXTURE_2D, 1, &name); 
  • glTextureParameterX等效于glTexParameterX
     glGenTextures(1, &name); glBindTexture(GL_TEXTURE_2D, name); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 
    现在,我们将这样编写它:
     glCreateTextures(GL_TEXTURE_2D, 1, &name); glTextureParameteri(name, GL_TEXTURE_WRAP_S, GL_CLAMP); glTextureParameteri(name, GL_TEXTURE_WRAP_T, GL_CLAMP); glTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTextureParameteri(name, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTextureStorage2D(name, 1, GL_RGBA8, width, height); glTextureSubImage2D(name, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 
  • glBindTextureUnit替换glActiveTexture + glBindTexture
    这是我们的操作方式:
     glActiveTexture(GL_TEXTURE0 + 3); glBindTexture(GL_TEXTURE_2D, name); 
    现在:
     glBindTextureUnit(3, name); 

所做的更改也影响了glTextureImage ,它不再使用,这是为什么:

glTexImage是相当不安全的,很容易获得无效的纹理,因为该函数在调用时不检查值,驱动程序在绘制时执行此操作。 添加了GlTexStorage替换它

glTexStorage提供了一种通过调用过程中执行的检查来创建纹理的方法,从而最大程度地减少了错误。 尽管可变纹理更可靠,但大多数(即使不是全部)由可变纹理引起的问题也可以解决纹理存储库。

更改还影响了帧缓冲区:


这些并非全部已更改的功能。 下一行是缓冲区的功能:


以下是DSA支持中现在包含的内容的列表:

  • 顶点数组对象
  • 帧缓冲对象
  • 程序对象
  • 缓冲对象
  • 矩阵堆栈
  • 很多过时的东西

除错


从4.3版开始,我认为调试已添加了新功能,非常有用和方便。 现在,OpenGL将调用我们的回调以获取错误和调试消息,我们可以对其进行调整。



我们只需要调用两个函数即可启用它们: glEnableglDebugMessageCallback ,再简单不过了。

 glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(message_callback, nullptr); 

现在,我们将编写一个回调函数来获取消息:

 void callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const* message, void const* user_param) { auto source_str = [source]() -> std::string { switch (source) { case GL_DEBUG_SOURCE_API: return "API"; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "WINDOW SYSTEM"; case GL_DEBUG_SOURCE_SHADER_COMPILER: return "SHADER COMPILER"; case GL_DEBUG_SOURCE_THIRD_PARTY: return "THIRD PARTY"; case GL_DEBUG_SOURCE_APPLICATION: return "APPLICATION"; case GL_DEBUG_SOURCE_OTHER: return "OTHER"; default: return "UNKNOWN"; } }(); auto type_str = [type]() { switch (type) { case GL_DEBUG_TYPE_ERROR: return "ERROR"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; case GL_DEBUG_TYPE_MARKER: return "MARKER"; case GL_DEBUG_TYPE_OTHER: return "OTHER"; default: return "UNKNOWN"; } }(); auto severity_str = [severity]() { switch (severity) { case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION"; case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; default: return "UNKNOWN"; } }(); std::cout << source_str << ", " << type_str << ", " << severity_str << ", " << id << ": " << message << std::endl; } 

我们还可以使用glDebugMessageControl配置过滤器。 过滤器可以按源/类型/重要性或使用其标识符的一组消息在过滤模式下工作。

过滤特定范围内的消息:

 glPushDebugGroup( GL_DEBUG_SOURCE_APPLICATION, DEPTH_FILL_ID, 11, “Depth Fill”); //  Render_Depth_Only_Pass(); //  glPopDebugGroup(); //  

禁用异步调用非常有用,这样我们可以确定函数调用的顺序,以及在调试时在堆栈上查找错误的位置。 这很简单地完成:

 glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); 

值得记住的是,在包装的调用函数中调用OpenGL或窗口函数并不安全,并且回调函数不是免费的,因此您不应将其包含在发行版本中。
有关这些以及其他小事情的更多详细信息-mm头,您可以在这里阅读。

SSO(单独的着色器对象)


一旦OpenGL成为“固定管道”,这意味着预编程处理将应用于为可视化传输的所有数据。 下一步是“可编程流水线”-可编程部分实现以GLSL编写的着色器,经典的GLSL程序由顶点和片段着色器组成,但是在现代OpenGL中添加了一些新类型的着色器,即几何形状,权重和计算的着色器(关于它们我将在下一部分中讲述)。


SSO允许我们即时更改着色器步骤,而无需重新链接它们。 创建和设置简单的软件管道而不进行调试如下:

 GLuint pipe = GL_NONE; // Create shaders GLuint fprog = glCreateShaderProgramv( GL_FRAGMENT_SHADER, 1, &text); GLuint vprog = glCreateShaderProgramv( GL_VERTEX_SHADER, 1, &text); // Bind pipeline glGenProgramPipelines( 1, &pipe); glBindProgramPipelines( pipe); // Bind shaders glUseProgramStages( pipe, GL_FRAGMENT_SHADER_BIT, fprog); glUseProgramStages( pipe, GL_VERTEX_SHADER_BIT, vprog); 

如我们所见, glCreateProgramPipelines生成描述符并初始化对象, glCreateShaderProgramv使用指定的源生成,初始化,编译和链接着色器程序, glUseProgramStages将程序步骤附加到管道对象。 glBindProgramPipeline-将管道与上下文关联。

但是有一个警告,现在着色器的输入和输出参数必须匹配。 我们可以使用相同的名称,相同的顺序声明输入/输出参数,或者在限定符的帮助下使它们的位置明显一致。
我建议使用后一种选项,这将使我们能够配置清晰定义的界面,并且在名称和顺序方面具有灵活性。

为了提供更严格的接口,我们还需要声明要在每个阶段使用的内置输入和输出块。

内置的块接口定义为( 来自wiki ):
顶点:

 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; }; 

镶嵌控制:
 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; } gl_out[]; 

镶嵌评估:
 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; }; 

几何形状:
 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; }; 

重新声明内置模块并在常规顶点着色器中使用属性位置的示例:

 #version 450 out gl_PerVertex { vec4 gl_Position; }; layout (location = 0) in vec3 position; layout (location = 1) in vec3 color; layout (location = 0) out v_out { vec3 color; } v_out; void main() { v_out.color = color; gl_Position = vec4(position, 1.0); } 

Source: https://habr.com/ru/post/zh-CN456932/


All Articles