Three.js實現雪糕地球的使用示例詳解

 更新時間:2022年07月05日 15:09:57   作者:戰場小包  
這篇文章主要為大家介紹了Three.js實現雪糕地球的使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

最近的天氣有幾分酷熱,去實驗室的道路也有幾分漫長,走著走著,小包感覺靈魂已經被熱出竅了?;氐綄嶒炇?,把空調打開,雪糕吃上,靜坐了幾分鐘,才重新感覺到靈魂的滋味,葛優躺在實驗室的小床上,思維開始天馬行空,世上有一萬種方式能讓小包涼快,但地球母親吶,她卻日漸炎熱,誰能來給她降降溫?

躺著躺著,進入了夢鄉,小包夢到未來有一天,人類超級發達,可以穿梭時空,但發展的代價也是巨大的,地球母親不堪重負,熱度超標,我們卻束手無策,科學家最后想出一個古老的辦法,將地球的一周用冰包裹起來,進行物理降溫。這很讓人驚悚,小包醒來后,枯坐了一會,決定做一個雪糕地球,不只是一種整活調侃,也是一種反思與警示,保護地球,人人有責。

  • style
* {
    -webkit-user-select: none;
       -moz-user-select: none;
        -ms-user-select: none;
            user-select: none;
  }
  body {
    height: 100vh;
    background-color: hotpink;
    margin: 0;
    padding: 0;
    overflow: hidden;
  }
  .loader {
    display: flex;
    color: white;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 5em;
    width: 100%;
    height: 100%;
    font-family: "Baloo Bhaijaan", cursive;
  }
  .loader span {
    text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb,
      0 6px transparent, 0 7px transparent, 0 8px transparent,
      0 9px transparent, 0 10px 10px rgba(0, 0, 0, 0.4);
    text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb,
        0 5px #bbb, 0 6px #bbb, 0 7px #bbb, 0 8px #bbb, 0 9px #bbb,
        0 50px 25px rgba(0, 0, 0, 0.2);
      transform: translateY(-20px);
  }
  •  script
/*
 * 基礎配置
 */
let isLoaded = false; // 紋理資源是否加載完畢
const loadingScreen = {
  scene: new THREE.Scene(),
  camera: new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  ),
  // 移除加載標志的函數
  removeText() {
    const loadingText = document.querySelector("#canvas-loader");
    if (loadingText.parentNode) {
      loadingText.parentNode.removeChild(loadingText);
    }
  },
};
// 初始化加載器
let loadingManager = new THREE.LoadingManager();
// 監聽加載器 onLoad 事件
loadingManager.onLoad = () => {
  loadingScreen.removeText();
  isLoaded = true;
};
// 創建場景
const scene = new THREE.Scene();
// 創建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
// 渲染器基本設置
renderer.setClearColor("hotpink");
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// canvas 外部容器
const canvasWrapper = document.querySelector("#canvas-wrapper");
// 創建透視相機
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
// 設置相機位置
camera.position.set(0, 0, 220);
// 創建平行光源
const light = new THREE.DirectionalLight();
light.position.set(0, 0, 1);
scene.add(light);
// 創建點光源
const point = new THREE.PointLight(0xeeeeee);
point.position.set(400, 200, 300); //點光源位置
scene.add(point); //點光源添加到場景中
// 創建球體
const cRadius = 100;
const geometry = new THREE.SphereBufferGeometry(
  cRadius,
  cRadius * 6.4,
  cRadius * 6.4
);
// 紋理圖
const textureLoader = new THREE.TextureLoader(loadingManager);
const textureSurface = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-surface.jpg"
);
const textureElevation = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-elevation.jpg"
);
const textureSpecular = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-specular.jpg"
);
// 材質信息
const materialOpt = {
  map: textureSurface,
  normalMap: textureElevation,
  specularMap: textureSpecular,
  shininess: 80,
};
const material = new THREE.MeshPhongMaterial(materialOpt);
// 創建網格體
const sphere = new THREE.Mesh(geometry, material);
// 設置環境貼圖的顏色深淺
sphere.material.normalScale.set(0.5, 0.5);
// 將模型添加到場景中
scene.add(sphere);
// 將 canvas 元素添加到頁面中
canvasWrapper.appendChild(renderer.domElement);
/*
 * 事件監聽實現動效
 */
