
Tudo de bom humor e temperatura mais baixa do lado de fora da janela. Como prometido, estou publicando uma continuação do artigo sobre o supervisor do OpenGL moderno. Quem não leu a primeira parte -
Ultramodern OpenGL. Parte 1Talvez você tenha sorte e eu possa colocar todo o material restante neste artigo, isso não é certo ...
Textura de matriz
Matrizes de textura foram adicionadas novamente no OpenGL 3.0, mas por algum motivo poucas pessoas escrevem sobre elas (as informações são ocultadas com segurança pelos Masons). Todos vocês estão familiarizados com programação e sabem o que
é matriz , embora seja melhor "abordar" do outro lado.
Para reduzir o número de alternância entre texturas e, como resultado, reduzir as operações de alternância de estado, as pessoas usam
atlas de textura (uma textura que armazena dados para vários objetos). Mas os caras espertos da Khronos desenvolveram uma alternativa para nós - textura Array. Agora podemos armazenar texturas como camadas nessa matriz, ou seja, é uma alternativa aos atlas. O OpenGL Wiki tem uma descrição ligeiramente diferente sobre mipmaps etc., mas parece muito complicado para mim (
link ).
As vantagens de usar essa abordagem em comparação com os atlas são que cada camada é considerada uma textura separada em termos de quebra e mipmap.
Mas voltando aos nossos carneiros ... A matriz de textura tem três tipos de alvo:
- GL_TEXTURE_1D_ARRAY
- GL_TEXTURE_2D_ARRAY
- GL_TEXTURE_CUBE_MAP_ARRAY
Código para criar uma matriz de textura:
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);
Os mais atentos perceberam que estamos criando um repositório para texturas 2D, mas por alguma razão estamos usando uma matriz 3D, não há erro ou erro de digitação aqui. Armazenamos texturas 2D, mas, como estão localizadas em "camadas", obtemos uma matriz 3D (na verdade, os dados de pixel são armazenados, não texturas. A matriz 3D possui camadas 2D com dados de pixel).
Aqui é fácil entender o exemplo da textura 1D. Cada linha em uma matriz de pixels 2D é uma camada 1D separada. Texturas Mipmap também podem ser criadas automaticamente.
Com isso, todas as dificuldades terminam e adicionar uma imagem a uma camada específica é bastante simples:
glTextureSubImage3D(texarray, mipmap_level, offset.x, offset.y, layer, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
Ao usar matrizes, precisamos alterar um pouco o shader
#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))); }
A melhor opção seria calcular a camada desejada fora do shader, para isso, podemos usar o
UBO /
SSBO (também é usado para transferir matrizes e muitos outros dados, mas de alguma forma é outra hora). Se alguém não puder esperar por
tyk_1 e
tyk_2 , você poderá ler.
Quanto às dimensões, ou seja, GL_MAX_ARRAY_TEXTURE_LAYERS, que é 256 no OpenGL 3.3 e 2048 no OpenGL 4.5.
Vale a pena falar sobre o Sampler Object (não relacionado à textura Array, mas uma coisa útil) - este é um objeto usado para ajustar o estado de uma unidade de textura, independentemente de qual objeto esteja atualmente anexado à unidade. Ajuda a separar os estados do amostrador de um objeto de textura específico, o que melhora a abstração.
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);
Acabei de criar um objeto de amostrador, habilitei a filtragem linear e a filtragem anisotrópica 16x para qualquer unidade de textura.
GLuint texture_unit = 0; glBindSampler(texture_unit, sampler_state);
Aqui, apenas ligamos o amostrador à unidade de textura desejada e quando ele deixa de ser o bindim 0 desejado para esta unidade.
glBindSampler(texture_unit, 0);
Quando vinculamos o amostrador, suas configurações têm precedência sobre as configurações da unidade de textura. Resultado: não há necessidade de modificar a base de código existente para adicionar objetos do amostrador. Você pode deixar a criação da textura como está (com seus próprios estados de amostrador) e apenas adicionar código para controlar e usar os objetos de amostrador.
Quando é hora de excluir o objeto, simplesmente chamamos esta função:
glDeleteSamplers(1, &sampler_state);
Vista de textura
Traduzirei isso como "um ponteiro de textura (pode ser mais correto que o link, sou xs)", porque não conheço a melhor tradução.
O que são ponteiros na perspectiva do OpenGL?
Tudo é muito simples, este é um ponteiro para os dados de uma textura imutável (ou seja, mutável), como podemos ver na figura abaixo.

