three.js 入門學習(二)

上一個小案例

   import * as THREE from 'three';
===================================   
		const width = 960;
        const height = 540;
        // 渲染器
        const renderer = new THREE.WebGLRenderer();
        //設置像素
        renderer.setPixelRatio(window.devicePixelRatio);
        // 設置大小
        renderer.setSize(width, height);
        document.body.appendChild(renderer.domElement);
        // 視口
        const scene = new THREE.Scene();
        // 攝像頭
        const camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000);
        camera.position.set(0, 0, 1000);
        // 一個網格物體
        const geometry = new THREE.BoxGeometry(400, 400, 400);
        // 材質 法線網格材質(種把法向量射到RGB顏色的材質)
        const material = new THREE.MeshNormalMaterial();
        const box = new THREE.Mesh(geometry, material);
        scene.add(box);

        function animate() {
            box.rotation.y += 0.01;
            // 渲染到頁面上, 視口,攝像頭
            renderer.render(scene, camera);
            requestAnimationFrame(animate);
        }

        animate()

準備一個畫布元素

<canvas id="myCanvas"></canvas>

WebGL 渲染的渲染器

const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector('#myCanvas')
});

在方法中setSize()設置大小

renderer.setSize(960, 540);

創造一個場景

創建一個場景。場景是放置 3D 對象、光源等的 3D 空間。

const scene = new THREE.Scene();

相機

此功能稱爲“視點”或“相機”

在 Three.js 中,THREE.PerspectiveCamera通過傳遞四個信息來創建相機:視角、縱橫比、繪製開始距離和繪製結束距離,作爲類構造函數中的參數。

const camera = new THREE.PerspectiveCamera(45, 960 / 540,1,1000);

##  立方體

網格的顯示對象創建的。要創建網格,我們需要準備兩種類型:幾何體(shape)和材料(material)。

BoxGeometry使用一種來生成像立方體和長方體這樣的類似盒子的形狀

const geometry = new THREE.BoxGeometry(500, 500, 500);

材質

法線網格材質(種把法向量射到RGB顏色的材質)MeshNormalMaterial

const material = new THREE.MeshNormalMaterial();

使用創建的幾何體和材料創建網格。讓我們將創建的網格添加到場景中。

const box = new THREE.Mesh(geometry, material);
scene.add(box);

爲了用 JavaScript 製作動畫,有必要隨着時間的推移不斷調用該函數。爲此,requestAnimationFrame()請使用名爲 requestAnimationFrame()將運行每幀作爲參數傳遞的函數。

tick();

function tick() {
  requestAnimationFrame(tick);

}

Three.js不會自動將屏幕切換到最新,所以需要寫一個指令來顯式更新屏幕。renderer.render()您可以使用命令指定更新

tick();

function tick() {
  requestAnimationFrame(tick);

  box.rotation.y += 0.01;
  renderer.render(scene, camera); 
}

創建 Three.js 對象分爲三個步驟:(1) 創建材質,(2) 創建幾何體,(3) 創建網格

// 球體
const geometry = new THREE.SphereGeometry(300, 30, 30);
// 物理材質的製作
const material = new THREE.MeshStandardMaterial({color: 0xFF0000});
// 網格物體
const mesh = new THREE.Mesh(geometry, material);
// 添加到場景中
scene.add(mesh);

光源

THREE.DirectionalLight  平行光

const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
directionalLight.position.set(1, 1, 1)
scene.add(directionalLight);

環境光

它可以作爲一盞燈來照亮整個空間。

THREE.AmbientLight

var pointLight = new THREE.PointLight("#ccffcc");
pointLight.position.set(0,10,10);
scene.add(pointLight);

材質中使用圖像

THREE.TextureLoader使用類指定文件路徑

     // 圖像加載器
        const loader = new THREE.TextureLoader();
        const texture=loader.load("/assets/img/單人主體 (3).jpg")
        // 物理材質
        const material2=new THREE.MeshStandardMaterial({
            map: texture,
        })

球體集合

const geometry = new THREE.SphereGeometry( 5, 32, 32 );
const material = new THREE.MeshBasicMaterial( {color: 0xFF0000} );
const sphere = new THREE.Mesh( geometry, material );
scene.add( sphere );

