簡體   English   中英

如何重新創建這種“波浪”圖像效果?

[英]How can I recreate this “wavy” image effect?

我正在尋找一種方法,在網頁上拍攝圖像或圖像的一部分,並渲染這種或類似的動畫效果,其中圖像是“波浪狀的”。 例子:

“精神”領域:

在此輸入圖像描述

長袍區

在此輸入圖像描述

長袍區

在此輸入圖像描述

優選地,我想參數化地控制波的速度和調制。

對我來說,它看起來像某種置換圖。 我想過或者使用一個片段着色器threejs \\ seriously.js或使用畫布元素來實現相同的圖像處理,但我不能確定要使用的算法。

什么是實現這一目標的方法?

振盪器和位移

您可以使用振盪器結合網格來解決這個問題。 網格中的每條線都是振盪的,線之間的差異用於移動圖像的一部分。

在我看來,最簡單的方法是首先創建一個振盪器對象。 它可以是非常基本的,但重點是可以實例化對象,並在本地跟蹤當前值。

第1步:振盪器對象

function Osc(speed) {

    var frame = 0;                           // pseudo frame to enable animation

    this.current = function(x) {             // returns current sinus value
        frame += 0.005 * speed;              // value to tweak..
        return Math.sin(frame + x * speed);
    };
}

例如,展開對象以使用頻率,幅度和速度作為參數。 如果要使用很多,也要考慮采用原型方法。

如果我們創建一個包含五個位置的簡單網格,其中三個中間垂直線正在振盪(邊緣,為1.和5.線),我們將得到這樣的結果(非調整值):

Snap1

振盪網格線的動畫可視化:

 function Osc(speed) { var frame = 0; this.current = function(x) { frame += 0.005 * speed; return Math.sin(frame + x * speed); }; } var canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d"), w = canvas.width, h = canvas.height; var o1 = new Osc(0.05), // oscillator 1 o2 = new Osc(0.03), // oscillator 2 o3 = new Osc(0.06); // oscillator 3 (function loop() { ctx.clearRect(0, 0, w, h); for (var y = 0; y < h; y++) { ctx.fillRect(w * 0.25 + o1.current(y * 0.2) * 10, y, 1, 1); ctx.fillRect(w * 0.50 + o2.current(y * 0.2) * 10, y, 1, 1); ctx.fillRect(w * 0.75 + o3.current(y * 0.2) * 10, y, 1, 1); } requestAnimationFrame(loop); })(); 
 <canvas width=230 height=250></canvas> 

第2步:使用網格中的線條之間的差異圖像

下一步是簡單地計算每條線的生成點之間的差異。

寬度

數學是直截了當的,我們唯一需要確保的是線條不重疊,因為這會產生負寬度:

// initial static values representing the grid line positions:
var w = canvas.width, h = canvas.height,
    x0 = 0, x1 = w * 0.25, x2 = w * 0.5, x3 = w * 0.75, x4 = w;

// absolute positions for wavy lines
var lx1 = x1 + o1.current(y*0.2);  // 0.2 is arbitrary and tweak-able
var lx2 = x2 + o2.current(y*0.2);
var lx3 = x3 + o3.current(y*0.2);

// calculate each segment's width
var w0 = lx1;        // - x0
var w1 = lx2 - lx1;
var w2 = lx3 - lx2;
var w3 =  x4 - lx3;

如果我們現在將這些值提供給目標的drawImage() ,使用源的靜態固定寬度(即網格單元大小),我們將得到如下結果。

