feat: 3D relief, lighting and reactive motion
The spectrogram now extrudes the geometry alongside the waveform, the skin is lit with a screen-space normal, and the sphere tumbles and pulses with the kick and signal energy.
This commit is contained in:
@@ -5,7 +5,9 @@ uniform float scrollOffset;
|
||||
uniform int colormapId;
|
||||
uniform int renderMode; // 0 = skin, 1 = points
|
||||
|
||||
in vec2 vSphereUV;
|
||||
in vec2 vSphereUV;
|
||||
in vec3 vViewPos;
|
||||
in float vWave;
|
||||
out vec4 fragColor;
|
||||
|
||||
// Polynomial colormap fits (public domain, Matt Zucker).
|
||||
@@ -34,9 +36,24 @@ void main() {
|
||||
float u = fract(vSphereUV.x - scrollOffset);
|
||||
float mag = clamp(texture(spectroTex, vec2(u, vSphereUV.y)).r, 0.0, 1.0);
|
||||
vec3 col = (colormapId == 0) ? magma(mag) : viridis(mag);
|
||||
|
||||
if (renderMode == 1) {
|
||||
// round point sprites + a touch of waveform sheen
|
||||
vec2 d = gl_PointCoord - vec2(0.5);
|
||||
if (dot(d, d) > 0.25) discard; // round the points
|
||||
if (dot(d, d) > 0.25) discard;
|
||||
col += 0.20 * abs(vWave);
|
||||
fragColor = vec4(col, 1.0);
|
||||
return;
|
||||
}
|
||||
fragColor = vec4(col, 1.0);
|
||||
|
||||
// skin: light the displaced relief with a screen-space face normal
|
||||
vec3 N = normalize(cross(dFdx(vViewPos), dFdy(vViewPos)));
|
||||
vec3 V = normalize(-vViewPos);
|
||||
if (dot(N, V) < 0.0) N = -N;
|
||||
vec3 L = normalize(vec3(0.45, 0.65, 0.75));
|
||||
float diff = max(dot(N, L), 0.0);
|
||||
float rim = pow(1.0 - max(dot(N, V), 0.0), 2.5);
|
||||
|
||||
vec3 lit = col * (0.35 + 0.85 * diff) + rim * vec3(0.35, 0.45, 0.65);
|
||||
fragColor = vec4(lit, 1.0);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
#version 150
|
||||
|
||||
uniform mat4 modelViewProjectionMatrix;
|
||||
uniform sampler2D waveformTex; // width = samples, height = 2 (row0 CH1, row1 CH2)
|
||||
uniform mat4 modelViewMatrix;
|
||||
uniform sampler2D waveformTex;
|
||||
uniform sampler2D spectroTex;
|
||||
uniform float displaceAmount;
|
||||
uniform float spectroAmount;
|
||||
uniform float scrollOffset;
|
||||
uniform float baseRadius;
|
||||
uniform int renderMode; // 0 = skin, 1 = points
|
||||
|
||||
in vec4 position;
|
||||
|
||||
out vec2 vSphereUV; // x = longitude [0,1], y = latitude [0,1]
|
||||
out vec2 vSphereUV;
|
||||
out vec3 vViewPos;
|
||||
out float vWave;
|
||||
|
||||
const float PI = 3.14159265359;
|
||||
|
||||
@@ -17,14 +23,22 @@ void main() {
|
||||
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;
|
||||
|
||||
// waveform ripple (per hemisphere) + scrolling spectrogram relief
|
||||
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);
|
||||
float wave = texture(waveformTex, vec2(lon, row)).r; // [-1,1]
|
||||
float spec = texture(spectroTex,
|
||||
vec2(fract(lon - scrollOffset), lat)).r; // [0,1]
|
||||
|
||||
float r = baseRadius * (1.0 + displaceAmount * wave
|
||||
+ spectroAmount * spec);
|
||||
if (renderMode == 1) {
|
||||
r *= 1.05; // float the point cloud just outside the skin
|
||||
r *= 1.06; // float the point cloud outside the skin
|
||||
gl_PointSize = 6.0;
|
||||
}
|
||||
|
||||
gl_Position = modelViewProjectionMatrix * vec4(dir * r, 1.0);
|
||||
vec4 viewPos = modelViewMatrix * vec4(dir * r, 1.0);
|
||||
gl_Position = modelViewProjectionMatrix * vec4(dir * r, 1.0);
|
||||
vViewPos = viewPos.xyz;
|
||||
vSphereUV = vec2(lon, lat);
|
||||
vWave = wave;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ void SphereViz::bindUniforms() {
|
||||
shader_.setUniformTexture("waveformTex", waveTex_, 1);
|
||||
shader_.setUniform1f("scrollOffset", scrollOffset_);
|
||||
shader_.setUniform1f("displaceAmount", displace_);
|
||||
shader_.setUniform1f("spectroAmount", spectroAmount_);
|
||||
shader_.setUniform1f("baseRadius", baseRadius_);
|
||||
shader_.setUniform1i("colormapId", colormapId_);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ private:
|
||||
int waveformLen_ = 0;
|
||||
float baseRadius_ = 200.0f;
|
||||
float scrollOffset_ = 0.0f;
|
||||
float displace_ = 0.18f;
|
||||
float displace_ = 0.28f;
|
||||
float spectroAmount_ = 0.40f;
|
||||
int colormapId_ = 0;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "ofApp.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
void ofApp::setup() {
|
||||
ofSetFrameRate(60);
|
||||
@@ -30,10 +32,6 @@ void ofApp::setup() {
|
||||
void ofApp::update() {
|
||||
if (frozen_) return;
|
||||
|
||||
// Auto-rotation advances only when the mouse is not orbiting the camera.
|
||||
if (!ofGetMousePressed())
|
||||
spin_ += 6.0f * static_cast<float>(ofGetLastFrameTime());
|
||||
|
||||
if (demoMode_) {
|
||||
demo_.next(buf1_, buf2_, 8192);
|
||||
} else {
|
||||
@@ -48,6 +46,17 @@ void ofApp::update() {
|
||||
analyzerCh1_.update(buf1_, buf1_, sr);
|
||||
analyzerCh2_.update(buf2_, buf2_, sr);
|
||||
|
||||
// Audio-reactive motion: rotation speed tracks signal energy, the whole
|
||||
// sphere pulses with the kick transient.
|
||||
const oscope::AudioBands& b1 = analyzerCh1_.bands();
|
||||
const oscope::AudioBands& b2 = analyzerCh2_.bands();
|
||||
const float energy = 0.5f * (b1.full + b2.full);
|
||||
const float kick = std::max(b1.kick, b2.kick);
|
||||
const float dt = static_cast<float>(ofGetLastFrameTime());
|
||||
if (!ofGetMousePressed())
|
||||
spin_ += (8.0f + 80.0f * energy) * dt;
|
||||
pulse_ += (1.0f + 0.20f * kick - pulse_) * 0.25f;
|
||||
|
||||
sphere_.setColormap(colormap_);
|
||||
sphere_.pushSpectrogramColumn(analyzerCh1_.magDown(),
|
||||
analyzerCh2_.magDown());
|
||||
@@ -56,9 +65,12 @@ void ofApp::update() {
|
||||
}
|
||||
|
||||
void ofApp::draw() {
|
||||
const float t = ofGetElapsedTimef();
|
||||
cam_.begin();
|
||||
ofPushMatrix();
|
||||
ofRotateYDeg(spin_);
|
||||
ofRotateXDeg(16.0f * std::sin(t * 0.27f)); // slow tumble
|
||||
ofScale(pulse_, pulse_, pulse_); // audio pulse
|
||||
if (layerA_) sphere_.drawSkin();
|
||||
if (layerC_) sphere_.drawPoints();
|
||||
if (layerB_) rings_.draw();
|
||||
|
||||
@@ -38,5 +38,6 @@ private:
|
||||
int colormap_ = 0;
|
||||
float scopeSr_ = 16.0e6f;
|
||||
float spin_ = 0.0f;
|
||||
float pulse_ = 1.0f;
|
||||
std::string statusText_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user