溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么用Three.js實(shí)現(xiàn)雪糕地球

發(fā)布時(shí)間:2022-07-06 10:16:29 來(lái)源:億速云 閱讀:137 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇“怎么用Three.js實(shí)現(xiàn)雪糕地球”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“怎么用Three.js實(shí)現(xiàn)雪糕地球”文章吧。

  • 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

/*
 * 基礎(chǔ)配置
 */
let isLoaded = false; // 紋理資源是否加載完畢
const loadingScreen = {
  scene: new THREE.Scene(),
  camera: new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  ),
  // 移除加載標(biāo)志的函數(shù)
  removeText() {
    const loadingText = document.querySelector("#canvas-loader");
    if (loadingText.parentNode) {
      loadingText.parentNode.removeChild(loadingText);
    }
  },
};
// 初始化加載器
let loadingManager = new THREE.LoadingManager();
// 監(jiān)聽(tīng)加載器 onLoad 事件
loadingManager.onLoad = () => {
  loadingScreen.removeText();
  isLoaded = true;
};
// 創(chuàng)建場(chǎng)景
const scene = new THREE.Scene();
// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
// 渲染器基本設(shè)置
renderer.setClearColor("hotpink");
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// canvas 外部容器
const canvasWrapper = document.querySelector("#canvas-wrapper");
// 創(chuàng)建透視相機(jī)
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
// 設(shè)置相機(jī)位置
camera.position.set(0, 0, 220);
// 創(chuàng)建平行光源
const light = new THREE.DirectionalLight();
light.position.set(0, 0, 1);
scene.add(light);
// 創(chuàng)建點(diǎn)光源
const point = new THREE.PointLight(0xeeeeee);
point.position.set(400, 200, 300); //點(diǎn)光源位置
scene.add(point); //點(diǎn)光源添加到場(chǎng)景中
// 創(chuàng)建球體
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"
);
// 材質(zhì)信息
const materialOpt = {
  map: textureSurface,
  normalMap: textureElevation,
  specularMap: textureSpecular,
  shininess: 80,
};
const material = new THREE.MeshPhongMaterial(materialOpt);
// 創(chuàng)建網(wǎng)格體
const sphere = new THREE.Mesh(geometry, material);
// 設(shè)置環(huán)境貼圖的顏色深淺
sphere.material.normalScale.set(0.5, 0.5);
// 將模型添加到場(chǎng)景中
scene.add(sphere);
// 將 canvas 元素添加到頁(yè)面中
canvasWrapper.appendChild(renderer.domElement);
/*
 * 事件監(jiān)聽(tīng)實(shí)現(xiàn)動(dòng)效
 */
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;
};
// 渲染函數(shù)
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();
  • 在線體驗(yàn)(支持PC與移動(dòng)端): 雪糕地球線上預(yù)覽

  • 源碼倉(cāng)庫(kù): 雪糕地球

ThreeJS 基礎(chǔ)&mdash;&mdash;實(shí)現(xiàn)轉(zhuǎn)動(dòng)的球體

Three.js 是一款運(yùn)行在瀏覽器中的 3D 引擎,你可以用它創(chuàng)建各種三維場(chǎng)景,包括了攝影機(jī)、光影、材質(zhì)等各種對(duì)象,大家或多或少應(yīng)該都見(jiàn)識(shí)過(guò) Three 的傳說(shuō)。這是小包第一次使用 Three,因此小包會(huì)圍繞雪糕地球實(shí)現(xiàn)的各種細(xì)節(jié)講起。

下面首先來(lái)看一下 Three 框架的基本組成要素

怎么用Three.js實(shí)現(xiàn)雪糕地球

Three 中最重要的三個(gè)對(duì)象即場(chǎng)景、相機(jī)和渲染器。場(chǎng)景即放置模型、光照的場(chǎng)地;相機(jī)設(shè)置以何種方式何種角度來(lái)觀看場(chǎng)景,渲染器將效果渲染到網(wǎng)頁(yè)中。這三個(gè)概念都不難理解,下面我們用代碼實(shí)現(xiàn)這三個(gè)對(duì)象。

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

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

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

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

aspect: 表示渲染窗口的長(zhǎng)寬比,如果網(wǎng)頁(yè)中只有一個(gè) canvas,其值通常設(shè)置為網(wǎng)頁(yè)視口的寬高比

near/far: near/far 分別代表攝像機(jī)的近剪切面和遠(yuǎn)剪切面

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

怎么用Three.js實(shí)現(xiàn)雪糕地球

打開(kāi)瀏覽器,看一下會(huì)渲染出什么?目前只能看到全粉色的網(wǎng)頁(yè),這是因?yàn)槟壳暗膱?chǎng)景中并沒(méi)有添加 3D 模型對(duì)象。

接下來(lái)我們來(lái)添加一個(gè)球體模型,作為地球的基底。

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

SphereBufferGeometryThree 中實(shí)現(xiàn)球體的 API,參數(shù)非常多,這里只介紹前三個(gè)參數(shù)

radius: 球體半徑

widthSegments: 沿經(jīng)線方向分段數(shù)

heightSegments: 沿緯線方向分段數(shù)

為球體添加材質(zhì) Material,目前我們只添加一個(gè)顏色屬性。

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

渲染網(wǎng)格體 Mesh,并將其添加到場(chǎng)景 Scene 中。

