4.4 Triângulos coloridos

Na seção 3.4, renderizamos pontos (GL_POINTS) para gerar o Triângulo de Sierpinski. Neste projeto, desenharemos triângulos (GL_TRIANGLES). Para cada quadro de exibição, renderizaremos um triângulo colorido com coordenadas 2D aleatórias dentro da janela de exibição. O resultado ficará como a seguir:

Ao longo da atividade veremos com mais detalhes os comandos do OpenGL utilizados para especificar os dados gráficos e configurar o pipeline.

Configuração inicial

Repita a configuração inicial dos projetos anteriores e mude o nome do projeto para coloredtriangles.

O arquivo abcg/examples/CMakeLists.txt ficará assim (com a compilação desabilitada para os projetos anteriores):

#add_subdirectory(helloworld)
#add_subdirectory(firstapp)
#add_subdirectory(sierpinski)
add_subdirectory(coloredtriangles)

O arquivo abcg/examples/coloredtriangles/CMakeLists.txt ficará assim:

project(coloredtriangles)
add_executable(${PROJECT_NAME} main.cpp openglwindow.cpp)
enable_abcg(${PROJECT_NAME})

Como nos projetos anteriores, crie os arquivos main.cpp, openglwindow.cpp e openglwindow.hpp em abcg/examples/coloredtriangles. Vamos editá-los a seguir.

main.cpp

O conteúdo de main.cpp é bem similar ao utilizado no projeto sierpinski e nos projetos anteriores:

#include <fmt/core.h>

#include "abcg.hpp"
#include "openglwindow.hpp"

int main(int argc, char **argv) {
  try {
    // Create application instance
    abcg::Application app(argc, argv);

    // Create OpenGL window
    auto window{std::make_unique<OpenGLWindow>()};
    window->setOpenGLSettings(
        {.samples = 2, .vsync = true, .preserveWebGLDrawingBuffer = true});
    window->setWindowSettings(
        {.width = 600, .height = 600, .title = "Colored Triangles"});

    // Run application
    app.run(std::move(window));
  } catch (const abcg::Exception &exception) {
    fmt::print(stderr, "{}\n", exception.what());
    return -1;
  }
  return 0;
}

Em setOpenGLSettings, .vsync = true habilita a sincronização vertical (vsync), que está desabilitada por padrão. Assim, OpenGLWindow::paintGL será chamada na mesma taxa de atualização do monitor (geralmente 60 Hz).

openglwindow.hpp

A definição da classe OpenGLWindow também é parecida com aquela do projeto sierpinski:

#ifndef OPENGLWINDOW_HPP_
#define OPENGLWINDOW_HPP_

#include <array>
#include <glm/vec4.hpp>
#include <random>

#include "abcg.hpp"

class OpenGLWindow : public abcg::OpenGLWindow {
 protected:
  void initializeGL() override;
  void paintGL() override;
  void paintUI() override;
  void resizeGL(int width, int height) override;
  void terminateGL() override;

 private:
  GLuint m_vao{};
  GLuint m_vboPositions{};
  GLuint m_vboColors{};
  GLuint m_program{};

  int m_viewportWidth{};
  int m_viewportHeight{};

  std::default_random_engine m_randomEngine;

  std::array<glm::vec4, 3> m_vertexColors{glm::vec4{0.36f, 0.83f, 1.00f, 1.0f},
                                          glm::vec4{0.63f, 0.00f, 0.61f, 1.0f},
                                          glm::vec4{1.00f, 0.69f, 0.30f, 1.0f}};

  void setupModel();
};

#endif

No projeto anterior utilizamos apenas um VBO (m_vboVertices). Agora há dois VBOs: um para a posição dos vértices (m_vboPositions) e outro para as cores (m_vboColors).

O arranjo m_vertexColors contém as cores RGBA que serão copiadas para m_vboColors. São três cores, uma para cada vértice do triângulo.

openglwindow.cpp

Primeiramente, vamos incluir os arquivos de cabeçalho:

#include "openglwindow.hpp"

#include <imgui.h>

#include <glm/vec2.hpp>
#include <glm/vec3.hpp>

#include "abcg.hpp"

