diff --git a/.cache/clangd/index/glwidget.cpp.C8B78D3731FA9869.idx b/.cache/clangd/index/glwidget.cpp.C8B78D3731FA9869.idx new file mode 100644 index 0000000..4a20677 Binary files /dev/null and b/.cache/clangd/index/glwidget.cpp.C8B78D3731FA9869.idx differ diff --git a/.cache/clangd/index/glwidget.h.1FDF732EFCED4BD8.idx b/.cache/clangd/index/glwidget.h.1FDF732EFCED4BD8.idx new file mode 100644 index 0000000..cbeed8a Binary files /dev/null and b/.cache/clangd/index/glwidget.h.1FDF732EFCED4BD8.idx differ diff --git a/.cache/clangd/index/main.cpp.1EFC64217C8EEBC3.idx b/.cache/clangd/index/main.cpp.1EFC64217C8EEBC3.idx new file mode 100644 index 0000000..932a45a Binary files /dev/null and b/.cache/clangd/index/main.cpp.1EFC64217C8EEBC3.idx differ diff --git a/.cache/clangd/index/mainwindow.cpp.106F8539DEEA62A2.idx b/.cache/clangd/index/mainwindow.cpp.106F8539DEEA62A2.idx new file mode 100644 index 0000000..694bf90 Binary files /dev/null and b/.cache/clangd/index/mainwindow.cpp.106F8539DEEA62A2.idx differ diff --git a/.cache/clangd/index/mainwindow.h.1CF791588B2BEEF8.idx b/.cache/clangd/index/mainwindow.h.1CF791588B2BEEF8.idx new file mode 100644 index 0000000..44c86b6 Binary files /dev/null and b/.cache/clangd/index/mainwindow.h.1CF791588B2BEEF8.idx differ diff --git a/.cache/clangd/index/videodecoder.cpp.B5F31E0C60CEA00D.idx b/.cache/clangd/index/videodecoder.cpp.B5F31E0C60CEA00D.idx new file mode 100644 index 0000000..18e07a9 Binary files /dev/null and b/.cache/clangd/index/videodecoder.cpp.B5F31E0C60CEA00D.idx differ diff --git a/.cache/clangd/index/videodecoder.h.D0CE50575247D653.idx b/.cache/clangd/index/videodecoder.h.D0CE50575247D653.idx new file mode 100644 index 0000000..276ee23 Binary files /dev/null and b/.cache/clangd/index/videodecoder.h.D0CE50575247D653.idx differ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3381b91..4f39cd6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.17) -project(crt_player_cuda VERSION 1.0.0 LANGUAGES CXX) +project(zero_copy_example VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -36,10 +36,10 @@ set(HEADERS ) # Definir ejecutable -add_executable(crt_player_cuda ${SOURCES} ${HEADERS}) +add_executable(zero_copy_example ${SOURCES} ${HEADERS}) # Enlazar librerías de Qt6, FFmpeg y CUDA -target_link_libraries(crt_player_cuda PRIVATE +target_link_libraries(zero_copy_example PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets @@ -52,7 +52,7 @@ target_link_libraries(crt_player_cuda PRIVATE ${CUDART_LIBRARY} ) -target_include_directories(crt_player_cuda PRIVATE +target_include_directories(zero_copy_example PRIVATE ${AVCODEC_INCLUDE_DIRS} ${AVFORMAT_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS} diff --git a/README.md b/README.md index 6b0b7be..78d934f 100644 --- a/README.md +++ b/README.md @@ -76,23 +76,25 @@ cudaMemcpy2DToArray(arrayUV, 0, 0, (void*)frame->data[1], frame->linesize[1], w, cudaGraphicsUnmapResources(1, &m_cudaResourceY, 0); ``` -### Punto 4: Conversión Cromática y Filtros en el Fragment Shader +### Punto 4: Conversión Cromática en el Fragment Shader Para corregir el origen invertido entre OpenGL y CUDA, las coordenadas del Quad están configuradas con el eje vertical invertido ($V = 1.0 - V$). En el fragment shader [shader.frag](file:///home/sebastian/repos/zero_copy_video_player/shader.frag), leemos ambas texturas en paralelo y realizamos la conversión matemática de NV12 a RGB: ```glsl -float y = texture(u_texY, uv).r; -vec2 uvVal = texture(u_texUV, uv).rg; +float y = texture(u_texY, v_texCoord).r; +vec2 uvVal = texture(u_texUV, v_texCoord).rg; + +// El formato NV12 almacena U y V entrelazados. Restamos 0.5 para centrar la crominancia. float u = uvVal.r - 0.5; float v = uvVal.g - 0.5; +// Matriz estándar de conversión YUV (NV12) a RGB float r = y + 1.402 * v; float g = y - 0.344136 * u - 0.714136 * v; float b = y + 1.772 * u; -vec3 color = vec3(r, g, b); -``` -Por último, sobre esta señal de color RGB, se aplican los filtros de curvatura 3D de la lente, líneas de barrido (scanlines), aberración cromática y viñeteado. +fragColor = vec4(r, g, b, 1.0); +``` --- diff --git a/glwidget.cpp b/glwidget.cpp index ac3b3c6..8cea4b9 100644 --- a/glwidget.cpp +++ b/glwidget.cpp @@ -13,13 +13,7 @@ GLWidget::GLWidget(QWidget *parent) , m_cudaResourceUV(nullptr) , m_allocatedWidth(0) , m_allocatedHeight(0) - , m_crtEnabled(true) - , m_curvature(2.0f) - , m_scanlineDensity(400.0f) - , m_scanlineOpacity(0.35f) - , m_aberration(0.0015f) - , m_vignette(0.20f) - , m_brightness(1.10f) + { m_localFrame = av_frame_alloc(); } @@ -51,40 +45,7 @@ void GLWidget::onFrameReady() { update(); } -void GLWidget::setCrtEnabled(bool enabled) { - m_crtEnabled = enabled; - update(); -} -void GLWidget::setCurvature(float value) { - m_curvature = value; - update(); -} - -void GLWidget::setScanlineDensity(float value) { - m_scanlineDensity = value; - update(); -} - -void GLWidget::setScanlineOpacity(float value) { - m_scanlineOpacity = value; - update(); -} - -void GLWidget::setAberration(float value) { - m_aberration = value; - update(); -} - -void GLWidget::setVignette(float value) { - m_vignette = value; - update(); -} - -void GLWidget::setBrightness(float value) { - m_brightness = value; - update(); -} void GLWidget::clearVideo() { cleanupCudaGL(); @@ -227,13 +188,7 @@ void GLWidget::paintGL() { glBindTexture(GL_TEXTURE_2D, m_texUV); m_shaderProgram.setUniformValue("u_texUV", 1); - m_shaderProgram.setUniformValue("u_crtEnabled", m_crtEnabled); - m_shaderProgram.setUniformValue("u_curvature", m_curvature); - m_shaderProgram.setUniformValue("u_scanlineDensity", m_scanlineDensity); - m_shaderProgram.setUniformValue("u_scanlineOpacity", m_scanlineOpacity); - m_shaderProgram.setUniformValue("u_aberration", m_aberration); - m_shaderProgram.setUniformValue("u_vignette", m_vignette); - m_shaderProgram.setUniformValue("u_brightness", m_brightness); + glBindBuffer(GL_ARRAY_BUFFER, m_vbo); int posLocation = m_shaderProgram.attributeLocation("a_position"); diff --git a/glwidget.h b/glwidget.h index c398e4d..b521120 100644 --- a/glwidget.h +++ b/glwidget.h @@ -23,14 +23,7 @@ public: void setVideoDecoder(VideoDecoder *decoder); - // Ajustes del Shader CRT - void setCrtEnabled(bool enabled); - void setCurvature(float value); - void setScanlineDensity(float value); - void setScanlineOpacity(float value); - void setAberration(float value); - void setVignette(float value); - void setBrightness(float value); + void clearVideo(); @@ -65,14 +58,7 @@ private: int m_allocatedWidth; int m_allocatedHeight; - // Estado del Shader - bool m_crtEnabled; - float m_curvature; - float m_scanlineDensity; - float m_scanlineOpacity; - float m_aberration; - float m_vignette; - float m_brightness; + }; #endif // GLWIDGET_H diff --git a/mainwindow.cpp b/mainwindow.cpp index b5176f3..719df25 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -12,8 +12,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_updatingSeek(false) { - setWindowTitle("Retro CRT Video Player (C++/Qt6 & Native CUDA Zero-Copy)"); - resize(1200, 720); + setWindowTitle("Zero-Copy CUDA-GL Video Player"); + resize(960, 600); m_glWidget = new GLWidget(this); m_videoDecoder = new VideoDecoder(this); @@ -35,7 +35,6 @@ void MainWindow::setupUI() { this->setStyleSheet( "QMainWindow { background-color: #08080f; }" "QWidget { color: #f0f0f5; font-family: 'Outfit', 'Segoe UI', sans-serif; font-size: 13px; }" - "QFrame#sidebar { background-color: #11111d; border-left: 1px solid #22223b; border-radius: 0px; }" "QPushButton { background-color: #ff007f; color: white; border: none; padding: 8px 15px; border-radius: 6px; font-weight: bold; min-width: 60px; }" "QPushButton:hover { background-color: #ff3399; }" "QPushButton:pressed { background-color: #cc0066; }" @@ -45,22 +44,16 @@ void MainWindow::setupUI() { "QSlider::handle:horizontal { background: #ffffff; border: 1px solid #ff007f; width: 14px; height: 14px; margin: -4px 0; border-radius: 7px; }" "QSlider::handle:horizontal:hover { background: #ff007f; }" "QLabel { color: #a0a0c0; }" - "QCheckBox { spacing: 8px; }" - "QCheckBox::indicator { width: 18px; height: 18px; border: 1px solid #ff007f; border-radius: 4px; background: #1a1a2e; }" - "QCheckBox::indicator:checked { background-color: #ff007f; image: url(check.png); /* O usar un color plano */ }" - "QGroupBox { font-weight: bold; border: 1px solid #22223b; border-radius: 8px; margin-top: 15px; padding-top: 15px; color: #ff007f; }" ); QWidget *centralWidget = new QWidget(this); setCentralWidget(centralWidget); - QHBoxLayout *mainLayout = new QHBoxLayout(centralWidget); + QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget); mainLayout->setContentsMargins(10, 10, 10, 10); mainLayout->setSpacing(10); - // --- COLUMNA IZQUIERDA: REPRODUCTOR --- - QVBoxLayout *playerLayout = new QVBoxLayout(); - playerLayout->addWidget(m_glWidget, 1); + mainLayout->addWidget(m_glWidget, 1); // Controles Multimedia QHBoxLayout *controlsLayout = new QHBoxLayout(); @@ -90,135 +83,7 @@ void MainWindow::setupUI() { m_timeLabel->setAlignment(Qt::AlignCenter); controlsLayout->addWidget(m_timeLabel); - playerLayout->addLayout(controlsLayout); - mainLayout->addLayout(playerLayout, 3); // 3/4 del ancho de ventana - - // --- COLUMNA DERECHA: SIDEBAR DE AJUSTES --- - QFrame *sidebar = new QFrame(this); - sidebar->setObjectName("sidebar"); - sidebar->setFixedWidth(300); - - QVBoxLayout *sidebarLayout = new QVBoxLayout(sidebar); - sidebarLayout->setContentsMargins(15, 15, 15, 15); - - QLabel *sidebarTitle = new QLabel("AJUSTES CRT RETRO", this); - sidebarTitle->setStyleSheet("font-size: 16px; font-weight: bold; color: #ff007f; margin-bottom: 10px;"); - sidebarTitle->setAlignment(Qt::AlignCenter); - sidebarLayout->addWidget(sidebarTitle); - - QScrollArea *scrollArea = new QScrollArea(this); - scrollArea->setWidgetResizable(true); - scrollArea->setFrameShape(QFrame::NoFrame); - - QWidget *scrollWidget = new QWidget(this); - QVBoxLayout *scrollLayout = new QVBoxLayout(scrollWidget); - scrollLayout->setContentsMargins(0, 0, 0, 0); - - // Activador General - m_crtCheckbox = new QCheckBox("Habilitar Efecto CRT", this); - m_crtCheckbox->setChecked(true); - m_crtCheckbox->setStyleSheet("font-weight: bold; font-size: 14px; color: #ffffff;"); - connect(m_crtCheckbox, &QCheckBox::toggled, this, &MainWindow::onCrtToggled); - scrollLayout->addWidget(m_crtCheckbox); - - // Grupo de Controles Geométricos - QGroupBox *geoGroup = new QGroupBox("Geometría y Lente", this); - QVBoxLayout *geoLayout = new QVBoxLayout(geoGroup); - - QLabel *curvLabel = new QLabel("Curvatura de Pantalla:", this); - geoLayout->addWidget(curvLabel); - QHBoxLayout *curvLayout = new QHBoxLayout(); - m_curvatureSlider = new QSlider(Qt::Horizontal, this); - m_curvatureSlider->setRange(0, 50); // 0.0 a 5.0 - m_curvatureSlider->setValue(20); - connect(m_curvatureSlider, &QSlider::valueChanged, this, &MainWindow::onCurvatureChanged); - curvLayout->addWidget(m_curvatureSlider); - m_curvatureLabel = new QLabel("2.0", this); - m_curvatureLabel->setFixedWidth(30); - curvLayout->addWidget(m_curvatureLabel); - geoLayout->addLayout(curvLayout); - scrollLayout->addWidget(geoGroup); - - // Grupo de Estilo Scanline - QGroupBox *scanlineGroup = new QGroupBox("Efecto Scanlines", this); - QVBoxLayout *scanlineLayout = new QVBoxLayout(scanlineGroup); - - QLabel *densLabel = new QLabel("Densidad de Líneas:", this); - scanlineLayout->addWidget(densLabel); - QHBoxLayout *densLayout = new QHBoxLayout(); - m_scanlineDensitySlider = new QSlider(Qt::Horizontal, this); - m_scanlineDensitySlider->setRange(100, 1000); - m_scanlineDensitySlider->setValue(400); - connect(m_scanlineDensitySlider, &QSlider::valueChanged, this, &MainWindow::onScanlineDensityChanged); - densLayout->addWidget(m_scanlineDensitySlider); - m_scanlineDensityLabel = new QLabel("400", this); - m_scanlineDensityLabel->setFixedWidth(30); - densLayout->addWidget(m_scanlineDensityLabel); - scanlineLayout->addLayout(densLayout); - - QLabel *opacLabel = new QLabel("Opacidad de Líneas:", this); - scanlineLayout->addWidget(opacLabel); - QHBoxLayout *opacLayout = new QHBoxLayout(); - m_scanlineOpacitySlider = new QSlider(Qt::Horizontal, this); - m_scanlineOpacitySlider->setRange(0, 100); // 0% a 100% - m_scanlineOpacitySlider->setValue(35); - connect(m_scanlineOpacitySlider, &QSlider::valueChanged, this, &MainWindow::onScanlineOpacityChanged); - opacLayout->addWidget(m_scanlineOpacitySlider); - m_scanlineOpacityLabel = new QLabel("0.35", this); - m_scanlineOpacityLabel->setFixedWidth(30); - opacLayout->addWidget(m_scanlineOpacityLabel); - scanlineLayout->addLayout(opacLayout); - scrollLayout->addWidget(scanlineGroup); - - // Grupo de Imperfecciones - QGroupBox *imperfGroup = new QGroupBox("Óptica y Color", this); - QVBoxLayout *imperfLayout = new QVBoxLayout(imperfGroup); - - QLabel *aberrLabel = new QLabel("Aberración Cromática (RGB split):", this); - imperfLayout->addWidget(aberrLabel); - QHBoxLayout *aberrLayout = new QHBoxLayout(); - m_aberrationSlider = new QSlider(Qt::Horizontal, this); - m_aberrationSlider->setRange(0, 100); // 0.0000 a 0.0100 - m_aberrationSlider->setValue(15); - connect(m_aberrationSlider, &QSlider::valueChanged, this, &MainWindow::onAberrationChanged); - aberrLayout->addWidget(m_aberrationSlider); - m_aberrationLabel = new QLabel("0.0015", this); - m_aberrationLabel->setFixedWidth(45); - aberrLayout->addWidget(m_aberrationLabel); - imperfLayout->addLayout(aberrLayout); - - QLabel *vigLabel = new QLabel("Viñeteado (Esquinas oscuras):", this); - imperfLayout->addWidget(vigLabel); - QHBoxLayout *vigLayout = new QHBoxLayout(); - m_vignetteSlider = new QSlider(Qt::Horizontal, this); - m_vignetteSlider->setRange(5, 50); // 0.05 a 0.50 - m_vignetteSlider->setValue(20); - connect(m_vignetteSlider, &QSlider::valueChanged, this, &MainWindow::onVignetteChanged); - vigLayout->addWidget(m_vignetteSlider); - m_vignetteLabel = new QLabel("0.20", this); - m_vignetteLabel->setFixedWidth(30); - vigLayout->addWidget(m_vignetteLabel); - imperfLayout->addLayout(vigLayout); - - QLabel *brightLabel = new QLabel("Brillo Adicional:", this); - imperfLayout->addWidget(brightLabel); - QHBoxLayout *brightLayout = new QHBoxLayout(); - m_brightnessSlider = new QSlider(Qt::Horizontal, this); - m_brightnessSlider->setRange(80, 150); // 0.80 a 1.50 - m_brightnessSlider->setValue(110); - connect(m_brightnessSlider, &QSlider::valueChanged, this, &MainWindow::onBrightnessChanged); - brightLayout->addWidget(m_brightnessSlider); - m_brightnessLabel = new QLabel("1.10", this); - m_brightnessLabel->setFixedWidth(30); - brightLayout->addWidget(m_brightnessLabel); - imperfLayout->addLayout(brightLayout); - scrollLayout->addWidget(imperfGroup); - - scrollLayout->addStretch(1); - scrollArea->setWidget(scrollWidget); - sidebarLayout->addWidget(scrollArea); - - mainLayout->addWidget(sidebar); + mainLayout->addLayout(controlsLayout); } void MainWindow::openVideoFile() { @@ -289,43 +154,4 @@ void MainWindow::onPlaybackFinished() { m_seekBar->setValue(0); } -// --- SLOTS DE PARÁMETROS DEL SHADER --- -void MainWindow::onCrtToggled(bool checked) { - m_glWidget->setCrtEnabled(checked); -} - -void MainWindow::onCurvatureChanged(int value) { - float curvatureVal = value / 10.0f; - m_curvatureLabel->setText(QString::number(curvatureVal, 'f', 1)); - m_glWidget->setCurvature(curvatureVal); -} - -void MainWindow::onScanlineDensityChanged(int value) { - m_scanlineDensityLabel->setText(QString::number(value)); - m_glWidget->setScanlineDensity(static_cast(value)); -} - -void MainWindow::onScanlineOpacityChanged(int value) { - float opacityVal = value / 100.0f; - m_scanlineOpacityLabel->setText(QString::number(opacityVal, 'f', 2)); - m_glWidget->setScanlineOpacity(opacityVal); -} - -void MainWindow::onAberrationChanged(int value) { - float aberrationVal = value / 10000.0f; - m_aberrationLabel->setText(QString::number(aberrationVal, 'f', 4)); - m_glWidget->setAberration(aberrationVal); -} - -void MainWindow::onVignetteChanged(int value) { - float vignetteVal = value / 100.0f; - m_vignetteLabel->setText(QString::number(vignetteVal, 'f', 2)); - m_glWidget->setVignette(vignetteVal); -} - -void MainWindow::onBrightnessChanged(int value) { - float brightnessVal = value / 100.0f; - m_brightnessLabel->setText(QString::number(brightnessVal, 'f', 2)); - m_glWidget->setBrightness(brightnessVal); -} diff --git a/mainwindow.h b/mainwindow.h index 06c1fbe..98f7512 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -24,14 +24,7 @@ private slots: void onPositionChanged(qint64 positionMs); void onPlaybackFinished(); - // Slots de actualización de los sliders del Shader - void onCrtToggled(bool checked); - void onCurvatureChanged(int value); - void onScanlineDensityChanged(int value); - void onScanlineOpacityChanged(int value); - void onAberrationChanged(int value); - void onVignetteChanged(int value); - void onBrightnessChanged(int value); + private: void setupUI(); @@ -46,20 +39,7 @@ private: QSlider *m_seekBar; QLabel *m_timeLabel; - // Sliders del Shader y sus etiquetas indicadoras - QCheckBox *m_crtCheckbox; - QSlider *m_curvatureSlider; - QLabel *m_curvatureLabel; - QSlider *m_scanlineDensitySlider; - QLabel *m_scanlineDensityLabel; - QSlider *m_scanlineOpacitySlider; - QLabel *m_scanlineOpacityLabel; - QSlider *m_aberrationSlider; - QLabel *m_aberrationLabel; - QSlider *m_vignetteSlider; - QLabel *m_vignetteLabel; - QSlider *m_brightnessSlider; - QLabel *m_brightnessLabel; + }; #endif // MAINWINDOW_H diff --git a/shader.frag b/shader.frag index 51d1e2e..cc03f65 100644 --- a/shader.frag +++ b/shader.frag @@ -5,76 +5,19 @@ out vec4 fragColor; uniform sampler2D u_texY; uniform sampler2D u_texUV; -uniform bool u_crtEnabled; -uniform float u_curvature; -uniform float u_scanlineDensity; -uniform float u_scanlineOpacity; -uniform float u_aberration; -uniform float u_vignette; -uniform float u_brightness; -uniform float u_time; -// Función para distorsión de barril (curvatura CRT) -vec2 curve(vec2 uv) { - if (u_curvature <= 0.0) return uv; - uv = (uv - 0.5) * 2.0; - uv.x *= 1.0 + (uv.y * uv.y) * (u_curvature * 0.05); - uv.y *= 1.0 + (uv.x * uv.x) * (u_curvature * 0.05); - uv = (uv / 2.0) + 0.5; - return uv; -} - -// Conversión NV12 (Y + UV entrelazados) a RGB -vec3 getNv12Color(vec2 uv) { - float y = texture(u_texY, uv).r; - vec2 uvVal = texture(u_texUV, uv).rg; +void main() { + float y = texture(u_texY, v_texCoord).r; + vec2 uvVal = texture(u_texUV, v_texCoord).rg; + + // El formato NV12 almacena U y V entrelazados. Restamos 0.5 para centrar la crominancia. float u = uvVal.r - 0.5; float v = uvVal.g - 0.5; + // Matriz estándar de conversión YUV (NV12) a RGB float r = y + 1.402 * v; float g = y - 0.344136 * u - 0.714136 * v; float b = y + 1.772 * u; - return vec3(r, g, b); -} - -void main() { - vec2 uv = v_texCoord; - - if (u_crtEnabled) { - uv = curve(uv); - } - - // Verificar límites para recortar esquinas curvas - if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { - fragColor = vec4(0.0, 0.0, 0.0, 1.0); - return; - } - - vec3 color; - if (u_crtEnabled && u_aberration > 0.0) { - // Aberración Cromática (RGB split) utilizando muestreos desfasados de NV12 - float r = getNv12Color(vec2(uv.x - u_aberration, uv.y)).r; - float g = getNv12Color(uv).g; - float b = getNv12Color(vec2(uv.x + u_aberration, uv.y)).b; - color = vec3(r, g, b); - } else { - color = getNv12Color(uv); - } - - // Aplicar Brillo - color *= u_brightness; - - if (u_crtEnabled) { - // 1. Efecto Scanlines (Líneas de Barrido) - float scanline = sin(uv.y * u_scanlineDensity * 3.14159265); - scanline = mix(1.0, (scanline + 1.0) * 0.5, u_scanlineOpacity); - color *= scanline; - - // 2. Efecto Viñeteado (Esquinas oscuras) - float vignette = uv.x * uv.y * (1.0 - uv.x) * (1.0 - uv.y); - vignette = clamp(pow(16.0 * vignette, u_vignette), 0.0, 1.0); - color *= vignette; - } - - fragColor = vec4(color, 1.0); + + fragColor = vec4(r, g, b, 1.0); }