Three模型導(dǎo)入——初嘗

回顧與開始

在上一篇學(xué)習(xí)筆記《Three基礎(chǔ)使用:一篇就會(huì)!》中,我們已經(jīng)學(xué)會(huì)了如何創(chuàng)建一個(gè)場(chǎng)景并通過相機(jī)觀察它,同時(shí)我們還學(xué)習(xí)了一些three的相關(guān)概念,例如:場(chǎng)景、相機(jī)、渲染器等等。
流程走通了,但是相比沒有人會(huì)滿足與看一個(gè)不停旋轉(zhuǎn)的立方體~這一篇,我們就一起來研究如何導(dǎo)入一個(gè)3d模型

模型從哪兒來

可以從網(wǎng)上下載,也可以從blender等3d建模軟件中建模后導(dǎo)出。
three支持的3d模型的格式有多種,這里我們以gltf格式的模型為例

原理概述

three可用需要幾步:

  • 有畫布div
  • 有場(chǎng)景scene
  • 有光照l(shuí)ight
  • 有相機(jī)camera
  • 有渲染器renderer——循環(huán)渲染

導(dǎo)入模型意思就是把scene替換成外部引入的模型

開始

創(chuàng)建畫布

render() {
        return (
            <div id='l-canvas-frame' style={{ backgroundColor: "yellowgreen" }}>
            </div>
        );
    }

畫布作為容器,場(chǎng)景的起始位置與畫布有關(guān),但是畫布并不能限制場(chǎng)景的大小。

創(chuàng)建渲染器

const renderer=this.rendererinit(canvasId,rendererwidth,rendererheight,);
rendererinit(canvasId,width,height) {
        const render = new THREE.WebGLRenderer({
            antialias: true
        });//調(diào)用THREE里面的方法創(chuàng)建渲染器
        render.setSize(width, height); //設(shè)置渲染器的寬和高,這個(gè)真正能夠影響場(chǎng)景的大小
        document.getElementById(canvasId).appendChild(render.domElement);
        //把渲染器添加為畫布的子元素,這樣渲染的效果就會(huì)展示在畫布容器中
        render.setClearColor(0xffffff, 1.0);//渲染器中沒有模型的地方的顏色
        return render
    }

創(chuàng)建場(chǎng)景:這是本章重點(diǎn),導(dǎo)入網(wǎng)格對(duì)象!

let scene=this.initScene(ifShowAxes);
initScene(ifShowAxes) {
        const scene = new THREE.Scene();  //調(diào)用THREE的方法創(chuàng)建一個(gè)場(chǎng)景
        //下面是顯示坐標(biāo)軸的函數(shù),非必須
        if(ifShowAxes){
            var axisHelper = new THREE.AxesHelper(250);
            scene.add(axisHelper);
        }
        return scene
    }
向場(chǎng)景中添加物體
scene=this.initObject(url,scene);    //這里的url是需要導(dǎo)入的模型的地址
  • 導(dǎo)入網(wǎng)格對(duì)象
//這里以gltf模型為例,THREE不僅僅可以展示gltf模型
initObject(url,scene) {
        const loader = new GLTFLoader();  //使用THREE自帶的GLTFLoader去加載gltf模型
        loader.load(url, function (gltf) {  
        // .load方法的第一個(gè)參數(shù)是url,第二三四個(gè)參數(shù)分別是載入成功、載入中、載入失敗的回調(diào)函數(shù)
            // 返回的場(chǎng)景對(duì)象gltf.scene插入到threejs場(chǎng)景中
            gltf.scene.position.x = 100
            gltf.scene.position.z = -850
            scene.add(gltf.scene);
        })
        return scene
    }
  • 自己使用THREE創(chuàng)建網(wǎng)格對(duì)象