Agora vamos à definição da função membro OpenGLWindow::initializeGL:

void OpenGLWindow::initializeGL() {
  const auto *vertexShader{R"gl(
    #version 410

    layout(location = 0) in vec2 inPosition;
    layout(location = 1) in vec4 inColor;

    out vec4 fragColor;

    void main() {
      gl_Position = vec4(inPosition, 0, 1);
      fragColor = inColor;
    }
  )gl"};

  const auto *fragmentShader{R"gl(
    #version 410

    in vec4 fragColor;

    out vec4 outColor;

    void main() { outColor = fragColor; }
  )gl"};

  // Create shader program
  m_program = createProgramFromString(vertexShader, fragmentShader);

  // Clear window
  glClearColor(0, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT);

  // Start pseudo-random number generator
  auto seed{std::chrono::steady_clock::now().time_since_epoch().count()};
  m_randomEngine.seed(seed);
}

O código das linhas 35 a 44 é praticamente idêntico ao do projeto anterior. Vamos nos concentrar nos códigos dos shaders. Observe o conteúdo da string em vertexShader:

#version 410

layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec4 inColor;

out vec4 fragColor;

void main() {
  gl_Position = vec4(inPosition, 0, 1);
  fragColor = inColor;
}

Este vertex shader define dois atributos de entrada: inPosition, que recebe a posição 2D do vértice, e inColor que recebe a cor RGBA. A saída, fragColor, é também uma cor RGBA. Na função main, a posição \((x,y)\) do vértice é repassada sem modificações para gl_Position. A conversão de \((x,y)\) em coordenadas cartesianas para \((x,y,0,1)\) em coordenadas homogêneas preserva a geometria do triângulo19. A cor do atributo de entrada também é repassada sem modificações para o atributo de saída.

Vejamos agora o fragment shader:

#version 410

in vec4 fragColor;

out vec4 outColor;

void main() { outColor = fragColor; }

O fragment shader é ainda mais simples. O atributo de entrada (fragColor) é copiado sem modificações para o atributo de saída (outColor).

A compilação e ligação dos shaders é feita pela chamada a abcg::OpenGLWindow::createProgramFromString na linha 36. Consulte a definição dessa função em abcg/abcg/abcg_openglwindow.cpp para ver quais funções do OpenGL são utilizadas. O resultado de createProgramFromString é m_program, um número inteiro que identifica o programa de shader composto pelo par de vertex/fragment shader. Para ativar o programa no pipeline, devemos chamar glUseProgram(m_program). Para desativá-lo, podemos ativar outro programa (se existir) ou chamar glUseProgram(0).

A função OpenGLWindow::paintGL() é definida assim:

void OpenGLWindow::paintGL() {
  setupModel();

  abcg::glViewport(0, 0, m_viewportWidth, m_viewportHeight);

  abcg::glUseProgram(m_program);
  abcg::glBindVertexArray(m_vao);

  abcg::glDrawArrays(GL_TRIANGLES, 0, 3);

  abcg::glBindVertexArray(0);
  abcg::glUseProgram(0);
}

Novamente, o código é similar ao utilizado no projeto sierpinski. A função de renderização, glDrawArrays, dessa vez usa GL_TRIANGLES e 3 vértices, sendo que o índice inicial dos vértices no arranjo é 0. Isso significa que o pipeline desenhará apenas um triângulo.

Em OpenGLWindow::paintUI(), usaremos controles de interface da ImGui para criar uma pequena janela de edição das três cores dos vértices:

void OpenGLWindow::paintUI() {
  abcg::OpenGLWindow::paintUI();

  {
    auto widgetSize{ImVec2(250, 90)};
    ImGui::SetNextWindowPos(ImVec2(m_viewportWidth - widgetSize.x - 5,
                                   m_viewportHeight - widgetSize.y - 5));
    ImGui::SetNextWindowSize(widgetSize);
    auto windowFlags{ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar};
    ImGui::Begin(" ", nullptr, windowFlags);

    // Edit vertex colors
    auto colorEditFlags{ImGuiColorEditFlags_NoTooltip |
                        ImGuiColorEditFlags_NoPicker};
    ImGui::PushItemWidth(215);
    ImGui::ColorEdit3("v0", &m_vertexColors[0].x, colorEditFlags);
    ImGui::ColorEdit3("v1", &m_vertexColors[1].x, colorEditFlags);
    ImGui::ColorEdit3("v2", &m_vertexColors[2].x, colorEditFlags);
    ImGui::PopItemWidth();

    ImGui::End();
  }
}