let mouseX = 0;
let mouseY = 0;
const moveAnimate = {
  coordinates(clientX, clientY) {
    const limit = 270;
    const limitNeg = limit * -1;
    mouseX = clientX - window.innerWidth / 2;
    mouseY = clientY - window.innerHeight / 2;
    mouseX = mouseX >= limit ? limit : mouseX;
    mouseX = mouseX <= limitNeg ? limitNeg : mouseX;
    mouseY = mouseY >= limit ? limit : mouseY;
    mouseY = mouseY <= limitNeg ? limitNeg : mouseY;
  },
  onMouseMove(e) {
    moveAnimate.coordinates(e.clientX, e.clientY);
  },
  onTouchMove(e) {
    const touchX = e.changedTouches[0].clientX;
    const touchY = e.changedTouches[0].clientY;
    moveAnimate.coordinates(touchX, touchY);
  },
};
document.addEventListener("mousemove", moveAnimate.onMouseMove);
document.addEventListener("touchmove", moveAnimate.onTouchMove);
const onWindowResize = () => {
  const w = window.innerWidth;
  const h = window.innerHeight;
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h);
};
window.addEventListener("resize", onWindowResize);
const createAnimRotation = () => {
  const speed = 0.005;
  sphere.rotation.z += speed / 2;
  sphere.rotation.y += speed;
};
// 渲染函數
const render = () => {
  if (!isLoaded) {
    renderer.render(loadingScreen.scene, loadingScreen.camera);
    requestAnimationFrame(render);
    return;
  }
  camera.position.x += (mouseX * -1 - camera.position.x) * 0.05;
  camera.position.y += (mouseY - camera.position.y) * 0.05;
  camera.lookAt(scene.position);
  createAnimRotation();
  renderer.render(scene, camera);
  requestAnimationFrame(render);
};
render();

ThreeJS 基礎——實現轉動的球體

Three.js 是一款運行在瀏覽器中的 3D 引擎,你可以用它創建各種三維場景,包括了攝影機、光影、材質等各種對象,大家或多或少應該都見識過 Three 的傳說。這是小包第一次使用 Three,因此小包會圍繞雪糕地球實現的各種細節講起。

下面首先來看一下 Three 框架的基本組成要素(圖源: Three.js 教程)

Three 中最重要的三個對象即場景、相機和渲染器。場景即放置模型、光照的場地;相機設置以何種方式何種角度來觀看場景,渲染器將效果渲染到網頁中。這三個概念都不難理解,下面我們用代碼實現這三個對象。

// 場景
const scene = new THREE.Scene();
// 透視相機
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
// 渲染器
const renderer = new THREE.WebGLRenderer();
// 設置渲染區域尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
// body元素中插入canvas對象
document.body.appendChild(renderer.domElement);
// 設置背景顏色
renderer.setClearColor("hotpink");
// 執行渲染操作   指定場景、相機作為參數
renderer.render(scene, camera);

Three 中有多種相機,本文章主要使用透視相機(PerspectiveCamera),其原理與人眼所看的景象類似,共有四個參數:

PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )

fov: 表示能看到的角度范圍,值為角度,類似于人的視角。

aspect: 表示渲染窗口的長寬比,如果網頁中只有一個 canvas,其值通常設置為網頁視口的寬高比

near/far: near/far 分別代表攝像機的近剪切面和遠剪切面

文字有些難以理解,可以參考一下下圖:

打開瀏覽器,看一下會渲染出什么?目前只能看到全粉色的網頁,這是因為目前的場景中并沒有添加 3D 模型對象。

接下來我們來添加一個球體模型,作為地球的基底。

const cRadius = 100;
const geometry = new THREE.SphereBufferGeometry(
  cRadius,
  cRadius * 6.4,
  cRadius * 6.4
);

SphereBufferGeometryThree 中實現球體的 API,參數非常多,這里只介紹前三個參數

radius: 球體半徑

widthSegments: 沿經線方向分段數

heightSegments: 沿緯線方向分段數

為球體添加材質 Material,目前我們只添加一個顏色屬性。

// 材質對象Material
const material = new THREE.MeshLambertMaterial({
  color: 0x0000ff,
});

渲染網格體 Mesh,并將其添加到場景 Scene 中。

// 網格體 Mesh,兩個參數分別為幾何體和材質
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);

重新打開網站,并沒有看到球體,還是一片粉茫茫的寂寥,天理何在?

Three 相機的初始位置默認為 (0,0,0),相機焦點默認為 Z 軸負半軸方向,球體的半徑是 100,也就是說目前相機位于球體內部,因此我們需要調整相機位置。

// 設置相機的位置
camera.position.set(0, 0, 220);
// 設置相機焦點的方向
camera.lookAt(scene.position);

當當當當,網頁中就可以成功看到一個黑色球體了,額有點奇怪,我們明明設置的是 0x0000ff 顏色,怎么會顯示一個黑色模型?

小包苦思冥想: 萬物本沒有顏色,顏色是光的反射。在整個場景中,目前是沒有光源的,因此下面分別添加平行光(DirectionalLight)和點光源(PointLight)

平行光是沿著特定方向發射的光,其表現類似無限遠的陽光,文章使用它來模擬太陽光。點光源是從一個點向各個方向發射的光源,使用它來增加整體的亮度。