initObject(url,scene) {
        const geometry =new THREE.BoxGeometry()  //創(chuàng)建一個(gè)正方體模型
        const mesh = new THREE.MeshLambertMaterial({color:0x00ff00}) 
        //給模型加上材質(zhì):這里是用來設(shè)置其顏色為綠色
        const cube = new THREE.Mesh(geometry,mesh) //模型+材質(zhì)=網(wǎng)格對(duì)象

        const edgeMaterial = new THREE.LineBasicMaterial({color:0xff0000,linewidth:5}) 
        //創(chuàng)建線的某種材質(zhì)。用于設(shè)置線條顏色和寬度
        const edges = new THREE.EdgesGeometry(geometry) 
        //創(chuàng)建一個(gè)線條模型對(duì)象,這一這里傳入了參數(shù)geometry,
        // 這個(gè)方法為模型geometry創(chuàng)建了一個(gè)棱線框,這決定了線條對(duì)象的形狀
        const line = new THREE.LineSegments(edges,edgeMaterial)  
        //利用線的模型對(duì)象及其材質(zhì)生成一個(gè)網(wǎng)格對(duì)象

        scene.add(cube)  //將正方體網(wǎng)格對(duì)象添加進(jìn)場(chǎng)景
        scene.add(line)  //將線框的方格對(duì)象添加進(jìn)場(chǎng)景
        return scene
    }

紅色的就是注釋中提到的線框

向場(chǎng)景中添加燈光

光線的種類很多


  • 注意有些模型材質(zhì),例如金屬,表面足夠光滑是沒有漫反射效果的,所以Three里面的某些光線,如環(huán)境光是不生效的
  • 下面以平行光(THREE.DirectionalLight)為例,從上左右前后五個(gè)方向進(jìn)行打光
scene=this.initLight(scene);
initLight(scene) {
        //FIXME: 實(shí)現(xiàn)自定義的燈光。根據(jù)傳參數(shù)組,遍歷以生成相應(yīng)的燈光
        const lightIntensity=2.6
        const lightColor1=0xf0f0f0
        const directionalLight = new THREE.DirectionalLight(lightColor1, lightIntensity);
        //平行光:第一個(gè)參數(shù)是光的顏色,第二個(gè)參數(shù)是光的強(qiáng)度,默認(rèn)為1,但是可以大于1
        directionalLight.position.set(0,1,0);//設(shè)置平行光照射的方向,這里表示順著y軸向負(fù)方向照射
        scene.add(directionalLight);
        const directionalLight1 = new THREE.DirectionalLight(lightColor1, lightIntensity);//平行光
        directionalLight1.position.set(1,0,0);
        scene.add(directionalLight1);
        const directionalLight2 = new THREE.DirectionalLight(lightColor1, lightIntensity);//平行光
        directionalLight2.position.set(-1,0,0);
        scene.add(directionalLight2);
        const directionalLight3 = new THREE.DirectionalLight(lightColor1, lightIntensity);//平行光
        directionalLight3.position.set(0,0,1);
        scene.add(directionalLight3);
        const directionalLight4 = new THREE.DirectionalLight(lightColor1, lightIntensity);//平行光
        directionalLight4.position.set(0,0,-1);
        scene.add(directionalLight4);

        return scene
    }

創(chuàng)建相機(jī)

從哪兒看?睜開多大的眼睛看?怎么看?睜?zhēng)字谎劬矗?br> 相機(jī)也有很多種:陣列相機(jī)、抽象相機(jī)基類、立方相機(jī)、正交、立體、透視相機(jī)



下面的例子中用的是正交相機(jī)

const camera=this.initCamera(cameraWidth,cameraHeight,scale,cameraPos,cameraLookPos,scene);
initCamera(width,height,scale,cameraPos,cameraLookPos,scene) {
        const k = width / height; //窗口寬高比
        const s=scale//三維場(chǎng)景顯示范圍控制系數(shù),系數(shù)越大,顯示的范圍越大
        //創(chuàng)建相機(jī)對(duì)象,六個(gè)參數(shù)依次為左右上下近遠(yuǎn)的最大可視距離
        const camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
        camera.position.set(cameraPos.x, cameraPos.y, cameraPos.z); //設(shè)置相機(jī)位置
        if(cameraLookPos){
        //設(shè)置相機(jī)看向哪個(gè)點(diǎn)
            camera.lookAt(cameraLookPos.x,cameraLookPos.y,cameraLookPos.z)
        }
        else{
        //設(shè)置坐標(biāo)看向場(chǎng)景位置,
            camera.lookAt(scene.position); //設(shè)置相機(jī)方向(指向的場(chǎng)景對(duì)象)
        }
        return camera
    }