長方體幾何

const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( {color: 0xFF0000} );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );

平面幾何

const geometry = new THREE.PlaneGeometry( 5, 20, 32 );
const material = new THREE.MeshBasicMaterial( {color: 0xFF0000, side: THREE.DoubleSide} );
const plane = new THREE.Mesh( geometry, material );
scene.add( plane );

圓錐幾何

// 半徑, 高度, 管道橫截面的分段數
const geometry = new THREE.ConeGeometry( 5, 20, 32 );
const material = new THREE.MeshBasicMaterial( {color: 0xFF0000} );
const cone = new THREE.Mesh( geometry, material );
scene.add( cone );

圓柱幾何

// 上圓半徑,下圓半徑,高度,管道橫截面的分段數
const geometry = new THREE.CylinderGeometry( 5, 5, 20, 32 );
const material = new THREE.MeshBasicMaterial( {color: 0xFF0000} );
const cylinder = new THREE.Mesh( geometry, material );
scene.add( cylinder );

甜甜圈形幾何(圓環緩衝幾何體)

radius - 環面的半徑,從環面的中心到管道橫截面的中心。默認值是1。
tube — 管道的半徑,默認值爲0.4。
radialSegments — 管道橫截面的分段數,默認值爲12。
tubularSegments — 管道的分段數,默認值爲48。
arc — 圓環的圓心角(單位是弧度),默認值爲Math.PI * 2

const geometry = new THREE.TorusGeometry( 10, 3, 16, 100 );
const material = new THREE.MeshBasicMaterial( { color: 0xFF0000 } );
const torus = new THREE.Mesh( geometry, material );
scene.add( torus );

相機控制

  • 自動旋轉
  • 定位鼠標
  • 鼠標拖放

相機自動繞地球圓周移動

設置相機位置會爲camera對象的屬性position分配一個數值。

const radian = rot * Math.PI / 180;
camera.position.x = 1000 * Math.sin(radian);
camera.position.z = 1000 * Math.cos(radian);

使用cameraobject方法來指定原點座標。方法是可以強制從任意位置指向指定座標的指令。lookAt() (0, 0, 0)

camera.lookAt(new THREE.Vector3(0, 0, 0));

圍繞地球旋轉相機

       let rot = 0;
        function animate() {
            // 渲染到頁面上, 視口,攝像頭
            renderer.render(scene, camera);

            // 緩動公式
            rot += 0.2;
            const radian = rot * Math.PI / 180;
            camera.position.x = 1000 * Math.sin(radian);
            camera.position.z = 1000 * Math.cos(radian);
            camera.lookAt(new THREE.Vector3(0, 0, 0));

            requestAnimationFrame(animate);
        }
        animate()

自動控制相機移動的THREE.OrbitControls

  • 安排相機繪製圓形軌道
  • 使用指針操作更改相機位置和角度
        const   controls = new OrbitControls( camera, renderer.domElement );

  function animate() {
            controls.update();  
  }

材質

THREE.MeshBasicMaterial類	(網格基礎材質)是一種不考慮光照的材質。由於沒有陰影
THREE.MeshNormalMaterial	(網格法線材質)該類是一種可視化 RGB 中正常顏色的材質
THREE.MeshLambertMaterial   (網狀朗伯材質) 表現無光澤度的磨砂質感的材質。因爲出現了陰影,所以可以表現出深度感。一種需要陰影的材料,所以它需要光
THREE.MeshPhongMaterial     表現光澤紋理的材質
THREE.MeshToonMaterial      (網狀卡通材質) 可以實現類似動漫的卡通着色
THREE.MeshStandardMaterial   (網格標準材料)基於物理的渲染材質

光源

環境光源

AmbientLight類是實現環境光源的類。均勻照亮整個 3D 空間。當你想要均勻地提亮時使用它是很好的。由於無法產生陰影和投射陰影,因此僅此光源無法表現出三維效果。通常與其他燈一起使用。

顏色, 光照強度
const light = new THREE.AmbientLight(0xFFFFFF, 1.0);
scene.add(light);

平行光源