// 聲明平行光
const light = new THREE.DirectionalLight();
// 設置平行光源位置
light.position.set(0, 0, 1);
// 將平行光源添加到場景中
scene.add(light);
// 聲明點光源
const point = new THREE.PointLight(0xeeeeee);
// 設置點光源位置
point.position.set(400, 200, 300);
// 點光源添加到場景中
scene.add(point);

立體效果看起來不明顯,沒事,接下來我們讓球體動起來。接下來,給球體添加一個 z 軸和 y 軸的轉動。

const createAnimRotation = () =&gt; {
  const speed = 0.005;
  sphere.rotation.z += speed / 2;
  sphere.rotation.y += speed;
};
const render = () =&gt; {
  createAnimRotation();
  renderer.render(scene, camera);
  requestAnimationFrame(render);
};
render();

由于球體是對稱的,轉動看起來并不明顯,如果你特別想看到轉動效果,可以將 SphereBufferGeometry 暫時更換為 BoxBufferGeometry。

ThreeJS 紋理——實現轉動的地球

上文已經成功實現地球,接下來我們來為地球披上衣服。本文實現的是雪糕地球,因此小包直接為其披上雪糕外衣。

Three 可以將一張紋理圖映射到幾何體上,具體的映射原理我們不做探究,映射的思想可以參考下圖。

選取一張雪糕地球的紋理圖,使用下面的代碼實現紋理貼圖效果。

// 紋理加載器對象
const textureLoader = new THREE.TextureLoader();
const textureSurface = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-surface.jpg"
);
// 設置紋理貼圖
const material = new THREE.MeshLambertMaterial({ map: textureSurface });

只使用普通貼圖的雪糕地球看起來已經非常不錯了,但還有進一步美化的空間,Three 提供了高光貼圖,使用高光貼圖,會有高亮部分顯示。

const textureSpecular = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-specular.jpg"
);
const material = new THREE.MeshPhongMaterial({
  map: textureSurface,
  specularMap: textureSpecular,
  shininess: 80, // 高光部分的亮度
});

雖然動圖錄制的幀數太低,還是依稀可以看到一些高亮區域。

Three 還提供了環境貼圖,環境貼圖可以增加表面的細節,使三維模型更加立體。

const textureElevation = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-elevation.jpg"
);
const material = new THREE.MeshPhongMaterial({
  map: textureSurface,
  normalMap: textureElevation,
  specularMap: textureSpecular,
  shininess: 80,
});

立體效果是有了,但是具體看起來一言難盡,顏色有些許暗淡,不符合雪糕的風格。

小包繼續開始查看文檔,環境貼圖中有 normalScale 屬性,可以設置顏色的深淺程度,減少對應屬性值為 0.5,0.5。

sphere.material.normalScale.set(0.5, 0.5);

交互式雪糕地球

給地球加一些交互效果:

  • 當鼠標靠近,地球放大;鼠標遠離時,地球縮小
  • 地球隨鼠標方向轉動

上述動效我們可以通過移動相機位置實現。首先設定地球旋轉的最大正負角度為 270。

// 定義動效對象
let mouseX = 0;
let mouseY = 0;
const moveAnimate = {
  coordinates(clientX, clientY) {
    const limit = 270;
    const limitNeg = limit * -1;
    mouseX = clientX - window.innerWidth / 2;
    mouseY = clientY - window.innerHeight / 2;
    mouseX = mouseX &gt;= limit ? limit : mouseX;
    mouseX = mouseX &lt;= limitNeg ? limitNeg : mouseX;
    mouseY = mouseY &gt;= limit ? limit : mouseY;
    mouseY = mouseY &lt;= limitNeg ? limitNeg : mouseY;
  },
  onMouseMove(e) {
    moveAnimate.coordinates(e.clientX, e.clientY);
  },
};
document.addEventListener("mousemove", moveAnimate.onMouseMove);

通過上述事件計算出 mouseXmouseY 的值,在 render 函數中,修改 camera 的位置。

camera.position.x += (mouseX * -1 - camera.position.x) * 0.05;
camera.position.y += (mouseY - camera.position.y) * 0.05;
camera.lookAt(scene.position);

移動端同步監聽 touchmove 事件,手機也可以看到雪糕地球的動態效果。

const moveAnimate = {
  onTouchMove(e) {
    const touchX = e.changedTouches[0].clientX;
    const touchY = e.changedTouches[0].clientY;
    moveAnimate.coordinates(touchX, touchY);
  },
};
document.addEventListener("touchmove", moveAnimate.onTouchMove);

添加 loading 效果

紋理的加載需要一定的時間,因此添加一個轉場 loading 效果。

loading 效果使用小包前面的實現躍動的文字中的效果。

