Created
April 5, 2025 06:26
-
-
Save olilarkin/8694bf8d06196a97bdd4a36e1e1565fd to your computer and use it in GitHub Desktop.
Shader's Gambit iPlug2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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