DirectionalLight類別是在特定方向發射的光。假設光源無限遠,從它發出的所有光線都是平行的。一個簡單的例子是陽光。由於太陽離地球如此之遠,它的位置可以被認爲是無限的。從太陽到地球表面的光線是平行的。

半球形光源

HemisphereLightAmbientLight與類類似,但是你可以將來自上方的光的顏色和來自下方的光的顏色分開。來自下方的光是反射光,類似於室外的光

// 天空的顏色, 地面的顏色,光強度
const light = new THREE.HemisphereLight(0x888888, 0x0000FF, 1.0);

點光源

PointLight類是從一個點向所有方向發射的光源。一個很好的例子是裸燈泡。裸露的燈泡照亮了周圍的環境。

射燈光源

SpotLight一個類是一種光源,它從一個點沿着一個圓錐體向一個方向發射。一個很好的例子是想象舞臺上的手電筒或聚光燈。您可以指定衰減率和光的方向,因此可以指定的參數很多。如果放置很多,就會產生三維效果和存在感。

顏色、光強度、距離、照明角度、背景虛化、衰減率
const light = new THREE.SpotLight(0xFFFFFF, 4, 30, Math.PI / 4, 10, 0.5);

輔助類

// 射燈光源輔助類
const lightHelper = new THREE.SpotLightHelper(light);

矩形光源

// 顏色、光強、寬度、高度
const light = new THREE.RectAreaLight(0xFFFFFF, 5.0, 10, 10);
scene.add(light);

陰影

允許物體在其他物體上投射陰影對着光源。投射陰影將改善現實。

使用此功能有四種設置。

  1. 在渲染器中啓用陰影
  2. 啓用光源陰影
  3. 設置要投射陰影的網格對象
  4. 設置 Mesh 對象以接收陰影

需要注意:“投射陰影”和“接收陰影”。

分別設置castShadow屬性(投射陰影的屬性)和屬性(接收陰影的屬性)。receiveShadow

在渲染器屬性shadowMap中啓用它

renderer.shadowMap.enabled = true;

啓用光源的castShadow屬性。光源使用定向SpotLight發光。PointLight

// 創建照明
const light = new THREE.SpotLight(0xFFFFFF, 2, 100, Math.PI / 4, 1);
// 在燈光上啓用陰影
light.castShadow = true;
scene.add(light);

投射陰影的網格receiveShadow啓用屬性。

const meshKnot = new THREE.Mesh(
  new THREE.TorusKnotGeometry(3, 1, 100, 16),
  new THREE.MeshStandardMaterial());
// 設置上了一層陰影
meshKnot.castShadow = true;
scene.add(meshKnot);

設置陰影大小

light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;

相機

相機類型

  • THREE.PerspectiveCamera: 應用透視的相機
  • THREE.OrthographicCamera :應用平行投影的相機

正交相機

// 左,右,上,下,近截面,遠截面
// new THREE.OrthographicCamera(left, right, top, bottom, near, far)
const camera = new THREE.OrthographicCamera(-480, +480, 270, -270, 1, 1000);

霧效

一種使遠處的物體看起來朦朧的效果

通過設置與相機的起始和結束距離,中間的對象將以指定的顏色變暗。

// 顏色、開始距離、結束距離
scene.fog = new THREE.Fog(0x000000, 50, 2000);

應用於霧效

material.fog = true;

創建嵌套結構,THREE.Object3D或使用THREE.Group類方法add()添加它。相反,如果要從嵌套結構中刪除,remove()請使用 方法。

const wrap = new THREE.Object3D(); 
wrap.add(mesh); 
scene.add(wrap); 
const wrap = new THREE.Group(); 
wrap.add(mesh); 
scene.add(wrap); 

世界座標

THREE.Object3D getWorldPosition()您可以使用類方法獲取世界座標。由於我們需要計算世界座標,所以要計算的3D對象必須添加到場景中

const world = object3D.getWorldPosition(new THREE.Vector3());

加載模型數據

GLTF 文件

const loader = new THREE.GLTFLoader();
 const gltf = loader.loadAsync('./models/gltf/glTF/ToyCar.gltf');
const model = gltf.scene;
  scene.add(model);

3ds 文件

  const loader = new THREE.TDSLoader();
