动态

详情 返回 返回

純前端實現圖片偽3D視差效果 - 动态 详情

作者:vivo 互聯網前端團隊- Su Ning

本文通過depth-anything獲取圖片的深度圖,同時基於pixi.js,通過着色器編程,實現了通過深度圖驅動的偽3D效果。該方案支持鼠標/手勢與手機陀螺儀雙模式交互,在保證性能的同時,為不同終端用户提供沉浸式的視覺體驗。

本文提供配套演示代碼,可下載體驗:

Github | vivo-parallax

一、引言

在當今的網頁設計與交互中,3D 效果能極大地提升用户的視覺體驗和沉浸感。但是3D的物料設計成本依然很高,不僅需要專門的設計師掌握專業的建模工具,而且高精度模型帶來的渲染壓力也使移動端適配變得困難。

在這樣的背景下,利用2D圖片實現偽3D的效果,就展現出獨特的價值。開發者能以極低的資源消耗,在常規圖片素材上構建出具有空間縱深的交互效果。這種技術路徑不僅規避了傳統3D內容生產的複雜性,同時實現了視覺效果與性能消耗的平衡。

二、實現思路

相比二維平面,三維物體多了一個 z 軸作為深度信息。要讓 2D 平面呈現 3D 縱深感,關鍵在於隨着視角偏移時,畫面中的物體產生不同程度的位移,從而營造前後視差,實現偽 3D 效果。

為此,我們可以通過深度圖來獲取圖片的深度信息,根據這些信息對圖片進行分層。當視角改變時,通過調整不同層的偏移來實現視差效果。

三、獲取深度圖

在前端獲取深度圖可以藉助現有的預訓練模型。例如使用 @huggingface/transformers 庫,指定任務類型為 'depth-estimation',並選擇合適的預訓練模型,目前的深度圖推理模型尺寸普遍比較大,綜合效果和模型尺寸最終選擇了 'Xenova/depth-anything-small-hf',量化後的模型尺寸為27.5mb。

import { pipeline } from '@huggingface/transformers';
export async function depthEstimator(url) {
  const depth_estimator = await pipeline('depth-estimation', 'Xenova/depth-anything-small-hf');
  const output = await depth_estimator(url);
  const blob=await output.depth.toBlob()
  return URL.createObjectURL(blob)
}

四、視差效果的實現

若想借助深度圖實現圖片分層,可依據深度區間進行劃分。假設深度圖中純白的色值為 0,純黑色值為 1,若將圖片切分為兩層,那麼第一層的色值範圍為 0 - 0.5,第二層則是 0.5 - 1。為使畫面過渡更自然,可適當增加分層的數量。當鏡頭偏移時,層數越小的圖片位移幅度越大,層數越大的圖片位移幅度越小,藉此便能實現視差效果。

然而,簡單的分層會引發一個問題:不同層的位移可能導致上層的部分區域遮擋背景圖案,而另一側則會出現空白。

圖片

針對空白部分,可採用光線步進算法進行顏色採樣。

在此,我們選用 Pixi.js 來實現這一效果。作為一款輕量級的 2D 渲染引擎,Pixi.js 在跨平台 2D 動畫、遊戲及圖形界面開發領域表現出色。其精靈支持自定義渲染管線,通過定製圖片片段着色器,能夠輕鬆實現視差效果。

4.1 光線步進算法(Ray Marching)

首先我們獲取到需要採樣顏色的座標ray_origin,並根據用户的交互事件(鼠標,觸摸,陀螺儀)增加鏡頭偏移offset。得到光線發射的起始座標。

設置採樣步數step,設置光線的偏移向量ray\_direction,每一步將光線增加ray\_direction/step的座標。獲取到當前深度圖座標的深度信息,由於顏色越淺數值越大,要對深度值進行反轉,比對此時光線的z軸是否大於深度的反轉值,如果滿足條件則挑出循環,取此時光線座標圖片的顏色。

由於每一步增加的偏移值可能跨度比較大,即使滿足z軸大於深度反轉值的條件,但是二者值的差距依然過大,我們還需要做一個二分搜索來優化採樣結果。即偏移值大於深度值,但二者的差值大於閾值的時候,回退一步光線,並將步進值再除以2,可以顯著提升採樣的精度。

圖片

代碼實現