使用渲染器將場(chǎng)景和相機(jī)鏈接起來

renderer.render(scene, camera);
但是為了當(dāng)場(chǎng)景變化時(shí)我們能夠反復(fù)地去渲染畫面,需要調(diào)用requestAnimationFrame方法,完整寫法是這樣的:

        function adnimation(){
            renderer.render(scene, camera);
            requestAnimationFrame(adnimation);
        }

        adnimation()

完整代碼如下:

已封裝為react組件

//TITLE: 本組件用于gltf模型的載入,單純用于展示
import React, { Component } from 'react';
import * as THREE from 'three';
import { Loader } from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

/**TODO:
 * 對(duì)于燈光,目前設(shè)置的是固定的,后續(xù)需要開放這一塊的靈活性,思路是設(shè)置為數(shù)組對(duì)象的形式接收參數(shù),遍歷以設(shè)置燈光
 * 對(duì)于模型的url,目前是固定的,因?yàn)椴惶宄@個(gè)路徑是怎么檢測(cè),后面改掉
 */

/**USEAGE: 本組件傳參:
 *  canvasId,           
    rendererwidth,  渲染器的寬高:這個(gè)是真正影響到我們看到的場(chǎng)景的大小的。畫布并不能限制場(chǎng)景的大小
    rendererheight,


    cameraWidth,
    cameraHeight,
    scale,
    cameraPos,
    cameraLookPos,
    ifShowAxes,
    url
 */

class LoadModel extends Component {
    constructor(props) {
        super(props);
        this.state = {};
    }

    threeStart(param) {
        const {canvasId,
            rendererwidth,
            rendererheight,
            cameraWidth,
            cameraHeight,
            scale,
            cameraPos,
            cameraLookPos,
            ifShowAxes,
            url,
            
        }=param

        //初始化渲染器
        const renderer=this.rendererinit(canvasId,rendererwidth,rendererheight,);
        //初始化場(chǎng)景
        let scene=this.initScene(ifShowAxes);
        //初始化相機(jī)
        const camera=this.initCamera(cameraWidth,cameraHeight,scale,cameraPos,cameraLookPos,scene);
        //初始化燈光并將燈光加入scene------------這個(gè)后續(xù)需要開放更多靈活性
        scene=this.initLight(scene);
        //初始化object并將object加入到scene——在這一步導(dǎo)入模型:當(dāng)前只支持gltf
        scene=this.initObject(url,scene);
        /**
         * NOTE:
         * 下面必須這樣寫,不能通過傳參的形式去執(zhí)行animation,目的是要改變r(jià)enderer的本體,而非傳參得到的復(fù)制體
         */
        function adnimation(){
            renderer.render(scene, camera);
            requestAnimationFrame(adnimation);
        }
        adnimation()
        
        

        /**
         * NOTE:
         * 下面這個(gè)是必須要實(shí)例化的,用作模型的控制器,鼠標(biāo)才能對(duì)模型進(jìn)行拖動(dòng)
         */
        const controls = new OrbitControls(camera, renderer.domElement);//創(chuàng)建控件對(duì)象

    }

    rendererinit(canvasId,width,height) {
        const render = new THREE.WebGLRenderer({
            antialias: true
        });
        render.setSize(width, height);
        document.getElementById(canvasId).appendChild(render.domElement);
        render.setClearColor(0xffffff, 1.0);
        return render
    }

    initScene(ifShowAxes) {
        const scene = new THREE.Scene();
        if(ifShowAxes){
            var axisHelper = new THREE.AxesHelper(250);
            scene.add(axisHelper);
        }
        

        return scene
    }

    initCamera(width,height,scale,cameraPos,cameraLookPos,scene) {
        const k = width / height; //窗口寬高比
        const s=scale//三維場(chǎng)景顯示范圍控制系數(shù),系數(shù)越大,顯示的范圍越大
        //創(chuàng)建相機(jī)對(duì)象
        const camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
        camera.position.set(cameraPos.x, cameraPos.y, cameraPos.z); //設(shè)置相機(jī)位置

        if(cameraLookPos){
            camera.lookAt(cameraLookPos.x,cameraLookPos.y,cameraLookPos.z)
        }
        else{
            camera.lookAt(scene.position); //設(shè)置相機(jī)方向(指向的場(chǎng)景對(duì)象)
        }
        

        return camera
    }

