286 Codepen

Ночь, улица, фонарь... шейдеры

Эксперимент с шейдерами. Мелькающий свет фонарей и машин сквозь окно в дождливую ночь... воображение достраивает примерно такую картину. Реализовано с помощью 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();

Комментарии

  • Facebook
  • Вконтакте

Похожие статьи