Skip to content

Instantly share code, notes, and snippets.

@olilarkin
Created April 5, 2025 06:26
Show Gist options
  • Save olilarkin/8694bf8d06196a97bdd4a36e1e1565fd to your computer and use it in GitHub Desktop.
Save olilarkin/8694bf8d06196a97bdd4a36e1e1565fd to your computer and use it in GitHub Desktop.
Shader's Gambit iPlug2
#include "IPlugEffect.h"
#include "IPlug_include_in_plug_src.h"
#include "IControls.h"
#include "IShaderControl.h"
IPlugEffect::IPlugEffect(const InstanceInfo& info)
: iplug::Plugin(info, MakeConfig(kNumParams, kNumPresets))
{
GetParam(kGain)->InitDouble("Gain", 0., 0., 100.0, 0.01, "%");
#if IPLUG_EDITOR // http://bit.ly/2S64BDd
mMakeGraphicsFunc = [&]() {
return MakeGraphics(*this, PLUG_WIDTH, PLUG_HEIGHT, PLUG_FPS, GetScaleForScreen(PLUG_WIDTH, PLUG_HEIGHT));
};
mLayoutFunc = [&](IGraphics* pGraphics) {
pGraphics->AttachCornerResizer(EUIResizerMode::Scale, false);
pGraphics->AttachPanelBackground(COLOR_GRAY);
pGraphics->LoadFont("Roboto-Regular", ROBOTO_FN);
const IRECT b = pGraphics->GetBounds();
// Skia shader from https://www.youtube.com/watch?v=wUsFNlas620
pGraphics->AttachControl(new IShaderControl(b.GetPadded(-20), R"(
uniform float iTime;
uniform float2 iResolution;
uniform float2 uMouse;
uniform float2 uMouseBut;
const vec3 backgroundColor = vec3(0.3, 0.6, 1.0);
const vec3 cameraLookAt = vec3(0., 0., 0.);
const float cameraRoll = 0;
const float PI = 3.141592653589793;
// Material properties for PBR lighting
struct Material {
vec3 color;
float roughness;
float metalness;
float reflectivity;
};
struct Hit {
float dist;
int materialId;
Material material;
};
struct Light {
vec3 position;
vec3 color;
vec3 intensity;
};
Hit minWithMaterial(Hit a, Hit b) {
if (a.dist < b.dist) {
return a;
} else {
return b;
}
}
Light light;
void setupLights() {
// Main directional light (sun)
light.position = normalize(vec3(1.0, 2.0, 1.0));
light.color = vec3(1.0, 1.0, 1.0);
light.intensity = vec3(1.0);
}
float sdRoundCone(vec3 a, vec3 b, float r1, float r2, vec3 p)
{
vec3 ba = b - a;
float l2 = dot(ba,ba);
float rr = r1 - r2;
float a2 = l2 - rr*rr;
float il2 = 1.0/l2;
vec3 pa = p - a;
float y = dot(pa,ba);
float z = y - l2;
vec3 x = pa*l2 - ba*y;
float x2 = dot(x, x);
float y2 = y*y*l2;
float z2 = z*z*l2;
float k = sign(rr)*rr*rr*x2;
if( sign(z)*a2*z2 > k ) return sqrt(x2 + z2) *il2 - r2;
if( sign(y)*a2*y2 < k ) return sqrt(x2 + y2) *il2 - r1;
return (sqrt(x2*a2*il2)+y*rr)*il2 - r1;
}
float sdCone(vec2 c, float h, vec3 p)
{
float q = length(p.xz);
return max(dot(c.xy,vec2(q,p.y)),-h-p.y);
}
float sdfCone( vec3 p, vec2 c, float h )
{
// c is the sin/cos of the angle, h is height
// Alternatively pass q instead of (c,h),
// which is the point at the base in 2D
vec2 q = h*vec2(c.x/c.y,-1.0);
vec2 w = vec2( length(p.xz), p.y );
vec2 a = w - q*clamp( dot(w,q)/dot(q,q), 0.0, 1.0 );
vec2 b = w - q*vec2( clamp( w.x/q.x, 0.0, 1.0 ), 1.0 );
float k = sign( q.y );
float d = min(dot( a, a ),dot(b, b));
float s = max( k*(w.x*q.y-w.y*q.x),k*(w.y-q.y) );
return sqrt(d)*sign(s);
}
float sdEllipsoid(vec3 r, vec3 p)
{
float k0 = length(p/r);
float k1 = length(p/(r*r));
return k0*(k0-1.0)/k1;
}
float sdCappedCylinder(float h, float r, vec3 p)
{
vec2 d = abs(vec2(length(p.xz),p.y)) - vec2(r,h);
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
float sdSphere(vec3 o, float r, vec3 p)
{
return length(p - o) - r;
}
float sdRoundCone( vec3 p, vec3 b, float r )
{
vec3 q = abs(p) - b + r;
return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
}
float dot2( in vec3 v ) { return dot(v,v); }
float sdTorus(vec2 t, vec3 p)
{
vec2 q = vec2(length(p.xz)-t.x,p.y);
return length(q)-t.y;
}
float sdCappedTorus( vec3 p, vec2 sc, float ra, float rb)
{
p.x = abs(p.x);
float k = (sc.y*p.x>sc.x*p.y) ? dot(p.xy,sc) : length(p.xy);
return sqrt( dot(p,p) + ra*ra - 2.0*ra*k ) - rb;
}
float sdHPlane(float h, vec3 p)
{
return p.y - h;
}
float smin(float a, float b, float k)
{
float h = max( k-abs(a-b), 0.0 )/k;
return min( a, b ) - h*h*k*(1.0/4.0);
}
float smax(float a, float b, float k)
{
k *= 1.4;
float h = max(k-abs(a-b),0.0);
return max(a, b) + h*h*h/(6.0*k*k);
}
// Queen SDF is based on https://www.shadertoy.com/view/3sVfW3
float sdQueen( vec3 p ) {
p += vec3(0., -1.4, 0.);
// cylinder body
vec3 p0 = p - vec3(0., 0.5, 0.);
float r = (1./6.)*pow(p0.y, 2.) - (4./30.)*p0.y + 0.3067;
float d0 = sdCappedCylinder(1.5, r, p0) - 0.02;
// hole in the top of the cylinder
vec3 p1 = p - vec3(0., 1.9, 0.);
float d1 = sdCappedCylinder(0.2, r - 0.1, p1);
d0 = smax(d0, -d1, 0.03);
// crown
{
// Transform point for crown spikes with 8-fold radial symmetry
vec3 p2 = p - vec3(0., 2.05, 0.); // Move reference point to crown height
// Convert x,z coordinates to polar angle (theta)
float theta = atan(p2.z, p2.x);
// Add PI/8 offset, wrap to PI/4 segments (8 segments total), then remove offset
// This creates 8 identical segments around the circle
float repeatedAngle = mod(theta + PI/8., PI/4.) - PI/8.;
// Get radius in xz-plane
float radius = length(vec2(p2.x, p2.z));
// Convert back to cartesian coordinates, but using the repeated angle
// y remains unchanged, but x,z are replaced with the new angle-adjusted coordinates
p2 = vec3(p2.y, radius * cos(repeatedAngle), radius * sin(repeatedAngle));
// Create cylinder for each spike
float d2 = sdCappedCylinder(0.6, 0.12, p2);
d0 = smax(d0, -d2, 0.07);
}
// head cone
vec3 p3 = p - vec3(0., 2.15, 0.);
float d3 = sdfCone(p3, vec2(0.001), 0.31);
d0 = smin(d0, d3, 0.045);
// head sphere
float d4 = sdSphere(vec3(0., 2.18, 0.), 0.09, p);
d0 = smin(d0, d4, 0.03);
// ring 1
vec3 p5 = p - vec3(0., 1.4, 0.);
vec3 radii = vec3(0.5, 0.07, 0.5);
float d5 = sdEllipsoid(radii, p5);
d0 = smin(d0, d5, 0.03);
// ring 2
vec3 p6 = p - vec3(0., 1.51, 0.);
float d6 = sdEllipsoid(vec3(0.42, 0.07, 0.42), p6);
d0 = smin(d0, d6, 0.03);
// base torus
vec3 p7 = p - vec3(0., -1., 0.);
float d7 = sdTorus(vec2(0.43, 0.5), p7);
d0 = smin(d0, d7, 0.03);
// inner base ring 1
float d9 = sdTorus(vec2(0.586, 0.01), p - vec3(0., -0.425, 0.));
d0 = smax(d0, -d9, 0.05);
// inner base ring 2
float d10 = sdTorus(vec2(0.553, 0.01), p - vec3(0., -0.345, 0.));
d0 = smax(d0, -d10, 0.05);
return d0;
}
vec3 checkboard(vec3 p, vec3 color) {
float cornerRadius = 0.05; // Adjust this value to control the roundness
vec2 q = abs(p.xz);
float ratio = 1.0 - 0.1;
float border = ratio * 8.2;
float cornerDist = length(max(q - border + cornerRadius, 0.0));
if (cornerDist > cornerRadius) {
return color;
}
vec2 ss = sin(4.0 * 3.141592653589793 * p.xz * 1/border);
vec3 stripes = sign(ss.x) * sign(ss.y) > 0.0 ? vec3(0.9) : vec3(0.05);
return stripes;
}
Material createMaterial(vec3 color, float roughness, float metalness, float reflectivity) {
Material mat;
mat.color = color;
mat.roughness = roughness;
mat.metalness = metalness;
mat.reflectivity = reflectivity;
return mat;
}
Hit sceneSDF(float3 p) {
Hit board;
board.dist = sdRoundCone(p, vec3(8.2, 0.35, 8.2), 0.2);
board.material = createMaterial(checkboard(p, vec3(0.756 ,0.603, 0.419)), 0.8, 0.5, 0.5);
Hit queen;
queen.dist = sdQueen(p);
queen.material = createMaterial(vec3(0.9, 0.9, 0.9), 1, 0.5, 0.5);
Hit result = minWithMaterial(queen, board);
return result;
}
float3 getNormal(float3 p) {
const float h = 0.0001;
const float2 k = float2(1.0, -1.0);
return normalize(k.xyy*sceneSDF(p + k.xyy*h).dist +
k.yyx*sceneSDF(p + k.yyx*h).dist +
k.yxy*sceneSDF(p + k.yxy*h).dist +
k.xxx*sceneSDF(p + k.xxx*h).dist);
}
Hit raymarch(float3 ro, float3 rd) {
float t = 0.0;
Hit hit;
for(int i = 0; i < 512; i++) {
float3 p = ro + rd * t;
hit = sceneSDF(p);
if(hit.dist < 0.0001 || t > 100.0) {
break;
}
t += hit.dist;
}
hit.dist = t;
return hit;
}
// Calculate shadows using ray marching
float calcShadow(vec3 ro, vec3 rd, float mint, float maxt, float k) {
float res = 1.0;
float t = mint;
for(int i = 0; i < 32; i++) {
float h = sceneSDF(ro + rd * t).dist;
if(h < 0.001) return 0.0;
res = min(res, k * h / t);
t += clamp(h, 0.01, 0.2);
if(t > maxt) break;
}
return clamp(res, 0.0, 1.0);
}
float calcAO(vec3 pos, vec3 nor) {
float occ = 0.0;
float sca = 1.0;
for(int i = 0; i < 5; i++) {
float h = 0.01 + 0.12 * float(i) / 4.0;
float d = sceneSDF(pos + h * nor).dist;
occ += (h - d) * sca;
sca *= 0.95;
}
return clamp(1.0 - 3.0 * occ, 0.0, 1.0);
}
// Lighting function to calculate illumination for a point
vec3 calculateLighting(vec3 point, vec3 normal, vec3 baseColor, int materialType) {
// Light settings
vec3 ambientColor = vec3(0.1, 0.1, 0.1);
vec3 diffuseColor = vec3(0.7, 0.7, 0.7);
vec3 lightPosition = vec3(1., 1., 1.); // Position of the point light
float lightIntensity = 1;
// Ambient component
vec3 ambient = ambientColor * baseColor;
// Diffuse lighting (directional light from above)
vec3 lightDir = normalize(vec3(0.0, 5.0, 0.0));
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * diffuseColor * baseColor;
// Point light calculation
vec3 pointLightDir = normalize(lightPosition - point);
float pointDiff = max(dot(normal, pointLightDir), 0.0);
// Attenuation (light gets weaker with distance)
float distance = length(lightPosition - point);
float attenuation = 1.0 / (1.0 + 0.1 * distance + 0.01 * distance * distance);
vec3 pointLight = pointDiff * lightIntensity * attenuation * baseColor;
// Combine all lighting components
return ambient + diffuse + pointLight;
}
vec3 stylizedTonemap(vec3 color) {
// Moderate exposure boost for balanced brightness
color *= 5.2; // Slightly reduced exposure for better balance
// Use a more extreme tone mapping curve
// Modified Reinhard with slightly reduced white point for controlled highlights
float whitePoint = 6.5;
color = (color * (1.0 + color/(whitePoint*whitePoint))) / (1.0 + color);
// Apply color correction before saturation boost
// This helps prevent oversaturation of already bright areas
color = pow(color, vec3(0.85)); // Gamma adjustment for brighter midtones
// Enhance saturation significantly
float luminance = dot(color, vec3(0.299, 0.587, 0.114));
color = mix(vec3(luminance), color, 1.9); // More controlled saturation boost
// Apply a more aggressive contrast S-curve for punchier look
color = color * color * (3.0 - 1.7 * color); // Modified to prevent too much darkening
// Remove color bias for neutral black and white rendering
// If you still want some vibrancy in colored objects but neutral blacks/whites,
// you could use a selective approach instead
return clamp(color, 0.0, 1.0);
}
// PBR lighting calculation
vec3 pbr(vec3 p, vec3 n, vec3 v, Material mat) {
setupLights();
// Material properties
vec3 albedo = mat.color;
float roughness = mat.roughness;
float metalness = mat.metalness;
// Constants for PBR
const float PI = 3.14159265359;
// Base reflectivity for dielectrics (non-metals)
vec3 F0 = mix(vec3(0.04), albedo, metalness);
// Calculate ambient occlusion
float ao = calcAO(p, n);
// Initialize the accumulator for direct lighting
vec3 Lo = vec3(0.0);
// Calculate lighting contribution from each light
vec3 L;
float attenuation = 1.0;
// Directional light
// Directional light
L = normalize(light.position);
// Simple shadow calculation for directional light
float shadow = calcShadow(p, L, 0.1, 20.0, 16.0);
attenuation *= shadow;
// Half vector between view and light directions
vec3 H = normalize(v + L);
// Calculate dot products for lighting equation
float NdotL = max(dot(n, L), 0.0);
float NdotV = max(dot(n, v), 0.0);
float NdotH = max(dot(n, H), 0.0);
float HdotV = max(dot(H, v), 0.0);
if(NdotL > 0.0) {
// Diffuse term (Lambert)
vec3 diffuse = albedo / PI;
// Specular term (simplified GGX)
float alpha = roughness * roughness;
float alpha2 = alpha * alpha;
// Normal Distribution Function (D)
float denom = NdotH * NdotH * (alpha2 - 1.0) + 1.0;
float D = alpha2 / (PI * denom * denom);
// Fresnel term (F) - Schlick approximation
vec3 F = F0 + (1.0 - F0) * pow(1.0 - HdotV, 5.0);
// Geometry term (G) - simplified
float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
float G = NdotL * NdotV / ((NdotL * (1.0 - k) + k) * (NdotV * (1.0 - k) + k));
// Combine terms for specular
vec3 specular = D * F * G / (4.0 * NdotV * NdotL + 0.001);
//specular *= 0.6; // Reduce specular intensity to 60%
// Energy conservation: metallic surfaces don't have diffuse
vec3 kD = (1.0 - F) * (1.0 - metalness);
// Combine diffuse and specular
Lo += (kD * diffuse + specular) * light.color * light.intensity * NdotL * attenuation;
}
// Ambient lighting (simplified IBL)
vec3 ambient = vec3(0.03) * albedo * ao;
// Final color
vec3 color = ambient + Lo;
color = stylizedTonemap(color);
return color;
}
float3 render(float3 ro, float3 rd) {
float3 col = backgroundColor;
Hit hit = raymarch(ro, rd);
// If we hit something
if(hit.dist < 100.0) {
vec3 p = ro + rd * hit.dist;
vec3 n = getNormal(p);
// View direction (from point to camera)
vec3 v = normalize(ro - p);
// Apply PBR lighting
col = pbr(p, n, v, hit.material);
}
return col;
}
vec4 main(float2 fragCoord) {
const int samples = 2; // Use 2 for AA
float sampleStrength = 1.0/float(samples*samples);
float3 finalColor = float3(0.0);
// Perform supersampling
for(int m = 0; m < samples; m++) {
for(int n = 0; n < samples; n++) {
// Calculate offset for this sample (only if using AA)
vec2 offset = (samples > 1) ?
(vec2(float(m), float(n)) / float(samples) - 0.5/float(samples)) :
vec2(0.0);
// Get UV coordinates with the offset
float2 uv = ((fragCoord + offset) - 0.5 * iResolution.xy) / iResolution.y;
uv.y = -uv.y; // Flip Y coordinate to match Skia's coordinate system
// Camera setup
float dist = 12.0;
float rotateX = PI / 4.0; // Fixed X rotation
float rotateY = PI / 4.0; // Reduced rotation speed and using cameraPosition
// Calculate camera position
rotateY+=iTime*0.5;
float3 ro = vec3(
dist * cos(rotateX) * cos(rotateY),
dist * sin(rotateX),
dist * cos(rotateX) * sin(rotateY)
);
float3 ta = cameraLookAt;
float cr = cameraRoll;
// Camera matrix
float3 ww = normalize(ta - ro);
float3 cp = float3(sin(cameraRoll), cos(cameraRoll), 0.0);
float3 uu = normalize(cross(ww, cp));
float3 vv = normalize(cross(uu, ww));
float3 rd = normalize(uv.x * uu + uv.y * vv + 1.5 * ww);
// Render this sample
float3 col = render(ro, rd);
// Accumulate color
finalColor += col * sampleStrength;
}
}
// Final gamma correction
finalColor = pow(finalColor, vec3(0.4545));
return vec4(finalColor, 1.0);
}
)"));
};
#endif
}
#if IPLUG_DSP
void IPlugEffect::ProcessBlock(sample** inputs, sample** outputs, int nFrames)
{
const double gain = GetParam(kGain)->Value() / 100.;
const int nChans = NOutChansConnected();
for (int s = 0; s < nFrames; s++) {
for (int c = 0; c < nChans; c++) {
outputs[c][s] = inputs[c][s] * gain;
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment