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();
};
#endifNo 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 do vértice é repassada sem modificações para gl_Position. A conversão de em coordenadas cartesianas para 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 . Vimos no projeto anterior que, para uma primitiva ser vista no viewport, ela precisa ser especificada entre e . 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);glGenBufferscria 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.glBindBuffercom o argumentoGL_ARRAY_BUFFERvincula 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 comglBindBuffer(0), ou vinculando outro objeto de buffer.glBufferDataaloca 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_DRAWsignifica 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);
}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}};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:
O conceito de coordenadas homogêneas será abordado futuramente, quando trabalharmos com transformações geométricas 3D.↩︎
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
glBufferDataa cada quadro. Em nossa aplicação, optamos por chamarsetupModela cadapaintGLapenas para manter o código mais simples.↩︎