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 argumentoGL_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 comglBindBuffer(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);
}
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},
0.63f, 0.00f, 0.61f, 0.5f},
glm::vec4{1.00f, 0.69f, 0.30f, 0.5f}}; glm::vec4{
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
glBufferData
a cada quadro. Em nossa aplicação, optamos por chamarsetupModel
a cadapaintGL
apenas para manter o código mais simples.↩︎