精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

如何用Three.js + Blender打造一個(gè)web 3D展覽館

開發(fā) 前端
運(yùn)營活動(dòng)新玩法層出不窮,web 3D炙手可熱,本文將一步步帶大家了解如何利用Three.js和Blender來打造一個(gè)沉浸式web 3D展覽館。

一、前言

3D展覽館是什么,先來預(yù)覽下效果:

圖片

看起來像個(gè)3D冒險(xiǎn)類手游,用戶可以操縱屏幕中央的虛擬搖桿,以第一人稱視角在房間內(nèi)自由移動(dòng)、看展覽。

1.1 為什么做3D展覽館

首先介紹一個(gè)背景,我們的工作內(nèi)容是做游戲中心的用戶運(yùn)營活動(dòng),會(huì)做些好玩的活動(dòng)讓用戶參與,并get一些福利。

當(dāng)時(shí)的活動(dòng)背景是我司一年一度的vivo游戲節(jié),并且元宇宙是大熱詞。所以做它的原因有幾個(gè):

  • vivo游戲節(jié)主題
  • 契合元宇宙熱點(diǎn)
  • 新玩法、新體驗(yàn)

1.2 技術(shù)選型

用到的組合方案:Three.js + Blender

  • why Three.js

開源的3D框架有很多,但最常用的有兩種:Three.js、Babylon.js,我們只需要從中二選一。分析后發(fā)現(xiàn)兩者各有優(yōu)勢(shì):

圖片

考慮到3D展覽館的幾個(gè)基本特性:

  1. 簡(jiǎn)單的小型3D場(chǎng)景,沒有復(fù)雜的交互(對(duì)鏡頭的要求不高)
  2. 投放在移動(dòng)設(shè)備,需要盡可能小的包體,以提升性能
  3. 工期短,需要快速上手及更多的案例參考

Three.js包體更小、有更多參考案例、上手更快,所以雖然Babylon.js有它的優(yōu)勢(shì),但Three.js更適合這個(gè)項(xiàng)目。

  • why Blender

Blender是一款輕量的開源3D建模軟件,有很多好用的免費(fèi)插件,而且Blender能導(dǎo)出GLTF / GLB模型(后面會(huì)對(duì)GLTF / GLB模型做簡(jiǎn)介),匹配Three.js的使用方式,整體更簡(jiǎn)單好用一些。

所以,就是它了。

二、實(shí)踐部分

2.1 了解GLTF / GLB模型

在進(jìn)入開發(fā)之前,先簡(jiǎn)單了解Blender和GLTF / GLB模型。

  • 簡(jiǎn)單了解 Blender

首先,Blender大概長這樣,圖中是設(shè)計(jì)師交付的3D展覽館稿子。簡(jiǎn)單理解為,左側(cè)是模型的層次結(jié)構(gòu),中間是模型的預(yù)覽效果,右側(cè)是模型的屬性面板。

一般來說,作為開發(fā)者我們不需要掌握太多Blender相關(guān)知識(shí),只需知道如何看懂模型結(jié)構(gòu)、導(dǎo)出GLTF / GLB模型以及烘焙的基本原理即可。

圖片

  • GLTF / GLB模型

GLTF(Graphics Language Transmission Format)是一種標(biāo)準(zhǔn)的3D模型文件格式,它以JSON的形式存儲(chǔ)3D模型信息,例如模型的層次結(jié)構(gòu)、材質(zhì)、動(dòng)畫、紋理等。

模型中依賴的靜態(tài)資源,比如圖片,可以通過外部URI的方式來引入,也可以轉(zhuǎn)成base64直接插入在GLTF文件中。

它包含兩種形式的后綴,分別是.gltf(JSON/ASCII).glb(Binary)。.gltf是以JSON的形式存儲(chǔ)信息。.glb則是.gltf的擴(kuò)展格式,它以二進(jìn)制的形式存儲(chǔ)信息,因此導(dǎo)出的模型體積也更小一些。如果我們不需要通過JSON對(duì).gltf模型進(jìn)行直接修改,建議使用.glb模型,它更小、加載更快。

  • Blender導(dǎo)出GLTF / GLB模型

在blender中,可以直接將模型導(dǎo)出為GLTF / GLB格式,三種選項(xiàng)的差別不再贅述,我們先簡(jiǎn)單選擇最高效的.glb格式。

圖片

有了模型之后,我們可以開始通過Three.js創(chuàng)建場(chǎng)景,并導(dǎo)入這個(gè)模型了。

2.2 Three.js 加載模型

為了防止篇幅過長,這里假設(shè)大家已經(jīng)掌握了Three.js的一些基本語法。文章重點(diǎn)放在如何加載模型,并一步步進(jìn)行調(diào)優(yōu)和實(shí)現(xiàn)最終的3D展覽館效果。

怎么加載一個(gè)模型?

(1)創(chuàng)建一個(gè)空?qǐng)鼍?/strong>

首先創(chuàng)建一個(gè)空?qǐng)鼍皊cene,后續(xù)所有的模型或材質(zhì)都會(huì)被添加到這個(gè)場(chǎng)景中。

import * as THREE from 'three'
// 1. 創(chuàng)建場(chǎng)景
const scene = new THREE.Scene(); 
// 2. 創(chuàng)建鏡頭
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
// 3. 創(chuàng)建Renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

(2)導(dǎo)入GLTF / GLB模型

通過GLTFLoader導(dǎo)入.glb模型,并添加到場(chǎng)景中。

import GLTFLoader from 'GLTFLoader'
const loader = new GLTFLoader()
loader.load('path/to/gallery.glb',
  gltf => {
    scene.add(gltf.scene) // 添加到場(chǎng)景中
  } 
}

(3)開始渲染

通過requestAnimationFrame來調(diào)用renderer.render方法,開始實(shí)時(shí)渲染場(chǎng)景。

function animate() {
      requestAnimationFrame( animate );
      renderer.render( scene, camera );
}
animate();

ok,這樣我們就完成了3D模型的導(dǎo)入,但是發(fā)現(xiàn)整個(gè)場(chǎng)景一片漆黑。

圖片圖片

試試加個(gè)環(huán)境光。

const ambientLight = new THREE.AmbientLight(0xffffff, 1)
scene.add(ambientLight)

圖片

ok,亮起來了,但是效果依然很差,很劣質(zhì)。

原因是模型中的材質(zhì)效果、光源、陰影、環(huán)境紋理,這些全都丟失了,所以當(dāng)我們導(dǎo)入模型時(shí),看到的就是一堆簡(jiǎn)陋的純色形狀。

所以我們要一步步將這些丟失東西找回,還原設(shè)計(jì)稿。

2.3 還原設(shè)計(jì)稿

接下來一步步還原設(shè)計(jì)稿。

(1)加上光源

查看Blender模型,看到設(shè)計(jì)稿中添加了一堆點(diǎn)光源、平行光源。

圖片

點(diǎn)光源可以理解為房間中的燈泡,光線強(qiáng)弱隨著距離衰減;

平行光源可以理解為太陽的直射光,它和點(diǎn)光源不同,光線強(qiáng)弱不隨著距離衰減。

于是我們也增加一些光源:

// 一些燈光選項(xiàng)
// 如果是平行光則沒有distance、decay選項(xiàng)
const lightOptions = [
  {
    type: 'point', // 燈光類型:1. point點(diǎn)光源、2. directional平行光源
    color: 0xfff0bf, // 燈光顏色
    intensity: 0.4, // 燈光強(qiáng)度
    distance: 30,    // 光照距離
    decay: 2,    // 衰減速度
    position: { // 光源位置
      x: 2,
      y: 6,
      z: 0
    }
  },
  ...
]
function createLights() {
  pointLightOptions.forEach(option => {
    const light = option.type === 'point' ?
        new THREE.PointLight(option.color, option.intensity, option.distance, option.decay) :
        new THREE.DirectionalLight(option.color, option.intensity)
    const position = option.position
    light.position.set(position.x, position.y, position.z)
    scene.add(light)
  })
}
createLights()

可以看到場(chǎng)景比之前好了一些,有了光源后,模型變得立體和真實(shí)了,多了一些反色的光澤。

圖片圖片

圖片圖片

但是我們注意到,畫面中的logo、長椅的兩側(cè)都是黑色的,并且旁邊的球體、椅子等都顯得不夠真實(shí)。

所以,我們需要進(jìn)行下一步調(diào)整:調(diào)整模型材質(zhì)、增加環(huán)境紋理。

(2)調(diào)整模型材質(zhì),增加環(huán)境紋理

先簡(jiǎn)單了解一下材質(zhì)和環(huán)境紋理。

  • 材質(zhì)(material)

材質(zhì)就像物體的皮膚,我們可以調(diào)整皮膚的光澤、金屬度、粗糙度、透明與否等屬性,讓物體有不同的視覺效果。

一般從blender導(dǎo)出的模型中,已經(jīng)包含了一些材質(zhì)屬性,但是Three.js中的材質(zhì)屬性和Blender中的屬性并非完全的映射關(guān)系,模型在導(dǎo)入到Three.js后,效果和設(shè)計(jì)稿會(huì)有差異。這時(shí)候我們需要手動(dòng)調(diào)整材質(zhì)的屬性,來達(dá)到和設(shè)計(jì)稿近似的效果。

  • 環(huán)境紋理(environment map)

環(huán)境紋理就是讓模型映射周圍的環(huán)境,讓場(chǎng)景或物體更真實(shí)。例如我們要渲染一個(gè)立方體,把立方體放進(jìn)一個(gè)屋子里,這個(gè)屋子的環(huán)境就會(huì)影響立方體的渲染效果。

比如鏡面的物體被貼上環(huán)境紋理后,就可以實(shí)時(shí)反射周圍的環(huán)境鏡像,看起來很real。

設(shè)計(jì)稿中也是將一個(gè)大廳作為了環(huán)境紋理,讓場(chǎng)景更真實(shí)。

圖片

環(huán)境紋理分為:球形紋理和立方體形紋理。兩者都可以,這里我們采用一張大廳的球形紋理作為環(huán)境貼圖。

圖片

以畫面中的vivo游戲節(jié)logo為例,我們通過調(diào)整它的材質(zhì)和環(huán)境紋理,讓它變得更真實(shí)。

  1. 根據(jù)在blender中的命名,找到logo模型
  2. 調(diào)整logo的表面粗糙度和金屬度
  3. 加載并設(shè)置環(huán)境紋理貼圖