    initLight(scene) {
        //FIXME: 實(shí)現(xiàn)自定義的燈光。根據(jù)傳參數(shù)組,遍歷以生成相應(yīng)的燈光
        const lightIntensity=2.6
        const lightColor1=0xf0f0f0
        const directionalLight = new THREE.DirectionalLight(lightColor1, lightIntensity);//平行光
        directionalLight.position.set(0,1,0);
        scene.add(directionalLight);
        // const directionalLight0 = new THREE.DirectionalLight(lightColor1, lightIntensity);//平行光
        // directionalLight0.position.set(0,-1,0);
        // scene.add(directionalLight0);
        const directionalLight1 = new THREE.DirectionalLight(lightColor1, lightIntensity);//平行光
        directionalLight1.position.set(1,0,0);
        scene.add(directionalLight1);
        const directionalLight2 = new THREE.DirectionalLight(lightColor1, lightIntensity);//平行光
        directionalLight2.position.set(-1,0,0);
        scene.add(directionalLight2);
        const directionalLight3 = new THREE.DirectionalLight(lightColor1, lightIntensity);//平行光
        directionalLight3.position.set(0,0,1);
        scene.add(directionalLight3);
        const directionalLight4 = new THREE.DirectionalLight(lightColor1, lightIntensity);//平行光
        directionalLight4.position.set(0,0,-1);
        scene.add(directionalLight4);

        // const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 200);//半球光
        // hemisphereLight.position.set(0, 0, 0);
        // scene.add(hemisphereLight);
    
        // const point = new THREE.PointLight(0xFFFFF5);
        // point.position.set(300, 300, 300); //點(diǎn)光源位置
        // scene.add(point); //點(diǎn)光源添加到場(chǎng)景中

        // const ambient = new THREE.AmbientLight(0xFFFFFF,100);//環(huán)境光
        // scene.add(ambient);
        return scene
    }

    initObject(url,scene) {


        const loader = new GLTFLoader();
        loader.load(url, function (gltf) {
            // 返回的場(chǎng)景對(duì)象gltf.scene插入到threejs場(chǎng)景中
            gltf.scene.position.x = 100
            gltf.scene.position.z = -850

            //FIXME: 這里需要我自由替換導(dǎo)入的gltf的材質(zhì),純金屬材質(zhì)并不好看
            // var model = gltf.scene;
            // var newMaterial = new THREE.MeshStandardMaterial({color: 0xff0000});
            // model.traverse((o) => {
            // if (o.isMesh) o.material = newMaterial;
            // });
        
            //FIXME: 弄清楚引用組件時(shí),url的路徑是如何計(jì)算的

            scene.add(gltf.scene);
        },
        function(e){
            // console.log("正在導(dǎo)入",e);
        },
        function(e){
            console.log("導(dǎo)入失敗",e);
        })
        return scene
    }





    /**
     * 開始Three
     *
     * @memberof LoadModel
     */
    componentDidMount() {

        const {canvasId,rendererwidth,rendererheight,cameraWidth,cameraHeight,scale,cameraPos,cameraLookPos,ifShowAxes,url}=this.props
        const param={
            //畫布相關(guān)參數(shù)
            canvasId,
            rendererwidth:rendererwidth||window.innerWidth,
            rendererheight:rendererheight||window.innerHeight,

            //相機(jī)相關(guān)參數(shù)
            cameraWidth:cameraWidth||window.innerWidth,
            cameraHeight:cameraHeight||window.innerHeight,
            scale:scale||100,
            cameraPos:cameraPos||{x:500,y:500,z:500},
            cameraLookPos:cameraLookPos,
            //場(chǎng)景相關(guān)參數(shù)
            ifShowAxes:ifShowAxes||true,
            url,//模型的url

        }

        this.threeStart(param);
    }
    render() {
        const {canvasId}=this.props
        return (
            <div id={canvasId} style={{ backgroundColor: "yellowgreen" }}>
            </div>
        );
    }
}

export default LoadModel;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容