From 1f0cdaa115159a572a9b02efdaadd01f6545e997 Mon Sep 17 00:00:00 2001 From: lannerwsf Date: Wed, 20 May 2026 18:35:38 +0800 Subject: [PATCH 1/2] Add Bass/Mid/Treble frequency band energy extraction with EMA smoothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the 'Add Audio Variables' feature request (#2): - Compute band loudness from FFT magnitudes: - Bass: 20–250 Hz - Mid: 250–4000 Hz - Treble: 4000 Hz – Nyquist - Add EMA-smoothed (attenuated) variants: bassAtt, midAtt, trebleAtt - Expose all six values through FFTProcessor and AudioProcessor APIs - Smoothing factor of 0.15 for natural decay response Reference: projectM implementation pattern for band energy extraction. --- audio/FFTProcessor.cpp | 81 ++++++++++++++++++++++++++++++++++++++++-- audio/FFTProcessor.h | 23 ++++++++++++ src/Audio.cpp | 27 ++++++++++++++ src/Audio.h | 8 +++++ 4 files changed, 136 insertions(+), 3 deletions(-) diff --git a/audio/FFTProcessor.cpp b/audio/FFTProcessor.cpp index 2ed50fc..8ccffe8 100644 --- a/audio/FFTProcessor.cpp +++ b/audio/FFTProcessor.cpp @@ -2,11 +2,14 @@ #include #include #include +#include using namespace std; FFTProcessor::FFTProcessor(size_t bufferSize) - : bufferSize(bufferSize), magnitudes(bufferSize / 2, 0.0f) { + : bufferSize(bufferSize), magnitudes(bufferSize / 2, 0.0f), + bass(0.0f), mid(0.0f), treble(0.0f), + bassAtt(0.0f), midAtt(0.0f), trebleAtt(0.0f) { // Allocate FFT input/output arrays fftInput = new float[bufferSize]; fftOutput = new float[bufferSize]; @@ -40,11 +43,83 @@ void FFTProcessor::computeFFT(const vector& audioData) { fftwf_execute(static_cast(fftPlan)); // Compute magnitudes from FFT output - for (size_t i = 0; i < bufferSize / 2; ++i) { + size_t numBins = bufferSize / 2; + for (size_t i = 0; i < numBins; ++i) { float real = fftOutput[i]; - float imag = (i == 0 || i == bufferSize / 2) ? 0 : fftOutput[bufferSize - i]; + float imag = (i == 0 || i == numBins) ? 0 : fftOutput[bufferSize - i]; magnitudes[i] = sqrt(real * real + imag * imag); } + + // Compute band energies from the fresh magnitudes + computeBandEnergies(); +} + +void FFTProcessor::computeBandEnergies() { + size_t numBins = magnitudes.size(); + if (numBins == 0) return; + + // Frequency ranges (approximate for 44100 Hz sample rate, bufferSize=1024): + // Bin width = sampleRate / bufferSize ≈ 43 Hz + float sampleRate = 44100.0f; // typical default + float binWidth = sampleRate / static_cast(bufferSize); + + // -- Band boundaries (in Hz) -- + // Bass: 20 Hz – 250 Hz + // Mid: 250 Hz – 4000 Hz + // Treble: 4000 Hz – Nyquist (sampleRate/2) + int bassEnd = static_cast(250.0f / binWidth); // up to 250 Hz + int midEnd = static_cast(4000.0f / binWidth); // up to 4 kHz + int trebleEnd = numBins; // up to Nyquist + + // Clamp to valid range + bassEnd = min(bassEnd, static_cast(numBins)); + midEnd = min(midEnd, static_cast(numBins)); + + // Start bass from bin 1 to skip DC component (bin 0) + int bassStart = 1; + + // Compute raw band energies (mean magnitude per band) + float newBass = 0.0f, newMid = 0.0f, newTreble = 0.0f; + + int bassCount = bassEnd - bassStart; + if (bassCount > 0) { + for (int i = bassStart; i < bassEnd; ++i) + newBass += magnitudes[i]; + newBass /= static_cast(bassCount); + } + + int midCount = midEnd - bassEnd; + if (midCount > 0) { + for (int i = bassEnd; i < midEnd; ++i) + newMid += magnitudes[i]; + newMid /= static_cast(midCount); + } + + int trebleCount = trebleEnd - midEnd; + if (trebleCount > 0) { + for (int i = midEnd; i < trebleEnd; ++i) + newTreble += magnitudes[i]; + newTreble /= static_cast(trebleCount); + } + + // Assign raw values + bass = newBass; + mid = newMid; + treble = newTreble; + + // EMA smoothing for attenuated versions + // bassAtt = bassAtt * (1 - SMOOTHING) + newBass * SMOOTHING + // On first frame, initialise from raw value. + if (bassAtt == 0.0f && bass != 0.0f) { + bassAtt = bass; + midAtt = mid; + trebleAtt = treble; + } else { + float s = SMOOTHING; + bassAtt = bassAtt * (1.0f - s) + bass * s; + midAtt = midAtt * (1.0f - s) + mid * s; + trebleAtt = trebleAtt * (1.0f - s) + treble * s; + } } const vector& FFTProcessor::getMagnitudes() const { diff --git a/audio/FFTProcessor.h b/audio/FFTProcessor.h index 82cad36..4249c75 100644 --- a/audio/FFTProcessor.h +++ b/audio/FFTProcessor.h @@ -13,12 +13,35 @@ class FFTProcessor { void computeFFT(const vector& audioData); const vector& getMagnitudes() const; + // Frequency band energy accessors + float getBass() const { return bass; } + float getMid() const { return mid; } + float getTreble() const { return treble; } + float getBassAtt() const { return bassAtt; } + float getMidAtt() const { return midAtt; } + float getTrebleAtt() const { return trebleAtt; } + private: + void computeBandEnergies(); + size_t bufferSize; vector magnitudes; float* fftInput; float* fftOutput; void* fftPlan; // Plan type depends on FFTW version + + // Frequency band loudness (raw) + float bass; + float mid; + float treble; + + // Smoothed/attenuated versions (EMA) + float bassAtt; + float midAtt; + float trebleAtt; + + // EMA smoothing factor (0..1, higher = slower response) + static constexpr float SMOOTHING = 0.15f; }; #endif // FFTPROCESSOR_H diff --git a/src/Audio.cpp b/src/Audio.cpp index aec08f6..3b5d421 100644 --- a/src/Audio.cpp +++ b/src/Audio.cpp @@ -62,6 +62,33 @@ vector AudioProcessor::getFFTData() { return fftProcessor->getMagnitudes(); } +// --- Frequency band energy accessors --- +// These must be called AFTER getFFTData() in the same render cycle. + +float AudioProcessor::getBass() const { + return fftProcessor ? fftProcessor->getBass() : 0.0f; +} + +float AudioProcessor::getMid() const { + return fftProcessor ? fftProcessor->getMid() : 0.0f; +} + +float AudioProcessor::getTreble() const { + return fftProcessor ? fftProcessor->getTreble() : 0.0f; +} + +float AudioProcessor::getBassAtt() const { + return fftProcessor ? fftProcessor->getBassAtt() : 0.0f; +} + +float AudioProcessor::getMidAtt() const { + return fftProcessor ? fftProcessor->getMidAtt() : 0.0f; +} + +float AudioProcessor::getTrebleAtt() const { + return fftProcessor ? fftProcessor->getTrebleAtt() : 0.0f; +} + int AudioProcessor::audioCallback(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData) { AudioProcessor* processor = static_cast(userData); diff --git a/src/Audio.h b/src/Audio.h index a5f365e..4c7d1f9 100644 --- a/src/Audio.h +++ b/src/Audio.h @@ -18,6 +18,14 @@ class AudioProcessor { vector getFFTData(); + // Frequency band energy accessors + float getBass() const; + float getMid() const; + float getTreble() const; + float getBassAtt() const; + float getMidAtt() const; + float getTrebleAtt() const; + bool startProcessing(); void cleanup(); From 826fb6090221d21e37a1c02ad5d21df7a404dbab Mon Sep 17 00:00:00 2001 From: lannerwsf <63063344@qq.com> Date: Wed, 20 May 2026 19:33:09 +0800 Subject: [PATCH 2/2] feat: add 3D Bar Visualization with perspective projection (fixes #1) Convert the 2D bar graph into a 3D bar graph visualization with: - Perspective projection and orbiting camera for depth perception - X-axis: frequency bands, Y-axis: amplitude, Z-axis: time evolution - 3D bar boxes (6 faces, 12 triangles per bar) with depth testing - FFT history buffer stores up to 64 frames along the Z-axis - Automatic camera orbit for full 3D viewing experience - Age-based dimming on older bars for depth cue - New visualization option #5 in main menu - Dedicated 3D vertex shader with MVP matrix uniforms Files: - src/visualizations/BarVisualization3D.h/.cpp - src/visualizations/vertexShader3D.glsl - src/main.cpp (added option 5) - src/Makefile (added new source) --- src/Makefile | 2 +- src/main.cpp | 5 + src/visualizations/BarVisualization3D.cpp | 284 ++++++++++++++++++++++ src/visualizations/BarVisualization3D.h | 39 +++ src/visualizations/vertexShader3D.glsl | 14 ++ 5 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 src/visualizations/BarVisualization3D.cpp create mode 100644 src/visualizations/BarVisualization3D.h create mode 100644 src/visualizations/vertexShader3D.glsl diff --git a/src/Makefile b/src/Makefile index d1e29d0..b449033 100644 --- a/src/Makefile +++ b/src/Makefile @@ -4,7 +4,7 @@ CXXFLAGS = -std=c++17 -Wall -I"C:/msys64/mingw64/include" -L"C:/msys64/mingw64/l # Source files -SRC = main.cpp Audio.cpp ShaderUtils.cpp ../audio/AudioReader.cpp ../audio/FFTProcessor.cpp visualizations/CircleVisualization.cpp visualizations/CircularBarVisualization.cpp visualizations/BarVisualization.cpp visualizations/ColorUtils.cpp visualizations/MountainVisualization.cpp +SRC = main.cpp Audio.cpp ShaderUtils.cpp ../audio/AudioReader.cpp ../audio/FFTProcessor.cpp visualizations/CircleVisualization.cpp visualizations/CircularBarVisualization.cpp visualizations/BarVisualization.cpp visualizations/BarVisualization3D.cpp visualizations/ColorUtils.cpp visualizations/MountainVisualization.cpp # Output binary OUT = audio_visualizer diff --git a/src/main.cpp b/src/main.cpp index 1acefc5..723e032 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include "visualizations/BarVisualization.h" #include "visualizations/CircularBarVisualization.h" #include "visualizations/MountainVisualization.h" +#include "visualizations/BarVisualization3D.h" #include #include @@ -25,6 +26,7 @@ int main() { cout << "2. Bar Visualization\n"; cout << "3. Circular Bar Visualization\n"; cout << "4. Mountain Visualization\n"; + cout << "5. 3D Bar Visualization (perspective projection, time on Z-axis)\n"; cout << "Enter choice: "; cin >> choice; @@ -43,6 +45,9 @@ int main() { case 4: visualization = make_unique(); break; + case 5: + visualization = make_unique(); + break; default: cout << "Invalid choice. Exiting.\n"; return -1; diff --git a/src/visualizations/BarVisualization3D.cpp b/src/visualizations/BarVisualization3D.cpp new file mode 100644 index 0000000..c4313a0 --- /dev/null +++ b/src/visualizations/BarVisualization3D.cpp @@ -0,0 +1,284 @@ +#define _USE_MATH_DEFINES +#include "BarVisualization3D.h" +#include "ColorUtils.h" +#include +#include +#include + +// ── Minimal 4×4 matrix helpers (column-major for OpenGL) ────────────────────── + +static void mat4Identity(float* m) { + std::memset(m, 0, 16 * sizeof(float)); + m[0] = m[5] = m[10] = m[15] = 1.0f; +} + +static void mat4Perspective(float* m, float fovRad, float aspect, float near, float far) { + std::memset(m, 0, 16 * sizeof(float)); + float f = 1.0f / std::tan(fovRad * 0.5f); + m[0] = f / aspect; + m[5] = f; + m[10] = (far + near) / (near - far); + m[11] = -1.0f; + m[14] = (2.0f * far * near) / (near - far); +} + +static void mat4LookAt(float* m, float ex, float ey, float ez, + float tx, float ty, float tz, + float ux, float uy, float uz) { + float f[3] = { tx - ex, ty - ey, tz - ez }; + float flen = std::sqrt(f[0]*f[0] + f[1]*f[1] + f[2]*f[2]); + if (flen > 1e-8f) { f[0] /= flen; f[1] /= flen; f[2] /= flen; } + float s[3] = { f[1]*uz - f[2]*uy, f[2]*ux - f[0]*uz, f[0]*uy - f[1]*ux }; + float slen = std::sqrt(s[0]*s[0] + s[1]*s[1] + s[2]*s[2]); + if (slen > 1e-8f) { s[0] /= slen; s[1] /= slen; s[2] /= slen; } + float u[3] = { s[1]*f[2] - s[2]*f[1], s[2]*f[0] - s[0]*f[2], s[0]*f[1] - s[1]*f[0] }; + m[0] = s[0]; m[1] = u[0]; m[2] = -f[0]; m[3] = 0.0f; + m[4] = s[1]; m[5] = u[1]; m[6] = -f[1]; m[7] = 0.0f; + m[8] = s[2]; m[9] = u[2]; m[10] = -f[2]; m[11] = 0.0f; + m[12] = -(s[0]*ex + s[1]*ey + s[2]*ez); + m[13] = -(u[0]*ex + u[1]*ey + u[2]*ez); + m[14] = (f[0]*ex + f[1]*ey + f[2]*ez); + m[15] = 1.0f; +} + +static void mat4Multiply(float* dst, const float* a, const float* b) { + float tmp[16]; + for (int r = 0; r < 4; ++r) + for (int c = 0; c < 4; ++c) { + tmp[c*4+r] = a[r]*b[c*4] + a[4+r]*b[c*4+1] + a[8+r]*b[c*4+2] + a[12+r]*b[c*4+3]; + } + std::memcpy(dst, tmp, 16 * sizeof(float)); +} + +// ── BarVisualization3D implementation ──────────────────────────────────────── + +BarVisualization3D::BarVisualization3D() + : window(nullptr), vbo(0), vao(0), smoothedFFT(128, 0.0f), + shaderProgram(0), modelLoc(-1), viewLoc(-1), projLoc(-1), + timeAccum(0.0f), generation(0) {} + +BarVisualization3D::~BarVisualization3D() { + cleanup(); +} + +static void fb_callback_3d(GLFWwindow* window, int width, int height) { + glViewport(0, 0, width, height); +} + +bool BarVisualization3D::initialize(int windowWidth, int windowHeight) { + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW." << std::endl; + return false; + } + + window = glfwCreateWindow(windowWidth, windowHeight, "3D Bar Visualization", nullptr, nullptr); + if (!window) { + std::cerr << "Failed to create GLFW window." << std::endl; + glfwTerminate(); + return false; + } + + glfwMakeContextCurrent(window); + glewExperimental = GL_TRUE; + if (glewInit() != GLEW_OK) { + std::cerr << "Failed to initialize GLEW." << std::endl; + return false; + } + + glfwSetFramebufferSizeCallback(window, fb_callback_3d); + + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + + // Vertex format: vec3 aPos (location 0) + vec3 aColor (location 1) = 6 floats + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + + glClearColor(0.05f, 0.05f, 0.1f, 1.0f); + glEnable(GL_DEPTH_TEST); + + shaderProgram = createShaderProgram("visualizations/vertexShader3D.glsl", + "visualizations/fragmentShader.glsl"); + if (shaderProgram == 0) { + std::cerr << "ERROR: Failed to create 3D shader program!" << std::endl; + exit(1); + } + + glUseProgram(shaderProgram); + modelLoc = glGetUniformLocation(shaderProgram, "model"); + viewLoc = glGetUniformLocation(shaderProgram, "view"); + projLoc = glGetUniformLocation(shaderProgram, "projection"); + + return true; +} + +void BarVisualization3D::addBar3D(float x, float yBase, float yHeight, float zPos, + float barW, float barD, + const Color& color, std::vector& verts) { + float left = x; + float right = x + barW; + float bot = yBase; + float top = yBase + yHeight; + float front = zPos; + float back = zPos + barD; + + float r = color.r, g = color.g, b = color.b; + + // Each face: 2 triangles => 6 vertices + // Front face (z = front) + verts.insert(verts.end(), { + left, bot, front, r,g,b, right, bot, front, r,g,b, left, top, front, r,g,b, + right, bot, front, r,g,b, right, top, front, r,g,b, left, top, front, r,g,b, + }); + // Back face (z = back) + verts.insert(verts.end(), { + right, bot, back, r,g,b, left, bot, back, r,g,b, left, top, back, r,g,b, + right, bot, back, r,g,b, left, top, back, r,g,b, right, top, back, r,g,b, + }); + // Left face (x = left) + verts.insert(verts.end(), { + left, bot, back, r,g,b, left, bot, front,r,g,b, left, top, front,r,g,b, + left, bot, back, r,g,b, left, top, front,r,g,b, left, top, back, r,g,b, + }); + // Right face (x = right) + verts.insert(verts.end(), { + right, bot, front,r,g,b, right, bot, back, r,g,b, right, top, back, r,g,b, + right, bot, front,r,g,b, right, top, back, r,g,b, right, top, front,r,g,b, + }); + // Top face (y = top) + verts.insert(verts.end(), { + left, top, front,r,g,b, right, top, front,r,g,b, right, top, back, r,g,b, + left, top, front,r,g,b, right, top, back, r,g,b, left, top, back, r,g,b, + }); + // Bottom face (y = bot) + verts.insert(verts.end(), { + left, bot, back, r,g,b, right, bot, back, r,g,b, right, bot, front,r,g,b, + left, bot, back, r,g,b, right, bot, front,r,g,b, left, bot, front,r,g,b, + }); +} + +void BarVisualization3D::render(const std::vector& fftMagnitudes) { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glUseProgram(shaderProgram); + + if (fftMagnitudes.empty()) { + glfwSwapBuffers(window); + glfwPollEvents(); + return; + } + + // ── Build projection + view matrices ── + int fbWidth, fbHeight; + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + float aspect = (float)fbWidth / (float)fbHeight; + + float proj[16]; + mat4Perspective(proj, 45.0f * (float)M_PI / 180.0f, aspect, 0.1f, 100.0f); + + // Camera: orbit around the scene + float camAngle = timeAccum * 0.15f; + float camRadius = 3.5f; + float camX = camRadius * std::sin(camAngle); + float camZ = camRadius * std::cos(camAngle); + float camY = 1.2f; + + float view[16]; + mat4LookAt(view, camX, camY, camZ, 0.0f, -0.2f, -2.0f, 0.0f, 1.0f, 0.0f); + + glUniformMatrix4fv(projLoc, 1, GL_FALSE, proj); + glUniformMatrix4fv(viewLoc, 1, GL_FALSE, view); + + // ── Process FFT data ── + size_t numBars = fftMagnitudes.size() / 8; + if (numBars < 1) numBars = 1; + + if (smoothedFFT.size() != numBars) { + smoothedFFT.resize(numBars, 0.0f); + } + + float maxMagnitude = 0.0f; + for (size_t i = 0; i < numBars; ++i) { + if (fftMagnitudes[i] > maxMagnitude) maxMagnitude = fftMagnitudes[i]; + } + if (maxMagnitude < 1e-6f) maxMagnitude = 1.0f; + + // Smoothed magnitudes for current frame + std::vector currentBars(numBars); + for (size_t i = 0; i < numBars; ++i) { + float norm = fftMagnitudes[i] / maxMagnitude; + float logMag = std::log10(1.0f + norm * 10.0f) / std::log10(2.0f); + smoothedFFT[i] = (smoothedFFT[i] * 0.9f) + (0.1f * logMag); + float h = smoothedFFT[i] * 0.9f; + if (h < 0.02f) h = 0.02f; + currentBars[i] = h; + } + + // ── Store frame in history (z-axis = time) ── + fftHistory.push_back(currentBars); + if (fftHistory.size() > MAX_HISTORY) { + fftHistory.erase(fftHistory.begin()); + } + + // ── Build 3D geometry ── + std::vector vertices; + float barW = 1.2f / numBars; + float barD = 0.08f; + float zStep = 0.08f; + + // World-space: bars sit on y=-1.0, z goes negative into the screen + float zStart = 0.0f; + + for (int g = 0; g < (int)fftHistory.size(); ++g) { + const auto& frame = fftHistory[g]; + float zPos = zStart - (fftHistory.size() - 1 - g) * zStep; + // Dimmer / smaller for older frames (depth cue) + float ageFactor = 0.3f + 0.7f * (float)(g + 1) / (float)fftHistory.size(); + + for (size_t i = 0; i < frame.size(); ++i) { + float h = frame[i] * ageFactor; + float x = -0.6f + i * barW; + Color c = getColorFromMagnitude(frame[i] / (ageFactor > 0.01f ? ageFactor : 1.0f), 0.0f, 1.0f); + // Dim color for older frames + c.r *= ageFactor; + c.g *= ageFactor; + c.b *= ageFactor; + addBar3D(x, -1.0f, h, zPos, barW * 0.8f, barD, c, vertices); + } + } + + // ── Upload and draw ── + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_DYNAMIC_DRAW); + + glBindVertexArray(vao); + + // Identity model matrix + float model[16]; + mat4Identity(model); + glUniformMatrix4fv(modelLoc, 1, GL_FALSE, model); + + glDrawArrays(GL_TRIANGLES, 0, vertices.size() / 6); + + // ── Advance time ── + timeAccum += 0.016f; // ~60 fps step + + glfwSwapBuffers(window); + glfwPollEvents(); +} + +bool BarVisualization3D::shouldClose() { + return glfwWindowShouldClose(window); +} + +void BarVisualization3D::cleanup() { + if (vbo != 0) glDeleteBuffers(1, &vbo); + if (vao != 0) glDeleteVertexArrays(1, &vao); + if (window) glfwDestroyWindow(window); + glfwTerminate(); +} diff --git a/src/visualizations/BarVisualization3D.h b/src/visualizations/BarVisualization3D.h new file mode 100644 index 0000000..8f5e610 --- /dev/null +++ b/src/visualizations/BarVisualization3D.h @@ -0,0 +1,39 @@ +#ifndef BAR_VISUALIZATION_3D_H +#define BAR_VISUALIZATION_3D_H + +#include "..\ShaderUtils.h" +#include "BaseVisualization.h" +#include +#include +#include + +class BarVisualization3D : public BaseVisualization { +public: + BarVisualization3D(); + ~BarVisualization3D(); + + bool initialize(int windowWidth, int windowHeight) override; + void render(const std::vector& fftMagnitudes) override; + bool shouldClose() override; + void cleanup() override; + +private: + GLFWwindow* window; + GLuint vbo, vao; + std::vector smoothedFFT; + GLuint shaderProgram; + GLint modelLoc, viewLoc, projLoc; + + // Camera / time tracking + float timeAccum; + int generation; + static constexpr int MAX_HISTORY = 64; + std::vector> fftHistory; + + // Build a 3D bar box (8 vertices, 12 triangles = 36 floats per bar) + void addBar3D(float x, float yBase, float yHeight, float zPos, + float barWidth, float barDepth, + const Color& color, std::vector& verts); +}; + +#endif diff --git a/src/visualizations/vertexShader3D.glsl b/src/visualizations/vertexShader3D.glsl new file mode 100644 index 0000000..e883dc5 --- /dev/null +++ b/src/visualizations/vertexShader3D.glsl @@ -0,0 +1,14 @@ +#version 330 core +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec3 aColor; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +out vec3 vertexColor; + +void main() { + gl_Position = projection * view * model * vec4(aPos, 1.0); + vertexColor = aColor; +}