「譯」如何實現互動式 WebGL 懸停效果

前端zenblo發表於2020-08-28

本文主要內容是介紹如何通過一些簡單的步驟,在影像上實現互動式滑鼠懸停效果。

檢視 Demo下載原始碼

我很喜歡 WebGL,在本文中,我將介紹如何在掌握著色器基礎上做出炫酷效果。我想要重現的這個效果,源自 Jesper Landberg 的網站,他是一個非常酷的傢伙,請務必檢視一下他網站上的東西。

image

讓我們開始吧!讓我們從編寫簡單的 HTML 開始:

<div class="item">
    <img src="img.jpg" class="js-image" alt="">
    <h2>Some title</h2>
    <p>Lorem ipsum.</p>
</div>
<script src="app.js"></script>

這個例子再簡單不過了!接下來,讓我們來新增些樣式,以使其它起來更漂亮:

image

所有動畫效果將在 Canvas 元素中呈現,同時我們還需要新增部分 JavaScript 程式碼。我在這裡使用 Parcel,因為學習起來非常簡單,我還將在 WebGL 部分中使用 Three.js

到這裡,我們開始編寫 JavaScript 程式碼,並按照官方文件著手進行基本的 Three.js 設定:

import * as THREE from "three";

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );

var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );


camera.position.z = 5;

var animate = function () {
    requestAnimationFrame( animate );

    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;

    renderer.render( scene, camera );
};

animate();

讓我們設定 Canvas 元素的樣式:

body { margin: 0; }

canvas { 
    display: block; 
    position: fixed;
    z-index: -1; // 將其設定為背景
    left: 0; // 鋪滿整個螢幕
    top: 0; // 鋪滿整個螢幕
}

當你完成所有這些操作,就可以使用 parcel index.html 執行它。現在,您不會看到太多,到目前為止,它是一個空的 3D 場景。讓我們暫時擱置 HTML,專注於 3D 場景操作。

讓我們建立一個帶有影像的簡單 PlaneBufferGeometry 物件。像這樣:

let TEXTURE = new TextureLoader().load('supaAmazingImage.jpg'); 
let mesh = new Mesh(
    new PlaneBufferGeometry(), 
    new MeshBasicMaterial({map: TEXTURE})
)

現在,我們將看到以下內容:

image

顯然我們還沒有實現效果,我們需要追蹤滑鼠的顏色軌跡。當然,我們還需要著色器。如果您對著色器感興趣,或者您可能已經學過一些有關如何放置影像的教程,例如如何將滑鼠放在懸停位置上?液體變形效果?

但是我們有一個問題:我們只能在上面的示例中在該影像上(和內部)使用著色器。但是效果並不侷限於任何影像邊界,而是流動的,覆蓋整個區域,就像整個螢幕一樣。

後期處理進行渲染

事實證明 Three.js 渲染器的輸出只是另一幅影像。我們可以利用它,並在該輸出上應用著色器位移!

這是程式碼的補充部分:

// 設定後期處理
let composer = new EffectComposer(renderer);
let renderPass = new RenderPass(scene, camera);
// 用影像渲染場景
composer.addPass(renderPass);

// 我們的自定義著色器傳遞整個螢幕,以替換以前的渲染
let customPass = new ShaderPass({vertexShader,fragmentShader});
// 確保我們正在渲染它
customPass.renderToScreen = true;
composer.addPass(customPass);

// 最後真正使用我們的著色器渲染場景
composer.render()
// 而不是以前的 render()
// renderer.render(scene, camera);

但整個過程用一句話概括就是,著色器被應用到了整個螢幕上。

接下來,讓我們完成具有炫酷效果的最終著色器:

// 在滑鼠周圍留一個小圓圈,並保持一定距離
float c = circle(uv, mouse, 0.0, 0.2);
// 獲取 3 次紋理,每次具有不同的偏移量,具體取決於滑鼠速度:
float r = texture2D(tDiffuse, uv.xy += (mouseVelocity * .5)).x;
float g = texture2D(tDiffuse, uv.xy += (mouseVelocity * .525)).y;
float b = texture2D(tDiffuse, uv.xy += (mouseVelocity * .55)).z;
// 將所有內容合併到最終輸出
color = vec4(r, g, b, 1.);

您可以在第一個演示中看到此結果。

將效果應用於多張影像

螢幕具有不同尺寸,3D 影像也各自具有尺寸。因此,我們現在要做的是計算這兩者之間的某種關係。

就像我一樣嗎?在上一篇文章中,我們可以製作一個寬度為 1 的平面,並將其完全適配螢幕寬度。所以實際上,我們使用了: WidthOfPlane=ScreenSize

對於我們的 Three.js 場景,這意味著如果要在螢幕上顯示 100px 寬的影像,我們將建立一個 Three.js 物件,其寬度為 100*(WidthOfPlane/ScreenSize)。通過這種數學運算,我們還可以輕鬆設定一些邊距和位置。

頁面載入後,我將遍歷所有影像,獲取它們的尺寸,並將它們新增到我的 3D 世界中:

let images = [...document.querySelectorAll('.js-image')];
images.forEach(image=>{
    // 現在,我們有了影像的大小和左邊、上邊的位置
    let dimensions = image.getBoundingClientRect();
    // 隱藏原始影像
    image.style.visibility = hidden;
    // 根據其 HTML 將 3D 物件新增到場景中
    createMesh(dimensions);
})

現在,製作這個HTML-3D混合結構非常簡單。

關於 mouseVelocity 我想補充的是,我用它來改變效果的半徑,滑鼠移動得越快,半徑越大。

要使其可滾動,我們只需要移動整個場景即可,與滾動螢幕的數量相同。使用我之前提到的相同公式:NumberOfPixels*(WidthOfPlane/ScreenSize)

有時,WidthOfPlane 等於甚至更容易 ScreenSize。這樣,您最終在兩個世界中得到的數字完全相同!

探索不同的效果

使用不同的著色器,您可以使用此方法產生任何效果。因此,我決定使用一些引數。

無需將影像分為三個顏色層,我們可以根據與滑鼠的距離來簡單地移動影像:

vec2 newUV = mix(uv, mouse, circle); 
color = texture2D(tDiffuse,newUV);

對於最後一個效果,我使用了一些隨機性,以在滑鼠游標周圍獲得畫素化效果。

最後一個演示中,您可以在效果之間切換以檢視可以進行的一些修改。有了“縮放”效果,我只使用了一個位移,但是在最後一箇中,我還對畫素進行了隨機化,這對我來說看起來很酷!

很高興看到您對此動畫的想法。您將用這種技術產生什麼樣的效果?

GitHub 上找到這個專案。

相關文章