使用Three.js制作一個3D獎牌頁面
背景
本文使用 React + Three.js
技術(shù)棧,實(shí)現(xiàn)粉絲突破1000的3D紀(jì)念頁面,包含的主要知識點(diǎn)包括:Three.js
提供的光源、DirectionLight
平行光、HemisphereLight
半球光源、AmbientLight
環(huán)境光、獎牌素材生成、貼圖知識、MeshPhysicalMaterial
物理材質(zhì)、TWEEN
鏡頭補(bǔ)間動畫、CSS
禮花動畫等。
效果
效果如上圖,頁面由包含我的個人信息的獎牌、1000+ Followers
模型構(gòu)成
在線預(yù)覽:https://dragonir.github.io/3d/#/segmentfault
實(shí)現(xiàn)
引入資源
首先引入開發(fā)功能所需的庫,其中 FBXLoader
用于加在 1000+
字體模型、OrbitControls
鏡頭軌道控制、TWEEN
用于生成補(bǔ)間動畫、Stats
用于開發(fā)時性能查看。
import * as THREE from "three"; import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import { TWEEN } from "three/examples/jsm/libs/tween.module.min.js"; import Stats from "three/examples/jsm/libs/stats.module";
場景初始化
這部分內(nèi)容主要用于初始化場景和參數(shù),詳細(xì)講解可點(diǎn)擊文章末尾鏈接閱讀我之前的文章,本文不再贅述。
container = document.getElementById('container'); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.needsUpdate = true; container.appendChild(renderer.domElement); // 場景 scene = new THREE.Scene(); // 給場景設(shè)置好看的背景 scene.background = new THREE.TextureLoader().load(backgroundTexture); // 攝像機(jī) camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 0, 0); camera.lookAt(new THREE.Vector3(0, 0, 0)); // 控制器 controls = new OrbitControls(camera, renderer.domElement); controls.target.set(0, 0, 0); controls.enableDamping = true; controls.enableZoom = false; controls.enablePan = false; controls.rotateSpeed = .2;
為了達(dá)到更好的視覺效果,為OrbitControls
設(shè)置了縮放禁用、平移禁用和減小默認(rèn)旋轉(zhuǎn)速度
光照效果
為了模擬真實(shí)的物理場景,本示例中使用了 3種
光源。
// 直射光 const cubeGeometry = new THREE.BoxGeometry(0.001, 0.001, 0.001); const cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff }); const cube = new THREE.Mesh(cubeGeometry, cubeMaterial); cube.position.set(0, 0, 0); light = new THREE.DirectionalLight(0xffffff, 1); light.intensity = 1; light.position.set(18, 20, 60); light.castShadow = true; light.target = cube; light.shadow.mapSize.width = 512 * 12; light.shadow.mapSize.height = 512 * 12; light.shadow.camera.top = 80; light.shadow.camera.bottom = -80; light.shadow.camera.left = -80; light.shadow.camera.right = 80; scene.add(light); // 半球光 const ambientLight = new THREE.AmbientLight(0xffffff); ambientLight.intensity = .8; scene.add(ambientLight); // 環(huán)境光 const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0xfffc00); hemisphereLight.intensity = .3; scene.add(hemisphereLight);
Three.js 提供的光源
Three.js
庫提供了一些列光源,而且沒種光源都有特定的行為和用途。這些光源包括:
光源名稱 | 描述 |
---|---|
AmbientLight 環(huán)境光 | 這是一種基礎(chǔ)光源,它的顏色會添加到整個場景和所有對象的當(dāng)前顏色上 |
PointLight 點(diǎn)光源 | 空間中的一點(diǎn),朝所有的方向發(fā)射光線 |
SpotLight 聚光燈光源 | 這種光源有聚光的效果,類似臺燈、天花板上的吊燈,或者手電筒 |
DirectionLight 平行光 | 也稱為無限光。從這種光源發(fā)出的光線可以看著平行的。例如,太陽光 |
HemishpereLight 半球光 | 這是一種特殊光源,可以用來創(chuàng)建更加自然的室外光線,模擬放光面和光線微弱的天空 |
AreaLight 面光源 | 使用這種光源可以指定散發(fā)光線的平面,而不是空間中的一個點(diǎn) |
LensFlare 鏡頭眩光 | 這不是一種光源,但是通過 LensFlare 可以為場景中的光源添加眩光效果 |
THREE.DirectionLight 平行光
THREE.DirectionLight
可以看作是距離很遠(yuǎn)的光,它發(fā)出的所有光線都是相互平行的。平行光的一個范例就是太陽光。被平行光照亮的整個區(qū)域接受到的光強(qiáng)是一樣的。
構(gòu)造函數(shù):
new THREE.DirectionLight(color);
屬性說明:
- position:光源在場景中的位置。
target
:目標(biāo)。它的指向很重要。使用target
屬性,你可以將光源指向場景中的特定對象或位置。此屬性需要一個THREE.Object3D
對象。intensity
:光源照射的強(qiáng)度,默認(rèn)值:1
。castShadow
:投影,如果設(shè)置為true
,這個光源就會生成陰影。onlyShadow
:僅陰影,如果此屬性設(shè)置為true
,則該光源只生成陰影,而不會在場景中添加任何光照。shadow.camera.near
:投影近點(diǎn),表示距離光源的哪一個位置開始生成陰影。shadow.camera.far
:投影遠(yuǎn)點(diǎn),表示到距離光源的哪一個位置可以生成陰影。shadow.camera.left
:投影左邊界。shadow.camera.right
:投影右邊界。shadow.camera.top
:投影上邊界。shadow.camera.bottom
:投影下邊界。shadow.map.width
和shadow.map.height
:陰影映射寬度和陰影映射高度。決定了有多少像素用來生成陰影。當(dāng)陰影具有鋸齒狀邊緣或看起來不光滑時,可以增加這個值。在場景渲染之后無法更改。兩者的默認(rèn)值均為:512
。
THREE.HemisphereLight 半球光光源
使用半球光光源,可以創(chuàng)建出更加貼近自然的光照效果。
構(gòu)造函數(shù):
new THREE.HeimsphereLight(groundColor, color, intensity);
屬性說明:
groundColor
:從地面發(fā)出的光線顏色。Color
:從天空發(fā)出的光線顏色。intensity
:光線照射的強(qiáng)度。
THREE.AmbientLight 環(huán)境光
在創(chuàng)建 THREE.AmbientLight
時,顏色會應(yīng)用到全局。該光源并沒有特別的來源方向,并且不會產(chǎn)生陰影。
構(gòu)造函數(shù):
new THREE.AmbientLight(color);
使用建議:
- 通常不能將
THREE.AmbientLight
作為場景中唯一的光源,因?yàn)樗鼤鼍爸械乃形矬w渲染為相同的顏色。 - 使用其他光源,如
THREE.SpotLight
或THREE.DirectionLight
的同時使用它,目的是弱化陰影或給場景添加一些額外顏色。 - 由于
THREE.AmbientLight
光源不需要指定位置并且會應(yīng)用到全局,所以只需要指定個顏色,然后將它添加到場景中即可。
添加網(wǎng)格和地面
添加網(wǎng)格是為了方便開發(fā),可以調(diào)整模型的合適的相對位置,本例中保留網(wǎng)格的目的是為了頁面更有 3D景深效果
。透明材質(zhì)的地面是為了顯示模型的陰影。
// 網(wǎng)格 const grid = new THREE.GridHelper(200, 200, 0xffffff, 0xffffff); grid.position.set(0, -30, -50); grid.material.transparent = true; grid.material.opacity = 0.1; scene.add(grid); // 創(chuàng)建地面,透明材質(zhì)顯示陰影 var planeGeometry = new THREE.PlaneGeometry(200, 200); var planeMaterial = new THREE.ShadowMaterial({ opacity: .5 }); var plane = new THREE.Mesh(planeGeometry, planeMaterial); plane.rotation.x = -0.5 * Math.PI; plane.position.set(0, -30, -50); plane.receiveShadow = true; scene.add(plane);
創(chuàng)建獎牌
由于時間關(guān)系,本示例獎牌模型直接使用 Three.js
自帶的基礎(chǔ)立方體模型 THREE.BoxGeometry
來實(shí)現(xiàn),你也可以使用其他立方體如球體、圓珠等,甚至可以使用 Blender
等專業(yè)建模軟件創(chuàng)建自己喜歡的獎牌形狀。(ps
:個人覺得立方體也挺好看的 ??
)
獎牌UI素材生成
獎牌上下面和側(cè)面貼圖制作:
為了生成的獎牌有黃金質(zhì)感,本例中使用下圖該材質(zhì)貼圖,來生成亮瞎眼的24K純金效果 。
獎牌正面和背面貼圖制作:
獎牌的正面和背面使用的貼圖是 SegmentFault 個人中心頁的截圖,為了更具有金屬效果,我用上面金屬材質(zhì)貼圖給它添加了一個帶有圓角的邊框。
Photoshop 生成圓角金屬邊框具體方法:截圖上面添加金屬圖層 -> 使用框選工具框選需要刪除的內(nèi)容 -> 點(diǎn)擊選擇 -> 點(diǎn)擊修改 -> 點(diǎn)擊平滑 -> 輸入合適的圓角大小 -> 刪除選區(qū) -> 合并圖層 -> 完成并導(dǎo)出圖片。
最終的正反面的材質(zhì)貼圖如下圖所示,為了顯示更清晰,我在 Photoshop 中同時修改了圖片的對比度 和 飽和度,并加了 SegmentFault 的 Logo 在上面。
獎牌正面和背面的法相貼圖制作:
為了生成凹凸質(zhì)感,就需要為模型添加法相貼圖。使用上面已經(jīng)生成的正面和背面的材質(zhì)貼圖,就可以使用在線工具自動生成法相貼圖。生成時可以根據(jù)需要,通過調(diào)整 Strength
、Level
、Blur
等參數(shù)進(jìn)行樣式微調(diào),并且能夠?qū)崟r預(yù)覽。調(diào)整好后點(diǎn)擊 Download
下載即可。
貼圖在線制作工具傳送門:NormalMap-Online
通過多次調(diào)節(jié)優(yōu)化,最終使用的法相貼圖如下圖所示。
使用上面生成的素材,現(xiàn)在進(jìn)行獎牌模型的構(gòu)建。正面和背面使用個人信息材質(zhì),其他面使用金屬材質(zhì)。然后遍歷對所有面調(diào)整金屬度和粗糙度樣式。
let segmentMap = new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(segmentTexture), normalMap: new THREE.TextureLoader().load(normalMapTexture) }); let metalMap = new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(metalTexture)}); // 創(chuàng)建紋理數(shù)組 const boxMaps = [metalMap, metalMap, metalMap, metalMap, segmentMap, segmentMap]; // ?? 立方體長寬高比例需要和貼圖的大小比例一致,厚度可以隨便定 box = new THREE.Mesh(new THREE.BoxGeometry(297, 456, 12), boxMaps); box.material.map(item => { // 材質(zhì)樣式調(diào)整 item.metalness = .5; item.roughness = .4; item.refractionRatio = 1; return item; }); box.scale.set(0.085, 0.085, 0.085); box.position.set(-22, 2, 0); box.castShadow = true; meshes.push(box); scene.add(box);
上面 4 張效果圖依次對應(yīng)的是:
- 圖1:創(chuàng)建沒有貼圖的 BoxGeometry,只是一個白色的立方體。
- 圖2:立方體添加 材質(zhì)貼圖,此時沒有凹凸效果。
- 圖3:立方體添加 法相貼圖,此時產(chǎn)生凹凸效果。
- 圖4:調(diào)節(jié)立方體材質(zhì)的 金屬度、粗糙程度 和 反射率,更具有真實(shí)感。
Three.js 中的貼圖
貼圖類型
map
:材質(zhì)貼圖normalMap
:法線貼圖bumpMap
:凹凸貼圖envMap
:環(huán)境貼圖specularMap
:高光貼圖lightMap
:光照貼圖
貼圖原理
通過紋理貼圖加載器 TextureLoader()
去新創(chuàng)建一個貼圖對象出來,然后再去調(diào)用里面的 load()
方法去加載一張圖片,這樣就會返回一個紋理對象,紋理對象可以作為模型材質(zhì)顏色貼圖 map
屬性的值,材質(zhì)的顏色貼圖屬性 map
設(shè)置后,模型會從紋理貼圖上采集像素值。
MeshPhysicalMaterial 物理材質(zhì)
MeshPhysicalMaterial
類是 PBR
物理材質(zhì),可以更好的模擬光照計算,相比較高光網(wǎng)格材質(zhì)MeshPhongMaterial
渲染效果更逼真。
如果你想展示一個產(chǎn)品,為了更逼真的渲染效果最好選擇該材質(zhì),如果游戲?yàn)榱烁玫娘@示效果可以選擇 PBR
材質(zhì) MeshPhysicalMaterial
,而不是高光材質(zhì) MeshPhongMaterial
。
特殊屬性
.metalness
金屬度屬性:表示材質(zhì)像金屬的程度。非金屬材料,如木材或石材,使用 0.0,金屬使用 1.0,中間沒有(通常). 默認(rèn) 0.5. 0.0 到 1.0 之間的值可用于生銹的金屬外觀。如果還提供了粗糙度貼圖.metalnessMap
,則兩個值都相乘。.roughness
粗糙度屬性:表示材質(zhì)的粗糙程度. 0.0 表示平滑的鏡面反射,1.0 表示完全漫反射. 默認(rèn) 0.5. 如果還提供粗糙度貼圖.roughnessMap
,則兩個值相乘..metalnessMap
金屬度貼圖:紋理的藍(lán)色通道用于改變材料的金屬度..roughnessMap
粗糙度貼圖:紋理的綠色通道用于改變材料的粗糙度。
注意使用物理材質(zhì)的時候,一般需要設(shè)置環(huán)境貼圖 .envMap
。
加載1000+文字模型
1000+
字樣的模型使用 THREE.LoadingManager
和 FBXLoader
加載。詳細(xì)使用方法也不再本文中贅述,可參考文章末尾鏈接查看我的其他文章,里面有詳細(xì)描述。
const manager = new THREE.LoadingManager(); manager.onProgress = async(url, loaded, total) => { if (Math.floor(loaded / total * 100) === 100) { // 設(shè)置加載進(jìn)度 _this.setState({ loadingProcess: Math.floor(loaded / total * 100) }); // 加載鏡頭移動補(bǔ)間動畫 Animations.animateCamera(camera, controls, { x: 0, y: 4, z: 60 }, { x: 0, y: 0, z: 0 }, 3600, () => {}); } else { _this.setState({ loadingProcess: Math.floor(loaded / total * 100) }); } }; const fbxLoader = new FBXLoader(manager); fbxLoader.load(textModel, mesh => { mesh.traverse(child => { if (child.isMesh) { // 生成陰影 child.castShadow = true; // 樣式調(diào)整 child.material.metalness = 1; child.material.roughness = .2; meshes.push(mesh); } }); mesh.position.set(16, -4, 0); mesh.rotation.x = Math.PI / 2 mesh.scale.set(.08, .08, .08); scene.add(mesh); });
補(bǔ)間動畫
相機(jī)移動實(shí)現(xiàn)漫游等動畫,頁面打開時,模型加載完畢從大變小的動畫就是通過 TWEEN
實(shí)現(xiàn)的。
animateCamera: (camera, controls, newP, newT, time = 2000, callBack) => { var tween = new TWEEN.Tween({ x1: camera.position.x, // 相機(jī)x y1: camera.position.y, // 相機(jī)y z1: camera.position.z, // 相機(jī)z x2: controls.target.x, // 控制點(diǎn)的中心點(diǎn)x y2: controls.target.y, // 控制點(diǎn)的中心點(diǎn)y z2: controls.target.z, // 控制點(diǎn)的中心點(diǎn)z }); tween.to({ x1: newP.x, y1: newP.y, z1: newP.z, x2: newT.x, y2: newT.y, z2: newT.z, }, time); tween.onUpdate(function (object) { camera.position.x = object.x1; camera.position.y = object.y1; camera.position.z = object.z1; controls.target.x = object.x2; controls.target.y = object.y2; controls.target.z = object.z2; controls.update(); }); tween.onComplete(function () { controls.enabled = true; callBack(); }); tween.easing(TWEEN.Easing.Cubic.InOut); tween.start(); }
動畫更新
最后不要忘了要在 requestAnimationFrame
中更新場景、軌道控制器、TWEEN
、以及模型的自轉(zhuǎn)等。
// 監(jiān)聽頁面縮放,更新相機(jī)和渲染 function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); stats && stats.update(); controls && controls.update(); TWEEN && TWEEN.update(); // 獎牌模型自轉(zhuǎn) box && (box.rotation.y += .04); }
禮花動畫
最后,通過 box-shadow
和簡單的 CSS
動畫,給頁面添加禮花綻放效果,營造歡慶氛圍!
<div className="firework_1"></div> <div className="firework_2"></div> <!-- ... --> <div className="firework_10"></div>
樣式動畫:
[class^=firework_] { position: absolute; width: 0.1rem; height: 0.1rem; border-radius: 50%; transform: scale(8) } .firework_1 { animation: firework_lg 2s both infinite; animation-delay: 0.3s; top: 5%; left: 5%; } @keyframes firework_lg { 0%, 100% { opacity: 0; } 10%, 70% { opacity: 1; } 100% { box-shadow: -0.9rem 0rem 0 #fff, 0.9rem 0rem 0 #fff, 0rem -0.9rem 0 #fff, 0rem 0.9rem 0 #fff, 0.63rem -0.63rem 0 #fff, 0.63rem 0.63rem 0 #fff, -0.63rem -0.63rem 0 #fff, -0.63rem 0.63rem 0 #fff; } }
實(shí)現(xiàn)效果:
總結(jié)
本文中主要涉及到的知識點(diǎn)包括:
Three.js
提供的光源THREE.DirectionLight
平行光THREE.HemisphereLight
半球光光源THREE.AmbientLight
環(huán)境光- 獎牌
UI
素材生成 Three.js
中的貼圖MeshPhysicalMaterial
物理材質(zhì)TWEEN
鏡頭補(bǔ)間動畫CSS
禮花動畫
以上就是使用Three.js制作一個3D獎牌頁面的詳細(xì)內(nèi)容,更多關(guān)于Three.js 3D獎牌的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序?qū)崿F(xiàn)電子簽名并導(dǎo)出圖片
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)電子簽名,并導(dǎo)出圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05JS繼承與閉包及JS實(shí)現(xiàn)繼承的三種方式
大家都知道,面向?qū)ο蟮娜筇卣鳌庋b、繼承、多態(tài)。下面通過本文給大家介紹JS繼承與閉包及JS實(shí)現(xiàn)繼承的三種方式,感興趣的朋友一起看看吧2017-10-10js前端加密庫Crypto-js進(jìn)行MD5/SHA256/BASE64/AES加解密的方法與示例
js加密解密可以使用crypto-js它可以進(jìn)行MD5、SHA-1、SHA-256、Base64、AES、DES、等算法和加密,這是一個對稱加密的庫,可以使用 AES、DES、但沒有rsa等非對稱加密的方法2023-12-12