De fato, este é um objeto que compartilha os dados texel de um determinado objeto de textura, por analogia, podemos usar
std :: shared_ptr do C ++ . Enquanto houver pelo menos um ponteiro de textura, a textura original não será removida pelo driver.
O
wiki é descrito em mais detalhes, bem como vale a pena ler sobre os tipos de textura e alvo (eles não precisam corresponder)
Para criar um ponteiro, precisamos obter um descritor de textura chamando
glGenTexture (nenhuma inicialização é necessária) e depois
glTextureView .
glGenTextures(1, &texture_view); glTextureView(texture_view, GL_TEXTURE_2D, source_name, internal_format, min_level, level_count, 5, 1);
Ponteiros de textura podem apontar para o enésimo nível do mipmap, bastante útil e conveniente. Os ponteiros podem ser matrizes de textura, partes de matrizes, uma camada específica nessa matriz ou uma fatia de uma textura 3D como uma textura 2D.
Buffer único para índice e vértice
Bem, tudo será rápido e fácil. Anteriormente, a especificação OpenGL para o
Vertex Buffer Object recomendava que o desenvolvedor dividisse os dados de vértice e índice em buffers diferentes, mas agora isso não é necessário (um longo histórico porque não).
Tudo o que precisamos é salvar os índices na frente dos vértices e dizer onde os vértices começam (mais precisamente, o deslocamento), para isso existe o comando
glVertexArrayVertexBufferAqui está como faríamos:
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);
Tecelagem e sombreamento de computação
Não vou falar sobre o shader de mosaico, já que há muito material no Google sobre isso (em russo), eis algumas lições:
1 ,
2 ,
3 . Passamos a considerar o sombreador para os cálculos (bliiin, também muito material, vou lhe dizer brevemente).
A vantagem das placas de vídeo em um número muito grande de núcleos, as placas de vídeo são projetadas para um grande número de pequenas tarefas que podem ser executadas em paralelo. O sombreador de cálculo, como o nome indica, permite resolver problemas que não estão relacionados aos gráficos (não é necessário).
Uma imagem, não sei como chamá-la (como os fluxos são agrupados).

Para que podemos usar?
- Processamento de imagem
- Bloom
- Algoritmos baseados em blocos (sombreamento atrasado)
- Simulações
- Partículas
- Agua
Além disso, não vejo razão para escrever, também há muitas informações no Google, aqui está um exemplo simples de uso:
Aqui está um exemplo de um sombreador de computação vazio:
#version 430 layout(local_size_x = 1, local_size_y = 1) in; layout(rgba32f, binding = 0) uniform image2D img_output; void main() {
Aqui estão alguns links para uma análise mais profunda de
1 ,
2 ,
3 ,
4 .
Renderização de caminho
Esta é uma nova extensão (não nova) da
NVidia , cujo objetivo principal é a renderização em vetor 2D. Podemos usá-lo para textos ou interface do usuário e, como os gráficos são vetoriais, não depende da resolução, o que é sem dúvida uma grande vantagem e nossa interface do usuário ficará ótima.
O conceito básico é um estêncil, depois uma capa (capa no original). Defina o estêncil do caminho e visualize os pixels.
Para gerenciamento, GLuint padrão é usado e as funções de criação e exclusão têm uma convenção de nomenclatura padrão.
glGenPathsNV
Aqui está um pouco sobre como podemos obter o caminho:
- SVG ou PostScript em string'e
glPathStringNV
- matriz de comandos com coordenadas correspondentes
glPathCommandsNV
e para atualizar dados glPathSubCommands, glPathCoords, glPathSubCoords
- fontes
glPathGlyphsNV, glPathGlyphRangeNV
- combinações lineares de caminhos existentes (interpolação de um, dois ou mais caminhos)
glCopyPathNV, glInterpolatePathsNV, glCombinePathsNV
- transformação linear de um caminho existente
glTransformPathNV
Lista de comandos padrão:
- mover para (x, y)
- caminho próximo
- linha para (x, y)
- curva quadrática (x1, y1, x2, y2)
- curva cúbica (x1, y1, x2, y2, x3, y3)
- curva quadrática suave (x, y)
- curva cúbica suave (x1, y1, x2, y2)
- arco elíptico (rx, ry, rotação do eixo x, bandeira de arco grande, bandeira de varredura, x, y)
Aqui está a aparência da string de caminho no 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”
E aqui no 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”
Ainda existem todos os tipos de bolos com tipos de recheios, bordas e dobras:

Não vou descrever tudo aqui, pois há muito material e será necessário um artigo inteiro (se for interessante, escreverei de alguma forma).
Aqui está uma lista de primitivas de renderização
- Curvas cúbicas
- Curvas quadráticas
- Linhas
- Glifos de fonte
- Arcos
- Estilo Dash & Endcap
Aqui está algum código e, em seguida, há muito texto:
Isso é tudo.
Parece-me que este artigo saiu menos interessante e informativo, era difícil destacar a principal coisa no material. Se alguém estiver interessado em aprender mais detalhadamente, posso descartar alguns materiais e links da NVidia para as especificações (se me lembro onde os salvei). Também estou feliz por qualquer ajuda na edição do artigo.
Conforme prometido, escreverei o seguinte artigo sobre como otimizar e reduzir as chamadas de empate. Gostaria de pedir que você escreva nos comentários sobre o que mais você gostaria de ler e o que você está interessado:
- Escrevendo um jogo no cocos2d-x (somente prática, sem água)
- Tradução de uma série de artigos sobre Vulkan
- Alguns tópicos em OpenGL (quaternions, nova funcionalidade)
- Algoritmos de computação gráfica (iluminação, oclusão ambiental da tela espacial, reflexão da tela espacial)
- Suas opções
Obrigado a todos pela atenção.