Popcorn ceiling
GLSL shader by scry · created 2026-06-27 · 10s loop · 2 passes
This page opens the shader in the ShaderKit browser GLSL editor: edit it live, fork it, or render it to video, GIF or images up to 8K. Also available as a full-screen view and an embeddable player.
Shader source (GLSL)
Common
#define pi acos(-1.)
#define deg pi/180.
#define time iTime*2.*pi/10.
#define R iResolution.xy
#define ar R.x/R.y
vec3 cs = vec3(1.,2.,3.);
mat2 r2d(float a) {
return mat2(cos(a),sin(a),-sin(a),cos(a));
}
Buffer A (iChannel0)
float hash(vec2 p) {
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}
float hash(float n) {
return fract(sin(n) * 43758.5453);
}
// 3D value noise for smooth temporal evolution
float valueNoise3D(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float n = i.x + i.y * 57.0 + 113.0 * i.z;
return mix(
mix(mix(hash(n), hash(n + 1.0), f.x),
mix(hash(n + 57.0), hash(n + 58.0), f.x), f.y),
mix(mix(hash(n + 113.0), hash(n + 114.0), f.x),
mix(hash(n + 170.0), hash(n + 171.0), f.x), f.y),
f.z
);
}
float valueNoise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), f.x), mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), f.x), f.y);
}
// Cellular/Worley noise for popcorn blob shapes
float cellNoise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
float d1 = 1.0;
float d2 = 1.0;
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
vec2 neighbor = vec2(float(x), float(y));
vec2 cell = i + neighbor;
vec2 pt = vec2(hash(cell), hash(cell * 1.73 + 0.5));
// Randomize point size slightly for irregular blobs
float d = length(neighbor + pt - f);
if (d < d1) {
d2 = d1;
d1 = d;
} else if (d < d2) {
d2 = d;
}
}
}
// Return both distances packed: d1 for cell distance, d2-d1 for edge
return d1;
}
// Second version returning edge factor (d2 - d1)
float cellEdge(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
float d1 = 1.0;
float d2 = 1.0;
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
vec2 neighbor = vec2(float(x), float(y));
vec2 cell = i + neighbor;
vec2 pt = vec2(hash(cell), hash(cell * 1.73 + 0.5));
float d = length(neighbor + pt - f);
if (d < d1) {
d2 = d1;
d1 = d;
} else if (d < d2) {
d2 = d;
}
}
}
return d2 - d1;
}
float popcornHeight(vec2 p) {
// Use 3D noise with time as third dimension for smooth evolution
float t1 = iTime * 00.102;
float t2 = iTime * 00.05;
float t3 = iTime * 0.1018;
// Large irregular blobs - the main popcorn splatter bumps
vec2 offset1 = vec2(valueNoise3D(vec3(p * 0.5, t1)), valueNoise3D(vec3(p * 0.5 + 100.0, t1))) * 2.0;
float c1 = cellNoise(p * 7.0 + offset1);
float e1 = cellEdge(p * 1.0 + offset1);
// Rounded blob profile: raised centers with soft falloff
float blobs = 1.0 - smoothstep(0.0, 0.4, c1);
blobs = pow(blobs, 1.2);
// Add ridge detail at cell boundaries (dried paint edges)
float ridges = smoothstep(0.0, 0.08, e1);
blobs *= mix(0.7, 1.0, ridges);
// Secondary smaller blobs - overlapping splatter
vec2 offset2 = vec2(valueNoise3D(vec3(p * 0.5, t2)), valueNoise3D(vec3(p * 0.5 + 200.0, t2))) * 2.0;
float c2 = cellNoise(p * 14.0 + 3.7 + offset2);
float blobs2 = 1.0 - smoothstep(0.0, 0.35, c2);
blobs2 = pow(blobs2, 1.8);
// Tiny aggregate bumps - the gritty sand-like particles
vec2 offset3 = vec2(valueNoise3D(vec3(p * 0.5, t3)), valueNoise3D(vec3(p * 0.5 + 300.0, t3))) * 2.0;
float c3 = cellNoise(p * 39.0 + 7.1 + offset3);
float grit = 1.0 - smoothstep(0.0, 0.45, c3);
grit = pow(grit, 1.5);
// Fine surface roughness - stipple texture
float t4 = iTime * 0.01;
float stipple = valueNoise3D(vec3(p * 80.0, t4)) * 0.5 +
valueNoise3D(vec3(p * 160.0, t4 * 1.5)) * 0.25;
// Modulate stipple by blob presence (more texture on raised areas)
float onBlob = smoothstep(0.1, 0.5, blobs + blobs2 * 0.5);
//stipple *= mix(0.3, 1.0, onBlob);
// Domain-warped noise for organic variation in overall density
float t5 = iTime * 0.012;
vec2 wp = p + vec2(valueNoise3D(vec3(p * 3.0, t5)) * 0.15,
valueNoise3D(vec3(p * 3.0 + 5.0, t5)) * 0.15);
float density = valueNoise3D(vec3(wp * 8.0, t5));
// Some areas have more buildup than others
float densityMask = smoothstep(0.15, 0.75, density);
// Combine layers
float h = blobs * 0.20 + blobs2 * 0.25 + grit * 0.10 + stipple * 0.15;
// Apply density variation
h = mix(h * 0.5, h, densityMask);
return h;
}
float sdPlane(vec3 p, vec3 n, float h) {
return dot(p, n) + h;
}
vec4 map(vec3 p) {
float sphere = length(p) - 0.5;
// Displace the plane with popcorn texture for real 3D bumps
float plane = p.y + 1.0 - popcornHeight(p.xz) * 0.055;
float d = min(sphere, plane);
return vec4(p, d);
}
vec2 RM(vec3 ro, vec3 rd) {
float dO = 0.0;
float ii = 0.0;
for (int i=0; i<5; i++) {
vec3 p = ro + rd*dO;
float dS = map(p).w;
dO += dS;
ii += 1.0; // Can be used for effects based on steps
if (dO > 20.0 || dS < 0.001) break;
}
return vec2(dO, ii);
}
vec3 calcNormal(vec3 p) {
const vec2 e = vec2(0.0005, 0.0);
return normalize(vec3(
map(p + e.xyy).w - map(p - e.xyy).w,
map(p + e.yxy).w - map(p - e.yxy).w,
map(p + e.yyx).w - map(p - e.yyx).w
));
}
float softShadow(vec3 ro, vec3 rd, float mint, float tmax) {
float res = 1.0;
float t = mint;
for (int i = 0; i < 32; i++) {
float h = map(ro + rd * t).w;
res = min(res, 8.0 * h / t);
t += clamp(h, 0.02, 0.1);
if (res < 0.001 || t > tmax) break;
}
return clamp(res, 0.0, 1.0);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord.xy / iResolution.xy;
vec2 tv = uv;
uv -= 0.5;
uv.x *= ar;
vec3 col = vec3(0.);
vec3 ro = vec3(0.0, 4.0, 5.0); // Ray Origin
vec3 rd = normalize(vec3(uv, -1.0)); // Ray Direction
rd.yz *= r2d(deg*90.);
vec2 d = RM(ro, rd);
float t = d.x; // Distance to hit
vec3 p = ro + rd * t; // Hit point in 3D space
if (t < 20.0) {
vec3 normal = calcNormal(p);
vec3 lightPos = vec3(12.*sin(time), 23., 14.*cos(time));
vec3 lightDir = normalize(lightPos - p);
// Material color - slight warm off-white for popcorn ceiling
vec3 baseCol = vec3(0.92, 0.90, 0.87);
// Sphere gets a different color
if (length(p) < 0.6) baseCol = vec3(0.8, 0.2, 0.2);
float diff = max(dot(normal, lightDir), 0.0);
float shadow = softShadow(p + normal * 0.01, lightDir, 0.02, length(lightPos - p));
vec3 ambient = vec3(0.1, 0.1, 0.15);
vec3 diffuse = diff * shadow * vec3(1.0, 0.95, 0.9);
col = baseCol * (ambient + diffuse * 0.8);
}
fragColor = vec4(col, 1.0);
}
Image
Not used
More shaders by scry
- Project_2026-08-15_18-22-06
- Project_2026-08-14_16-45-00
- Project_2026-07-24_15-10-23
- Project_2026-07-15_03-18-26
- Glass of Orange Juice (raymarched)
- Project_2026-07-13_00-53-50
- Project_2026-07-10_16-39-54
- Project_2026-06-20_15-32-19
- Project_2026-06-25_12-22-43
- Visual Enhancer
- Project_2026-06-24_11-11-39
- Project_2026-06-23_19-17-29
Browse all public shaders · All shaders by scry · ShaderKit home
Unread comments
×Projects with new comments. Click to open and read.