As funções ImGui::SetNextWindowPos e ImGui::SetNextWindowSize definem a posição e tamanho da janela da ImGui que está prestes a ser criada na linha 70. A janela é inicializada com alguns flags para que ela não possa ser redimensionada (ImGuiWindowFlags_NoResize) e não tenha a barra de título (ImGuiWindowFlags_NoTitleBar). Os controles ImGui::ColorEdit3 também são criados com flags para desabilitar o color picker (ImGuiColorEditFlags_NoPicker) e os tooltips (ImGuiColorEditFlags_NoTooltip), pois eles podem atrapalhar o desenho dos triângulos.

A definição de OpenGLWindow::resizeGL é idêntica à do projeto sierpinski. A definição de OpenGLWindow::terminateGL também é bem semelhante e libera os recursos do pipeline:

void OpenGLWindow::terminateGL() {
  abcg::glDeleteProgram(m_program);
  abcg::glDeleteBuffers(1, &m_vboPositions);
  abcg::glDeleteBuffers(1, &m_vboColors);
  abcg::glDeleteVertexArrays(1, &m_vao);
}

Vamos agora definir a função membro OpenGLWindow::setupModel e detalhar as funções do OpenGL que são utilizadas. A definição completa é dada abaixo, mas em seguida faremos uma análise mais detalhada de cada trecho:

void OpenGLWindow::setupModel() {
  abcg::glDeleteBuffers(1, &m_vboPositions);
  abcg::glDeleteBuffers(1, &m_vboColors);
  abcg::glDeleteVertexArrays(1, &m_vao);

  // Create vertex positions
  std::uniform_real_distribution<float> rd(-1.5f, 1.5f);
  std::array positions{glm::vec2(rd(m_randomEngine), rd(m_randomEngine)),
                       glm::vec2(rd(m_randomEngine), rd(m_randomEngine)),
                       glm::vec2(rd(m_randomEngine), rd(m_randomEngine))};

  // Create vertex colors
  std::vector<glm::vec4> colors(0);
  colors.emplace_back(m_vertexColors[0]);
  colors.emplace_back(m_vertexColors[1]);
  colors.emplace_back(m_vertexColors[2]);

  // Generate VBO of positions
  abcg::glGenBuffers(1, &m_vboPositions);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboPositions);
  abcg::glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions.data(),
                     GL_STATIC_DRAW);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  // Generate VBO of colors
  abcg::glGenBuffers(1, &m_vboColors);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboColors);
  abcg::glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(glm::vec4),
                     colors.data(), GL_STATIC_DRAW);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  // Get location of attributes in the program
  GLint positionAttribute{abcg::glGetAttribLocation(m_program, "inPosition")};
  GLint colorAttribute{abcg::glGetAttribLocation(m_program, "inColor")};

  // Create VAO
  abcg::glGenVertexArrays(1, &m_vao);

  // Bind vertex attributes to current VAO
  abcg::glBindVertexArray(m_vao);

  abcg::glEnableVertexAttribArray(positionAttribute);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboPositions);
  abcg::glVertexAttribPointer(positionAttribute, 2, GL_FLOAT, GL_FALSE, 0,
                              nullptr);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  abcg::glEnableVertexAttribArray(colorAttribute);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboColors);
  abcg::glVertexAttribPointer(colorAttribute, 4, GL_FLOAT, GL_FALSE, 0,
                              nullptr);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  // End of binding to current VAO
  abcg::glBindVertexArray(0);
}

As linhas 100 a 102 liberam os VBOs e o VAO, caso tenham sido criados anteriormente:

  abcg::glDeleteBuffers(1, &m_vboPositions);
  abcg::glDeleteBuffers(1, &m_vboColors);
  abcg::glDeleteVertexArrays(1, &m_vao);

