Ночь, улица, фонарь... шейдеры
Эксперимент с шейдерами. Мелькающий свет фонарей и машин сквозь окно в дождливую ночь... воображение достраивает примерно такую картину. Реализовано с помощью three.js и tweenmax
HTML
<div class="content-canvas"></div>
<script id="vertex" type="x-shader/x-vertex">
varying vec3 v_position;
varying vec2 vUv;
void main(){
vUv = uv;
v_position = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragment" type="x-shader/x-fragment">
#define S(x, y, t) smoothstep(x, y, t)
uniform vec2 u_mouse;
uniform vec2 u_res;
uniform float u_time;
varying vec2 vUv;
varying vec3 v_position;
struct ray{
vec3 o, d;
};
ray GetRay(vec2 uv, vec3 camPos, vec3 lookAt, float zoom){
ray a;
a.o = camPos;
vec3 f = normalize(lookAt - camPos);
vec3 r = cross(vec3(0, 1, 0), f);
vec3 u = cross(f, r);
vec3 c = a.o + f * zoom;
vec3 i = c + uv.x * r + uv.y * u;
a.d = normalize(i-a.o);
return a;
}
vec3 ClosestPoint(ray r, vec3 p){
return r.o + max(0., dot(p-r.o, r.d)) * r.d;
}
float DistRay(ray r, vec3 p){
return length(p - ClosestPoint(r, p));
}
float Bokeh(ray r, vec3 p, float size, float blur){
float d = DistRay(r, p);
size *= length(p);
float c = S(size, size * (1.-blur), d);
c *= mix(.7, 1. ,S(size * .8, size, d));
return c;
}
float N(float t){
return fract(sin(t * 3456.) * 6547.);
}
vec4 N14(float t){
return fract(sin(t * vec4(123., 1024., 3456., 9564.) * vec4(6547., 354, 8799., 1564.)));
}
vec3 Headlights(ray r, float t){
float w1 = .25;
float w2 = w1 * 1.2;
float s = 1./10.;
float m = 0.;
for(float i = 0.; i < 1.; i+= 1./10.){
float n = N(i);
if(n > .1) continue;
float ti = fract(t + i);
float z = 100. - ti * 100.;
float fade = ti * ti * ti * ti;
float focus = S(.8, 1., ti);
float size = mix(.05, .03, focus);
m += Bokeh(r, vec3(-1. - w1, .15, z), size, .1) * fade;
m += Bokeh(r, vec3(-1. + w1, .15, z), size, .1) * fade;
m += Bokeh(r, vec3(-1. - w2, .15, z), size, .1) * fade;
m += Bokeh(r, vec3(-1. + w2, .15, z), size, .1) * fade;
float ref = 0.;
ref += Bokeh(r, vec3(-1. - w2, -.15, z), size * 3., 1.) * fade;
ref += Bokeh(r, vec3(-1. + w2, -.15, z), size * 3., 1.) * fade;
m += ref * focus;
}
return vec3(.9, .9, 1.) * m;
}
vec3 Taillights(ray r, float t) {
t *= .25;
float w1 = .25;
float w2 = w1*1.2;
float s = 1./15.;
float m = 0.;
for(float i=0.; i<1.; i+=1./15.) {
float n = N(i);
if(n>.5) continue;
float lane = step(.25, n);
float ti = fract(t+i);
float z = 100.-ti*100.;
float fade = ti*ti*ti*ti*ti;
float focus = S(.9, 1., ti);
float size = mix(.05, .03, focus);
float laneShift = S(1., .96, ti);
float x = 1.5 - lane * laneShift;
float blink = step(0., sin(t*1000.))*7.*lane*step(.96, ti);
m += Bokeh(r, vec3(x-w1,.15, z), size, .1)*fade;
m += Bokeh(r, vec3(x+w1,.15, z), size, .1)*fade;
m += Bokeh(r, vec3(x-w2,.15, z), size, .1)*fade;
m += Bokeh(r, vec3(x+w2,.15, z), size, .1)*fade*(1.+blink);
float ref = 0.;
ref += Bokeh(r, vec3(x-w2, -.15, z), size*3., 1.)*fade;
ref += Bokeh(r, vec3(x+w2, -.15, z), size*3., 1.)*fade*(1.+blink*.1);
m += ref*focus;
}
return vec3(1., .1, .03)*m;
}
vec3 Envlights(ray r, float t) {
float side = step(r.d.x, 0.);
r.d.x = abs(r.d.x);
float s = 1./10.;
float m = 0.;
vec3 c = vec3(0.);
for(float i = 0.; i < 1.; i+= 1./10.){
float ti = fract(t + i + side * s * 0.5);
float z = 100. - ti * 100.;
float fade = ti * ti * ti;
vec4 n = N14(i + side * 100.);
float occlusion = sin(ti * 6.28 * 10. * n.x) * .5 + .5;
float x = mix(2.5, 10., n.x);
float y = mix(.1, .15, n.y);
vec3 p = vec3(x, y, 50. - ti * 50.);
vec3 col = n.wzy;
c += Bokeh(r, p, .05, .1) * fade * col * .5;
}
return c;
}
vec3 Streetlights(ray r, float t){
float side = step(r.d.x, 0.);
r.d.x = abs(r.d.x);
float s = 1./10.;
float m = 0.;
for(float i = 0.; i < 1.; i+= 1./10.){
float ti = fract(t + i + side * s * 0.5);
float z = 100. - ti * 100.;
float fade = ti * ti * ti;
m += Bokeh(r, vec3(2., 2., z), .05, .1) * fade;
m += Bokeh(r, vec3(2., 2., z), .05, .1) * fade;
}
return vec3(1., .5, 0.) * m;
}
vec2 Rain(vec2 uv, float t){
t *= 40.;
vec2 a = vec2(3., 1.);
vec2 st = uv * a;
vec2 id = floor(st);
st.y += t * .22;
float n = fract(sin(id.x * 716.34) * 768.34);
uv.y += n;
st.y += n;
id = floor(st);
st = fract(st) - .5;
t += fract(sin(id.x * 716.34 + id.y * 1453.7) * 768.34) * 6.283;
float y = -sin(t + sin(t + sin(t) * .5)) * .43;
vec2 p1 = vec2(0., y);
vec2 o1 = (st - p1)/ a;
float d = length(o1);
float m1 = S(.07, .0, d);
vec2 o2 = (fract(uv * a.x * vec2(1., 2.)) - .5) / vec2(1., 2.);
d = length(o2);
float m2 = S(.3 * (.5 - st.y), .0, d) * S(-.1, .1, st.y - p1.y);
//if(st.x > .46 || st.y > .49) m1 = 1.;
return vec2(m1 * o1 * 30. + m2 * o2 * 10.);
}
void main(){
vec2 uv = gl_FragCoord.xy / u_res;
float aspect = u_res.x / u_res.y;
uv -= .5;
uv.x *= aspect;
vec2 m = u_mouse / u_res;
vec3 camPos = vec3(0, .2, 0);
vec3 lookAt = vec3(0, .2, .1);
float t = u_time * .05 + m.x;
vec2 rainDistort = Rain(uv * 5., t) * .5;
rainDistort += Rain(uv * 7., t) * .5;
uv.x += sin(uv.y * 70.) * .005;
uv.y += sin(uv.x * 170.) * .003;
ray r = GetRay(uv - rainDistort * .5, camPos, lookAt, 2.);
vec3 color = Streetlights(r, t);
color += Headlights(r, t);
color += Taillights(r, t);
color += Envlights(r, t);
color += (r.d.y * .25 * vec3(.2, .1, .5));
//color = vec3(rainDistort, 0.);
gl_FragColor = vec4(color, 1);
//gl_FragColor = mix(color2, vec4(.0), length(center * 1.5)); // vignette effect
}
</script>
CSS
body{
margin: 0;
padding: 0;
}
canvas{
display: block;
}
JS
https://cdnjs.cloudflare.com/ajax/libs/three.js/102/three.min.js
https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.2/TweenMax.min.js
скриптclass MathUtils {
constructor() {}
lerp(a, b, n) {
return n * (b - a) + a;
}
to(obj, time, set) {
const start = performance.now();
const duration = time * 1000;
return new Promise(resolve => {
this.opts = {
obj,
time,
duration,
start,
set,
resolve
};
this.update();
});
}
update() {
const now = performance.now();
const p = (now - this.opts.start) / this.opts.duration;
if (p >= 1) {
this.opts.completed = true;
return this.opts.resolve();
}
for (let v in this.opts.set) {
this.opts.obj[v] = this.lerp(
this.opts.obj[v],
this.opts.set[v],
this.outElastic(p)
);
}
requestAnimationFrame(this.update);
}
}
const init = () => {
const content = document.querySelector(".content-canvas");
const shader = {
v: document.querySelector("#vertex").textContent,
f: document.querySelector("#fragment").textContent
};
const mathUtils = new MathUtils();
const mouse = {
x: 0,
y: 0
};
const gl = {
renderer: new THREE.WebGLRenderer(),
camera: new THREE.PerspectiveCamera(
75,
innerWidth / innerHeight,
0.1,
1000
),
scene: new THREE.Scene(),
loader: new THREE.TextureLoader(),
clock: new THREE.Clock()
};
const uniforms = {
u_time: { type: "f", value: 0 },
u_res: { type: "v2", value: new THREE.Vector2(innerWidth, innerHeight) },
u_mouse: { type: "v2", value: new THREE.Vector2(0, 0) }
};
const addScene = () => {
gl.renderer.setPixelRatio(devicePixelRatio);
gl.renderer.setSize(innerWidth, innerHeight);
gl.camera.position.z = 5;
content.append(gl.renderer.domElement);
gl.scene.add(gl.camera);
};
const addMesh = () => {
const geometry = new THREE.PlaneGeometry(1, 1);
const material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: shader.v,
fragmentShader: shader.f
});
gl.mesh = new THREE.Mesh(geometry, material);
gl.scene.add(gl.mesh);
};
let elapsed = 0;
const update = e => {
elapsed = gl.clock.getElapsedTime();
uniforms.u_time.value = elapsed;
uniforms.u_mouse.value.x = mathUtils.lerp(
uniforms.u_mouse.value.x,
mouse.x,
0.05
);
uniforms.u_mouse.value.y = mathUtils.lerp(
uniforms.u_mouse.value.y,
mouse.y,
0.05
);
render();
requestAnimationFrame(update);
};
const resize = () => {
const w = innerWidth;
const h = innerHeight;
gl.renderer.setSize(w, h);
gl.camera.aspect = w / h;
// calculate scene
const dist = gl.camera.position.z - gl.mesh.position.z;
const height = 1;
gl.camera.fov = 2 * (180 / Math.PI) * Math.atan(height / (2 * dist));
if (w / h > 1) {
gl.mesh.scale.x = gl.mesh.scale.y = 1.05 * w / h;
}
gl.camera.updateProjectionMatrix();
};
const render = () => {
gl.renderer.render(gl.scene, gl.camera);
};
addScene();
addMesh();
update();
resize();
window.addEventListener("resize", resize);
window.addEventListener("mousemove", ({ clientX, clientY }) => {
mouse.x = clientX;
mouse.y = innerHeight - clientY ;
});
};
init();