// 指定紋理路徑
  loader.setResourcePath('models/3ds/portalgun/textures/');
// 指定 3ds 文件的路徑
  const object = loader.loadAsync('models/3ds/portalgun/portalgun.3ds');
  scene.add(object);

Collada 文件

  const loader = new THREE.ColladaLoader();
 const collada = await loader.loadAsync('./models/collada/elf/elf.dae');
  const model = collada.scene;
  scene.add(model);

調整大小

window.addEventListener('resize', ()=>{
  const width = window.innerWidth;
  const height = window.innerHeight;
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);

  camera.aspect = width / height;
  camera.updateProjectionMatrix();
});

檢查與對象的交叉點

鼠標座標

const mouse = new THREE.Vector2();
// 註冊鼠標事件
canvas.addEventListener('mousemove', handleMouseMove);

//  鼠標移動事件
function handleMouseMove(event) {
  const element = event.currentTarget;
  //畫布元素上的 XY 座標
  const x = event.clientX - element.offsetLeft;
  const y = event.clientY - element.offsetTop;
  // 畫布元素的寬度/高度
  const w = element.offsetWidth;
  const h = element.offsetHeight;

  //在 -1 到 +1 範圍內註冊當前鼠標座標
  mouse.x = ( x / w ) * 2 - 1;
  mouse.y = -( y / h ) * 2 + 1;
}
射線
const raycaster = new THREE.Raycaster();
tick();
// 每幀運行的循環事件
function tick() {

  // 直接從鼠標位置生成光線矢量
  raycaster.setFromCamera(mouse, camera);

  // 獲取被射線擊中的物體
  const intersects = raycaster.intersectObjects(scene.children);

  if(intersects.length > 0){
    // 對碰撞的物體做某事
  }

  renderer.render(scene, camera);
  requestAnimationFrame(tick);
}

顯示大量粒子

    // 創建形狀數據
        const SIZE = 3000;
        // 要放置的數字
        const LENGTH = 1000;
        const vertices = [];
        for (let i = 0; i < LENGTH; i++) {
            const x = SIZE * (Math.random() - 0.5);
            const y = SIZE * (Math.random() - 0.5);
            const z = SIZE * (Math.random() - 0.5);

            vertices.push(x, y, z);
        }
        // 創建形狀數據
        const geometry7 = new THREE.BufferGeometry();
        geometry7.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
        const material7 = new THREE.PointsMaterial({
            size: 10,
            color: 0xffffff,
        });
        const mesh7 = new THREE.Points(geometry7, material7);
        scene.add(mesh7);

移動幾何物體

THREE.BufferAttribute保存頂點座標信息。

THREE.BufferAttribute是一個不是數組的對象,所以它的用法有點特殊。count由於您可以獲得屬性中的頂點數,因此for使用與頂點數一樣多的循環語句會很好。 您可以使用 getX()方法獲取每個頂點的位置信息getY()getZ()

const position = mesh.geometry.attributes.position;
for (let i = 0; i < position.count; i++) {
  // 各頂點的XYZ座標
  const x = position.getX(i);
  const y = position.getY(i);
  const z = position.getZ(i);
}   

完整點的案例

       // 平面幾何
        const geometry = new THREE.PlaneGeometry(400, 400, 20, 20);

        // 骨架線性的
        const material = new THREE.MeshBasicMaterial({ wireframe: true });

        // 物體制作
        const mesh = new THREE.Mesh(geometry, material);
        mesh.rotation.x = Math.PI / 2; // x軸的角度
        scene.add(mesh);

        tick();

        function tick() {

            // 幾何頂點座標信息
            const position:any = mesh.geometry.attributes["position"];
            for (let i = 0; i < position.count; i++) {
            //     // 各頂點的XYZ座標
                const x = position.getX(i);
                const y = position.getY(i);
                const z = position.getZ(i);

                // 計算高度(物體的Z 座標)
                const nextZ = Math.sin(x * 0.03 + y * 0.02 + Date.now() * 0.002) * 30;

                // position.setX(i, x); // 您可以省略 x 和 y,因爲它們不會改變
                // position.setY(i, y);
                position.setZ(i, nextZ);
            }

            // 需要更新頂點
            position.needsUpdate = true;

            renderer.render(scene, camera);

            requestAnimationFrame(tick);
        }

