1. 「冰墩墩」代碼,開源了!

        共 10149字,需瀏覽 21分鐘

         ·

        2022-02-12 11:10

        隨著前兩天冬奧會序幕的正式拉開,也成功帶火了本次吉祥物冰墩墩。憨厚可愛的熊貓形象,讓冰墩墩的實體公仔、鑰匙扣都被一搶而空,眾多網(wǎng)友呼吁現(xiàn)在真的是「一墩難求」!
        為了圓大家「人手一墩」的夢想,國內(nèi)一位程序員 dragonir,用前端 + 建模的技術(shù)自己實現(xiàn)了一個冰墩墩,并將代碼開源到了 GitHub 上。
        下面咱們就來看下具體技術(shù)實現(xiàn)細節(jié)吧。

        原文地址:https://segmentfault.com/a/1190000041363089

        背景

        本文使用?Three.js + React?技術(shù)棧,實現(xiàn)冬日和奧運元素,制作了一個充滿趣味和紀念意義的冬奧主題?3D?頁面。

        本文涉及到的知識點主要包括:TorusGeometry?圓環(huán)面、MeshLambertMaterial?非光澤表面材質(zhì)、MeshDepthMaterial?深度網(wǎng)格材質(zhì)、custromMaterial?自定義材質(zhì)、Points?粒子、PointsMaterial?點材質(zhì)等。

        效果

        實現(xiàn)效果如以下????動圖所示,頁面主要由?2022?冬奧會吉祥物?冰墩墩?、奧運五環(huán)、舞動的旗幟???、樹木????以及下雪效果????等組成。

        按住鼠標左鍵移動可以改為相機位置,獲得不同視圖。

        ???在線預覽:https://dragonir.github.io/3d/#/olympic(部署在?GitHub,加載速度可能會有點慢???

        實現(xiàn)

        引入資源

        首先引入開發(fā)頁面所需要的庫和外部資源,OrbitControls?用于鏡頭軌道控制、TWEEN?用于補間動畫實現(xiàn)、GLTFLoader?用于加載?glb?或?gltf?格式的?3D?模型、以及一些其他模型、貼圖等資源。

        import?React?from?'react';
        import?{?OrbitControls?}?from?"three/examples/jsm/controls/OrbitControls";
        import?{?TWEEN?}?from?"three/examples/jsm/libs/tween.module.min.js";
        import?{?GLTFLoader?}?from?"three/examples/jsm/loaders/GLTFLoader";
        import?bingdundunModel?from?'./models/bingdundun.glb';
        //?...

        頁面 DOM 結(jié)構(gòu)

        頁面?DOM?結(jié)構(gòu)非常簡單,只有渲染?3D?元素的?#container?容器和顯示加載進度的?.olympic_loading?元素。


        ??"container">

        ??{this.state.loadingProcess?===?100???''?:?(
        ????"olympic_loading">
        ??????"box">{this.state.loadingProcess}?%

        ????

        ??)}

        場景初始化

        初始化渲染容器、場景、相機。關(guān)于這部分內(nèi)容的詳細知識點,可以查閱我往期的文章,本文中不再贅述。

        container?=?document.getElementById('container');
        renderer?=?new?THREE.WebGLRenderer({?antialias:?true?});
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth,?window.innerHeight);
        renderer.shadowMap.enabled?=?true;
        container.appendChild(renderer.domElement);
        scene?=?new?THREE.Scene();
        scene.background?=?new?THREE.TextureLoader().load(skyTexture);
        camera?=?new?THREE.PerspectiveCamera(60,?window.innerWidth?/?window.innerHeight,?0.1,?1000);
        camera.position.set(0,?30,?100);
        camera.lookAt(new?THREE.Vector3(0,?0,?0));

        添加光源

        本示例中主要添加了兩種光源:DirectionalLight?用于產(chǎn)生陰影,調(diào)節(jié)頁面亮度、AmbientLight?用于渲染環(huán)境氛圍。

        //?直射光
        const?light?=?new?THREE.DirectionalLight(0xffffff,?1);
        light.intensity?=?1;
        light.position.set(16,?16,?8);
        light.castShadow?=?true;
        light.shadow.mapSize.width?=?512?*?12;
        light.shadow.mapSize.height?=?512?*?12;
        light.shadow.camera.top?=?40;
        light.shadow.camera.bottom?=?-40;
        light.shadow.camera.left?=?-40;
        light.shadow.camera.right?=?40;
        scene.add(light);
        //?環(huán)境光
        const?ambientLight?=?new?THREE.AmbientLight(0xcfffff);
        ambientLight.intensity?=?1;
        scene.add(ambientLight);

        加載進度管理

        使用?THREE.LoadingManager?管理頁面模型加載進度,在它的回調(diào)函數(shù)中執(zhí)行一些與加載進度相關(guān)的方法。

        本例中的頁面加載進度就是在?onProgress?中完成的,當頁面加載進度為?100%?時,執(zhí)行?TWEEN?鏡頭補間動畫。

        const?manager?=?new?THREE.LoadingManager();
        manager.onStart?=?(url,?loaded,?total)?=>?{};
        manager.onLoad?=?()?=>?{?console.log('Loading?complete!')};
        manager.onProgress?=?(url,?loaded,?total)?=>?{
        ??if?(Math.floor(loaded?/?total?*?100)?===?100)?{
        ????this.setState({?loadingProcess:?Math.floor(loaded?/?total?*?100)?});
        ????//?鏡頭補間動畫
        ????Animations.animateCamera(camera,?controls,?{?x:?0,?y:?-1,?z:?20?},?{?x:?0,?y:?0,?z:?0?},?3600,?()?=>?{});
        ??}?else?{
        ????this.setState({?loadingProcess:?Math.floor(loaded?/?total?*?100)?});
        ??}
        };

        創(chuàng)建地面

        本示例中凹凸起伏的地面是使用?Blender?構(gòu)建模型,然后導出?glb?格式加載創(chuàng)建的。

        當然也可以只使用?Three.js?自帶平面網(wǎng)格加凹凸貼圖也可以實現(xiàn)類似的效果。使用?Blender?自建模型的優(yōu)點在于可以自由可視化地調(diào)整地面的起伏效果。

        var?loader?=?new?THREE.GLTFLoader(manager);
        loader.load(landModel,?function?(mesh)?{
        ??mesh.scene.traverse(function?(child)?{
        ????if?(child.isMesh)?{
        ??????child.material.metalness?=?.1;
        ??????child.material.roughness?=?.8;
        ??????//?地面
        ??????if?(child.name?===?'Mesh_2')?{
        ????????child.material.metalness?=?.5;
        ????????child.receiveShadow?=?true;
        ??????}
        ??});
        ??mesh.scene.rotation.y?=?Math.PI?/?4;
        ??mesh.scene.position.set(15,?-20,?0);
        ??mesh.scene.scale.set(.9,?.9,?.9);
        ??land?=?mesh.scene;
        ??scene.add(land);
        });

        創(chuàng)建冬奧吉祥物冰墩墩

        現(xiàn)在添加可愛的冬奧會吉祥物熊貓冰墩墩???,冰墩墩同樣是使用?glb?格式模型加載的。

        它的原始模型來源于這里:

        從這個網(wǎng)站免費現(xiàn)在模型后,原模型是使用?3D max?建的我發(fā)現(xiàn)并不能直接用在網(wǎng)頁中,需要在?Blender?中轉(zhuǎn)換模型格式,還需要調(diào)整調(diào)整模型的貼圖法線,才能還原渲染圖效果。

        原模型

        冰墩墩貼圖:

        轉(zhuǎn)換成 Blender 支持的模型,并在 Blender 中調(diào)整模型貼圖法線、并添加貼圖:

        導出 glb 格式:

        仔細觀察冰墩墩 ??可以發(fā)現(xiàn),它的外面有一層透明塑料或玻璃質(zhì)感外殼,這個效果可以通過修改模型的透明度、金屬度、粗糙度等材質(zhì)參數(shù)實現(xiàn),最后就可以渲染出如??? banner圖?所示的那種效果,具體如以下代碼所示。
        loader.load(bingdundunModel,?mesh?=>?{
        ??mesh.scene.traverse(child?=>?{
        ????if?(child.isMesh)?{
        ??????//?內(nèi)部
        ??????if?(child.name?===?'oldtiger001')?{
        ????????child.material.metalness?=?.5
        ????????child.material.roughness?=?.8
        ??????}
        ??????//?半透明外殼
        ??????if?(child.name?===?'oldtiger002')?{
        ????????child.material.transparent?=?true;
        ????????child.material.opacity?=?.5
        ????????child.material.metalness?=?.2
        ????????child.material.roughness?=?0
        ????????child.material.refractionRatio?=?1
        ????????child.castShadow?=?true;
        ??????}
        ????}
        ??});
        ??mesh.scene.rotation.y?=?Math.PI?/?24;
        ??mesh.scene.position.set(-8,?-12,?0);
        ??mesh.scene.scale.set(24,?24,?24);
        ??scene.add(mesh.scene);
        });

        創(chuàng)建奧運五環(huán)

        奧運五環(huán)由基礎幾何模型圓環(huán)面?TorusGeometry?來實現(xiàn),創(chuàng)建五個圓環(huán)面,并調(diào)整它們的材質(zhì)顏色和位置來構(gòu)成藍黑紅黃綠順序的五環(huán)結(jié)構(gòu)。五環(huán)材質(zhì)使用的是?MeshLambertMaterial。
        const?fiveCycles?=?[
        ??{?key:?'cycle_0',?color:?0x0885c2,?position:?{?x:?-250,?y:?0,?z:?0?}},
        ??{?key:?'cycle_1',?color:?0x000000,?position:?{?x:?-10,?y:?0,?z:?5?}},
        ??{?key:?'cycle_2',?color:?0xed334e,?position:?{?x:?230,?y:?0,?z:?0?}},
        ??{?key:?'cycle_3',?color:?0xfbb132,?position:?{?x:?-125,?y:?-100,?z:?-5?}},
        ??{?key:?'cycle_4',?color:?0x1c8b3c,?position:?{?x:?115,?y:?-100,?z:?10?}}
        ];
        fiveCycles.map(item?=>?{
        ??let?cycleMesh?=?new?THREE.Mesh(new?THREE.TorusGeometry(100,?10,?10,?50),?new?THREE.MeshLambertMaterial({
        ????color:?new?THREE.Color(item.color),
        ????side:?THREE.DoubleSide
        ??}));
        ??cycleMesh.castShadow?=?true;
        ??cycleMesh.position.set(item.position.x,?item.position.y,?item.position.z);
        ??meshes.push(cycleMesh);
        ??fiveCyclesGroup.add(cycleMesh);
        });
        fiveCyclesGroup.scale.set(.036,?.036,?.036);
        fiveCyclesGroup.position.set(0,?10,?-8);
        scene.add(fiveCyclesGroup);

        ?? TorusGeometry 圓環(huán)面

        TorusGeometry?一個用于生成圓環(huán)幾何體的類。

        構(gòu)造函數(shù)

        TorusGeometry(radius:?Float,?tube:?Float,?radialSegments:?Integer,?tubularSegments:?Integer,?arc:?Float)
        • radius:圓環(huán)的半徑,從圓環(huán)的中心到管道(橫截面)的中心。默認值是?1。
        • tube:管道的半徑,默認值為?0.4。
        • radialSegments:圓環(huán)的分段數(shù),默認值為?8。
        • tubularSegments:管道的分段數(shù),默認值為?6。
        • arc:圓環(huán)的圓心角(單位是弧度),默認值為?Math.PI * 2。

        ?? MeshLambertMaterial 非光澤表面材質(zhì)

        一種非光澤表面的材質(zhì),沒有鏡面高光。

        該材質(zhì)使用基于非物理的?Lambertian?模型來計算反射率。這可以很好地模擬一些表面(例如未經(jīng)處理的木材或石材),但不能模擬具有鏡面高光的光澤表面(例如涂漆木材)。

        構(gòu)造函數(shù)

        MeshLambertMaterial(parameters?:?Object)
        • parameters:(可選)用于定義材質(zhì)外觀的對象,具有一個或多個屬性。材質(zhì)的任何屬性都可以從此處傳入。

        創(chuàng)建旗幟

        旗面模型是從 sketchfab 下載的,還需要一個旗桿,可以在?Blender?中添加了一個柱狀立方體,并調(diào)整好合適的長寬高和旗面結(jié)合起來。

        旗面貼圖

        旗面添加了動畫,需要在代碼中執(zhí)行動畫幀播放。

        loader.load(flagModel,?mesh?=>?{
        ??mesh.scene.traverse(child?=>?{
        ????if?(child.isMesh)?{
        ??????child.castShadow?=?true;
        ??????//?旗幟
        ??????if?(child.name?===?'mesh_0001')?{
        ????????child.material.metalness?=?.1;
        ????????child.material.roughness?=?.1;
        ????????child.material.map?=?new?THREE.TextureLoader().load(flagTexture);
        ??????}
        ??????//?旗桿
        ??????if?(child.name?===?'柱體')?{
        ????????child.material.metalness?=?.6;
        ????????child.material.roughness?=?0;
        ????????child.material.refractionRatio?=?1;
        ????????child.material.color?=?new?THREE.Color(0xeeeeee);
        ??????}
        ????}
        ??});
        ??mesh.scene.rotation.y?=?Math.PI?/?24;
        ??mesh.scene.position.set(2,?-7,?-1);
        ??mesh.scene.scale.set(4,?4,?4);
        ??//?動畫
        ??let?meshAnimation?=?mesh.animations[0];
        ??mixer?=?new?THREE.AnimationMixer(mesh.scene);
        ??let?animationClip?=?meshAnimation;
        ??let?clipAction?=?mixer.clipAction(animationClip).play();
        ??animationClip?=?clipAction.getClip();
        ??scene.add(mesh.scene);
        });

        創(chuàng)建樹木

        為了充實畫面,營造冬日氛圍,于是就添加了幾棵松樹????作為裝飾。
        添加松樹的時候用到一個技巧非常重要:我們知道因為樹的模型非常復雜,有非常多的面數(shù),面數(shù)太多會降低頁面性能,造成卡頓。
        本文中使用兩個如下圖????所示的兩個交叉的面來作為樹的基座,這樣的話樹只有兩個面數(shù),使用這個技巧可以和大程度上優(yōu)化頁面性能,而且樹????的樣子看起來也是有?3D?感的。

        材質(zhì)貼圖

        為了使樹只在貼圖透明部分透明、其他地方不透明,并且可以產(chǎn)生樹狀陰影而不是長方體陰影,需要給樹模型添加如下?MeshPhysicalMaterial、MeshDepthMaterial?兩種材質(zhì),兩種材質(zhì)使用同樣的紋理貼圖,其中?MeshDepthMaterial?添加到模型的?custromMaterial?屬性上。
        ?let?treeMaterial?=?new?THREE.MeshPhysicalMaterial({
        ??map:?new?THREE.TextureLoader().load(treeTexture),
        ??transparent:?true,
        ??side:?THREE.DoubleSide,
        ??metalness:?.2,
        ??roughness:?.8,
        ??depthTest:?true,
        ??depthWrite:?false,
        ??skinning:?false,
        ??fog:?false,
        ??reflectivity:?0.1,
        ??refractionRatio:?0,
        });
        let?treeCustomDepthMaterial?=?new?THREE.MeshDepthMaterial({
        ??depthPacking:?THREE.RGBADepthPacking,
        ??map:?new?THREE.TextureLoader().load(treeTexture),
        ??alphaTest:?0.5
        });
        loader.load(treeModel,?mesh?=>?{
        ??mesh.scene.traverse(child?=>{
        ????if?(child.isMesh)?{
        ??????child.material?=?treeMaterial;
        ??????child.custromMaterial?=?treeCustomDepthMaterial;
        ????}
        ??});
        ??mesh.scene.position.set(14,?-9,?0);
        ??mesh.scene.scale.set(16,?16,?16);
        ??scene.add(mesh.scene);
        ??//?克隆另兩棵樹
        ??let?tree2?=?mesh.scene.clone();
        ??tree2.position.set(10,?-8,?-15);
        ??tree2.scale.set(18,?18,?18);
        ??scene.add(tree2)
        ??//?...
        });
        實現(xiàn)效果也可以從????上面?Banner?圖中可以看到,為了畫面更好看,我取消了樹的陰影顯示。
        ???在?3D?功能開發(fā)中,一些不重要的裝飾模型都可以采取這種策略來優(yōu)化。

        ?? MeshDepthMaterial 深度網(wǎng)格材質(zhì)

        一種按深度繪制幾何體的材質(zhì)。深度基于相機遠近平面,白色最近,黑色最遠。
        構(gòu)造函數(shù)
        MeshDepthMaterial(parameters:?Object)
        • parameters:(可選)用于定義材質(zhì)外觀的對象,具有一個或多個屬性。材質(zhì)的任何屬性都可以從此處傳入。

        特殊屬性
        • .depthPacking[Constant]depth packing?的編碼。默認為?BasicDepthPacking。
        • .displacementMap[Texture]:位移貼圖會影響網(wǎng)格頂點的位置,與僅影響材質(zhì)的光照和陰影的其他貼圖不同,移位的頂點可以投射陰影,阻擋其他對象,以及充當真實的幾何體。
        • .displacementScale[Float]:位移貼圖對網(wǎng)格的影響程度(黑色是無位移,白色是最大位移)。如果沒有設置位移貼圖,則不會應用此值。默認值為?1。
        • .displacementBias[Float]:位移貼圖在網(wǎng)格頂點上的偏移量。如果沒有設置位移貼圖,則不會應用此值。默認值為?0。

        ?? custromMaterial 自定義材質(zhì)

        給網(wǎng)格添加?custromMaterial?自定義材質(zhì)屬性,可以實現(xiàn)透明外圍?png?圖片貼圖的內(nèi)容區(qū)域陰影。

        創(chuàng)建雪花

        創(chuàng)建雪花???,就要用到粒子知識。THREE.Points?是用來創(chuàng)建點的類,也用來批量管理粒子。本例中創(chuàng)建了?1500?個雪花粒子,并為它們設置了限定三維空間的隨機坐標及橫向和豎向的隨機移動速度。
        //?雪花貼圖
        let?texture?=?new?THREE.TextureLoader().load(snowTexture);
        let?geometry?=?new?THREE.Geometry();
        let?range?=?100;
        let?pointsMaterial?=?new?THREE.PointsMaterial({
        ??size:?1,
        ??transparent:?true,
        ??opacity:?0.8,
        ??map:?texture,
        ??//?背景融合
        ??blending:?THREE.AdditiveBlending,
        ??//?景深衰弱
        ??sizeAttenuation:?true,
        ??depthTest:?false
        });
        for?(let?i?=?0;?i???let?vertice?=?new?THREE.Vector3(Math.random()?*?range?-?range?/?2,?Math.random()?*?range?*?1.5,?Math.random()?*?range?-?range?/?2);
        ??//?縱向移速
        ??vertice.velocityY?=?0.1?+?Math.random()?/?3;
        ??//?橫向移速
        ??vertice.velocityX?=?(Math.random()?-?0.5)?/?3;
        ??//?加入到幾何
        ??geometry.vertices.push(vertice);
        }
        geometry.center();
        points?=?new?THREE.Points(geometry,?pointsMaterial);
        points.position.y?=?-30;
        scene.add(points);

        ?? Points 粒子

        Three.js?中,雨????、雪???、云???、星辰???等生活中常見的粒子都可以使用?Points?來模擬實現(xiàn)。
        構(gòu)造函數(shù)
        new?THREE.Points(geometry,?material);
        • 構(gòu)造函數(shù)可以接受兩個參數(shù),一個幾何體和一個材質(zhì),幾何體參數(shù)用來制定粒子的位置坐標,材質(zhì)參數(shù)用來格式化粒子;

        • 可以基于簡單幾何體對象如?BoxGeometry、SphereGeometry?等作為粒子系統(tǒng)的參數(shù);

        • 一般來講,需要自己指定頂點來確定粒子的位置。

        ?? PointsMaterial 點材質(zhì)

        通過?THREE.PointsMaterial?可以設置粒子的屬性參數(shù),是?Points?使用的默認材質(zhì)。
        構(gòu)造函數(shù)
        PointsMaterial(parameters?:?Object)
        • parameters:(可選)用于定義材質(zhì)外觀的對象,具有一個或多個屬性。材質(zhì)的任何屬性都可以從此處傳入。

        ?? 材質(zhì)屬性 .blending

        材質(zhì)的.blending?屬性主要控制紋理融合的疊加方式,.blending?屬性的值包括:
        • THREE.NormalBlending:默認值
        • THREE.AdditiveBlending:加法融合模式
        • THREE.SubtractiveBlending:減法融合模式
        • THREE.MultiplyBlending:乘法融合模式
        • THREE.CustomBlending:自定義融合模式,與?.blendSrc,?.blendDst?或?.blendEquation?屬性組合使用

        ?? 材質(zhì)屬性 .sizeAttenuation

        粒子的大小是否會被相機深度衰減,默認為?true(僅限透視相機)。

        ?? Three.js 向量

        幾維向量就有幾個分量,二維向量?Vector2?有?x?和?y?兩個分量,三維向量?Vector3?有?x、y、z?三個分量,四維向量?Vector4?有?xy、zw?四個分量。
        相關(guān) API
        • Vector2:二維向量
        • Vector3:三維向量
        • Vector4:四維向量

        鏡頭控制、縮放適配、動畫

        controls?=?new?OrbitControls(camera,?renderer.domElement);
        controls.target.set(0,?0,?0);
        controls.enableDamping?=?true;
        //?禁用平移
        controls.enablePan?=?false;
        //?禁用縮放
        controls.enableZoom?=?false;
        //?垂直旋轉(zhuǎn)角度限制
        controls.minPolarAngle?=?1.4;
        controls.maxPolarAngle?=?1.8;
        //?水平旋轉(zhuǎn)角度限制
        controls.minAzimuthAngle?=?-.6;
        controls.maxAzimuthAngle?=?.6;
        window.addEventListener('resize',?()?=>?{
        ??camera.aspect?=?window.innerWidth?/?window.innerHeight;
        ??camera.updateProjectionMatrix();
        ??renderer.setSize(window.innerWidth,?window.innerHeight);
        },?false);
        function?animate()?{
        ??requestAnimationFrame(animate);
        ??renderer.render(scene,?camera);
        ??controls?&&?controls.update();
        ??//?旗幟動畫更新
        ??mixer?&&?mixer.update(new?THREE.Clock().getDelta());
        ??//?鏡頭動畫
        ??TWEEN?&&?TWEEN.update();
        ??//?五環(huán)自轉(zhuǎn)
        ??fiveCyclesGroup?&&?(fiveCyclesGroup.rotation.y?+=?.01);
        ??//?頂點變動之后需要更新,否則無法實現(xiàn)雨滴特效
        ??points.geometry.verticesNeedUpdate?=?true;
        ??//?雪花動畫更新
        ??let?vertices?=?points.geometry.vertices;
        ??vertices.forEach(function?(v)?{
        ????v.y?=?v.y?-?(v.velocityY);
        ????v.x?=?v.x?-?(v.velocityX);
        ????if?(v.y?<=?0)?v.y?=?60;
        ????if?(v.x?<=?-20?||?v.x?>=?20)?v.velocityX?=?v.velocityX?*?-1;
        ??});
        }
        ???完整代碼:
        https://github.com/dragonir/3d/tree/master/src/containers/Olympic

        總結(jié)

        ???本文中主要包含的新知識點包括:
        • TorusGeometry?圓環(huán)面
        • MeshLambertMaterial?非光澤表面材質(zhì)
        • MeshDepthMaterial?深度網(wǎng)格材質(zhì)
        • custromMaterial?自定義材質(zhì)
        • Points?粒子
        • PointsMaterial?點材質(zhì)
        • 材質(zhì)屬性?.blending、.sizeAttenuation
        • Three.js?向量
        進一步優(yōu)化的空間:
        • 添加更多的交互功能、界面樣式進一步優(yōu)化;
        • 吉祥物冰墩墩添加骨骼動畫,并可以通過鼠標和鍵盤控制其移動和交互。
        如果本文對你有幫助的話,請不要吝嗇你的贊,謝謝!
        瀏覽 41
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 国产寡妇亲子伦一区二区三区 | 国内自拍激情视频 | 三级片网站视频 | 成人毛片在线观看视频免费现在 | 青草青青在线视频 |