OpenGL visual plugins 64-bit

Started by Patrice,

Patrice

Ian,

Here is the link to download the OpenGL 64-bit visual plugins.
http://www.objreader.com/download/demo/Plugin64.7z

I made a VS2022 C/C++ project named Plugin64.sln to show in context how to use the plugins.

See how to use the project here.

Patrice

I made a brand new collection of BassBox64 visual plugins based on GLSL.
Many of them have been converted from "shadertoy" to bring them to life with audio.

Video

BassBox64 plugins aka "bbp_plugin" are very small C/C++ 64-bit DLL.
Documentation pasted from my private forum (www.objreader.com).

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.

Patrice

Due to size restriction, I was not able to attach a complete Visual Studio project to my previous post.
If you want to learn more contact me at: objreader at gmail dot com.

Example of BassBox GLSL shader:

static 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;
}

Patrice

Shadertoy shaders in a standalone compound format

Hello everyone,

SHCplayer plays Shadertoy shaders natively on Windows, with audio reactivity.

What it does
  • Plays Shadertoy shaders, including multipass buffers, cubemaps, video and music inputs, keyboard and mouse, and mainSound audio shaders.
  • Copy a Shadertoy URL, click "Paste from clipboard", and the shader is fetched and cached locally as a single .shc file. From then on it plays offline.
  • "Select SHC shader" opens a thumbnail picker of your cached collection.
  • Drag and drop any audio file: the shader reacts to it through the usual FFT/waveform channel.

The .shc format
A .shc file bundles everything a shader needs in one file: the code, textures, cubemaps, video, music, and a PNG thumbnail that Explorer can display. No dependency on the website once cached, and your collection stays yours.

How it is built
All the work lives in SHC.dll (C, Win32, OpenGL, BASS). The host only deals with the UI, through a handful of plain C exports:

SH_ProcessSHC(hGL, zPath)       load a cached .shc
SH_ProcessURL(hGL, zUrl)        fetch and cache a Shadertoy URL
SH_IsShaderUrl(zText)           test clipboard text
SH_Render(hGL)                  render one frame
SH_ReadInfo(zFile, zName, cch)  read a .shc title
SH_PlayAudio(hGL, zFile)        play a dropped audio file
SH_Pause(bPause)                Play audio checkbox
SH_SetVolume(percent)           volume slider
SH_Close(hGL) / SH_AudioFree()  cleanup

The FreeBASIC host is a direct port of the C++ one: same structs, same Win32 calls, WinLIFT for the skin and GDImage for the picker and marquee. Porting SHCplayer to another language comes down to declaring these exports.

Requirements
Windows 10/11 64-bit, with SHC.dll and bass.dll next to the executable.

Download the FB demo project
link to download the ZIP file



Credits: Bass.dll by Ian Luck (un4seen.com), and the Shadertoy authors whose work makes this fun.

C/C++ version available on request.

Patrice Terrier
www.objreader.com