Фрагментні шейдери в браузері · book of shaders · iquilezles.org · клік по коду = копія
precision mediump float;
uniform vec2 u_res;
uniform float u_time;
void main(){
vec2 uv = gl_FragCoord.xy / u_res;
gl_FragColor = vec4(uv, 0.5, 1.0);
}#version 300 es мусить бути ПЕРШИМ рядком файлу — навіть коментар вище ламає компіляцію#version 300 es
precision highp float;
uniform vec2 u_res;
out vec4 fragColor;
void main(){
fragColor = vec4(gl_FragCoord.xy/u_res, 0.5, 1.0);
}void mainImage(out vec4 O, in vec2 U){
vec2 uv = U / iResolution.xy;
O = vec4(uv, 0.5, 1.0);
}vec2 p = (2.0*gl_FragCoord.xy - u_res)/u_res.y;
float d = length(p) - 0.4*u_density;| Shadertoy uniform | тип | що всередині |
|---|---|---|
| iResolution | vec3 | розмір вьюпорта в пікселях; беруть .xy, .z — pixel aspect |
| iTime | float | секунди від старту шейдера |
| iTimeDelta | float | тривалість кадру, секунди |
| iFrame | int | номер кадру; зручно для «зробити щось у кадрі 0» |
| iMouse | vec4 | .xy — поточна позиція поки затиснута ЛКМ, .zw — точка кліку (знак zw кодує стан кнопки) |
| iChannel0..3 | sampler2D | слоти входу: текстура, буфер, кубмапа, відео, аудіо, клавіатура |
| iChannelResolution[4] | vec3 | розміри того, що прив'язане до слотів |
| iDate | vec4 | рік, місяць, день, секунди доби — рандом-сід, що не повторюється |
| iSampleRate | float | частота дискретизації звуку (44100) |
1 і 1.0 — різні типиfloat x = 1.0; // ок
float y = 1; // помилка компіляції
float z = 2.0/3; // теж помилкаvec3 c = col.bgr; // перестановка
vec3 g = col.rrr; // дублювання
col.rg = vec2(1.0, 0.0);
// col.rx — помилка: різні набориvec3 white = vec3(1.0);
vec4 rgba = vec4(rgb, 1.0);
vec2 xy = pos.xy;mat2(a,b,c,d) заповнює СТОВПЦІ, не рядки. Звідси плутанина зі знаком у поворотахmat2 rot(float a){
float c = cos(a), s = sin(a);
return mat2(c, s, -s, c); // CCW для rot(a)*p
}
// mat2(c,-s,s,c) поверне в інший бік#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endifconst float TAU = 6.28318530718;
uniform float u_time;
varying vec2 v_uv; // ES 1.00
in vec2 v_uv; // ES 3.00vec2 uv = fragCoord / iResolution.xy;vec2 p = (2.0*fragCoord - iResolution.xy)
/ iResolution.y;atan(y,x) — двоаргументний, дає −π..π з правильним квадрантомfloat r = length(p);
float a = atan(p.y, p.x);vec2 g = p * 4.0;
vec2 id = floor(g);
vec2 q = fract(g) - 0.5;p = abs(p) - 0.3;
if (p.x < p.y) p = p.yx;float n = 6.0;
float a = atan(p.y, p.x);
float r = length(p);
a = mod(a, TAU/n) - TAU/(2.0*n);
p = vec2(cos(a), sin(a)) * r;a*(1−t) + b*t; t поза 0..1 не обрізаєтьсяvec3 col = mix(bg, fg, 0.3);clamp(x, 0.0, 1.0)float s = clamp(x, 0.0, 1.0);float m = step(0.5, uv.x);e0 >= e1 результат за специфікацією не визначенийfloat m = smoothstep(0.4, 0.5, r);
// зворотний край: smoothstep(0.5, 0.4, r)x − floor(x); для від'ємних теж додатний — база всіх повтореньfloat saw = fract(uv.x * 8.0);x − y*floor(x/y), знак наслідує y. Це НЕ % з C: mod(-1.0, 3.0) = 2.0. Оператор % для float у GLSL відсутній узагаліfloat t = mod(u_time, 4.0);float r = length(p);
float d2 = dot(p, p);vec3 n = normalize(v + 1e-6);x < 0 і для x = 0, y <= 0. Перед pow — clamp або absfloat g = pow(max(x, 0.0), 2.2);float a = atan(p.y, p.x);float d = min(dCircle, dBox);#extension GL_OES_standard_derivatives : enable
float w = fwidth(d);
float m = smoothstep(w, -w, d);d
зробити маску. Заливка: smoothstep(w,-w,d). Контур: abs(d)-th. Ореол: exp(-d*k).
float d = length(p) - r;vec2 q = abs(p) - b;
float d = length(max(q, 0.0))
+ min(max(q.x, q.y), 0.0);vec2 pa = p - a, ba = b - a;
float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0);
float d = length(pa - ba*h) - 0.02;float uni = min(a, b);
float ins = max(a, b);
float sub = max(-a, b);float smin(float a, float b, float k){
float h = clamp(0.5 + 0.5*(b-a)/k, 0.0, 1.0);
return mix(b, a, h) - k*h*(1.0-h);
}float w = fwidth(d);
float mask = smoothstep(w, -w, d);
col = mix(col, shapeCol, mask);float hash21(vec2 p){
return fract(sin(dot(p, vec2(12.9898,78.233)))
* 43758.5453);
}float hash21(vec2 p){
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}float noise(vec2 p){
vec2 i = floor(p), f = fract(p);
vec2 u = f*f*(3.0 - 2.0*f);
return mix(mix(hash21(i), hash21(i+vec2(1,0)), u.x),
mix(hash21(i+vec2(0,1)),hash21(i+vec2(1,1)), u.x), u.y);
}float fbm(vec2 p){
float v = 0.0, a = 0.5;
for(int i = 0; i < 5; i++){
v += a * noise(p);
p *= 2.02; a *= 0.5;
}
return v;
}vec2 q = vec2(fbm(p), fbm(p + 5.2));
float v = fbm(p + 2.0*q + u_time*0.05);i < octaves з uniform не скомпілюється. Обхід — константна межа + break за умовоюfor(int i = 0; i < 8; i++){
if(float(i) >= u_oct) break;
// ...
}vec3 pal(float t, vec3 a, vec3 b, vec3 c, vec3 d){
return a + b*cos(6.28318*(c*t + d));
}
// pal(t, vec3(.5), vec3(.5), vec3(1.),
// vec3(0., .33, .67))vec3 hsv2rgb(vec3 c){
vec3 p = abs(fract(c.xxx + vec3(0.,2./3.,1./3.))*6.0 - 3.0);
return c.z * mix(vec3(1.0), clamp(p-1.0,0.0,1.0), c.y);
}col = pow(col, vec3(1.0/2.2));col = col / (1.0 + col);col *= smoothstep(1.4, 0.3, length(p));col += (hash21(gl_FragCoord.xy) - 0.5) * 0.02;float T = 8.0;
float ph = TAU * mod(u_time, T) / T;
float k = sin(ph); // період рівно TiTime ≈ 3600, і mediump перестає розрізняти кадри — рух стає ривкамиfloat t = mod(u_time, 600.0);float bass = texture(iChannel0, vec2(0.05, 0.25)).x;
float wave = texture(iChannel0, vec2(uv.x, 0.75)).x;float hit = smoothstep(0.35, 0.75, bass);
float r = 0.3 + 0.12*hit;uniform float u_warp;
p += u_warp * 0.3 * vec2(sin(p.y*3.0+t), cos(p.x*3.0-t));float k = smoothstep(0.0, 1.0, fract(t*0.25));setFunction додає свій шматок під власним іменем,
і далі він чейниться нарівно з osc чи kaleid. Усередині доступні: _st (координати),
_c0 / _c1 (вхідні кольори), time, resolution.
Тіло — це тіло функції: пишеться return vec4(...), оголосити поруч допоміжну функцію не вийде — інлайнити руками.
hydra · custom glsl →
| type | сигнатура по суті | чейниться як |
|---|---|---|
| src | координата → колір | джерело: myThing().out() |
| coord | координата → координата | геометрія: osc().myWarp() |
| color | колір → колір | колір: osc().myGrade() |
| combine | два кольори → колір | blend: osc().myMix(noise()) |
| combineCoord | колір + координата → координата | modulate: osc().myMod(noise()) |
setFunction({
name: 'cells', type: 'src',
inputs: [{ type:'float', name:'n', default:8 }],
glsl: `
vec2 g = _st * n;
vec2 id = floor(g);
float r = fract(sin(dot(id, vec2(12.9898,78.233)))*43758.5453);
float k = 0.5 + 0.5*sin(time*2.0 + r*6.2831);
return vec4(vec3(k*r), 1.0);
`
})
cells(12).out()setFunction({
name: 'polar', type: 'coord',
inputs: [{ type:'float', name:'amt', default:1 }],
glsl: `
vec2 p = _st - 0.5;
float r = length(p);
float a = atan(p.y, p.x) + r*amt*3.0;
return vec2(cos(a), sin(a))*r + 0.5;
`
})
osc(20, 0.05, 0.9).polar(1.5).out()void mainImage(out vec4 O, in vec2 U){
vec2 p = (2.0*U - iResolution.xy)/iResolution.y;
float t = iTime*0.3;
float v = sin(p.x*3.0 + t)
+ sin(p.y*3.0 - t*1.3)
+ sin(length(p)*6.0 - t*2.0);
vec3 col = 0.5 + 0.5*cos(6.28318*(vec3(0.0,0.33,0.67) + v*0.25));
col *= smoothstep(1.6, 0.2, length(p));
O = vec4(pow(col, vec3(1.0/2.2)), 1.0);
}void mainImage(out vec4 O, in vec2 U){
vec2 p = (2.0*U - iResolution.xy)/iResolution.y;
float bass = texture(iChannel0, vec2(0.05, 0.25)).x;
float hit = smoothstep(0.30, 0.75, bass);
float d = abs(length(p) - (0.35 + 0.10*hit)) - 0.004;
float w = fwidth(d);
float core = smoothstep(w, -w, d);
float glow = exp(-abs(d)*18.0) * (0.35 + 0.65*hit);
vec3 col = vec3(0.05,0.02,0.08)
+ vec3(1.0,0.35,0.75)*glow
+ vec3(1.0)*core;
O = vec4(pow(col/(1.0+col), vec3(1.0/2.2)), 1.0);
}float hash21(vec2 p){
p = fract(p*vec2(123.34,456.21));
p += dot(p, p+45.32);
return fract(p.x*p.y);
}
float noise(vec2 p){
vec2 i = floor(p), f = fract(p);
vec2 u = f*f*(3.0-2.0*f);
return mix(mix(hash21(i), hash21(i+vec2(1,0)), u.x),
mix(hash21(i+vec2(0,1)), hash21(i+vec2(1,1)), u.x), u.y);
}
float fbm(vec2 p){
float v=0.0, a=0.5;
for(int i=0;i<5;i++){ v+=a*noise(p); p*=2.02; a*=0.5; }
return v;
}
void mainImage(out vec4 O, in vec2 U){
vec2 p = (2.0*U - iResolution.xy)/iResolution.y * 2.0;
float t = iTime*0.08;
vec2 q = vec2(fbm(p + t), fbm(p + 5.2 - t));
float v = fbm(p + 2.4*q);
vec3 col = mix(vec3(0.03,0.05,0.12), vec3(0.95,0.55,0.30), v*v);
O = vec4(pow(col, vec3(1.0/2.2)), 1.0);
}void mainImage(out vec4 O, in vec2 U){
vec2 p = (2.0*U - iResolution.xy)/iResolution.y;
float t = iTime*0.25;
float n = 6.0;
float a = mod(atan(p.y,p.x), 6.28318/n) - 3.14159/n;
p = vec2(cos(a), sin(a)) * length(p);
vec2 g = p*3.0 + t;
vec2 id = floor(g);
vec2 q = fract(g) - 0.5;
float r = fract(sin(dot(id, vec2(12.9898,78.233)))*43758.5453);
float d = length(q) - (0.12 + 0.22*r);
float w = fwidth(d);
float m = smoothstep(w, -w, d);
vec3 col = mix(vec3(0.02,0.03,0.06),
0.5+0.5*cos(6.28318*(vec3(0.0,0.33,0.67)+r+t*0.2)), m);
O = vec4(pow(col, vec3(1.0/2.2)), 1.0);
}| симптом | причина | що робити |
|---|---|---|
| чорний екран, помилок нема | десь NaN: normalize(0), pow від'ємного, 0/0. NaN проходить множення і заражає весь піксель | вивести проміжок: O = vec4(vec3(d), 1.0) і дивитись, де він зникає |
| коло — еліпс | uv 0..1 без корекції аспекту | (2.0*U − res.xy)/res.y |
| драбинка по краях | step або порівняння замість згладженої маски | smoothstep(w,−w,d) з w = fwidth(d) |
| смуги в темних градієнтах | 8 біт на канал + лінійний градієнт | додати зерно ±0.01 і гамму |
| шум інший на телефоні | hash на sin + mediump: реалізація sin для великих аргументів різниться | hash без sin, або highp |
| тремтіння через кілька хвилин | час росте, mediump втрачає розрізнення сусідніх кадрів | mod(u_time, 600.0) |
| 21 fps без видимої причини | fbm у 8+ октав, length у циклі, гілки if, які на GPU виконуються обидві | менше октав, dot(p,p) замість length, гілки → mix/step |
| працює локально, не в Shadertoy (або навпаки) | texture2D проти texture, gl_FragColor проти out | визначитись із версією; ES 3.00 = #version 300 es першим рядком |
| дірка в фігурі на стику | max(-a, b) переплутано порядком аргументів | віднімається ЗАВЖДИ той, що зі знаком мінус |
| повідомлення | що насправді |
|---|---|
| no matching overloaded function found | типи аргументів не ті: mix(vec3, float, float) або 1 замість 1.0 |
| cannot convert from 'int' to 'float' | та сама зникла крапка |
| No precision specified for (float) | у фрагментному шейдері ES 1.00 нема precision mediump float; |
| 'assign' : l-value required | запис у свізл із повтором (col.rr = ...) або у varying/uniform |
| Loop index cannot be compared with non-constant | ES 1.00: межа циклу мусить бути константою |
| 'texture' : no matching overloaded function | ES 1.00 очікує texture2D |
| #version directive must occur before anything else | перед #version 300 es є коментар, пробіл або BOM |
| undeclared identifier: 'fwidth' | WebGL1 без #extension GL_OES_standard_derivatives : enable |
| 'return' : function return is not matching type | у hydra setFunction тип coord повертає vec2, решта — vec4 |
| задача | інструмент | чому |
|---|---|---|
| швидко накидати ідею, поділитись лінком | Shadertoy | нуль налаштування, готові uniform, аудіо і відео в слотах |
| візуал у джемі під музику | Hydra | ланцюг замість шейдера; свій GLSL — точково через setFunction |
| візуал з телефона, руками, з експортом | touchviz | тач, гіроскоп, мік; віддає готовий .glsl |
| шейдер у власному застосунку | сирий WebGL2 | свої uniform, свої проходи, нічия чужа обгортка |
| треба фігури, а не текстури | SDF | одна формула дає заливку, контур і ореол |
| треба органіка, а не геометрія | fbm + domain warp | дві октави шуму дають те, чого не дасть жоден набір примітивів |
| треба зациклений кліп | період T у всіх функціях часу | інакше шов буде видно на кожному повторі |