É importante fazer isso, pois a função setupModel é chamada continuamente em paintGL. Se não liberarmos os recursos, em algum momento eles consumirão toda a memória da GPU e CPU20.

As linhas 104 a 114 criam arranjos com os dados que serão copiados para os VBOs:

  // Create vertex positions
  std::uniform_real_distribution<float> rd(-1.5f, 1.5f);
  std::array positions{glm::vec2(rd(m_randomEngine), rd(m_randomEngine)),
                       glm::vec2(rd(m_randomEngine), rd(m_randomEngine)),
                       glm::vec2(rd(m_randomEngine), rd(m_randomEngine))};

  // Create vertex colors
  std::vector<glm::vec4> colors(0);
  colors.emplace_back(m_vertexColors[0]);
  colors.emplace_back(m_vertexColors[1]);
  colors.emplace_back(m_vertexColors[2]);

Observe que as coordenadas das posições dos vértices são números pseudoaleatórios do intervalo \([-1.5, 1.5]\). Vimos no projeto anterior que, para uma primitiva ser vista no viewport, ela precisa ser especificada entre \([-1, -1]\) e \([1, 1]\). Logo, nossos triângulos terão partes que ficarão para fora da janela. O pipeline se encarregará de recortar os triângulos e mostrar apenas os fragmentos que estão dentro do viewport.

Nas linhas 116 a 128 são criados os VBOs (um para as posições 2D, outro para as cores RGBA):

  // Generate VBO of positions
  abcg::glGenBuffers(1, &m_vboPositions);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboPositions);
  abcg::glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions.data(),
                     GL_STATIC_DRAW);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  // Generate VBO of colors
  abcg::glGenBuffers(1, &m_vboColors);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboColors);
  abcg::glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(glm::vec4),
                     colors.data(), GL_STATIC_DRAW);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);
  • glGenBuffers cria o identificador de um objeto de buffer (buffer object). Um objeto de buffer é um arranjo de dados alocado pelo OpenGL, geralmente na memória da GPU.
  • glBindBuffer com o argumento GL_ARRAY_BUFFER vincula o objeto de buffer a um buffer de atributos de vértices. Isso define o objeto de buffer como um objeto de buffer de vértice (VBO). O objeto de buffer pode ser desvinculado com glBindBuffer(0), ou vinculando outro objeto de buffer.
  • glBufferData aloca a memória e inicializa o buffer com o conteúdo copiado de um ponteiro alocado na CPU. O primeiro parâmetro indica o tipo de objeto de buffer utilizado. O segundo parâmetro é o tamanho do buffer em bytes. O terceiro parâmetro é um ponteiro para os dados que serão copiados, na quantidade de bytes correspondente ao tamanho do buffer. O quarto parâmetro é uma “dica” ao driver de vídeo de como o buffer será usado. GL_STATIC_DRAW significa que o buffer será modificado apenas uma vez, potencialmente será utilizado muitas vezes, e que os dados serão usados para renderizar algo no framebuffer.

Após a cópia dos dados com o glBufferData, o arranjo de origem não é mais necessário e pode ser destruído. No nosso código, positions e colors estão alocados na pilha e são liberados no fim do escopo.

As linhas 130 a 132 usam glGetAttribLocation para pegar a localização de cada atributo de entrada do vertex shader de m_program:

  // Get location of attributes in the program
  GLint positionAttribute{abcg::glGetAttribLocation(m_program, "inPosition")};
  GLint colorAttribute{abcg::glGetAttribLocation(m_program, "inColor")};

O resultado de positionAttribute será 0, pois o vertex shader define inPosition com layout(location = 0). Da mesma forma, colorAttribute será 1, pois o vertex shader define inColor com layout(location = 1). Poderíamos omitir esse código e usar os valores diretamente no trecho a seguir, mas é sempre preferível fazer a consulta da localização com glGetAttribLocation do que usar valores hard-coded.