圖片

const loader = new GLTFLoader()
loader.load('path/to/gallery.glb',
  gltf => {
      // 1. 根據(jù)Blender中物體的名字,找到logo模型
      gltf.scene.traverse(child => {
        if (isLogo(child)) {
       initLogo(child)   // 2. 調(diào)整材質(zhì)
       setEnvMap(child)  // 3. 設(shè)置環(huán)境紋理
      }
      })
    scene.add(gltf.scene)
  } 
}
// 判斷是否為Logo
const isLogo = object.name === 'logo'
function initLogo(object) {
  object.material.roughness = 0   // 調(diào)整表面粗糙度
  object.material.metalness = 1   // 調(diào)整金屬度
}
// 加載環(huán)境紋理
let envMap
const envmaploader = new THREE.PMREMGenerator(renderer)
const setEnvMap = (object) => {
      if(envMap) {
        object.material.envMap = envMap.texture
      } else {
        textureLoader.load('path/to/envMap.jpg',
             texture => {
             texture.encoding = THREE.sRGBEncoding
             envMap = envmaploader.fromCubemap(texture)
                 object.material.envMap = envMap.texture
             })
      }
}

經(jīng)過上面的處理后,可以看到原先黑色的logo有了金屬光澤,并且會(huì)反射周圍的環(huán)境紋理。

其它物體經(jīng)過類似的處理后,也變得更真實(shí)一些。

圖片圖片

圖片圖片

圖片圖片

圖片圖片

現(xiàn)在整個(gè)場(chǎng)景更接近了設(shè)計(jì)稿一些,但場(chǎng)景中少了陰影,顯得很干癟。

加上陰影。

(3)增加陰影

增加陰影分四步:

  1. 對(duì)renderer開啟陰影支持:renderer.shadowMap.enabled = true
  2. 對(duì)光源設(shè)置:castShadow = true
  3. 對(duì)需要投影的物體設(shè)置:castShadow = true
  4. 對(duì)需要被投影的平面或物體(比如地板)設(shè)置:receiveShadow = true
// 1. renderer
const renderer = new THREE.WebGLRenderer()
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 2. light
const light = new THREE.DirectionalLight()
light.castShadow = true;
// 3. object
gltf.scene.traverse(function (child) {
   if (child.isMesh) {
     child.castShadow = true;
   }
});
// 4. floor
floor.receiveShadow = true

圖片

加陰影后,有質(zhì)的提升,發(fā)現(xiàn)整個(gè)場(chǎng)景立體了很多,此時(shí)還原度已經(jīng)很高。

如果不考慮性能損耗,這個(gè)場(chǎng)景的樣式已經(jīng)可以投入使用了。(后續(xù)會(huì)提到性能優(yōu)化)

小結(jié)一下,剛剛做的幾件事:

  1. 添加光源
  2. 調(diào)整模型材質(zhì)、增加環(huán)境紋理
  3. 增加陰影

現(xiàn)在3D展覽館場(chǎng)景已經(jīng)還原的差不多了,接下來要構(gòu)造一個(gè)虛擬移動(dòng)搖桿,控制第一人稱鏡頭的移動(dòng)和轉(zhuǎn)向,實(shí)現(xiàn)沉浸式逛展的效果。

2.4 虛擬移動(dòng)搖桿

要實(shí)現(xiàn)通過虛擬移動(dòng)搖桿控制鏡頭的移動(dòng)和轉(zhuǎn)向,我們需要三個(gè)東西:

  • 一個(gè)移動(dòng)搖桿(handler)
  • 一個(gè)長方體(player):用于承載第一人稱視角
  • 一個(gè)鏡頭(camera):之前已經(jīng)創(chuàng)建過了

有人會(huì)問為什么需要一個(gè)player,通過搖桿直接控制鏡頭不就行了嗎?其實(shí)player的作用是用于做碰撞檢測(cè),當(dāng)player遇到凳子、墻壁等障礙物時(shí),需要停止鏡頭移動(dòng)。直接控制鏡頭,是無法做碰撞檢測(cè)的。

所以,實(shí)際上鏡頭移動(dòng)的邏輯是:

用戶操縱搖桿 → 更新player位置和朝向 →從而同步更新camera位置和朝向

(1)創(chuàng)建移動(dòng)搖桿

移動(dòng)搖桿的實(shí)現(xiàn)原理很簡(jiǎn)單,這里僅做簡(jiǎn)述。

核心在于創(chuàng)建一個(gè)圓盤,監(jiān)聽觸摸手勢(shì),并根據(jù)手勢(shì)的方向來實(shí)時(shí)更新move參數(shù),控制鏡頭的移動(dòng)和轉(zhuǎn)向。

