Menggabungkan WebGL Dalam HTML – 5

Sebelum memulai tutorial ini, kita akan install library gsap untuk mempermudah coding.

$ npm install gsap

Efek animasi ripple akan dipicu saat mouse hover diatas image. Agar event mouse hover dapat dihubungkan dengan WebGL, diperlukan object Raycaster.

Buka file js/app.js, lalu tambahkan code import gsap.

import gsap from 'gsap';

Pada method constructor tambahkan inisialisasi object Raycaster dan variable untuk menyimpan data mouse.

this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();

Pada bagian block promise done, tambahkan pemanggilan method mouseMoves() (saat ini belum dibuat).

Promise.all(allDone).then(()=>{
    this.scroll = new Scroll();
    this.addImages();
    this.setPosition();

    this.mouseMoves();

    this.resize();
    this.setupResize();

    this.render();
});

Kemudian buat method baru mouseMoves(), yang isinya akan membaca posisi mouse dan menhubungkan dengan object raycaster.

mouseMoves(){
    window.addEventListener('mousemove', (event)=>{
        this.mouse.x = (event.clientX / this.width) * 2 - 1;
        this.mouse.y = - (event.clientY / this.height) * 2 + 1;

        this.raycaster.setFromCamera(this.mouse, this.camera);
        const intersects = this.raycaster.intersectObjects(this.scene.children);

        if (intersects.length>0){
            let obj = intersects[0].object;
            obj.material.uniforms.hover.value = intersects[0].uv;
        }
    }, false);
}

Pada method addImages() tambahkan code untuk membuat shaderMaterial. Berikut isi lengkap method addImage.

addImages(){
    this.material = new THREE.ShaderMaterial({
        uniforms:{
            time: {value: 0},
            uImage: {value:0},
            hover : {value : new THREE.Vector2(0.5, 0.5)},
            hoverState : {value: 0},
            //oceanTexture: {value: new THREE.TextureLoader().load(ocean)},
        },
        side: THREE.DoubleSide,
        fragmentShader: fragment,
        vertexShader: vertex,
    });

    this.materials = [];

    this.imageStore = this.images.map(img=>{
        let bounds = img.getBoundingClientRect();
        let geometry = new THREE.PlaneBufferGeometry(bounds.width, bounds.height, 10, 10);

        let texture = new THREE.Texture(img);
        texture.needsUpdate = true;

        let material = this.material.clone();

        img.addEventListener('mouseenter', ()=>{
            gsap.to(material.uniforms.hoverState, {
                duration: 1,
                value:1
            });
        });

        img.addEventListener('mouseout', ()=>{
            gsap.to(material.uniforms.hoverState, {
                duration: 1,
                value:0
            });				
        });

        this.materials.push(material);
        material.uniforms.uImage.value = texture;

        let mesh = new THREE.Mesh(geometry, material);

        this.scene.add(mesh);

        return{
            img: img,
            mesh: mesh,
            top: bounds.top,
            left: bounds.left,
            width: bounds.width,
            height: bounds.height,
        }
    });
}

Kemudian buka file js/shaders/fragment.glsl, ganti dengan code berikut


varying float vNoise;
varying vec2 vUv;
uniform sampler2D uImage;
uniform float time;

void main(){
	vec2 newUV = vUv;
	vec4 oceanView = texture2D(uImage, newUV);

	gl_FragColor = vec4(vUv,0.,1.);

	gl_FragColor = oceanView;
	gl_FragColor.rgb += 0.06*vec3(vNoise);
}

kemudian buka file js/shaders/vertex.glsl, ganti dengan code berikut

uniform float time;
uniform vec2 hover;
uniform float hoverState;
varying float vNoise;
varying vec2 vUv;

void main() {  
  vec3 newPos = position;
  

  float dist = distance(uv, hover);
  newPos.z += hoverState * 10.*sin(dist*10. + time);
  
  vNoise = hoverState* sin(dist*10. - time);
  vUv = uv;

  gl_Position = projectionMatrix * modelViewMatrix * vec4( newPos, 1.0 );
}

Sebagai referensi, berikut isi lengkap file js/app.js

import * as THREE from 'three';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';
import imagesloaded from 'imagesloaded';
import FontFaceObserver from 'fontfaceobserver';
import gsap from 'gsap';
import Scroll from './scroll';
import fragment from './shaders/fragment.glsl';
import vertex from './shaders/vertex.glsl';
import ocean from './../img/ocean.jpg';