// 網(wǎng)格體 Mesh,兩個(gè)參數(shù)分別為幾何體和材質(zhì)
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);

重新打開(kāi)網(wǎng)站,并沒(méi)有看到球體,還是一片粉茫茫的寂寥,天理何在?

怎么用Three.js實(shí)現(xiàn)雪糕地球

Three 相機(jī)的初始位置默認(rèn)為 (0,0,0),相機(jī)焦點(diǎn)默認(rèn)為 Z 軸負(fù)半軸方向,球體的半徑是 100,也就是說(shuō)目前相機(jī)位于球體內(nèi)部,因此我們需要調(diào)整相機(jī)位置。

// 設(shè)置相機(jī)的位置
camera.position.set(0, 0, 220);
// 設(shè)置相機(jī)焦點(diǎn)的方向
camera.lookAt(scene.position);

當(dāng)當(dāng)當(dāng)當(dāng),網(wǎng)頁(yè)中就可以成功看到一個(gè)黑色球體了,額有點(diǎn)奇怪,我們明明設(shè)置的是 0x0000ff 顏色,怎么會(huì)顯示一個(gè)黑色模型?

怎么用Three.js實(shí)現(xiàn)雪糕地球

小包苦思冥想: 萬(wàn)物本沒(méi)有顏色,顏色是光的反射。在整個(gè)場(chǎng)景中,目前是沒(méi)有光源的,因此下面分別添加平行光(DirectionalLight)和點(diǎn)光源(PointLight)

平行光是沿著特定方向發(fā)射的光,其表現(xiàn)類似無(wú)限遠(yuǎn)的陽(yáng)光,文章使用它來(lái)模擬太陽(yáng)光。點(diǎn)光源是從一個(gè)點(diǎn)向各個(gè)方向發(fā)射的光源,使用它來(lái)增加整體的亮度。

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

怎么用Three.js實(shí)現(xiàn)雪糕地球

立體效果看起來(lái)不明顯,沒(méi)事,接下來(lái)我們讓球體動(dòng)起來(lái)。接下來(lái),給球體添加一個(gè) z 軸和 y 軸的轉(zhuǎn)動(dòng)。

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();

由于球體是對(duì)稱的,轉(zhuǎn)動(dòng)看起來(lái)并不明顯,如果你特別想看到轉(zhuǎn)動(dòng)效果,可以將 SphereBufferGeometry 暫時(shí)更換為 BoxBufferGeometry。

ThreeJS 紋理&mdash;&mdash;實(shí)現(xiàn)轉(zhuǎn)動(dòng)的地球

上文已經(jīng)成功實(shí)現(xiàn)地球,接下來(lái)我們來(lái)為地球披上衣服。本文實(shí)現(xiàn)的是雪糕地球,因此小包直接為其披上雪糕外衣。

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

怎么用Three.js實(shí)現(xiàn)雪糕地球

選取一張雪糕地球的紋理圖,使用下面的代碼實(shí)現(xiàn)紋理貼圖效果。

怎么用Three.js實(shí)現(xiàn)雪糕地球

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

只使用普通貼圖的雪糕地球看起來(lái)已經(jīng)非常不錯(cuò)了,但還有進(jìn)一步美化的空間,Three 提供了高光貼圖,使用高光貼圖,會(huì)有高亮部分顯示。

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, // 高光部分的亮度
});

雖然動(dòng)圖錄制的幀數(shù)太低,還是依稀可以看到一些高亮區(qū)域。

Three 還提供了環(huán)境貼圖,環(huán)境貼圖可以增加表面的細(xì)節(jié),使三維模型更加立體。

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,
});

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

小包繼續(xù)開(kāi)始查看文檔,環(huán)境貼圖中有 normalScale 屬性,可以設(shè)置顏色的深淺程度,減少對(duì)應(yīng)屬性值為 0.5,0.5。

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

交互式雪糕地球

給地球加一些交互效果:

  • 當(dāng)鼠標(biāo)靠近,地球放大;鼠標(biāo)遠(yuǎn)離時(shí),地球縮小

  • 地球隨鼠標(biāo)方向轉(zhuǎn)動(dòng)

上述動(dòng)效我們可以通過(guò)移動(dòng)相機(jī)位置實(shí)現(xiàn)。首先設(shè)定地球旋轉(zhuǎn)的最大正負(fù)角度為 270。

// 定義動(dòng)效對(duì)象
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);

通過(guò)上述事件計(jì)算出 mouseXmouseY 的值,在 render 函數(shù)中,修改 camera 的位置。

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

移動(dòng)端同步監(jiān)聽(tīng) touchmove 事件,手機(jī)也可以看到雪糕地球的動(dòng)態(tài)效果。

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 效果

紋理的加載需要一定的時(shí)間,因此添加一個(gè)轉(zhuǎn)場(chǎng) loading 效果。

loading 效果使用小包前面的實(shí)現(xiàn)躍動(dòng)的文字中的效果。

.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,其功能是處理并跟蹤已加載和待處理的數(shù)據(jù)。當(dāng)所有加載器加載完成后,會(huì)調(diào)用 LoadingManager 上的 onLoad 事件。

因此我們定義一個(gè) LoadingManager,當(dāng)觸發(fā) onLoad 事件后,將頁(yè)面中的加載標(biāo)志移除。

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

以上就是關(guān)于“怎么用Three.js實(shí)現(xiàn)雪糕地球”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI