feat: displace sphere with live waveform
Upload the per-channel waveform as a texture and displace icosphere vertices radially in the vertex shader: CH1 drives the northern hemisphere, CH2 the southern.
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
#version 150
|
||||
|
||||
uniform mat4 modelViewProjectionMatrix;
|
||||
uniform sampler2D waveformTex; // width = samples, height = 2 (row0 CH1, row1 CH2)
|
||||
uniform float displaceAmount;
|
||||
uniform float baseRadius;
|
||||
|
||||
in vec4 position;
|
||||
|
||||
out vec2 vSphereUV; // x = longitude [0,1], y = latitude [0,1]
|
||||
@@ -11,6 +15,11 @@ void main() {
|
||||
vec3 dir = normalize(position.xyz);
|
||||
float lon = atan(dir.z, dir.x) / (2.0 * PI) + 0.5;
|
||||
float lat = asin(clamp(dir.y, -1.0, 1.0)) / PI + 0.5;
|
||||
gl_Position = modelViewProjectionMatrix * position;
|
||||
|
||||
float row = (dir.y >= 0.0) ? 0.25 : 0.75; // CH1 north, CH2 south
|
||||
float wave = texture(waveformTex, vec2(lon, row)).r; // [-1,1]
|
||||
float r = baseRadius * (1.0 + displaceAmount * wave);
|
||||
|
||||
gl_Position = modelViewProjectionMatrix * vec4(dir * r, 1.0);
|
||||
vSphereUV = vec2(lon, lat);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "SphereViz.h"
|
||||
#include <algorithm>
|
||||
|
||||
void SphereViz::setup(int icoIterations, int spectroWidth, int spectroHeight) {
|
||||
void SphereViz::setup(int icoIterations, int spectroWidth, int spectroHeight,
|
||||
int waveformLen) {
|
||||
waveformLen_ = waveformLen;
|
||||
spectro_ = std::make_unique<oscope::SpectrogramBuffer>(spectroWidth,
|
||||
spectroHeight);
|
||||
|
||||
@@ -23,6 +25,12 @@ void SphereViz::setup(int icoIterations, int spectroWidth, int spectroHeight) {
|
||||
spectroTex_.setTextureWrap(GL_REPEAT, GL_CLAMP_TO_EDGE);
|
||||
spectroTex_.setTextureMinMagFilter(GL_LINEAR, GL_LINEAR);
|
||||
|
||||
wavePix_.allocate(waveformLen, 2, OF_PIXELS_GRAY);
|
||||
wavePix_.set(0.0f);
|
||||
waveTex_.allocate(wavePix_);
|
||||
waveTex_.setTextureWrap(GL_REPEAT, GL_CLAMP_TO_EDGE);
|
||||
waveTex_.setTextureMinMagFilter(GL_LINEAR, GL_LINEAR);
|
||||
|
||||
if (!shader_.load("shaders/sphere"))
|
||||
ofLogError("SphereViz") << "failed to load shaders/sphere";
|
||||
}
|
||||
@@ -37,10 +45,34 @@ void SphereViz::pushSpectrogramColumn(const std::vector<float>& magCh1,
|
||||
static_cast<float>(spectro_->width());
|
||||
}
|
||||
|
||||
void SphereViz::setWaveform(const std::vector<float>& ch1,
|
||||
const std::vector<float>& ch2) {
|
||||
float* px = wavePix_.getData();
|
||||
const int L = waveformLen_;
|
||||
auto fillRow = [&](const std::vector<float>& src, int row) {
|
||||
const int n = static_cast<int>(src.size());
|
||||
for (int i = 0; i < L; ++i) {
|
||||
float v = 0.0f;
|
||||
if (n > 0) {
|
||||
int idx = n - L + i; // newest L samples
|
||||
if (idx < 0) idx = 0;
|
||||
v = src[idx];
|
||||
}
|
||||
px[row * L + i] = v;
|
||||
}
|
||||
};
|
||||
fillRow(ch1, 0);
|
||||
fillRow(ch2, 1);
|
||||
waveTex_.loadData(wavePix_);
|
||||
}
|
||||
|
||||
void SphereViz::drawSkin() {
|
||||
shader_.begin();
|
||||
shader_.setUniformTexture("spectroTex", spectroTex_, 0);
|
||||
shader_.setUniformTexture("waveformTex", waveTex_, 1);
|
||||
shader_.setUniform1f("scrollOffset", scrollOffset_);
|
||||
shader_.setUniform1f("displaceAmount", displace_);
|
||||
shader_.setUniform1f("baseRadius", baseRadius_);
|
||||
shader_.setUniform1i("colormapId", colormapId_);
|
||||
mesh_.draw();
|
||||
shader_.end();
|
||||
|
||||
@@ -4,26 +4,33 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
// GL visual unit: an icosphere whose skin is the scrolling spectrogram.
|
||||
// The northern hemisphere shows CH1, the southern shows CH2.
|
||||
// GL visual unit: an icosphere skinned by the scrolling spectrogram and
|
||||
// displaced radially by the live waveform. Northern hemisphere = CH1,
|
||||
// southern = CH2.
|
||||
class SphereViz {
|
||||
public:
|
||||
void setup(int icoIterations, int spectroWidth, int spectroHeight);
|
||||
void setup(int icoIterations, int spectroWidth, int spectroHeight,
|
||||
int waveformLen);
|
||||
|
||||
// Advances the spectrogram by one column and uploads it.
|
||||
void pushSpectrogramColumn(const std::vector<float>& magCh1,
|
||||
const std::vector<float>& magCh2);
|
||||
void setWaveform(const std::vector<float>& ch1,
|
||||
const std::vector<float>& ch2);
|
||||
|
||||
void drawSkin();
|
||||
void setColormap(int id) { colormapId_ = id; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<oscope::SpectrogramBuffer> spectro_;
|
||||
ofVboMesh mesh_;
|
||||
ofShader shader_;
|
||||
ofTexture spectroTex_;
|
||||
ofVboMesh mesh_;
|
||||
ofShader shader_;
|
||||
ofTexture spectroTex_;
|
||||
ofTexture waveTex_;
|
||||
ofFloatPixels spectroPix_;
|
||||
ofFloatPixels wavePix_;
|
||||
int waveformLen_ = 0;
|
||||
float baseRadius_ = 200.0f;
|
||||
float scrollOffset_ = 0.0f;
|
||||
float displace_ = 0.18f;
|
||||
int colormapId_ = 0;
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ void ofApp::setup() {
|
||||
cam_.setNearClip(1.0f);
|
||||
cam_.setFarClip(5000.0f);
|
||||
|
||||
sphere_.setup(5, 512, 256);
|
||||
sphere_.setup(5, 512, 256, 1024);
|
||||
|
||||
hantek_.setSampleRate(16000000u);
|
||||
const oscope::HantekStatus st = hantek_.start();
|
||||
@@ -46,6 +46,7 @@ void ofApp::update() {
|
||||
sphere_.setColormap(colormap_);
|
||||
sphere_.pushSpectrogramColumn(analyzerCh1_.magDown(),
|
||||
analyzerCh2_.magDown());
|
||||
sphere_.setWaveform(buf1_, buf2_);
|
||||
}
|
||||
|
||||
void ofApp::draw() {
|
||||
|
||||
Reference in New Issue
Block a user