export default class Sketch{
	constructor(opt){
		this.time = 0;
		this.container = opt.dom;

		this.scene = new THREE.Scene();

		this.width = this.container.offsetWidth;
		this.height = this.container.offsetHeight;

		this.camera = new THREE.PerspectiveCamera( 70, this.width / this.height, 100, 2000 );
		
		this.camera.position.z = 600;
		this.camera.fov = 2*Math.atan((this.height/2)/600)*(180/Math.PI);

		this.renderer = new THREE.WebGLRenderer({ 
			antialias: true,
			alpha: true,
		});
		
		this.container.appendChild( this.renderer.domElement );
	
		this.controls = new OrbitControls(this.camera, this.renderer.domElement);

		this.images = [...document.querySelectorAll('img')];

        const fontOpen = new Promise(resolve => {
			new FontFaceObserver("Open Sans").load().then(() => {
				resolve();
			});
		});

		const fontPlayfair = new Promise(resolve => {
			new FontFaceObserver("Playfair Display").load().then(() => {
				resolve();
			});
		});

		// Preload images
		const preloadImages = new Promise((resolve, reject) => {
			imagesloaded(document.querySelectorAll("img"), { background: true }, resolve);
		});

		let allDone = [fontOpen,fontPlayfair,preloadImages]
		this.currentScroll = 0;
		this.raycaster = new THREE.Raycaster();
		this.mouse = new THREE.Vector2();

		
		Promise.all(allDone).then(()=>{
			this.scroll = new Scroll();
			this.addImages();
			this.setPosition();
	
			this.mouseMoves();
			this.resize();
			this.setupResize();

			this.render();
		});
		

	}
	
	mouseMoves(){
		window.addEventListener('mousemove', (event)=>{
			this.mouse.x = (event.clientX / this.width) * 2 - 1;
			this.mouse.y = - (event.clientY / this.height) * 2 + 1;

			this.raycaster.setFromCamera(this.mouse, this.camera);
			const intersects = this.raycaster.intersectObjects(this.scene.children);

			if (intersects.length>0){
				let obj = intersects[0].object;
				obj.material.uniforms.hover.value = intersects[0].uv;
			}
		}, false);
	}

	setupResize(){
		window.addEventListener('resize', this.resize.bind(this));
	}

	resize(){
		this.width = this.container.offsetWidth;
		this.height = this.container.offsetHeight;
		this.renderer.setSize (this.width, this.height);
		this.camera.aspect = this.width/this.height;
		this.camera.updateProjectionMatrix();
	}

	render(){
		this.time+=0.05;

		this.scroll.render();
		this.currentScroll = this.scroll.scrollToRender;
		this.setPosition();

		this.materials.forEach(m=>{
			m.uniforms.time.value = this.time;
		});
	
		this.renderer.render( this.scene, this.camera );

		window.requestAnimationFrame(this.render.bind(this));
	}

	addImages(){
		this.material = new THREE.ShaderMaterial({
			uniforms:{
				time: {value: 0},
				uImage: {value:0},
				hover : {value : new THREE.Vector2(0.5, 0.5)},
				hoverState : {value: 0},
				//oceanTexture: {value: new THREE.TextureLoader().load(ocean)},
			},
			side: THREE.DoubleSide,
			fragmentShader: fragment,
			vertexShader: vertex,
		});

		this.materials = [];

		this.imageStore = this.images.map(img=>{
			let bounds = img.getBoundingClientRect();
			let geometry = new THREE.PlaneBufferGeometry(bounds.width, bounds.height, 10, 10);

			let texture = new THREE.Texture(img);
			texture.needsUpdate = true;

			let material = this.material.clone();

			img.addEventListener('mouseenter', ()=>{
				gsap.to(material.uniforms.hoverState, {
					duration: 1,
					value:1
				});
			});

			img.addEventListener('mouseout', ()=>{
				gsap.to(material.uniforms.hoverState, {
					duration: 1,
					value:0
				});				
			});

			this.materials.push(material);
			material.uniforms.uImage.value = texture;

			let mesh = new THREE.Mesh(geometry, material);

			this.scene.add(mesh);

			return{
				img: img,
				mesh: mesh,
				top: bounds.top,
				left: bounds.left,
				width: bounds.width,
				height: bounds.height,
			}
		});
	}

	setPosition(){
		this.imageStore.forEach(o=>{
			o.mesh.position.y = this.currentScroll -o.top + this.height/2 - o.height/2;
			o.mesh.position.x = o.left - this.width/2 + o.width/2;
		});
	}
}

new Sketch({
	dom: document.getElementById('container')
});

Jika dijalankan parcel index.html, maka saat image kita sorot dengan mouse akan beranimasi.

Sharing is caring:

Leave a Comment