ObjReader Community
SDK programming => 64-bit SDK programming => Topic started by: Patrice Terrier on December 30, 2025, 06:22:41 pm
-
BassBox GLSL Plugin – Minimal Tutorial (Template Walkthrough)
This post explains how to use the provided bbp_GLSL.cpp + GLSL.h template
to build a simple GLSL-based visual plugin for BassBox64 / MBox64.
The goal is not to introduce “modern OpenGL”, but to show how a GLSL fragment shader
is integrated cleanly into the existing BassBox OpenGL pipeline.
The template uses:
- a legacy full-screen quad (glBegin / glVertex)
- a minimal passthrough vertex shader
- a fragment shader driven by audio and time
- a 32-color LUT system shared between CPU and GPU
----------------------------------------------------------------
1) Plugin lifecycle (BBProc)
----------------------------------------------------------------
The host communicates with the plugin through the exported function:
ExportC long BBProc(BBPLUGIN &BBP);
The most important messages are:
BBP_CREATE
Called once to declare plugin identity and rendering mode.
Here you set:
- title
- author
- version
- BBP_OPENGL (mandatory for GLSL plugins)
BBP_INIT
Called once when the plugin is initialized.
This is where you:
- bind the host OpenGL context (wglMakeCurrent)
- reset OpenGL state
- initialize time and audio variables
- create textures (including the LUT)
- compile and link GLSL shaders
- cache uniform and sampler locations
BBP_RENDER
Called every frame.
This is where you:
- update time and audio values
- send uniforms to the shader
- bind textures
- draw a full-screen quad
BBP_SIZE
Called when the plugin window is resized.
You must update glViewport and internal width/height.
BBP_DESTROY
Called when the plugin is unloaded.
You must delete:
- GLSL program
- textures
- any allocated resources
----------------------------------------------------------------
2) Rendering model
----------------------------------------------------------------
This template uses a very simple and reliable rendering model:
- A full-screen quad is drawn in clip space (-1 .. +1)
- The vertex shader simply forwards gl_Vertex to gl_Position
- The fragment shader runs once per pixel
No VBO, no VAO, no FBO, no multipass.
This keeps the focus on the shader logic itself.
----------------------------------------------------------------
3) Core uniforms provided to the shader
----------------------------------------------------------------
The following uniforms are automatically filled by the plugin:
uniform vec3 iResolution;
- x = viewport width in pixels
- y = viewport height in pixels
- z = 1.0 (convention)
uniform float iTime;
- Accumulated animation time
- Already modulated by audio on the CPU side
- Suitable for driving motion directly
uniform float iAudio;
- Smoothed audio level
- Typically in the range 0.0 .. 1.0 (may slightly exceed)
- Stable enough for visual modulation
uniform vec3 iLutColor;
- One color selected from the 32-color LUT
- Chosen on the CPU side according to audio peaks
- Already normalized (0..1)
uniform vec3 iMouse;
- Present for compatibility
- Currently set to (0,0,0) in the template
- Can be wired later via BBP_MOUSE
uniform vec4 iDate;
- Only iDate.w is used in the template
- Contains raw system time in seconds
- Useful if you need a non-audio-modulated time source
----------------------------------------------------------------
4) LUT system (CPU + GPU)
----------------------------------------------------------------
The template defines a 32-color palette:
- On the CPU:
- gP.color[32] : packed ARGB
- gP.fR/G/B[32]: normalized float values
- On the GPU:
- a 32x1 RGB texture bound to iChannel1
You can:
- use iLutColor for a single selected color
- sample iChannel1 in the shader for custom palette logic
The LUT texture uses:
- GL_NEAREST filtering
- GL_CLAMP_TO_EDGE wrapping
----------------------------------------------------------------
5) Demo fragment shader (what it shows)
----------------------------------------------------------------
The provided demo shader demonstrates:
- coordinate normalization using iResolution
- time-based animation using iTime
- audio-driven modulation using iAudio
- color tinting using iLutColor
- a simple ring/pulse effect
- a subtle vignette
It does NOT require:
- any texture input
- any mouse interaction
- any advanced OpenGL features
Alpha output is set to 1.0, producing an opaque image.
----------------------------------------------------------------
6) Where to modify things
----------------------------------------------------------------
To change the visual effect:
Edit the quoted GLSL string inside:
static GLuint CompileShaderFromString()
To add textures:
Populate gP.mt[] in BBP_INIT and sample them as iChannel0..3.
To enable transparency:
- Output alpha < 1.0 in gl_FragColor
- Enable blending in BBP_INIT or BBP_RENDER
- Ensure the host compositing mode supports it
To react more strongly to audio:
- Adjust the scaling of audioNow
- Modify smoothing factor
- Use iAudio directly in the shader
----------------------------------------------------------------
7) Why this template matters
----------------------------------------------------------------
This template is intentionally:
- minimal
- stable
- legacy-compatible
- host-friendly
It avoids:
- undefined OpenGL state
- hidden multipass logic
- fragile modern-only constructs
Once this base works reliably, more complex shaders and techniques
can be layered on top with confidence.
----------------------------------------------------------------
End of tutorial
----------------------------------------------------------------
The VS 2022 bbp_GLSL.zip is attached to this post
-
BBP_Cubism
Converted from https://www.shadertoy.com/view/ftGfDK
Original created by mrange on 2022-10-08
Audio + color LUT + BBP_plugin integration by Patrice Terrier
(http://www.objreader.com/download/images/Cubism.png)
Paste this in the BBProc section case BBP_CREATE:
// Host asks: "who are you and what do you render to?"
strcpy(BBP.title,"Cubism experiment");
strcpy(BBP.author, "Mrange (Shadertoy)");
BBP.version = MAKEWORD(1,0);
// Critical: This plugin renders using OpenGL into the host-provided GL context.
BBP.renderto = BBP_OPENGL;
// Default background ARGB used by host (informational/fallback).
BBP.backargb = bbpARGB(255,0,0,0);
break;
Paste this in the GLSL.h sectionstatic GLuint CompileShaderFromString() {
const char* CharBuffer =
"#version 330 core\n"
"\n"
"uniform vec3 iResolution;\n"
"uniform float iTime;\n"
"uniform float iAudio;\n"
"uniform vec3 iLutColor; // RGB directly from host (lutR,lutG,lutB)\n"
"\n"
"out vec4 FragColor;\n"
"\n"
"#define TIME iTime\n"
"#define RESOLUTION iResolution\n"
"#define PI 3.141592654\n"
"#define TAU (2.0*PI)\n"
"#define ROT(a) mat2(cos(a), sin(a), -sin(a), cos(a))\n"
"\n"
"#define TOLERANCE 0.0005\n"
"#define MAX_RAY_LENGTH 20.0\n"
"#define MAX_RAY_MARCHES 80\n"
"#define MAX_SHD_MARCHES 20\n"
"#define NORM_OFF 0.005\n"
"\n"
"const mat2 rot0 = ROT(0.0);\n"
"mat2 g_rot0 = rot0;\n"
"\n"
"const vec4 hsv2rgb_K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n"
"#define HSV2RGB(c) (c.z * mix(hsv2rgb_K.xxx, clamp(abs(fract(c.xxx + hsv2rgb_K.xyz) * 6.0 - hsv2rgb_K.www) - hsv2rgb_K.xxx, 0.0, 1.0), c.y))\n"
"\n"
"const float hoff = 0.0;\n"
"\n"
"// Keep sky exactly as original\n"
"const vec3 skyCol = HSV2RGB(vec3(hoff+0.57, 0.90, 0.25));\n"
"const vec3 skylineCol = HSV2RGB(vec3(hoff+0.02, 0.95, 0.5));\n"
"const vec3 sunCol = HSV2RGB(vec3(hoff+0.07, 0.95, 0.5));\n"
"\n"
"const vec3 sunDir1 = normalize(vec3(0., 0.05, -1.0));\n"
"\n"
"const vec3 lightPos1 = vec3(10.0, 10.0, 10.0);\n"
"const vec3 lightPos2 = vec3(-10.0, 10.0, -10.0);\n"
"\n"
"vec3 sRGB(vec3 t) {\n"
" return mix(1.055*pow(t, vec3(1./2.4)) - 0.055, 12.92*t, step(t, vec3(0.0031308)));\n"
"}\n"
"\n"
"vec3 aces_approx(vec3 v) {\n"
" v = max(v, 0.0);\n"
" v *= 0.6;\n"
" float a = 2.51;\n"
" float b = 0.03;\n"
" float c = 2.43;\n"
" float d = 0.59;\n"
" float e = 0.14;\n"
" return clamp((v*(a*v+b))/(v*(c*v+d)+e), 0.0, 1.0);\n"
"}\n"
"\n"
"float tanh_approx(float x) {\n"
" float x2 = x*x;\n"
" return clamp(x*(27.0 + x2)/(27.0+9.0*x2), -1.0, 1.0);\n"
"}\n"
"\n"
"float rayPlane(vec3 ro, vec3 rd, vec4 p) {\n"
" return -(dot(ro,p.xyz)+p.w)/dot(rd,p.xyz);\n"
"}\n"
"\n"
"float box(vec2 p, vec2 b) {\n"
" vec2 d = abs(p)-b;\n"
" return length(max(d,0.0)) + min(max(d.x,d.y),0.0);\n"
"}\n"
"\n"
"float box(vec3 p, vec3 b) {\n"
" vec3 q = abs(p) - b;\n"
" return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);\n"
"}\n"
"\n"
"float ref(inout vec3 p, vec3 r) {\n"
" float d = dot(p, r);\n"
" p -= r*min(0.0, d)*2.0;\n"
" return d < 0.0 ? 0.0 : 1.0;\n"
"}\n"
"\n"
"vec3 render0(vec3 ro, vec3 rd) {\n"
" vec3 col = vec3(0.0);\n"
" float sf = 1.0001-max(dot(sunDir1, rd), 0.0);\n"
" col += skyCol*pow((1.0-abs(rd.y)), 8.0);\n"
" col += clamp(vec3(mix(0.0025, 0.125, tanh_approx(.005/sf))/abs(rd.y))*skylineCol, 0.0, 10.0);\n"
" sf *= sf;\n"
" col += sunCol*0.00005/sf;\n"
"\n"
" float tp1 = rayPlane(ro, rd, vec4(vec3(0.0, -1.0, 0.0), 6.0));\n"
"\n"
" if (tp1 > 0.0) {\n"
" vec3 pos = ro + tp1*rd;\n"
" vec2 pp = pos.xz;\n"
" float db = box(pp, vec2(5.0, 9.0))-3.0;\n"
" col += vec3(4.0)*skyCol*rd.y*rd.y*smoothstep(0.25, 0.0, db);\n"
" col += vec3(0.8)*skyCol*exp(-0.5*max(db, 0.0));\n"
" }\n"
"\n"
" return clamp(col, 0.0, 10.0);\n"
"}\n"
"\n"
"float df(vec3 p) {\n"
" p.xz *= g_rot0;\n"
" vec3 p0 = p;\n"
" vec3 p1 = p;\n"
" vec3 p2 = p;\n"
"\n"
" // --- AUDIO BREATHING (SHRINK ON LOUD TO AVOID COLLISIONS) ---\n"
" float a = max(iAudio, 0.0);\n"
" a = 1.0 - exp(-1.5 * a);\n"
" float breathe = 1.0 - 0.10 * a;\n"
" breathe = clamp(breathe, 0.75, 1.0);\n"
"\n"
" const float ss = 1.;\n"
" p0.y -= -0.2;\n"
" p0.z = abs(p0.z);\n"
" p0.x = -abs(p0.x);\n"
" p0.x -= -0.4*ss;\n"
" ref(p0, normalize(vec3(1.0, -0.05, -1.0)));\n"
" p0.x -= 1.3*ss;\n"
" ref(p0, normalize(vec3(1.0, 0.30, 1.0)));\n"
" p0.x -= 1.4*ss;\n"
" p0.z -= 0.3*ss;\n"
" ref(p0, normalize(vec3(1.0, -1.0, 0.5)));\n"
" p0.x -= 1.25*ss;\n"
" p0.z -= -0.5*ss;\n"
" p0.y -= -0.3*ss;\n"
" float d0 = box(p0, vec3(0.5) * breathe)-0.0125;\n"
"\n"
" p1.x -= 0.4;\n"
" p1.y -= 0.75;\n"
" float d1 = box(p1, vec3(1.25) * breathe)-0.0125;\n"
"\n"
" p2.y += 2.0;\n"
" float d2 = p2.y;\n"
"\n"
" float d = d1;\n"
" d = min(d, d0);\n"
" d = min(d, d2);\n"
" return d;\n"
"}\n"
"\n"
"vec3 normal(vec3 pos) {\n"
" vec2 eps = vec2(NORM_OFF,0.0);\n"
" vec3 nor;\n"
" nor.x = df(pos+eps.xyy) - df(pos-eps.xyy);\n"
" nor.y = df(pos+eps.yxy) - df(pos-eps.yxy);\n"
" nor.z = df(pos+eps.yyx) - df(pos-eps.yyx);\n"
" return normalize(nor);\n"
"}\n"
"\n"
"float rayMarch(vec3 ro, vec3 rd, float initt) {\n"
" float t = initt;\n"
" for (int i = 0; i < MAX_RAY_MARCHES; ++i) {\n"
" if (t > MAX_RAY_LENGTH) { t = MAX_RAY_LENGTH; break; }\n"
" float d = df(ro + rd*t);\n"
" if (d < TOLERANCE) break;\n"
" t += d;\n"
" }\n"
" return t;\n"
"}\n"
"\n"
"float shadow(vec3 lp, vec3 ld, float mint, float maxt) {\n"
" const float ds = 1.0-0.4;\n"
" float t = mint;\n"
" float nd = 1E6;\n"
" const float soff = 0.05;\n"
" const float smul = 1.5;\n"
" for (int i=0; i < MAX_SHD_MARCHES; ++i) {\n"
" vec3 p = lp + ld*t;\n"
" float d = df(p);\n"
" if (d < TOLERANCE || t >= maxt) {\n"
" float sd = 1.0-exp(-smul*max(t/maxt-soff, 0.0));\n"
" return t >= maxt ? mix(sd, 1.0, smoothstep(0.0, 0.025, nd)) : sd;\n"
" }\n"
" nd = min(nd, d);\n"
" t += ds*d;\n"
" }\n"
" float sd = 1.0-exp(-smul*max(t/maxt-soff, 0.0));\n"
" return sd;\n"
"}\n"
"\n"
"vec3 boxCol(vec3 col, vec3 nsp, vec3 rd, vec3 nnor, vec3 nrcol, float nshd1, float nshd2) {\n"
" // --- LUT-driven cube palette (from host RGB) ---\n"
" vec3 lut = clamp(iLutColor, 0.0, 1.0);\n"
" vec3 diff1 = lut;\n"
" vec3 diff2 = mix(lut, vec3(1.0), 0.25);\n"
" vec3 rim = mix(lut, vec3(1.0), 0.50);\n"
"\n"
" float nfre = 1.0+dot(rd, nnor);\n"
" nfre *= nfre;\n"
"\n"
" vec3 nld1 = normalize(lightPos1-nsp);\n"
" vec3 nld2 = normalize(lightPos2-nsp);\n"
"\n"
" float ndif1 = max(dot(nld1, nnor), 0.0);\n"
" ndif1 *= ndif1;\n"
"\n"
" float ndif2 = max(dot(nld2, nnor), 0.0);\n"
" ndif2 *= ndif2;\n"
"\n"
" vec3 scol = vec3(0.0);\n"
" float rf = smoothstep(1.0, 0.9, nfre);\n"
" scol += diff1*ndif1*nshd1;\n"
" scol += diff2*ndif2*nshd2;\n"
" scol += 0.1*(skyCol+skylineCol);\n"
" scol += nrcol*0.75*mix(vec3(0.25), rim, nfre);\n"
"\n"
" col = mix(col, scol, rf*smoothstep(90.0, 20.0, dot(nsp, nsp)));\n"
" return col;\n"
"}\n"
"\n"
"vec3 render1(vec3 ro, vec3 rd) {\n"
" vec3 skyCol0 = render0(ro, rd);\n"
" vec3 col = skyCol0;\n"
"\n"
" float nt = rayMarch(ro, rd, 0.0);\n"
" if (nt < MAX_RAY_LENGTH) {\n"
" vec3 nsp = ro + rd*nt;\n"
" vec3 nnor = normal(nsp);\n"
"\n"
" vec3 nref = reflect(rd, nnor);\n"
" float nrt = rayMarch(nsp, nref, 0.2);\n"
" vec3 nrcol = render0(nsp, nref);\n"
"\n"
" if (nrt < MAX_RAY_LENGTH) {\n"
" vec3 nrsp = nsp + nref*nrt;\n"
" vec3 nrnor = normal(nrsp);\n"
" vec3 nrref = reflect(nref, nrnor);\n"
" nrcol = boxCol(nrcol, nrsp, nref, nrnor, render0(nrsp, nrref), 1.0, 1.0);\n"
" }\n"
"\n"
" float nshd1 = mix(0.0, 1.0, shadow(nsp, normalize(lightPos1 - nsp), 0.1, distance(lightPos1, nsp)));\n"
" float nshd2 = mix(0.0, 1.0, shadow(nsp, normalize(lightPos2 - nsp), 0.1, distance(lightPos2, nsp)));\n"
"\n"
" col = boxCol(col, nsp, rd, nnor, nrcol, nshd1, nshd2);\n"
" }\n"
"\n"
" return col;\n"
"}\n"
"\n"
"vec3 effect(vec2 p) {\n"
" g_rot0 = ROT(-0.2*TIME);\n"
"\n"
" const float fov = tan(TAU/6.0);\n"
" const vec3 ro = vec3(0.0, 2.5, 5.0);\n"
" const vec3 la = vec3(0.0, 0.0, 0.0);\n"
" const vec3 up = vec3(0.1, 1.0, 0.0);\n"
"\n"
" vec3 ww = normalize(la - ro);\n"
" vec3 uu = normalize(cross(up, ww));\n"
" vec3 vv = cross(ww,uu);\n"
" vec3 rd = normalize(-p.x*uu + p.y*vv + fov*ww);\n"
"\n"
" return render1(ro, rd);\n"
"}\n"
"\n"
"void main() {\n"
" vec2 fragCoord = gl_FragCoord.xy;\n"
" vec2 q = fragCoord/RESOLUTION.xy;\n"
" vec2 p = -1. + 2. * q;\n"
" p.x *= RESOLUTION.x/RESOLUTION.y;\n"
"\n"
" vec3 col = effect(p);\n"
" col = aces_approx(col);\n"
" col = sRGB(col);\n"
" FragColor = vec4(col, 1.0);\n"
"}\n";
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, CharBuffer);
return fs;
}
-
BBP_Glow
Converted from https://www.shadertoy.com/view/W3tSR4
Original created by Xor on 2025-08-23
Audio + color LUT + BBP_plugin integration by Patrice Terrier
(http://www.objreader.com/download/images/Glow.png)
Paste this in the BBProc section case BBP_CREATE:
// Host asks: "who are you and what do you render to?"
strcpy(BBP.title,"Volumetric Glow");
strcpy(BBP.author, "Xor (Shadertoy)");
BBP.version = MAKEWORD(1,0);
// Critical: This plugin renders using OpenGL into the host-provided GL context.
BBP.renderto = BBP_OPENGL;
// Default background ARGB used by host (informational/fallback).
BBP.backargb = bbpARGB(255,0,0,0);
break;
Paste this in the GLSL.h sectionstatic GLuint CompileShaderFromString() {
const char* CharBuffer =
"#version 330 core\n"
"\n"
"uniform vec3 iResolution;\n"
"uniform float iTime;\n"
"uniform float iAudio;\n" // smoothed audio level (0..~1)
"\n"
"out vec4 FragColor;\n"
"\n"
"#define STEPS 50.0\n"
"#define FOV 1.0\n"
"#define BASE_DENSITY 1.5\n"
"\n"
"float Audio01() {\n"
" float a = max(iAudio, 0.0);\n"
" a = 1.0 - exp(-1.8 * a);\n"
" return clamp(a, 0.0, 1.0);\n"
"}\n"
"\n"
"float volumeField(vec3 p, float density, float wfreq) {\n"
" float l = length(p);\n"
" vec3 v = cos(abs(p) * wfreq / max(4.0, l) + iTime);\n"
" return length(vec4(max(v, v.yzx) - 0.9, l - 4.0)) / density;\n"
"}\n"
"\n"
"vec3 rotate90(vec3 p, vec3 a) {\n"
" return a * dot(p, a) + cross(p, a);\n"
"}\n"
"\n"
"// MOIRE FIX: tiny stable per-pixel hash (no noise texture needed)\n"
"float hash12(vec2 p) {\n"
" vec3 p3 = fract(vec3(p.xyx) * 0.1031);\n"
" p3 += dot(p3, p3.yzx + 33.33);\n"
" return fract((p3.x + p3.y) * p3.z);\n"
"}\n"
"\n"
"void main() {\n"
" vec2 fragCoord = gl_FragCoord.xy;\n"
" vec2 center = 2.0 * fragCoord - iResolution.xy;\n"
"\n"
" float a = Audio01();\n"
"\n"
" float breathe = 1.0 - 0.08 * a;\n"
"\n"
" float density = BASE_DENSITY * (1.0 + 1.2 * a);\n"
" float wfreq = 8.0 * mix(1.0, 0.65, a);\n"
" float brightness = 0.002 * (1.0 + 0.6 * a);\n"
"\n"
" vec3 axis = normalize(cos(vec3(0.5 * iTime) + vec3(0.0, 2.0, 4.0)));\n"
"\n"
" vec3 dir = rotate90(normalize(vec3(center, FOV * iResolution.y)), axis);\n"
"\n"
" float camZ = -8.0 * mix(1.0, 0.92, a);\n"
" vec3 cam = rotate90(vec3(0.0, 0.0, camZ), axis);\n"
"\n"
" // Start point\n"
" vec3 pos = cam;\n"
"\n"
" // MOIRE FIX: jitter the start along the ray by a tiny amount (breaks band alignment)\n"
" float j = hash12(floor(fragCoord));\n"
" pos += dir * (j - 0.5) * 0.10;\n"
"\n"
" vec3 col = vec3(0.0);\n"
"\n"
" for (float i = 0.0; i < STEPS; i += 1.0) {\n"
" float vol = volumeField(pos * breathe, density, wfreq);\n"
" pos += dir * vol;\n"
" float inv = 1.0 / max(vol, 0.02);\n"
" col += (cos(pos.z / (1.0 + vol) + iTime + vec3(6.0, 1.0, 2.0)) + 1.2) * inv;\n"
" }\n"
"\n"
" col = tanh(brightness * col);\n"
" FragColor = vec4(col, 1.0);\n"
"}\n";
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, CharBuffer);
return fs;
}
-
BBP_DiscoBall
Converted from https://www.shadertoy.com/view/ssycDR
Original created by Shane on 2022-06-08
Audio + accumulator + BBP_plugin integration by Patrice Terrier
(http://www.objreader.com/download/images/DiscoBall.png)
This plugin is based on a GLSL shader by Shane and required a true temporal accumulator to reproduce the original visual quality.
The effect relies on stochastic sampling and progressive convergence over time, which means a classic single-pass shader is not sufficient.
A floating-point ping-pong accumulator is used to achieve stable lighting, clean reflections, and low noise in dark areas.
This version goes further by adding audio-driven behavior:- Monotonic, audio-sensitive sphere rotation.
- Automatic accumulation freeze during strong motion to avoid blur.
- High sample count for stable dark regions.
- Fully functional in landscape and portrait modes (BassBox64)
The result is a physically coherent, music-reactive disco sphere without geometry tricks or artificial brightness.
The full VS2022 source code is attached to this post.
You must also copy the OrganicUD.jpg texture, inside the bbplugin\texture folder.