Original shader code by Soulman : https://stackoverflow.com/a/5493122/388201
Added a simple UI to configure the parameters. Tap/hover the bottom of the screen and play with the sliders.
A Pen by Lea Rosema on CodePen.
<script id="fragmentShader" type="x-shader/fragment"> | |
/** | |
* Original shader code by Soulman | |
* https://stackoverflow.com/a/5493122/388201 | |
**/ | |
precision highp float; | |
const float PI = 3.14159265358979323846264; | |
const float TWOPI = PI*2.0; | |
const vec4 WHITE = vec4(0.9, 0.9, 0.9, 1.0); | |
const vec4 BLACK = vec4(0.0, 0.0, 0.0, 1.0); | |
const vec2 CENTER = vec2(0.0, 0.0); | |
const int MAX_RINGS = 100; | |
uniform float time; | |
uniform float width; | |
uniform float height; | |
uniform float ringDistance; | |
uniform int maxRings; | |
uniform float waveCount; | |
uniform float waveDepth; | |
uniform float yCenter; | |
uniform float direction; | |
void main(void) { | |
float rot = time*0.006; | |
float vmin = min(width, height); | |
vec2 position = vec2(- (width / 2.0) + gl_FragCoord.x, | |
- (height / 2.0) + gl_FragCoord.y) / (vmin / 2.0); | |
float x = position.x; | |
float y = position.y; | |
bool white = false; | |
float prevRingDist = ringDistance; | |
for (int i = 0; i < MAX_RINGS; i++) { | |
vec2 center = vec2(0.0, yCenter - ringDistance * float(i)*direction); | |
float radius = 0.5 + ringDistance / (pow(float(i+5), 1.1)*0.006); | |
float dist = distance(center, position); | |
dist = pow(dist, 1.0/3.0); | |
float ringDist = abs(dist-radius); | |
if (ringDist < ringDistance*prevRingDist*7.0) { | |
float angle = atan(y - center.y, x - center.x); | |
float thickness = 1.1 * abs(dist - radius) / prevRingDist; | |
float depthFactor = waveDepth * sin((angle+rot*radius) * waveCount); | |
if (dist > radius) { | |
white = (thickness < ringDistance * 5.0 - depthFactor * 2.0); | |
} | |
else { | |
white = (thickness < ringDistance * 5.0 + depthFactor); | |
} | |
break; | |
} | |
if (dist > radius || i >= maxRings) break; | |
prevRingDist = ringDist; | |
} | |
gl_FragColor = white ? WHITE : BLACK; | |
} | |
</script> | |
<script id="vertexShader" type="x-shader/vertex"> | |
precision mediump float; | |
attribute vec2 position; | |
void main () { | |
gl_Position = vec4(position, 0, 1); | |
} | |
</script> | |
<div class="ui"> | |
<div class="ui__field"> | |
<label for="ringDistance">Ring Distance</label> | |
<input id="ringDistance" type="range" min="0.04" max="0.06" value="0.05" step="0.00125"> | |
</div> | |
<div class="ui__field"> | |
<label for="maxRings">Max Rings</label> | |
<input id="maxRings" type="range" min="2" max="50" value="30" step="1" /> | |
</div> | |
<div class="ui__field"> | |
<label for="waveCount">Wave Count</label> | |
<input id="waveCount" type="range" min="2" max="100" value="60" step="1" /> | |
</div> | |
<div class="ui__field"> | |
<label for="waveDepth">Wave Depth</label> | |
<input id="waveDepth" type="range" min="0.01" max="0.2" value="0.04" step="0.005" /> | |
</div> | |
<div class="ui__field"> | |
<label for="yCenter">Y center</label> | |
<input id="yCenter" type="range" min="0.00" max="3.0" value="0.8" step="0.1" /> | |
</div> | |
<div class="ui__field"> | |
<label for="direction">Direction</label> | |
<input id="direction" type="range" min="-3.0" max="3.0" value="1.2" step="0.1" /> | |
</div> | |
</div> |
const $ = (sel) => document.querySelector(sel) || {} | |
const regl = createREGL() | |
const $code = (sel) => ( | |
document.getElementById(sel) || {} | |
).textContent || "void main() {}" | |
const drawFrame = regl({ | |
frag: $code('fragmentShader'), | |
vert: $code('vertexShader'), | |
attributes: { | |
position: [[-1,-1], | |
[-1, 1], | |
[ 1,-1], | |
[ 1,-1], | |
[-1, 1], | |
[ 1, 1]] | |
}, | |
uniforms: { | |
color: [1,0,0], | |
width: ctx => ctx.viewportWidth, | |
height: ctx => ctx.viewportHeight, | |
ringDistance: ctx => parseFloat($('#ringDistance').value), | |
maxRings: ctx => parseInt($('#maxRings').value), | |
waveCount: ctx => parseInt($('#waveCount').value), | |
waveDepth: ctx => parseFloat($('#waveDepth').value), | |
yCenter: ctx => parseFloat($('#yCenter').value), | |
direction: ctx => parseFloat($('#direction').value), | |
time: ctx => ctx.tick | |
}, | |
count: 6 | |
}) | |
regl.frame(ctx => { | |
regl.clear({ | |
color: [1,1,1,1], | |
depth: 1 | |
}) | |
drawFrame() | |
}) |
<script src="https://unpkg.com/[email protected]/dist/regl.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script> |
.ui { | |
position: absolute; | |
bottom: 0; | |
z-index: 1; | |
background: rgba(0,0,0,.7); | |
color: #fff; | |
font-family: monospace; | |
line-height: 1.2; | |
font-size: 133%; | |
width: 100%; | |
display: flex; | |
flex-wrap: wrap; | |
justify-content: center; | |
opacity: 0; | |
transition: opacity 250ms ease; | |
} | |
body:hover .ui { | |
opacity: 1; | |
} | |
.ui__field { | |
border-radius: 5px; | |
background: #000; | |
margin: .1em; | |
padding: .1em; | |
} | |
label { | |
font-size: 70%; | |
display: block; | |
font-size: .8; | |
} | |
input[type="range"] { | |
width: 7.1em; | |
} |
Original shader code by Soulman : https://stackoverflow.com/a/5493122/388201
Added a simple UI to configure the parameters. Tap/hover the bottom of the screen and play with the sliders.
A Pen by Lea Rosema on CodePen.