varying vec2 vTextureCoord;
uniform sampler2D depthMap;
uniform sampler2D uSampler;
uniform vec3 offset;
const float enlarge = 1.06;

vec3 perspective(vec2 uv) {
  const int step_count = 5;
  
  vec3 ray_origin = vec3(uv - 0.5, 0);
  ray_origin.xy -= offset.xy;
  
  vec3 ray_direction = vec3(0, 0, 1);
  ray_direction.xy += offset.xy;
  ray_direction /= float(step_count);
  
  const float hit_threshold = 0.01;
  vec4 color = vec4(0.0);
  for (int i = 0; i < step_count; i++) {
    ray_origin += ray_direction;
    float scene_z = 1.0 - texture2D(depthMap, ray_origin.xy + 0.5).x;
    if (ray_origin.z > scene_z) {
      if (ray_origin.z - scene_z < hit_threshold) {
        break;
      }
    ray_origin -= ray_direction;
    ray_direction /= 2.0;
    }
  }
  color = texture2D(uSampler, ray_origin.xy + 0.5);
  return color.rgb;
}

void main(void ) {
  vec2 uv = (vTextureCoord - vec2(0.5)) / vec2(enlarge) + vec2(0.5);
  gl_FragColor = vec4(
    perspective(uv),
    1.0
  );
}



五、深度圖膨脹

邊緣膨脹操作主要用於處理深度圖,通過對每個像素鄰域內的深度值進行分析和處理,增強圖像的邊緣,可以使視差圖的邊緣更加平滑。這裏使用一個簡單的膨脹函數實現。

圖片

varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
uniform float widthPx;
uniform float heightPx;
uniform float dilation;
uniform sampler2D uSampler;
const int MAX_RADIUS = 10;

float dilate(vec2 uv, vec2 px) {
  float maxValue = 0.0;
  float minValue = 1.0;
  for (int x = -MAX\_RADIUS; x <= +MAX_RADIUS; x++) {
    for (int y = -MAX\_RADIUS; y <= +MAX_RADIUS; y++) {
      vec2 offset = vec2(float(x), float(y));
      if (length(offset) > float(MAX_RADIUS)) continue;
      offset *= px;
      vec2 uv2 = uv + offset;
      float val = texture2D(uSampler, uv2).x;
      maxValue = max(val, maxValue);
      minValue = min(val, minValue);
    }
  }
  
  return dilation < 0.0
  ? minValue
  : maxValue;
}

  

void main(void ) {
  const float dilationScale = 1.26;
  float dilationStep = abs(dilationScale * dilation) / float(MAX_RADIUS);
  float aspect = widthPx / heightPx;
  vec2 px =
    widthPx > heightPx
      ? vec2(dilationStep / aspect, dilationStep)
      : vec2(dilationStep, dilationStep * aspect);
  gl_FragColor = vec4(vec3(dilate(vTextureCoord, px)), 1.0);

}



六、總結

綜上所述,我們先利用預訓練模型生成圖片的深度圖,再借助 Pixi.js 與光線步進算法達成視差效果,最終通過對深度圖進行膨脹處理,實現邊緣的平滑過渡。

通過上面的操作,我們成功實現了圖片的偽 3D 效果,為用户帶來了更具沉浸感的視覺體驗。

在實際應用過程中,我們觀察到,當視角偏移幅度過大時畫面會出現採樣失真現象。為解決這一問題,後續可考慮採用動態調整光線步進參數的方法,根據視角變化實時優化光線傳播路徑,從而減少採樣誤差;或者引入屏幕空間遮擋關係,通過精準模擬物體間的遮擋效果,增強畫面的真實感與層次感。隨着 WebGPU 技術的逐步普及,這一方案還有極大的優化空間。我們可藉助計算着色器強大的並行計算能力,對複雜的 3D 計算任務進行高效處理,進一步提升計算性能,為網頁端 3D 交互開闢更多可能性,打造更加流暢、逼真的 3D 交互場景。

user avatar zhidechaomian_detxs7 头像 yixiyidong 头像 yulong1992 头像 milton 头像 gvison 头像 chen_5ec331606ce75 头像 zhuyunbo 头像 javadog 头像 tgshell 头像 aphysia 头像 fennudebiandang 头像 doge_king 头像
点赞 22 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.