對平面的頂點應用波浪式運動

通過使用 SimplexNoise 生成噪聲,您可以創建類似於地面的表達式。雖然 SimplexNoise 本身不包含在 Three.js 中,但它是作爲附加組件提供的

使用時,THREE.SimplexNoise從類中創建一個實例並noise()使用該方法。noise()當傳遞一個數字作爲參數時,該方法返回一個介於 -1 和 1 之間的數字。

// 初始化噪聲
// 使用實例
const simplexNoise = new THREE.SimplexNoise();
// x1和y1是任意數值
const value = simplexNoise.noise(x1, y1); 

使用PlaneGeometry 創建類似地面的例子

  // 平行光源
        const light1 = new THREE.DirectionalLight(0x3399ff, 1);
        light1.position.set(1, 1, 1);
        scene.add(light1);
        // 平面幾何
        // width, height,寬度橫截面, 高度橫截面
        const geometry = new THREE.PlaneGeometry(1000, 1000, 80, 80);
        const material = new THREE.MeshLambertMaterial({
            // 頂點着色
            flatShading: true,
            // 定義將要渲染哪一面, 兩面
            side: THREE.DoubleSide,
        });
        const mesh = new THREE.Mesh(geometry, material);
        // 地面旋轉角度
        mesh.rotation.x = Math.PI / 2;
        scene.add(mesh);
        // 初始化噪聲
        const simplexNoise = new SimplexNoise();
        tick();
        function tick() {
            // 幾何頂點座標信息
            const position:any = mesh.geometry.attributes['position'];
            for (let i = 0; i < position.count; i++) {
                // 各頂點的XYZ座標
                const x = position.getX(i);
                const y = position.getY(i);

                const time = Date.now() * 0.0001;

                // 計算高度, 主要是z座標
                const nextZ = simplexNoise.noise(x * 0.002 + time, y * 0.001 + time) * 150;

                position.setZ(i, nextZ);
            }

            //            // 需要更新頂點
            position.needsUpdate = true;

            renderer.render(scene, camera);

            requestAnimationFrame(tick);
        }

setXYZ()您可以使用單獨的setX()setY()和方法setZ()來做同樣的事情

煙花效果

       const LENGTH = 1000;
        const vertices = [];
        /**
         * 存儲粒子速度的序列
         * @type {THREE.Vector3[]}
         */
        const speeds:any = [];

        for (let i = 0; i < LENGTH; i++) {
            // 頂點 初期座標
            vertices.push(0, 0, 0);
            // 定義粒子的速度
            const x = 2 * (Math.random() - 0.5);
            const y = 2 * (Math.random() - 0.5);
            const z = 2 * (Math.random() - 0.5);

            speeds.push(new THREE.Vector3(x, y, z));
        }

        // 緩衝存儲器
        const geometry = new THREE.BufferGeometry();
        geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
        const material = new THREE.PointsMaterial({
            size: 2,
            color: 0xffffff,
        });
        // 點
        const mesh = new THREE.Points(geometry, material);
        scene.add(mesh)
        tick();
        function tick() {
            // 幾何頂點座標信息
            const position:any = mesh.geometry.attributes['position'];
            for (let i = 0; i < position.count; i++) {
                // 各頂點のXYZ座標
                const x = position.getX(i);
                const y = position.getY(i);
                const z = position.getZ(i);
                // speeds序列是速度用序列。存儲了各個頂點的速度
                const nextX = x + speeds[i].x;
                const nextY = y + speeds[i].y;
                const nextZ = z + speeds[i].z;

                // 新座標
                position.setXYZ(i, nextX, nextY, nextZ);

                // 原點からの距離を計算
                const length = new THREE.Vector3(x, y, z).length();
                // 如果超過了一定的範圍
                if (length > 100) {
                    // 回到原點
                    position.setXYZ(i, 0, 0, 0);
                }
            }

           // 需要更新頂點
            position.needsUpdate = true;

            renderer.render(scene, camera);

            requestAnimationFrame(tick);
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章