-
Notifications
You must be signed in to change notification settings - Fork 682
/
Copy pathwebgl-shadertoy.html
359 lines (302 loc) · 10.4 KB
/
webgl-shadertoy.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
<!-- Licensed under a BSD license. See license.html for license -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>WebGL - Shadertoy</title>
<link type="text/css" href="resources/webgl-tutorials.css" rel="stylesheet" />
<style>
.divcanvas {
position: relative;
display: inline-block;
}
canvas {
display: block;
}
.playpause {
position: absolute;
left: 10px;
top: 10px;
width: 100%;
height: 100%;
font-size: 60px;
justify-content: center;
align-items: center;
color: rgba(255, 255, 255, 0.3);
transition: opacity 0.2s ease-in-out;
}
.playpausehide,
.playpause:hover {
opacity: 0;
}
.iframe .divcanvas {
display: block;
}
</style>
</head>
<body>
<div class="divcanvas">
<canvas id="canvas"></canvas>
<div class="playpause">▶</div>
</div>
</body>
<!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="resources/webgl-utils.js"></script>
<script>
"use strict";
function main() {
// Get A WebGL context
/** @type {HTMLCanvasElement} */
const canvas = document.querySelector("#canvas");
const gl = canvas.getContext("webgl");
if (!gl) {
return;
}
const vs = `
// an attribute will receive data from a buffer
attribute vec4 a_position;
// all shaders have a main function
void main() {
// gl_Position is a special variable a vertex shader
// is responsible for setting
gl_Position = a_position;
}
`;
const fs = `
precision highp float;
uniform vec2 iResolution;
uniform vec2 iMouse;
uniform float iTime;
// Protean clouds by nimitz (twitter: @stormoid)
// https://www.shadertoy.com/view/3l23Rh
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
// Contact the author for other licensing options
/*
Technical details:
The main volume noise is generated from a deformed periodic grid, which can produce
a large range of noise-like patterns at very cheap evalutation cost. Allowing for multiple
fetches of volume gradient computation for improved lighting.
To further accelerate marching, since the volume is smooth, more than half the density
information isn't used to rendering or shading but only as an underlying volume distance to
determine dynamic step size, by carefully selecting an equation (polynomial for speed) to
step as a function of overall density (not necessarialy rendered) the visual results can be
the same as a naive implementation with ~40% increase in rendering performance.
Since the dynamic marching step size is even less uniform due to steps not being rendered at all
the fog is evaluated as the difference of the fog integral at each rendered step.
*/
mat2 rot(in float a){float c = cos(a), s = sin(a);return mat2(c,s,-s,c);}
const mat3 m3 = mat3(0.33338, 0.56034, -0.71817, -0.87887, 0.32651, -0.15323, 0.15162, 0.69596, 0.61339)*1.93;
float mag2(vec2 p){return dot(p,p);}
float linstep(in float mn, in float mx, in float x){ return clamp((x - mn)/(mx - mn), 0., 1.); }
float prm1 = 0.;
vec2 bsMo = vec2(0);
vec2 disp(float t){ return vec2(sin(t*0.22)*1., cos(t*0.175)*1.)*2.; }
vec2 map(vec3 p)
{
vec3 p2 = p;
p2.xy -= disp(p.z).xy;
p.xy *= rot(sin(p.z+iTime)*(0.1 + prm1*0.05) + iTime*0.09);
float cl = mag2(p2.xy);
float d = 0.;
p *= .61;
float z = 1.;
float trk = 1.;
float dspAmp = 0.1 + prm1*0.2;
for(int i = 0; i < 5; i++)
{
p += sin(p.zxy*0.75*trk + iTime*trk*.8)*dspAmp;
d -= abs(dot(cos(p), sin(p.yzx))*z);
z *= 0.57;
trk *= 1.4;
p = p*m3;
}
d = abs(d + prm1*3.)+ prm1*.3 - 2.5 + bsMo.y;
return vec2(d + cl*.2 + 0.25, cl);
}
vec4 render( in vec3 ro, in vec3 rd, float time )
{
vec4 rez = vec4(0);
const float ldst = 8.;
vec3 lpos = vec3(disp(time + ldst)*0.5, time + ldst);
float t = 1.5;
float fogT = 0.;
for(int i=0; i<130; i++)
{
if(rez.a > 0.99)break;
vec3 pos = ro + t*rd;
vec2 mpv = map(pos);
float den = clamp(mpv.x-0.3,0.,1.)*1.12;
float dn = clamp((mpv.x + 2.),0.,3.);
vec4 col = vec4(0);
if (mpv.x > 0.6)
{
col = vec4(sin(vec3(5.,0.4,0.2) + mpv.y*0.1 +sin(pos.z*0.4)*0.5 + 1.8)*0.5 + 0.5,0.08);
col *= den*den*den;
col.rgb *= linstep(4.,-2.5, mpv.x)*2.3;
float dif = clamp((den - map(pos+.8).x)/9., 0.001, 1. );
dif += clamp((den - map(pos+.35).x)/2.5, 0.001, 1. );
col.xyz *= den*(vec3(0.005,.045,.075) + 1.5*vec3(0.033,0.07,0.03)*dif);
}
float fogC = exp(t*0.2 - 2.2);
col.rgba += vec4(0.06,0.11,0.11, 0.1)*clamp(fogC-fogT, 0., 1.);
fogT = fogC;
rez = rez + col*(1. - rez.a);
t += clamp(0.5 - dn*dn*.05, 0.09, 0.3);
}
return clamp(rez, 0.0, 1.0);
}
float getsat(vec3 c)
{
float mi = min(min(c.x, c.y), c.z);
float ma = max(max(c.x, c.y), c.z);
return (ma - mi)/(ma+ 1e-7);
}
//from my "Will it blend" shader (https://www.shadertoy.com/view/lsdGzN)
vec3 iLerp(in vec3 a, in vec3 b, in float x)
{
vec3 ic = mix(a, b, x) + vec3(1e-6,0.,0.);
float sd = abs(getsat(ic) - mix(getsat(a), getsat(b), x));
vec3 dir = normalize(vec3(2.*ic.x - ic.y - ic.z, 2.*ic.y - ic.x - ic.z, 2.*ic.z - ic.y - ic.x));
float lgt = dot(vec3(1.0), ic);
float ff = dot(dir, normalize(ic));
ic += 1.5*dir*sd*ff*lgt;
return clamp(ic,0.,1.);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 q = fragCoord.xy/iResolution.xy;
vec2 p = (gl_FragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
bsMo = (iMouse.xy - 0.5*iResolution.xy)/iResolution.y;
float time = iTime*3.;
vec3 ro = vec3(0,0,time);
ro += vec3(sin(iTime)*0.5,sin(iTime*1.)*0.,0);
float dspAmp = .85;
ro.xy += disp(ro.z)*dspAmp;
float tgtDst = 3.5;
vec3 target = normalize(ro - vec3(disp(time + tgtDst)*dspAmp, time + tgtDst));
ro.x -= bsMo.x*2.;
vec3 rightdir = normalize(cross(target, vec3(0,1,0)));
vec3 updir = normalize(cross(rightdir, target));
rightdir = normalize(cross(updir, target));
vec3 rd=normalize((p.x*rightdir + p.y*updir)*1. - target);
rd.xy *= rot(-disp(time + 3.5).x*0.2 + bsMo.x);
prm1 = smoothstep(-0.4, 0.4,sin(iTime*0.3));
vec4 scn = render(ro, rd, time);
vec3 col = scn.rgb;
col = iLerp(col.bgr, col.rgb, clamp(1.-prm1,0.05,1.));
col = pow(col, vec3(.55,0.65,0.6))*vec3(1.,.97,.9);
col *= pow( 16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y), 0.12)*0.7+0.3; //Vign
fragColor = vec4( col, 1.0 );
}
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
`;
// setup GLSL program
const program = webglUtils.createProgramFromSources(gl, [vs, fs]);
// look up where the vertex data needs to go.
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
// look up uniform locations
const resolutionLocation = gl.getUniformLocation(program, "iResolution");
const mouseLocation = gl.getUniformLocation(program, "iMouse");
const timeLocation = gl.getUniformLocation(program, "iTime");
// Create a buffer to put three 2d clip space points in
const positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// fill it with a 2 triangles that cover clipspace
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1, -1, // first triangle
1, -1,
-1, 1,
-1, 1, // second triangle
1, -1,
1, 1,
]), gl.STATIC_DRAW);
const playpauseElem = document.querySelector('.playpause');
const inputElem = document.querySelector('.divcanvas');
inputElem.addEventListener('mouseover', requestFrame);
inputElem.addEventListener('mouseout', cancelFrame);
let mouseX = 0;
let mouseY = 0;
function setMousePosition(e) {
const rect = inputElem.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = rect.height - (e.clientY - rect.top) - 1; // bottom is 0 in WebGL
}
inputElem.addEventListener('mousemove', setMousePosition);
inputElem.addEventListener('touchstart', (e) => {
e.preventDefault();
playpauseElem.classList.add('playpausehide');
requestFrame();
}, {passive: false});
inputElem.addEventListener('touchmove', (e) => {
e.preventDefault();
setMousePosition(e.touches[0]);
}, {passive: false});
inputElem.addEventListener('touchend', (e) => {
e.preventDefault();
playpauseElem.classList.remove('playpausehide');
cancelFrame();
}, {passive: false});
let requestId;
function requestFrame() {
if (!requestId) {
requestId = requestAnimationFrame(render);
}
}
function cancelFrame() {
if (requestId) {
cancelAnimationFrame(requestId);
requestId = undefined;
}
}
let then = 0;
let time = 0;
function render(now) {
requestId = undefined;
now *= 0.001; // convert to seconds
const elapsedTime = Math.min(now - then, 0.1);
time += elapsedTime;
then = now;
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the attribute
gl.enableVertexAttribArray(positionAttributeLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
gl.vertexAttribPointer(
positionAttributeLocation,
2, // 2 components per iteration
gl.FLOAT, // the data is 32bit floats
false, // don't normalize the data
0, // 0 = move forward size * sizeof(type) each iteration to get the next position
0, // start at the beginning of the buffer
);
gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
gl.uniform2f(mouseLocation, mouseX, mouseY);
gl.uniform1f(timeLocation, time);
gl.drawArrays(
gl.TRIANGLES,
0, // offset
6, // num vertices to process
);
requestFrame();
}
requestFrame();
requestAnimationFrame(cancelFrame);
}
main();
</script>
</html>