Create WebGL shaders, share them via URLs, and see the result live! Inputs: iResolution, iGlobalTime, iTimeDelta, iFrame, iMouse, iDate. Double-click for fullscreen.
for(_='onOatQloUfUQZDQe~],m[@m[0@1?inK/KnerJord$vec#=e=>"click"e.pageImage(ousea.(p),`)|c.value\\niOFrameidthTimeDeltaeight.style=Omf(gf,` #4 gl_FragCo void maKtop.UcQ.hashiResolut=new ~/1e3GUbalTimeuniform1i]]=[X*z*wJW,Y*hJH])KsertAdjacentHTML(`afterEnd`,`<textarea id=c>`+(Qob(.slice(y=z=1)|`// WebGL <3outcoUr,#2 co$){p=#4(co$/*2.-1.,0,1); coUr=p+sK(Qan.y,p.x)*9.)*sK*i/2.+9./dot,p)+i*5.);}`));for(i K g)g[i[0]+i[6]]=g[i];with(g)(OKput"{=btoa(h=FN,vA=cP(i"sS(s=cS(h++ece(s!aS,s)2,5120,bD(n=ET-3,Int8Array.of(i(`precis mediump Z; Z i,i,i~,i; #2 ;iM;`++`(){maK Ur, $.xy); Ur.w=1.;}n,!eV(bf(n,cB(m=[o=j=d=0,0]))i`Qtributep;(){gl_Posit=p;}n+82!Uug)f})(move"y||([?(l"d-f~fo+=d4iM?@2@3]2w,hdr(6,j++3requestAnimQ(l))(O[m[2@3;(Odblac(b`h:100vh;margK:0;box-sizKg:b$er-box;w:`)+100/(z^=3)+`%;Z:left`)(down=up"y^=1)';G=/[-"-$JK?@~ZUQO]/.exec(_);)with(_.split(G))_=join(shift());eval(_)
Zm9yKF89J29uT2F0UWxvVWZVUVpEUWV+XSxtW0BtWzBAMT9pbksvS25lckpvcmQkdmVjIz1lPT4iY2xpY2siH2UucGFnZR5JbWFnZSgdb3VzZRxhLhsocBopLBlgGRgpfBdjLnZhbHVlFlxcbhVpTxRGcmFtZRNpZHRoElRpbWVEZWx0YRFlaWdodBAuc3R5bGU9D09tHA5mKGdmGixgDCAjNCALZ2xfRnJhZ0NvCXZvaWQgbWFLCHRvcC5VY1EULmhhc2gHaVJlc29sdXQUBj1uZXcgfi8xZTMFR1ViYWxUaW1lBHVuaWZvcm0DAzEMaQJdXT1bHlgqeiobdxJKVxIsHlkqG2gQSkgQXSkBG0tzZXJ0QWRqYWNlbnRIVE1MKGBhZnRlckVuZGAsYDx0ZXh0YXJlYSBpZD1jPmArKFFvYigHLnNsaWNlKHk9ej0xKRd8YC8vIFdlYkdMIDwzFQgdb3V0C2NvVXIsIzIgY28kKXsVC3A9IzQoY28kLwYqMi4tMS4sMCwxKTsVIGNvVXI9cCtzSyhRYW4aLnkscC54KSo5Likqc0saKmkELzIuKzkuL2RvdBoscCkraQQqNS4pOxV9YCkpO2ZvcihpIEsgZylnW2lbMF0raVs2XV09Z1tpXTt3aXRoKGcpKE9LcHV0InsHPWJ0b2EoFhloPUZOLHZBGj1jUChpInNTKHM9Y1MoaCsrGWUXY2UocxchYVMaLHMpGTIsNTEyMCxiRChuPUVULTMsSW50OEFycmF5Lm9mKGkoYHByZWNpcxQgbWVkaXVtcCBaOwMgWiBpBCxpEyxpfixpETsDICMyIAY7AwtpTRw7FWArFitgCCgpe21hSx0JVXIsCSQueHkpOwlVci53PTEuO30YbiwhZVYoYmYobixjQihtPVtvPWo9ZD0wLDBdKSkZaWBRdHJpYnV0ZQtwOwgoKXtnbF9Qb3NpdBQ9cDt9GG4rODIZIVUaGXVnGikZZgV9KSgbDm1vdmUieXx8KFs/ARkobCICERhkBS1mFwJ+GGYFFwIEGG8rPWQXAzQMaU0cGD9AMkAzXRcDMgwGGBt3EiwbaBAXZHIoNiwCExhqKysZMxdyZXF1ZXN0QW5pbVEUEyhsKSkoG08fW21bMkAzATsoG09kYmwfYQ9jDyhiD2BoEDoxMDB2aDttYXJnSzowO2JveC1zaXpLZzpiJGVyLWJveDt3EjpgKSsxMDAvKHpePTMpK2AlO1o6bGVmdGApKA5kb3duPQ51cCJ5Xj0xKSc7Rz0vWwEtHyItJEpLP0B+WlVRT10vLmV4ZWMoXyk7KXdpdGgoXy5zcGxpdChHKSlfPWpvaW4oc2hpZnQoKSk7ZXZhbChfKQ==
/*
The following commented code is very complex due to the extreme code-golfing required to make it fit in 1kb.
For more readability, you can first read our first (ungolfed) version here: https://github.com/xem/MiniShadertoy/blob/gh-pages/js1k.ungolfed.js
~~~
JS1K's shim gives the following vars for free:
- a: canvas element
- b: body
- d: document
- g: webgl context for the canvas.
*/
// Add a textarea called "c" in the body.
// Its content is either the hash of the top window (minus the leading "#") decoded from base64, if any, or the built-in demo.
// y (mouse down flag) and z (fullscren flag) are initialized to "1" (i.e. "disabled").
a.insertAdjacentHTML(
`afterEnd`,
`<textarea id=c>` +
(
// Decoded hash
atob(
top.location.hash.slice(
y = z = 1
)
)
||
// Or the demo (line breaks are written as "\n" for RegPack optimization)
`// WebGL <3\nvoid mainImage(out vec4 color,vec2 coord){\n vec4 p=vec4(coord/iResolution*2.-1.,0,1);\n color=p+sin(atan(p.y,p.x)*9.)*sin(p*iGlobalTime/2.+9./dot(p,p)+iGlobalTime*5.);\n}`
)
);
// All the WebGL context methods and constants we'll need in this demo are hashed in order to access them with only 2 letters:
// createProgram => cP
// shaderSource => sS
// createShader => cS
// compileShader => ce
// attachShader => aS
// linkProgram => lo
// useProgram => ug
// bindBuffer => bf
// createBuffer => cB
// enableVertexAttribArray => eV
// vertexAttribPointer => vA
// bufferData => bD
// getUniformLocation => gf
// drawArrays => dr
// FRAGMENT_SHADER => FN (value: 35632)
// ELEMENT_ARRAY_BUFFER_BINDING => ET (value: 34965)
for(i in g){
g[i[0] + i[6]] = g[i]
}
// Put "g" in the current scope to use any method / constant without having to write "g.".
with(g){
// Define the oninput function, called every time the textarea is edited.
// (all the functions in this demo have a parameter called e to repeat "=e=>" and optimize RegPack compression)
(
oninput = e => {
// Update the main window's hash with the shader code in base64.
top.location.hash = btoa(c.value),
// h = g.FRAGMENT_SHADER = 35632.
h = FN,
// Use g.vertexAttribPointer with the following params:
// vertex_index = 0, components_per_vertex_attribute = 2, vertex_attributes_type = g.BYTE = 5120, normalized = 0, stride = 1, offset = 0.
vA(
// Use g.createProgram to create the program p and return 0 as the 1st param of vA.
p = cP(
// define the function i that takes a source string e...
// (statements are separated by "|" to avoid {braces})
i = e =>
// ... sets the shader's source (g.shaderSource)...
sS(
// ... with a new shader s (g.createShader), and increments h...
s = cS(h++),
// ... and e...
e
)
|
// ... then compiles the shader (g.compileShader)...
ce(s)
|
// ... and attaches the shader to p (g.attachShader).
// (the following "|" are used to return "1" everytime i is called).
!aS(p, s)
),
// 2nd param of vA.
2,
// 3rd param of vA.
5120,
// Fill bD (g.bufferData), and return 0 as the 4th param of vA.
bD(
// Initialize n (the 1st param of bD) to g.ARRAY_BUFFER = 34962 = g.ELEMENT_ARRAY_BUFFER_BINDING - 3.
// we can't use g.AB directly as it's overwritten by another constant during hashing.
n = ET - 3,
// Set the coordinates of a big triangle surrounding the canvas (x1=1, y1=x2=n, y2=x3=1, y3=1) as the 2nd param of bD.
// The coordinates overlap because the "stride" param of vA is set to 1.
// "Int8Array.of(...)" is similar to "new Int8Array([...])", just shorter.
Int8Array.of(
// We call i() a first time to define the fragment shader with the content of the textarea and the real main function (at this moment, g.FN = g.FRAGMENT_SHADER = 36532)
// it returns x1 = i(`...`) = 1.
i(
`precision mediump float;uniform float iGlobalTime,iFrame,iDate,iTimeDelta;uniform vec2 iResolution;uniform vec4 iMouse;\n`
+ c.value
+ `void main(){mainImage(gl_FragColor,gl_FragCoord.xy);gl_FragColor.w=1.;}`
),
// y1 = x2 = n = 34962
n,
// y2 = x3 = !eV(0) = !g.enableVertexAttribArray(0) = 1
!eV(
// Use bf (g.bindBuffer) to bind the buffer created on-the-fly with cB and return 0
bf(
// g.ARRAY_BUFFER
n,
// g.createBuffer
cB(
// Initialise m (iMouse). m[0] and m[1] are set to 0 as well as o (iGlobaltime), j (iFrame) and d (iTimeDelta)
m = [o = j = d = 0, 0]
)
)
),
// We call i() a second time to create the vertex shader (at this moment, n = 35633 = g.VERTEX_SHADER)
// it returns y3 = i(`...`) = 1.
// Parentheses can be omitted around template literals when they're used as a function parameter (more details: http://xem.github.io/articles/#webspeech)
i`attribute vec4 p;void main(){gl_Position=p;}`
),
// 3rd param of bD: g.STATIC_DRAW = 35044 = 34962 + 82
// We can't use g.S_ as it was overwritten during hashing
n + 82
),
// Link the program p (g.linkProgram), return 1 as 5th param of vA
!lo(p),
// Use the program P (g.useProgram), return 0 as 6th param of vA
ug(p)
),
// Reset f (iDate)
f = new Date / 1e3
}
)
// Call oninput on load
(
// The onmousemove function updates m[0] and m[1] (i.e. iMouse.xy) when the canvas is hovered and y=0 (when the left click is pressed)
// It's placed here in the oninput() call to save space (no ";" at the end)
// The ES6 deconstruction ([a,b]=[c,d]) looks longer than separate assignments but it packs better.
// Mouse coordinates are adjusted to be between 0 and the canvas size
a.onmousemove = e =>
y
||
(
[m[0], m[1]] = [e.pageX * z * a.width / innerWidth, e.pageY * a.height / innerHeight]
)
),
// Define the loop function "l"
// Statements are separated by "|" to avoid {braces}
(
l = e =>
// At each frame:
// Update iTimeDelta (time since last frame),
uniform1f(gf(p, `iTimeDelta`), d = new Date / 1e3 - f)
|
// iDate,
uniform1f(gf(p, `iDate`), f = new Date / 1e3)
|
// iGlobaltime (time since last input or reload),
uniform1f(gf(p, `iGlobalTime`), o += d)
|
// iMouse (mouse coords and click coords between 0 and 1000),
uniform4f(gf(p, `iMouse`), m[0], m[1], m[2], m[3])
|
// iResolution (always 1000 x 1000),
uniform2f(gf(p, `iResolution`), a.width, a.height)
|
// and iFrame (incremented at each frame).
// uniform1f returns 0, so it replaces the 0 in:
// g.drawArray(mode=g.TRIANGLE_FAN=6, first=0, count=3)
dr(
6,
uniform1f(gf(p, `iFrame`), j++),
3
)
|
// Recursive call synced with the screen's refresh rate
requestAnimationFrame(l)
)
// Call l onload to start the loop
(
// The onclick function updates m.zw (m[2] and m[3]) when the canvas is clicked
// It's placed here to save space.
a.onclick = e =>
[m[2], m[3]] = [e.pageX * z * a.width / innerWidth, e.pageY * a.height / innerHeight]
)
}
// The ondblclick function updates the style of the canvas (a) and the textarea (c) when the canvas is double-clicked
// It is also called on load to set their style and the style of the body (b) when the demo starts
(
a.ondblclick = e =>
// Define the CSS for a and c
a.style = c.style = (
// Define the CSS for b (also used by a and c)
b.style = `height:100vh;margin:0;box-sizing:border-box;width:`
)
// Specify the width for a and c (half-screen or fullscreen, depending on the value of z
// z toggles between 1 and 2 at each double-click
+ 100 / (z ^= 3)
// Add "float:left" to a and c to leave no blank space in the page, and avoid scrollbars
+ `%;float:left`
)
(
// The onmousedown function toggles y when the mouse is down (then, y=0) or if the mouse is up (then, y=1).
// It's placed here to save space.
onmousedown = onmouseup = e =>
y ^= 1
)
// Minified: 1577b
// RegPacked with the settings: 2/1/0: 1024b
// Cheers! <3