Agora que sabemos a localização dos atributos inPosition e inColor no vertex shader, podemos especificar ao OpenGL como os dados de cada VBO serão mapeados para esses atributos. Isso é feito nas linhas 134 a 153 a seguir:

  // Create VAO
  abcg::glGenVertexArrays(1, &m_vao);

  // Bind vertex attributes to current VAO
  abcg::glBindVertexArray(m_vao);

  abcg::glEnableVertexAttribArray(positionAttribute);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboPositions);
  abcg::glVertexAttribPointer(positionAttribute, 2, GL_FLOAT, GL_FALSE, 0,
                              nullptr);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  abcg::glEnableVertexAttribArray(colorAttribute);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboColors);
  abcg::glVertexAttribPointer(colorAttribute, 4, GL_FLOAT, GL_FALSE, 0,
                              nullptr);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  // End of binding to current VAO
  abcg::glBindVertexArray(0);

Na linha 135, glGenVertexArray cria um VAO que, como vimos no projeto sierpinski, armazena o estado da especificação de vinculação dos VBOs com o vertex shader. Neste projeto, essa especificação é feita nas linhas 140 a 150.

Em paintGL, antes de chamar glDrawArrays, quando vinculamos o VAO com glBindVertexArray, o estado da configuração dos VBOs com o programa de shader é recuperado automaticamente (isto é, é como se as linhas 140 a 150 fossem executadas). Na nossa aplicação isso não parece tão útil. As linhas 140 a 150 já são executadas para todo quadro de exibição, pois chamamos setupModel logo antes de glDrawArrays. Mas, em aplicações futuras, chamaremos setupModel apenas uma vez (por exemplo, em initializeGL). Geralmente, o modelo geométrico é definido apenas uma vez e não é mais alterado (ou é raramente alterado). Nesse caso, o VAO é útil para que não tenhamos de configurar manualmente a ligação dos VBOs com os atributos do vertex shader para todo quadro de exibição.

O código completo de openglwindow.cpp é mostrado a seguir:

#include "openglwindow.hpp"

#include <imgui.h>

#include <glm/vec2.hpp>
#include <glm/vec3.hpp>

#include "abcg.hpp"

void OpenGLWindow::initializeGL() {
  const auto *vertexShader{R"gl(
    #version 410

    layout(location = 0) in vec2 inPosition;
    layout(location = 1) in vec4 inColor;

    out vec4 fragColor;

    void main() {
      gl_Position = vec4(inPosition, 0, 1);
      fragColor = inColor;
    }
  )gl"};

  const auto *fragmentShader{R"gl(
    #version 410

    in vec4 fragColor;

    out vec4 outColor;

    void main() { outColor = fragColor; }
  )gl"};

  // Create shader program
  m_program = createProgramFromString(vertexShader, fragmentShader);

  // Clear window
  glClearColor(0, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT);

  // Start pseudo-random number generator
  auto seed{std::chrono::steady_clock::now().time_since_epoch().count()};
  m_randomEngine.seed(seed);
}

void OpenGLWindow::paintGL() {
  setupModel();

  abcg::glViewport(0, 0, m_viewportWidth, m_viewportHeight);

  abcg::glUseProgram(m_program);
  abcg::glBindVertexArray(m_vao);

  abcg::glDrawArrays(GL_TRIANGLES, 0, 3);

  abcg::glBindVertexArray(0);
  abcg::glUseProgram(0);
}

void OpenGLWindow::paintUI() {
  abcg::OpenGLWindow::paintUI();

  {
    auto widgetSize{ImVec2(250, 90)};
    ImGui::SetNextWindowPos(ImVec2(m_viewportWidth - widgetSize.x - 5,
                                   m_viewportHeight - widgetSize.y - 5));
    ImGui::SetNextWindowSize(widgetSize);
    auto windowFlags{ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar};
    ImGui::Begin(" ", nullptr, windowFlags);

    // Edit vertex colors
    auto colorEditFlags{ImGuiColorEditFlags_NoTooltip |
                        ImGuiColorEditFlags_NoPicker};
    ImGui::PushItemWidth(215);
    ImGui::ColorEdit3("v0", &m_vertexColors[0].x, colorEditFlags);
    ImGui::ColorEdit3("v1", &m_vertexColors[1].x, colorEditFlags);
    ImGui::ColorEdit3("v2", &m_vertexColors[2].x, colorEditFlags);
    ImGui::PopItemWidth();

    ImGui::End();
  }
}