.loader {
  display: flex;
  color: white;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 5em;
  width: 100%;
  height: 100%;
  font-family: "Baloo Bhaijaan", cursive;
}
.loader span {
  text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb, 0 6px
      transparent, 0 7px transparent, 0 8px transparent, 0 9px transparent, 0
      10px 10px rgba(0, 0, 0, 0.4);
  text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb, 0 6px
      #bbb, 0 7px #bbb, 0 8px #bbb, 0 9px #bbb, 0 50px 25px rgba(0, 0, 0, 0.2);
  transform: translateY(-20px);
}

Three 提供了 LoadingManager,其功能是處理并跟蹤已加載和待處理的數據。當所有加載器加載完成后,會調用 LoadingManager 上的 onLoad 事件。

因此我們定義一個 LoadingManager,當觸發 onLoad 事件后,將頁面中的加載標志移除。

const loadingScreen = {
  scene: new THREE.Scene(),
  camera: new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  ),
  // 移除加載標志的函數
  removeText() {
    const loadingText = document.querySelector("#canvas-loader");
    if (loadingText.parentNode) {
      loadingText.parentNode.removeChild(loadingText);
    }
  },
};
// 初始化加載器
let loadingManager = new THREE.LoadingManager();
// 監聽加載器 onLoad 事件
loadingManager.onLoad = () =&gt; {
  loadingScreen.removeText();
  isLoaded = true;
};
// 紋理圖加載器傳入 loadingManager
const textureLoader = new THREE.TextureLoader(loadingManager);

參考鏈接

Three中文文檔

以上就是Three.js實現雪糕地球的使用示例詳解的詳細內容,更多關于Three.js雪糕地球的資料請關注腳本之家其它相關文章!

相關文章

  • 微信小程序 flex實現導航實例詳解

    微信小程序 flex實現導航實例詳解

    這篇文章主要介紹了微信小程序 flex實現導航實例詳解的相關資料,需要的朋友可以參考下
    2017-04-04
  • 微信小程序 Page()函數詳解

    微信小程序 Page()函數詳解

    這篇文章主要介紹了微信小程序 Page()函數詳解的相關資料,在開發過程中肯定會遇到Page()函數,希望能幫助到大家,需要的朋友可以參考下
    2016-10-10
  • JS深入淺出Function與構造函數

    JS深入淺出Function與構造函數

    這篇文章主要介紹了JS深入淺出Function與構造函數,Function是一個構造函數,可以通過該構造函數去創建一個函數,創建的函數是一個Function對象,具體內容請參考下面文章的詳細內容,需要的朋友可以參考一下
    2021-12-12
  • 微信小程序 實戰程序簡易新聞的制作

    微信小程序 實戰程序簡易新聞的制作

    這篇文章主要介紹了微信小程序 實戰程序簡易新聞的制作的相關資料,需要的朋友可以參考下
    2017-01-01
  • 8個JS的reduce使用實例和reduce操作方式

    8個JS的reduce使用實例和reduce操作方式

    reduce方法是JavaScript中一個比較強大的方法,可能在平時開發中,有人根本沒用過,通過下面的8個例子,學會reduce的用法以及它的常用場景,需要的朋友可以參考一下
    2021-09-09
  • 微信小程序 Canvas增強組件實例詳解及源碼分享

    微信小程序 Canvas增強組件實例詳解及源碼分享

    這篇文章主要介紹了微信小程序 Canvas增強組件實例詳解及源碼分享的相關資料,WeZRender是一個微信小程序Canvas增強組件,這里詳細介紹,需要的朋友可以參考下
    2017-01-01
  • JavaScript中async,await的使用和方法

    JavaScript中async,await的使用和方法

    關于JavaScript中async和await學習,我們在這里通過 ECMAScript 2017 中添加 async 函數和 await 關鍵字,也會在主流腳本庫和其他 JavaScript 編程中得到一些應用。接下來我們大家一起來簡單學習一下
    2021-08-08
  • JS獲取對象屬性名總結

    JS獲取對象屬性名總結

    這篇文章主要總結介紹了JS如何獲取對象屬性名的方法,需要的朋友可以參考下
    2022-01-01
  • 微信小程序 新建登錄頁并實現tabBar隱藏

    微信小程序 新建登錄頁并實現tabBar隱藏

    這篇文章主要介紹了微信小程序 新建登錄頁并實現tabBar隱藏的相關資料,需要的朋友可以參考下
    2017-06-06
  • JavaScript阻止事件冒泡的方法

    JavaScript阻止事件冒泡的方法

    這篇文章主要介紹了基于JavaScript阻止事件冒泡,事件冒泡?開始時由最具體的元素接收,然后逐級向上傳播到到?DOM?最頂層節點。更多詳細內容請需要的小伙伴參考下面文章的具體內容希望對你有所幫助
    2021-12-12

最新評論

美丽人妻被按摩中出中文字幕