const speed = 8 // 移動(dòng)速度
const turnSpeed = 3  // 轉(zhuǎn)向速度
// move option,用于調(diào)整第一人稱鏡頭的移動(dòng)和轉(zhuǎn)向
const move = {
      turn: 0,  // 旋轉(zhuǎn)角度
      forward: 0     // 前進(jìn)距離
}
// 創(chuàng)建一個(gè)handler,并監(jiān)聽手勢(shì),調(diào)整move option
const handler = new Handler()
handler.onTouchMove = () => { // update move option }

(2)創(chuàng)建player

首先創(chuàng)建一個(gè)player對(duì)象,它是一個(gè)1.2 * 2 * 1的透明長方體。

function createPlayer() {
  const box = new THREE.BoxGeometry(1.2, 2, 1)
  const mat = new THREE.MeshBasicMaterial({
    color: 0x000000,
    wireframe: true
  })
  const mesh = new THREE.Mesh(box, mat)
  box.translate(0, 1, 0)
  return mesh
}
const player = createPlayer() // 創(chuàng)建player
player.position.set(4.5, 2, 12)     // 設(shè)置player的初始位置

(3)updatePlayer & updateCamera

每次渲染(render)時(shí),更新player的位置和朝向,并同步更新鏡頭的位置和朝向。

const clock = THREE.clock()
function render() {
  const dt = clock.delta()   // 獲取每幀之間的時(shí)間間隔,根據(jù)時(shí)間間隔長短來更新player和camera的移動(dòng)距離和轉(zhuǎn)向的多少
  updatePlayer(dt)
  updateCamera(dt)
  renderer.render(scene, camera)
  window.requestAnimationFrame(render)
}
// 更新player的位置和朝向
function updatePlayer(dt) {
  const pos = player.position.clone()
  pos.y -= 1.5 // 降低高度,后續(xù)用于計(jì)算碰撞檢測(cè)
  const dir = new THREE.Vector3()
  player.getWorldDirection(dir)
  dir.negate()
  if (move.forward < 0) dir.negate()
  // 調(diào)整鏡頭前進(jìn) or 后退
  if (move.forward !== 0) {
    player.translateZ(move.forward > 0 ? -dt * speed : dt * speed * 0.5)
  }
  // 調(diào)整鏡頭朝向
  if (move.turn !== 0) {
    player.rotateY(move.turn * 1.2 * dt)
  }
}
// 根據(jù)player的位置和朝向,同步更新camera的位置和朝向
function updateCamera(dt) {
  camera.position.lerp(activeCamera.getWorldPosition(new THREE.Vector3()), 0.08)
  const pos = player.position.clone()
  pos.y += 2.5
  camera.lookAt(pos)
}

注意:render方法中使用clock.delta()來計(jì)算每次渲染之間的時(shí)間間隔,并使用這個(gè)時(shí)間間隔來更新player和camera。因?yàn)樵诶硐氲?0幀率情況下,兩幀時(shí)間間隔為16.67ms,但實(shí)際上該數(shù)值會(huì)有波動(dòng),因此我們要根據(jù)實(shí)際的渲染時(shí)間間隔來更新player和camera,讓鏡頭的移動(dòng)和轉(zhuǎn)向幅度更自然一些。

完成上述步驟后,我們就可以通過控制虛擬移動(dòng)搖桿,來讓鏡頭移動(dòng)和轉(zhuǎn)向了。

接下來加入碰撞檢測(cè),對(duì)鏡頭移動(dòng)加點(diǎn)限制。

2.5 碰撞檢測(cè)

碰撞檢測(cè)的步驟也很簡(jiǎn)單:

  • 收集障礙物(colliders)
  • 檢測(cè)碰撞(基于THREE.Raycaster)

(1)收集障礙物

模型加載完成后,遍歷所有的child,如果child是一個(gè)物體(mesh),則把它加入到障礙物隊(duì)列(colliders)中。

const colliders = []
loader.load('path/to/gallery.glb',
  gltf => {
    gltf.scene.traverse(child => {
        // 收集障礙物
        if(isMesh(child)) {
          colliders.push(child) 
        }
    })
  } 
})

(2)檢測(cè)碰撞

調(diào)整剛剛的updatePlayer方法,在其中插入檢測(cè)碰撞的邏輯。

碰撞檢測(cè)邏輯基于THREE.Raycaster來實(shí)現(xiàn),racaster可以理解為一個(gè)射線,當(dāng)射線穿過了某個(gè)物體,我們就認(rèn)為射線和物體相交了。

我們讓射線的方向和player的朝向保持一致,并且在移動(dòng)過程中不斷判斷射線前方/后面是否有相交的物體,如果有相交的物體,且和射線頂點(diǎn)距離distance < 2.5則認(rèn)為遇到了障礙物,不能再繼續(xù)前進(jìn)。

function updatePlayer(dt) {
  const pos = player.position.clone()
  pos.y -= 1.5 // 降低高度,用于計(jì)算collision
  const dir = new THREE.Vector3()
  // 獲取當(dāng)前player的朝向
  player.getWorldDirection(dir)
  dir.negate()
  // 如果是向后退,需要對(duì)朝向取反
  if (move.forward < 0) dir.negate()
  // 利用Raycaster判斷player是否和colliders有碰撞行為
  const raycaster = new THREE.Raycaster(pos, dir)
  let blocked = false
  if (colliders.length > 0) {
    const intersect = raycaster.intersectObjects(colliders)
    if (intersect.length > 0) {
      // 如果相交距離<2.5,表示前方或后面有障礙物
      if (intersect[0].distance < 2.5) {
        blocked = true
      }
    }
  }
  // 如果遇到障礙物,則停滯移動(dòng)


  if (!blocked) {
    // 調(diào)整鏡頭前進(jìn) or 后退
    if (move.forward !== 0) {
      player.translateZ(move.forward > 0 ? -dt * speed : dt * speed * 0.5)
    }
  }
  // 調(diào)整鏡頭朝向
  if (move.turn !== 0) {
    player.rotateY(move.turn * 1.2 * dt)
  }
}