void OpenGLWindow::resizeGL(int width, int height) {
  m_viewportWidth = width;
  m_viewportHeight = height;

  abcg::glClear(GL_COLOR_BUFFER_BIT);
}

void OpenGLWindow::terminateGL() {
  abcg::glDeleteProgram(m_program);
  abcg::glDeleteBuffers(1, &m_vboPositions);
  abcg::glDeleteBuffers(1, &m_vboColors);
  abcg::glDeleteVertexArrays(1, &m_vao);
}

void OpenGLWindow::setupModel() {
  abcg::glDeleteBuffers(1, &m_vboPositions);
  abcg::glDeleteBuffers(1, &m_vboColors);
  abcg::glDeleteVertexArrays(1, &m_vao);

  // Create vertex positions
  std::uniform_real_distribution<float> rd(-1.5f, 1.5f);
  std::array positions{glm::vec2(rd(m_randomEngine), rd(m_randomEngine)),
                       glm::vec2(rd(m_randomEngine), rd(m_randomEngine)),
                       glm::vec2(rd(m_randomEngine), rd(m_randomEngine))};

  // Create vertex colors
  std::vector<glm::vec4> colors(0);
  colors.emplace_back(m_vertexColors[0]);
  colors.emplace_back(m_vertexColors[1]);
  colors.emplace_back(m_vertexColors[2]);

  // Generate VBO of positions
  abcg::glGenBuffers(1, &m_vboPositions);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboPositions);
  abcg::glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions.data(),
                     GL_STATIC_DRAW);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  // Generate VBO of colors
  abcg::glGenBuffers(1, &m_vboColors);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboColors);
  abcg::glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(glm::vec4),
                     colors.data(), GL_STATIC_DRAW);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  // Get location of attributes in the program
  GLint positionAttribute{abcg::glGetAttribLocation(m_program, "inPosition")};
  GLint colorAttribute{abcg::glGetAttribLocation(m_program, "inColor")};

  // Create VAO
  abcg::glGenVertexArrays(1, &m_vao);

  // Bind vertex attributes to current VAO
  abcg::glBindVertexArray(m_vao);

  abcg::glEnableVertexAttribArray(positionAttribute);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboPositions);
  abcg::glVertexAttribPointer(positionAttribute, 2, GL_FLOAT, GL_FALSE, 0,
                              nullptr);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  abcg::glEnableVertexAttribArray(colorAttribute);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, m_vboColors);
  abcg::glVertexAttribPointer(colorAttribute, 4, GL_FLOAT, GL_FALSE, 0,
                              nullptr);
  abcg::glBindBuffer(GL_ARRAY_BUFFER, 0);

  // End of binding to current VAO
  abcg::glBindVertexArray(0);
}
Dica

Experimente habilitar o modo de mistura de cor usando o código mostrado na seção 4.3. Inclua o código a seguir em OpenGLWindow::initializeGL:

glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 

Além disso, mude a componente A das cores RGBA de m_vertexColors. Por exemplo, com a definição a seguir, os triângulos ficarão 50% transparentes:

std::array<glm::vec4, 3> m_vertexColors{glm::vec4{0.36f, 0.83f, 1.00f, 0.5f},
                                        glm::vec4{0.63f, 0.00f, 0.61f, 0.5f},
                                        glm::vec4{1.00f, 0.69f, 0.30f, 0.5f}};
Exercício

Modifique o projeto coloredtriangles para suportar novas funcionalidades:

  • Geração de cores aleatórias nos vértices;
  • Possibilidade de desenhar cada triângulo com uma cor sólida;
  • Ajuste do intervalo de tempo entre a renderização de cada triângulo.

Um exemplo é dado a seguir:


  1. O conceito de coordenadas homogêneas será abordado futuramente, quando trabalharmos com transformações geométricas 3D.↩︎

  2. Em geral, destruir e criar os VBOs a cada quadro de exibição não é muito eficiente. É preferível criar o VBO apenas uma vez e, se necessário, modificá-lo com glBufferData a cada quadro. Em nossa aplicação, optamos por chamar setupModel a cada paintGL apenas para manter o código mais simples.↩︎