您好,登錄后才能下訂單哦!
這篇“怎么用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ù): 雪糕地球
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
中最重要的三個(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)剪切面
文字有些難以理解,可以參考一下下圖:
打開(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 );
SphereBufferGeometry
是 Three
中實(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
相機(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è)黑色模型?
小包苦思冥想: 萬(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);
立體效果看起來(lái)不明顯,沒(méi)事,接下來(lái)我們讓球體動(dòng)起來(lái)。接下來(lái),給球體添加一個(gè) z
軸和 y
軸的轉(zhuǎn)動(dòng)。
const createAnimRotation = () => { const speed = 0.005; sphere.rotation.z += speed / 2; sphere.rotation.y += speed; }; const render = () => { createAnimRotation(); renderer.render(scene, camera); requestAnimationFrame(render); }; render();
由于球體是對(duì)稱的,轉(zhuǎn)動(dòng)看起來(lái)并不明顯,如果你特別想看到轉(zhuǎn)動(dòng)效果,可以將 SphereBufferGeometry
暫時(shí)更換為 BoxBufferGeometry
。
上文已經(jīng)成功實(shí)現(xiàn)地球,接下來(lái)我們來(lái)為地球披上衣服。本文實(shí)現(xiàn)的是雪糕地球,因此小包直接為其披上雪糕外衣。
Three
可以將一張紋理圖映射到幾何體上,具體的映射原理我們不做探究,映射的思想可以參考下圖。
選取一張雪糕地球的紋理圖,使用下面的代碼實(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 >= 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); }, }; document.addEventListener("mousemove", moveAnimate.onMouseMove);
通過(guò)上述事件計(jì)算出 mouseX
與 mouseY
的值,在 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);
紋理的加載需要一定的時(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 = () => { 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è)資訊頻道。
免責(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)容。