這樣鏡頭的移動(dòng)和碰撞檢測(cè)就完成了。

當(dāng)我們移動(dòng)到椅子、墻壁等障礙物附近時(shí),鏡頭會(huì)停止移動(dòng)。鏡頭的移動(dòng)范圍也被我們限制在房間里,不會(huì)穿到房間外部。

圖片

三、性能調(diào)優(yōu)

3.1 紋理烘培

3D展覽館的基本功能已經(jīng)完成了,但還沒有做任何的性能調(diào)優(yōu)。當(dāng)我們把項(xiàng)目運(yùn)行在手機(jī)上,會(huì)發(fā)現(xiàn)設(shè)備發(fā)熱發(fā)燙,幀率很低,低端機(jī)型甚至無法運(yùn)行。

經(jīng)過分析,實(shí)時(shí)的光影渲染是罪魁禍?zhǔn)住?/p>

頁面中有10+個(gè)光源,每個(gè)光源都在實(shí)時(shí)投射陰影(尤其是點(diǎn)光源十分消耗資源,引起卡頓)。但實(shí)際,場(chǎng)景中的光源和物體位置都沒有發(fā)生改變,這意味著我們不需要計(jì)算實(shí)時(shí)陰影,只需要固定的陰影。

這點(diǎn)可以通過紋理烘焙來實(shí)現(xiàn)。并且在移動(dòng)端,經(jīng)過紋理烘焙的光影效果實(shí)際上要優(yōu)于設(shè)備計(jì)算的實(shí)時(shí)光影效果。

  • 紋理烘焙(Texture Baking)

紋理烘焙,是指通過將場(chǎng)景效果預(yù)渲染到指定紋理上,生成一個(gè)模型貼圖。在Blender中,我們可以選中任意對(duì)象進(jìn)行烘焙。

圖片

以3D展覽館的地板為例,我們可以通過紋理烘焙,將光影效果直接渲染到貼圖上。

左圖是原本的棋盤格紋理,右圖是結(jié)合了光影效果的烘焙貼圖。烘焙完成后,地板上的光影效果就被固定下來了,我們也不需要再做實(shí)時(shí)的光影渲染。

圖片圖片

圖片圖片

用同樣的方式,將地板、墻壁、天花板等物體,一一進(jìn)行烘焙處理,導(dǎo)出一個(gè)新的模型。由于光影效果已經(jīng)被渲染到貼圖上,我們可以將大部分光源去掉,只保留2-3個(gè)必要的點(diǎn)、平行光源和全局光。再次運(yùn)行后,發(fā)現(xiàn)卡頓、發(fā)燙的問題已經(jīng)不再明顯。并且效果其實(shí)比實(shí)時(shí)渲染更精細(xì)一些。

圖片圖片

圖片圖片

這里沒有對(duì)烘焙做過多介紹,要生成精致的烘焙結(jié)果還需要依賴對(duì)UV Map、烘焙參數(shù)的了解,雖然這些偏向于設(shè)計(jì)同學(xué)的工作,一般由他們來輸出烘焙紋理。但是作為開發(fā)者,了解了這些后才能和UI更好地溝通和配合。

3.2 優(yōu)化模型大小

模型大小約為23M,首次加載模型需要9s左右。(尤其是在做完紋理烘焙后,由于貼圖變得復(fù)雜,模型更大了)

以下是幾個(gè)優(yōu)化模型大小的建議:

  1. 優(yōu)先使用.glb而非.gltf格式。.glb是二進(jìn)制格式,它比.gltf的JSON格式小25% - 30%左右。
  2. 將紋理(Texture)和模型分離,并行加載。23M的模型中,其實(shí)只有2.3M為模型大小,其余都為紋理貼圖。將模型和紋理分開后,可以極大減少模型的加載速度。
  3. 使用Draco、gltfpack等工具或一些online compressor來壓縮模型(Blender在導(dǎo)出gltf模型時(shí),就帶有基于Draco的壓縮選項(xiàng))。本項(xiàng)目通過該步驟壓縮了50%的模型大小:3M → 1.2M。
  4. 壓縮紋理(Texture)。本項(xiàng)目用到了5張的Texture,壓縮后:18M→ 2M。

經(jīng)過優(yōu)化,初始模型大小由23M縮小為1.2M,首次加載時(shí)間由9s縮短到3s以內(nèi)。

(左圖為優(yōu)化前,右圖為優(yōu)化后)

圖片圖片

圖片圖片

四、總結(jié)

現(xiàn)在,我們基本完成了整個(gè)3D展覽館的開發(fā)。雖然有一些細(xì)節(jié)沒有在文中涉及到,但開發(fā)過程大致如此。

(1)了解Blender、GLTF / GLB模型

(2)js導(dǎo)入GLTF / GLB模型

(3)還原設(shè)計(jì)稿

  • 添加光源
  • 調(diào)整模型材質(zhì)、增加環(huán)境紋理
  • 增加陰影

(4)實(shí)現(xiàn)虛擬移動(dòng)搖桿,控制鏡頭移動(dòng)

(5)增加碰撞檢測(cè)

(6)性能調(diào)優(yōu):

  • 紋理烘培:通過紋理烘焙降低實(shí)時(shí)光影的性能損耗。
  • 優(yōu)化包體大小:
    - 優(yōu)先使用.glb而非.gltf格式
    - 紋理和模型分離
    - 壓縮模型
    - 壓縮紋理

五、其他

一些建議:

  • 設(shè)計(jì)師在Blender中命名物體、材質(zhì)時(shí)要規(guī)范化,避免出現(xiàn)奇怪或沒有標(biāo)識(shí)意義的命名,因?yàn)樵陂_發(fā)過程中會(huì)使用到,容易混淆。
  • 設(shè)計(jì)師在在Blender中復(fù)用材質(zhì)要謹(jǐn)慎,避免開發(fā)在調(diào)整某個(gè)材質(zhì)時(shí),影響到其它使用到相同材質(zhì)的物體(潛在bug)。
  • 模型加載緩慢時(shí),可以增加loading進(jìn)度條,緩解等待焦慮。Three.js loader支持加載進(jìn)度查詢。
  • Three.js在不同版本之間,接口頻繁變更,在使用時(shí)注意版本差異,google問題時(shí)也要注意接口兼容性。
  • Three.js實(shí)現(xiàn)物體發(fā)光效果較繁瑣,且消耗性能,設(shè)計(jì)時(shí)可盡量避免使用。
  • Three.js的鏡頭移動(dòng)不夠絲滑,注重鏡頭切換流暢性的項(xiàng)目,可以嘗試用Babylon.js。
  • 部分瀏覽器不支持videoTexture(在模型中播放視頻),謹(jǐn)慎設(shè)計(jì)該類型功能,或做好兼容處理。
責(zé)任編輯:龐桂玉 來源: vivo互聯(lián)網(wǎng)技術(shù)
相關(guān)推薦

2019-11-29 09:30:37

Three.js3D前端

2024-07-18 06:58:36

2022-01-16 19:23:25

Three.js粒子動(dòng)畫群星送福

2025-09-19 09:29:53

Web 3D引擎Three.jsGalacean

2021-11-23 22:50:14

.js 3D幾何體

2021-04-23 16:40:49

Three.js前端代碼

2021-11-27 10:42:01

Three.js3D可視化AudioContex

2022-03-07 09:20:00

JavaScripThree.jsNFT

2025-06-30 09:15:47

2025-06-17 08:15:00

VTK.jsThree.js3D

2016-06-01 09:19:08

開發(fā)3D游戲

2018-03-28 09:18:35

CITE智能制造3D打印館

2021-04-21 09:20:15

three.js3d前端

2023-08-04 09:56:15

2024-02-26 00:00:00

前端工具Space.js

2024-01-26 10:19:00

AI模型

2022-07-15 13:09:33

Three.js前端

2020-05-06 10:10:06

JavaScript 3D 游戲

2025-03-27 09:26:30

2010-09-30 10:31:43

J2ME3D
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