我們不需要迭代位圖中的像素,因為drawImage()可以是硬件加速的,不需要滿足CORS要求,並且會為我們進行插值:

 var img = new Image(); img.onload = waves; img.src = "//i.imgur.com/PwzfNTk.png"; function waves() { var canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d"), w = canvas.width, h = canvas.height; ctx.drawImage(this, 0, 0); var o1 = new Osc(0.05), o2 = new Osc(0.03), o3 = new Osc(0.06), x0 = 0, x1 = w * 0.25, x2 = w * 0.5, x3 = w * 0.75, x4 = w; (function loop() { ctx.clearRect(0, 0, w, h); for (var y = 0; y < h; y++) { // segment positions var lx1 = x1 + o1.current(y * 0.2) * 3; // scaled to enhance demo effect var lx2 = x2 + o2.current(y * 0.2) * 3; var lx3 = x3 + o3.current(y * 0.2) * 3; // segment widths var w0 = lx1; var w1 = lx2 - lx1; var w2 = lx3 - lx2; var w3 = x4 - lx3; // draw image lines ---- source ---- --- destination --- ctx.drawImage(img, x0, y, x1 , 1, 0 , y, w0, 1); ctx.drawImage(img, x1, y, x2 - x1, 1, lx1 - 0.5, y, w1, 1); ctx.drawImage(img, x2, y, x3 - x2, 1, lx2 - 1 , y, w2, 1); ctx.drawImage(img, x3, y, x4 - x3, 1, lx3 - 1.5, y, w3, 1); } requestAnimationFrame(loop); })(); } function Osc(speed) { var frame = 0; this.current = function(x) { frame += 0.002 * speed; return Math.sin(frame + x * speed * 10); }; } 
 <canvas width=230 height=300></canvas> 

請注意,由於我們使用小數值,我們需要用半個像素進行補償以與前一個段重疊,因為可以對內部像素進行插值。 否則我們將在結果中看到可見的波浪線。 我們可以使用整數值,但這會產生更“鋸齒”的動畫。

當然需要調整振盪器的值,定義網格尺寸等等。

下一步是重復水平軸的振盪器,並使用畫布本身作為圖像源。

優化和性能

使用畫布本身作為來源警告

當您從畫布到自身繪制某些內容時,瀏覽器必須根據規范制作當前內容的副本,並將其用作目標區域的源。

當畫布或CanvasRenderingContext2D對象被繪制到自身時,繪圖模型需要在繪制圖像之前復制源,因此可以將畫布的一部分或划痕位圖復制到自身的重疊部分。

這意味着對於我們使用畫布本身作為源的每個drawImage()操作,將發生此復制過程。

這可能會影響性能,所以為了避免這種情況,我們可以使用第二個canvas元素,我們首先復制完成的垂直傳遞,然后使用第二個畫布作為水平傳遞的源。

LUT和值緩存

要進一步提高性能,請緩存可緩存的每個值計算。 例如,每個段(x1-x0等)的上面的源寬度可以緩存到sw變量(或其他名稱)。 這就是所謂的微優化,但這是一個重要的情況。

對於正弦值,比例等,將計算緩存到LUT或查找表中可能是有利的。 可以選擇頻率以使表長度在某個水平上匹配。 我沒有在這里展示這個,但是如果瀏覽器難以跟上以防網格具有高分辨率,那么需要考慮一下。

整數值

使用整數值並關閉圖像平滑也是一種選擇。 結果並不像分數值那么好,但它會給復古十歲上下看動畫,並有更好的表現。

精靈片

作為最后的手段,可以動態預生成幀作為精靈表。 這需要更多的內存並且具有初始成本,但幾乎在任何情況下都能順利運行。 挑戰是找到一個循環點,而不是使用太多的內存。

帶alpha通道的圖像

使用alpha通道避免圖像(如下面的演示)將有助於您需要清除兩次額外的,一個用於離屏畫布,一個用於主畫布。 否則,先前的位移將顯示在后台。

最終結果的演示

這是一個包含垂直和水平波浪線的完整演示。 為簡單起見,我只使用4x4網格。

結果與示例看起來並不完全相同,但應該給出一個想法。 這只是提高網格分辨率和調整參數的問題。 此外,問題中給出的示例是使用附加效果和圖層預先設置動畫,而這些效果和圖層只能通過波浪/位移來實現。

其他變化是,現在每個片段的重疊分布在整個片段上,只需在開頭添加0.5,但也可以在結尾處添加。 水平傳遞也是內聯寬度差。

運行下面的演示時單擊“完整頁面”以更好地查看詳細信息。

 var img = new Image(); img.onload = waves; img.src = "//i.imgur.com/nMZoUok.png"; function waves() { var canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d"), w = canvas.width, h = canvas.height; ctx.drawImage(this, 0, 0); var o1 = new Osc(0.05), o2 = new Osc(0.03), o3 = new Osc(0.06), // osc. for vert o4 = new Osc(0.08), o5 = new Osc(0.04), o6 = new Osc(0.067), // osc. for hori // source grid lines x0 = 0, x1 = w * 0.25, x2 = w * 0.5, x3 = w * 0.75, x4 = w, y0 = 0, y1 = h * 0.25, y2 = h * 0.5, y3 = h * 0.75, y4 = h, // cache source widths/heights sw0 = x1, sw1 = x2 - x1, sw2 = x3 - x2, sw3 = x4 - x3, sh0 = y1, sh1 = y2 - y1, sh2 = y3 - y2, sh3 = y4 - y3, vcanvas = document.createElement("canvas"), // off-screen canvas for 2. pass vctx = vcanvas.getContext("2d"); vcanvas.width = w; vcanvas.height = h; // set to same size as main canvas (function loop() { ctx.clearRect(0, 0, w, h); for (var y = 0; y < h; y++) { // segment positions var lx1 = x1 + o1.current(y * 0.2) * 2.5, lx2 = x2 + o2.current(y * 0.2) * 2, lx3 = x3 + o3.current(y * 0.2) * 1.5, // segment widths w0 = lx1, w1 = lx2 - lx1, w2 = lx3 - lx2, w3 = x4 - lx3; // draw image lines ctx.drawImage(img, x0, y, sw0, 1, 0 , y, w0 , 1); ctx.drawImage(img, x1, y, sw1, 1, lx1 - 0.5, y, w1 + 0.5, 1); ctx.drawImage(img, x2, y, sw2, 1, lx2 - 0.5, y, w2 + 0.5, 1); ctx.drawImage(img, x3, y, sw3, 1, lx3 - 0.5, y, w3 + 0.5, 1); } // pass 1 done, copy to off-screen canvas: vctx.clearRect(0, 0, w, h); // clear off-screen canvas (only if alpha) vctx.drawImage(canvas, 0, 0); ctx.clearRect(0, 0, w, h); // clear main (onlyif alpha) for (var x = 0; x < w; x++) { var ly1 = y1 + o4.current(x * 0.32), ly2 = y2 + o5.current(x * 0.3) * 2, ly3 = y3 + o6.current(x * 0.4) * 1.5; ctx.drawImage(vcanvas, x, y0, 1, sh0, x, 0 , 1, ly1); ctx.drawImage(vcanvas, x, y1, 1, sh1, x, ly1 - 0.5, 1, ly2 - ly1 + 0.5); ctx.drawImage(vcanvas, x, y2, 1, sh2, x, ly2 - 0.5, 1, ly3 - ly2 + 0.5); ctx.drawImage(vcanvas, x, y3, 1, sh3, x, ly3 - 0.5, 1, y4 - ly3 + 0.5); } requestAnimationFrame(loop); })(); } function Osc(speed) { var frame = 0; this.current = function(x) { frame += 0.002 * speed; return Math.sin(frame + x * speed * 10); }; } 
 html, body {width:100%;height:100%;background:#555;margin:0;overflow:hidden} canvas {background:url(https://i.imgur.com/KbKlmpk.png);background-size:cover;height:100%;width:auto;min-height:300px} 
 <canvas width=230 height=300></canvas> 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM