超现代的OpenGL。 第二部分



窗外都心情愉快,温度较低。 如所承诺的,我将继续发表有关现代OpenGL超级duper的文章。 谁还没有阅读第一部分- 超现代OpenGL。 第一部分

也许您很幸运,我可以将所有剩余的材料推到本文中,但并不确定...

阵列纹理


纹理数组是在OpenGL 3.0中添加回去的,但由于某些原因,很少有人对此进行撰写(Masons可靠地隐藏了信息)。 大家都很熟悉编程,知道什么是数组 ,尽管我最好从另一面“尝试”。

为了减少纹理之间的切换次数,从而减少状态切换操作,人们使用了纹理地图集 (一种存储多个对象数据的纹理)。 但是Khronos的聪明人为我们开发了一种替代方法-阵列纹理。 现在,我们可以将纹理存储为该阵列中的图层,也就是说,它可以替代地图集。 OpenGL Wiki关于mipmap等的描述略有不同,但是对我来说似乎太复杂了( link )。

与地图集相比,使用此方法的优势在于,就包裹和mipmapping而言,每一层都被视为独立的纹理。

但是回到我们的公羊……纹理数组具有三种类型的目标:

  • GL_TEXTURE_1D_ARRAY
  • GL_TEXTURE_2D_ARRAY
  • GL_TEXTURE_CUBE_MAP_ARRAY

创建纹理数组的代码:

GLsizei width = 512; GLsizei height = 512; GLsizei layers = 3; glCreateTextures(GL_TEXTURE_2D_ARRAY, 1, &texture_array); glTextureStorage3D(texture_array, 0, GL_RGBA8, width, height, layers); 

最细心的人注意到我们正在为2D纹理创建存储库,但是由于某些原因,我们使用3D数组,因此这里没有错误或错别字。 我们存储2D纹理,但是由于它们位于“图层”中,因此我们得到了3D阵列(实际上,存储的是像素数据,而不是纹理。3D阵列具有包含像素数据的2D图层)。

在这里,关于一维纹理的示例很容易理解。 2D像素阵列中的每一行都是单独的1D层。 Mipmap纹理也可以自动创建。

这样,所有的困难就结束了,将图像添加到特定图层非常简单:

 glTextureSubImage3D(texarray, mipmap_level, offset.x, offset.y, layer, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 

使用数组时,我们需要稍微更改着色器

 #version 450 core layout (location = 0) out vec4 color; layout (location = 0) in vec2 texture_0; uniform sampler2DArray texture_array; uniform uint diffuse_layer; float getCoord(uint capacity, uint layer) { return max(0, min(float(capacity - 1), floor(float(layer) + 0.5))); } void main() { color = texture(texture_array, vec3(texture_0, getCoord(3, diffuse_layer))); } 

最好的选择是在着色器外部计算所需的图层,为此,我们可以使用UBO / SSBO (它也用于传输矩阵和许多其他数据,但是又是另一回事)。 如果没有人等待tyk_1tyk_2 ,您可以阅读。

至于大小,即GL_MAX_ARRAY_TEXTURE_LAYERS在OpenGL 3.3中为256,在OpenGL 4.5中为2048。

值得一提的是Sampler对象(与Array纹理无关,但很有用)-这是一个用于调整纹理单元状态的对象,无论当前将哪个对象附加到该单元。 它有助于将采样器状态与特定纹理对象分开,从而改善了抽象度。

 GLuint sampler_state = 0; glGenSamplers(1, &sampler_state); glSamplerParameteri(sampler_state, GL_TEXTURE_WRAP_S, GL_REPEAT); glSamplerParameteri(sampler_state, GL_TEXTURE_WRAP_T, GL_REPEAT); glSamplerParameteri(sampler_state, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(sampler_state, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glSamplerParameterf(sampler_state, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f); 

我刚刚创建了一个采样器对象,为任何纹理单元启用了线性过滤和16倍各向异性过滤。

 GLuint texture_unit = 0; glBindSampler(texture_unit, sampler_state); 

在这里,我们只将采样器绑定到所需的纹理单元,然后不再是所需的bindim 0到该单元。

 glBindSampler(texture_unit, 0); 

链接采样器时,其设置优先于纹理单元的设置。 结果:无需修改现有代码库即可添加采样器对象。 您可以保留纹理创建的原样(具有自己的采样器状态),而只需添加代码来控制和使用采样器对象。

当需要删除对象时,我们只需调用此函数:

 glDeleteSamplers(1, &sampler_state); 

纹理视图


我将其翻译为“纹理指针(可能比链接更正确,我是xs)”,因为我不知道最好的翻译。

OpenGL的角度是什么?

一切都很简单,这是一个指向不变(即可变)纹理的数据的指针,如下图所示。



实际上,这是一个共享特定纹理对象的texel数据的对象,以此类推,我们可以使用C ++中的std :: shared_ptr 。 只要有至少一个纹理指针,驱动程序就不会删除原始纹理。

对该Wiki进行了更详细的描述,还值得一读有关纹理和目标的类型(它们不必匹配)

要创建一个指针,我们需要通过调用glGenTexture (不需要初始化)然后再调用glTextureView来获取纹理描述符。

 glGenTextures(1, &texture_view); glTextureView(texture_view, GL_TEXTURE_2D, source_name, internal_format, min_level, level_count, 5, 1); 

纹理指针可以指向Mipmap的第N级,非常有用和方便。 指针可以是纹理数组,数组的一部分,此数组中的特定层,也可以是3D纹理的切片,作为2D纹理。

索引和顶点的单个缓冲区


好吧,一切都会变得简单快捷。 以前,OpenGL的“ 顶点缓冲区对象”规范建议开发人员将顶点和索引数据拆分到不同的缓冲区中,但是现在没有必要了(很长的历史了)。
我们需要做的就是将索引保存在顶点前面,并告诉顶点在哪里开始(更确切地说是偏移量),为此,有一个glVertexArrayVertexBuffer

这是我们的处理方式:

 GLint alignment = GL_NONE; glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment); const GLsizei ind_len = GLsizei(ind_buffer.size() * sizeof(element_t)); const GLsizei vrt_len = GLsizei(vrt_buffer.size() * sizeof(vertex_t)); const GLuint ind_len_aligned = align(ind_len, alignment); const GLuint vrt_len_aligned = align(vrt_len, alignment); GLuint buffer = GL_NONE; glCreateBuffers(1, &buffer); glNamedBufferStorage(buffer, ind_len_aligned + vrt_len_aligned, nullptr, GL_DYNAMIC_STORAGE_BIT); glNamedBufferSubData(buffer, 0, ind_len, ind_buffer.data()); glNamedBufferSubData(buffer, ind_len_aligned, vrt_len, vrt_buffer.data()); GLuint vao = GL_NONE; glCreateVertexArrays(1, &vao); glVertexArrayVertexBuffer(vao, 0, buffer, ind_len_aligned, sizeof(vertex_t)); glVertexArrayElementBuffer(vao, buffer); 


镶嵌和计算着色


我不会告诉您有关镶嵌细分着色器的信息,因为Google中有很多有关此内容的信息(俄语),这里有两节课: 1,2,3 。 我们继续考虑用于计算的着色器(bliiin,还有很多材料,我会简要地告诉您)。

视频卡具有大量内核的优点,因此,视频卡被设计用于可并行执行的大量小型任务。 顾名思义,计算着色器可以解决与图形无关的问题(不必要)。

图片,我不知道该怎么称呼(就像流被分组一样)。



我们可以用来做什么?

  • 影像处理
    1. 布卢姆
    2. 基于图块的算法(延迟着色)
  • 模拟
    1. 微粒

此外,我认为没有理由写,Google中也有很多信息,这是一个简单的用法示例:

 //     glUseProgramStages( pipeline, GL_COMPUTE_SHADER_BIT, cs); // ,    / glBindImageTexture( 0, tex, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); // 80x45   (  1280720) glDispatchCompute( 80, 45, 1); 


这是一个空的计算着色器的示例:
 #version 430 layout(local_size_x = 1, local_size_y = 1) in; layout(rgba32f, binding = 0) uniform image2D img_output; void main() { // base pixel color for image vec4 pixel = vec4(0.0, 0.0, 0.0, 1.0); // get index in global work group ie x,y position ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); // // interesting stuff happens here later // // output to a specific pixel in the image imageStore(img_output, pixel_coords, pixel); } 


以下是一些链接,可以更深入地了解1、2、3、4

路径渲染


这是NVidia的新扩展(不是新扩展),其主要目标是矢量2D渲染。 我们可以将其用于文本或UI,并且由于图形是矢量,因此它不依赖于分辨率,这无疑是一个很大的优点,并且我们的UI看起来很棒。

基本概念是模版,然后是封面(原件中的封面)。 设置路径模具,然后可视化像素。

对于管理,使用标准GLuint,并且create和delete函数具有标准命名约定。

 glGenPathsNV //  glDeletePathsNV //  


以下是有关如何获取路径的一些信息:
  • 字符串'e中的SVG或PostScript
     glPathStringNV 
  • 具有相应坐标的命令数组
     glPathCommandsNV 
    并用于更新数据
     glPathSubCommands, glPathCoords, glPathSubCoords 
  • 字型
     glPathGlyphsNV, glPathGlyphRangeNV 
  • 现有路径的线性组合(一个,两个或多个路径的插值)
     glCopyPathNV, glInterpolatePathsNV, glCombinePathsNV 
  • 现有路径的线性变换
     glTransformPathNV 

标准命令列表:

  • 移至(x,y)
  • 封闭路径
  • 线到(x,y)
  • 二次曲线(x1,y1,x2,y2)
  • 立方曲线(x1,y1,x2,y2,x3,y3)
  • 平滑二次曲线(x,y)
  • 三次三次曲线(x1,y1,x2,y2)
  • 椭圆弧(rx,ry,x轴旋转,大弧标志,后掠标志,x,y)

这是PostScript中的路径字符串:

 "100 180 moveto 40 10 lineto 190 120 lineto 10 120 lineto 160 10 lineto closepath” // "300 300 moveto 100 400 100 200 300 100 curveto 500 200 500 400 300 300 curveto closepath” // 

在SVG中:

 "M100,180 L40,10 L190,120 L10,120 L160,10 z” // "M300 300 C 100 400,100 200,300 100,500 200,500 400,300 300Z” // 

仍然有各种类型的of头,包括馅料,边缘,折弯类型:



我不会在这里描述所有内容,因为材料很多,并且将占用整篇文章的内容(如果有意思,我会以某种方式编写)。

这是渲染图元的列表

  • 三次曲线
  • 二次曲线
  • 线数
  • 字体字形
  • 弧线
  • 短划线和尾盖样式

这是一些代码,然后有很多文本:

 // SVG  glPathStringNV( pathObj, GL_PATH_FORMAT_SVG_NV, strlen(svgPathString), svgPathString); //  glStencilFillPathNV( pathObj, GL_COUNT_UP_NV, 0x1F); // //  ( ) glCoverFillPathNV( pathObj, GL_BOUNDING_BOX_NV); 

仅此而已。

在我看来,这篇文章的有趣性和信息性较差,很难选出材料中的主要内容。 如果有人有兴趣了解更多详细信息,我可以丢弃一些NVidia资料和指向规范的链接(如果我记得保存它们的位置)。 我也很高兴在编辑本文方面有任何帮助。

按照承诺,我将撰写以下有关优化和减少绘图调用的文章。 我想请您在评论中写一些您还要阅读的内容以及您感兴趣的内容:
  • 在cocos2d-x上编写游戏(仅练习,无水)
  • 有关Vulkan的一系列文章的翻译
  • OpenGL上的一些主题(四元数,新功能)
  • 计算机图形算法(照明,空间屏幕环境光遮挡,空间屏幕反射)
  • 您的选择


谢谢大家的关注。

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


All Articles