182tv在线播放| 国产男女无套免费网站| 精品毛片免费观看| 9191久久久久久久久久久| 欧美做暖暖视频| 蜜桃免费在线| 国产麻豆91精品| 欧美一级电影免费在线观看| 国产精品久久国产精麻豆96堂| 日韩中文字幕| 欧美影院一区二区三区| 国产精品久久久久7777| 成人资源www网在线最新版| 国产精品69毛片高清亚洲| 日韩av色综合| 久久精品国产亚洲av高清色欲| blacked蜜桃精品一区| 精品久久久久一区二区国产| 182午夜在线观看| 欧美裸体视频| 亚洲综合色噜噜狠狠| 五月婷婷综合色| 手机av免费在线观看| 九色综合狠狠综合久久| 国产成人精品久久久| 国产亚洲成人av| 99久久.com| 国产午夜精品一区理论片飘花| 亚洲图片欧美另类| 亚洲电影二区| 欧美在线小视频| 无码人妻丰满熟妇区96| av中文字幕在线看| 亚洲欧洲av另类| 亚洲精品视频一区二区三区| 猫咪在线永久网站| av动漫一区二区| 国产精品加勒比| jizz国产视频| 国产一区二区三区综合| 国产精品国产三级国产aⅴ浪潮| 中文字幕激情小说| 亚洲国产一区二区三区高清| 九九精品在线视频| 综合五月激情网| 日韩电影一区| 中文字幕亚洲欧美日韩高清| 女人又爽又黄免费女仆| 天天躁日日躁成人字幕aⅴ| 精品国产凹凸成av人导航| 日韩欧美色视频| 狂野欧美xxxx韩国少妇| 欧美一区二区三区视频免费| 日韩a一级欧美一级| 91精品网站在线观看| 欧美日韩国产高清一区| 怡红院亚洲色图| 日本精品久久| 日韩欧美在线123| 日本人dh亚洲人ⅹxx| 伊人精品久久| 亚洲电影av在线| 800av在线播放| 综合亚洲自拍| 中文字幕日韩精品在线观看| 影音先锋男人看片资源| 91精品国产福利在线观看麻豆| 这里只有精品视频在线| 亚洲一区二区三区无码久久| 欧美电影在线观看免费| 亚洲人精品午夜在线观看| 日韩精品电影一区二区| 91日韩欧美| 米奇精品一区二区三区在线观看| 麻豆疯狂做受xxxx高潮视频| 亚洲大胆在线| 日本sm极度另类视频| 无码人妻一区二区三区免费| 免费成人在线网站| 91九色单男在线观看| 国产乱淫av片免费| www.欧美精品一二区| 美国av一区二区三区| 一级日本在线| 一区二区三区小说| 韩国黄色一级大片| 欧洲中文在线| 欧美日韩国产精品一区| 91制片厂毛片| www.神马久久| 夜夜嗨av一区二区三区免费区 | 九热爱视频精品视频| 伊人久久久久久久久久久| 一区二区在线观看免费视频| 午夜亚洲一区| 91精品免费| 四虎国产精品永远| 国产精品的网站| 欧美成人免费在线观看视频| 成人1区2区| 亚洲黄色有码视频| 日本黄色片免费观看| 亚洲欧美不卡| 波多野结衣久草一区| 91女主播在线观看| 亚洲国产视频网站| 五月花丁香婷婷| 日韩精选在线| 欧美精品做受xxx性少妇| 无码一区二区三区| 国产·精品毛片| 亚洲精品久久久久久一区二区| 国产网红在线观看| 51精品久久久久久久蜜臀| 女人被狂躁c到高潮| 欧美激情精品久久久六区热门| 国产精品av在线播放| 亚洲欧美激情国产综合久久久| 国产精品萝li| 日韩中文字幕免费在线| 成人性生交大片免费看中文视频 | 久久蜜桃香蕉精品一区二区三区| 亚洲午夜精品一区二区| av剧情在线观看| 7777精品伊人久久久大香线蕉完整版 | 播放一区二区| 日韩av在线资源| 久久久久久久国产精品毛片| 久久99国内精品| 欧美极品一区| 成人国产二区| 亚洲精品99久久久久| 久久久久久国产精品视频| 精品无人区卡一卡二卡三乱码免费卡| 日本一区二区精品| 亚洲最大网站| 亚洲美女免费精品视频在线观看| 久草视频中文在线| 国产传媒欧美日韩成人| 少妇久久久久久被弄到高潮| 亚洲精品66| 久久久成人的性感天堂| 精品乱码一区内射人妻无码 | 天天免费亚洲黑人免费| 日韩精品极品视频| 自拍偷拍欧美亚洲| 成人免费视频一区| 日韩一级片免费视频| 日韩免费一级| 欧美激情乱人伦一区| www.色呦呦| 亚洲一区二区三区在线看| 台湾佬美性中文| 极品尤物久久久av免费看| 不卡视频一区二区| 暧暧视频在线免费观看| 亚洲国产另类久久精品| 精品欧美一区二区三区免费观看| 91在线丨porny丨国产| 四虎永久在线精品无码视频| 不卡一区综合视频| 91精品国产综合久久香蕉| 成人在线影视| 亚洲国产又黄又爽女人高潮的| 黄色激情视频在线观看| www激情久久| 波多结衣在线观看| 香蕉综合视频| 国产一区免费在线| 高清成人在线| 日韩一区二区三区xxxx| 国产xxxx在线观看| 亚洲成人高清在线| 国产免费无遮挡吸奶头视频| 蜜臀va亚洲va欧美va天堂| 熟女熟妇伦久久影院毛片一区二区| 日本精品在线播放| 69视频在线播放| jizzjizz在线观看| 欧美成人精品二区三区99精品| 日操夜操天天操| 日本一区二区视频在线| 四虎成人在线播放| 亚洲福利免费| 天堂va久久久噜噜噜久久va| 国产视频一区二| 午夜精品久久久久久99热| 成年在线电影| 日韩欧美久久一区| 亚洲大尺度在线观看| 亚洲精品老司机| 不卡一区二区在线观看| 国产一区二区不卡| 无码人妻精品一区二区三区在线| 日韩在线视屏| 精品久久久久久一区| 色诱色偷偷久久综合| 97视频网站入口| 日本三级在线视频| 国产手机视频精品| 99久久久久成人国产免费| 欧美性极品xxxx娇小| 欧美黑吊大战白妞| 欧美国产一区二区在线观看| 蜜臀av粉嫩av懂色av| 老司机精品视频导航| 国产成人无码精品久久久性色| 999国产精品| 日本亚洲欧洲精品| 黄色欧美在线| 亚洲最大av网| 99riav视频一区二区| 国内伊人久久久久久网站视频| 精品国产丝袜高跟鞋| 亚洲人高潮女人毛茸茸| 日本国产在线观看| 欧美一级视频精品观看| 中文字幕一区2区3区| 欧美日韩亚洲91| 成人精品视频99在线观看免费| 日本免费网站视频| 99久久综合精品| 欧美一级片在线免费观看| 久久av老司机精品网站导航| 爱情岛论坛成人| 香蕉国产精品偷在线观看不卡| 男人天堂手机在线视频| 天天做天天爱天天综合网| 日韩欧美三级一区二区| 精品在线播放| 精品国产免费人成电影在线观... 精品国产免费久久久久久尖叫 | 亚洲黄色小视频| 秋霞欧美一区二区三区视频免费 | 波多野结衣亚洲色图| 国产精品网站导航| 亚洲久久久久久久| 久久综合久久久久88| 一级特级黄色片| 波多野结衣亚洲一区| youjizz.com日本| 国产成人免费在线观看| 潘金莲一级淫片aaaaa| 国产乱子伦视频一区二区三区| 午夜剧场在线免费观看| 久久成人麻豆午夜电影| 中国黄色片一级| 国内成人免费视频| 亚洲国产综合av| 高清不卡一二三区| 国产xxxxxxxxx| 91丨porny丨国产| 手机看片福利视频| 国产精品―色哟哟| www.超碰在线观看| 亚洲综合在线第一页| 久久免费视频精品| 五月天婷婷综合| 久久久精品福利| 色噜噜夜夜夜综合网| 亚洲高清在线看| 欧美人妇做爰xxxⅹ性高电影| 91肉色超薄丝袜脚交一区二区| 欧美福利视频导航| 性一交一乱一精一晶| 精品国产欧美一区二区| 亚洲 小说区 图片区 都市| 亚洲欧美日韩国产成人| 国产69久久| 久久人人爽人人爽爽久久| 1区2区3区在线视频| 亚州精品天堂中文字幕| 羞羞影院欧美| 91精品久久久久久久久久久久久久| 国产精品麻豆| 国产一区不卡在线观看| 国产成人精品免费视| 一本一道久久久a久久久精品91| 欧美va天堂在线| 久久精品.com| 精品影视av免费| 又大又长粗又爽又黄少妇视频| 99精品在线免费| 日韩av片在线| 亚洲成国产人片在线观看| 无码人妻精品一区二| 91麻豆精品国产综合久久久久久| 可以免费观看的毛片| 亚洲片国产一区一级在线观看| 欧美jizzhd欧美| 91精品国产高清久久久久久| 涩涩av在线| 亚洲最大av网| 成人看的羞羞网站| 国产精品久久久久久久乖乖| 日韩av一级电影| 在线观看成人动漫| 中文字幕亚洲综合久久菠萝蜜| 日韩精品视频免费播放| 欧美挠脚心视频网站| 天天操天天射天天| 久久久精品一区| 欧美舌奴丨vk视频| 国产精品对白刺激久久久| 日韩综合一区| 亚洲乱码中文字幕久久孕妇黑人| 国产精品正在播放| 精品视频第一页| 一本一道久久a久久精品| 精品久久久中文字幕人妻| 在线视频日韩精品| 日韩激情电影| 国产伦精品一区二区三区高清版| 99精品一区| 日韩av片网站| 久久嫩草精品久久久久| 久久综合综合久久| 91精品国产综合久久久蜜臀粉嫩| 国产福利在线看| 欧美中文字幕在线观看| 精品福利一区| 国产精品日韩三级| 精品一区二区在线播放| 亚洲精品乱码久久久久久久久久久久| 亚洲高清一区二区三区| 精品人妻少妇AV无码专区| 久久精品视频导航| 日韩久久一区| 伊人精品久久久久7777| 日韩av一区二区在线影视| 色婷婷在线影院| 欧美视频国产精品| 天堂中文在线观看视频| 99精品国产一区二区三区2021 | 国产 日韩 欧美 精品| 欧美成人久久久| 久久九九精品视频| 欧美爱爱视频网站| 久久99精品国产.久久久久久| 免费黄色在线网址| 精品视频一区二区不卡| xxxxx日韩| 国产精品一区二区三区毛片淫片| 日韩国产一区| 亚洲欧美偷拍另类| 国产精品久久久久天堂| 亚洲一卡二卡在线观看| 日韩在线精品一区| 久久亚洲国产精品尤物| 五月天色一区| 国模一区二区三区白浆 | 欧洲精品在线观看| 风间由美一区| 国产精品综合久久久| 91一区二区三区四区| 天天操狠狠操夜夜操| 亚洲精品视频免费观看| 人妻精品一区一区三区蜜桃91| 久久免费视频网| 网友自拍区视频精品| 老司机午夜av| 综合av第一页| 丰满岳乱妇国产精品一区| 91精品国产高清| 欧美色图激情小说| 久久精品视频在线观看免费| 一区二区三区蜜桃网| 天天摸夜夜添狠狠添婷婷| 青青久久av北条麻妃海外网| 久久国产亚洲精品| 无码人妻丰满熟妇区毛片蜜桃精品| 精品免费在线观看| 国产色在线 com| 91日韩在线播放| 亚洲乱亚洲高清| 国产性猛交xx乱| 日韩一区二区电影网| 日韩脚交footjobhd| 亚洲一区三区视频在线观看 | 国产伦精品一区二区三区视频黑人| 国产精品一二| 麻豆一区在线观看| 亚洲福利影片在线| 福利精品一区| 成人午夜免费在线视频| 久久久精品日韩欧美| 国产精品自产拍| 91福利视频网| 91精品啪在线观看国产81旧版| 亚洲 欧美 日韩在线| 欧美日本在线看| av蜜臀在线| 一区二区三区我不卡| 99久久精品情趣| 夜夜嗨av禁果av粉嫩avhd| 91国产视频在线| 888久久久| 日韩免费成人av| 欧美精品一区二区久久婷婷| 久久亚洲精品中文字幕| 国产91对白刺激露脸在线观看|