使用Three.js 心得系列一 如何创建三维网格三使用Three.js 心得系列三 Three.js 如何加载GlTF文件

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Three.js 心得系列一 如何创建三维网格三使用Three.js 心得系列三 Three.js 如何加载GlTF文件相关的知识,希望对你有一定的参考价值。


一、使用Three.js 心得系列一 如何创建三维网格

二、使用Three.js 心得系列二 如何改变三维场景的GlTF模型的位置

三、使用Three.js 心得系列三 Three.js 如何加载GlTF文件

 

 

three.js如何创建三维网格。

官方提供了gridHelper的方式:

先看下我的测试效果:

使用Three.js

上源码:

源码为完整的源码,其中包括一个小实例,点击也年的移动按钮,人物会沿着X的方向,前进行走。

 

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试三维图</title>
<script src="js/libs/ammo.wasm.js"></script>


</head>
<body>
<input type="button" value="移动" id="btn_1" onclick="testMoveCube()" style="margin-top:100px;"/>
<div id="container">

</div>
<script type="module">

import * as THREE from ../build/three.module.js;

import Stats from ./jsm/libs/stats.module.js;

import OrbitControls from ./jsm/controls/OrbitControls.js;
import BufferGeometryUtils from ./jsm/utils/BufferGeometryUtils.js;
import SVGLoader from ./jsm/loaders/SVGLoader.js;
import GLTFLoader from ./jsm/loaders/GLTFLoader.js;
// Graphics variables
let container, stats;
let camera, controls, scene, renderer;
let textureLoader;
const clock = new THREE.Clock();
let clickRequest = false;
const mouseCoords = new THREE.Vector2();
const raycaster = new THREE.Raycaster();
const ballMaterial = new THREE.MeshPhongMaterial( color: 0x202020 );
const pos = new THREE.Vector3();
const quat = new THREE.Quaternion();

// Physics variables
const gravityConstant = - 9.8;
let physicsWorld;
const rigidBodies = [];
const softBodies = [];
const margin = 0.05;
let transformAux1;
let softBodyHelpers;

let guiData;
Ammo().then( function ( AmmoLib )

Ammo = AmmoLib;

init();
animate();

);

function init()
document.getElementById("btn_1").addEventListener("click",testMoveCube);
initGraphics();

initPhysics();

createObjects();

initInput();


guiData =
currentURL: ./01Images_Test/1.svg,
drawFillShapes: true,
drawStrokes: true,
fillShapesWireframe: false,
strokesWireframe: false
;


//绘制
function initGraphics()


container = document.getElementById( container );


//场景加个摄像机
var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
// camera attributes
var VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 20000;
// set up camera
camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);


//camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );

scene = new THREE.Scene();
scene.background = new THREE.Color( 0xbfd1e5 );
// scene.background = new THREE.Color( 0xe0e2e9 );

//显示坐标轴
var axes = new THREE.AxisHelper(200);
//axes.position.x=-1000;
//axes.position.z=-1000; 三维轴会到左上角

scene.add(axes);

//底图网格 (总长宽,分多少个网格,颜色,轴线颜色,和网格颜色 #e6e8ed)
var gridHelper = new THREE.GridHelper(2000, 100, 0x888888, 0x888888);
gridHelper.position.x=1000;
gridHelper.position.z=1000;
scene.add(gridHelper);

//camera.position.set( - 7, 5, 8 );
camera.position.set(0, 1000, 1500 );

renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMap.enabled = true;
renderer.setClearColor(#FFFFFF);//设置场景的颜色
container.appendChild( renderer.domElement );

//场景缩放
controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 2, 0 );
controls.update();

textureLoader = new THREE.TextureLoader();

const ambientLight = new THREE.AmbientLight( 0x404040 );
scene.add( ambientLight );

const light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( - 10, 10, 5 );
light.castShadow = true;
const d = 20;
light.shadow.camera.left = - d;
light.shadow.camera.right = d;
light.shadow.camera.top = d;
light.shadow.camera.bottom = - d;

light.shadow.camera.near = 2;
light.shadow.camera.far = 50;

light.shadow.mapSize.x = 1024;
light.shadow.mapSize.y = 1024;

scene.add( light );

stats = new Stats();
stats.domElement.style.position = absolute;
stats.domElement.style.top = 0px;
container.appendChild( stats.domElement );


window.addEventListener( resize, onWindowResize, false );
createBaseStation();


//物理特效
function initPhysics()

// Physics configuration

const collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
const dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
const broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();
const softBodySolver = new Ammo.btDefaultSoftBodySolver();
physicsWorld = new Ammo.btSoftRigidDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration, softBodySolver );
physicsWorld.setGravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );
physicsWorld.getWorldInfo().set_m_gravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );

transformAux1 = new Ammo.btTransform();
softBodyHelpers = new Ammo.btSoftBodyHelpers();



//创建场景内元素
function createObjects()

// Ground
pos.set( 0, - 0.5, 0 );
quat.set( 0, 0, 0, 1 );
const ground = createParalellepiped( 40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial( color: 0xFFFFFF ) );
ground.castShadow = true;
ground.receiveShadow = true;
textureLoader.load( "textures/grid.png", function ( texture )

texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 400, 400 );
ground.material.map = texture;
ground.material.needsUpdate = true;

);

// Create soft volumes
const volumeMass = 15;

//创建球
// const sphereGeometry = new THREE.SphereBufferGeometry( 1.5, 40, 25 );
// sphereGeometry.translate( 5, 5, 0 );
// createSoftVolume( sphereGeometry, volumeMass, 250 );

//创建盒子
// const boxGeometry = new THREE.BoxBufferGeometry( 1, 1, 5, 4, 4, 20 );
// boxGeometry.translate( - 2, 5, 0 );
// createSoftVolume( boxGeometry, volumeMass, 120 );

// Ramp
//pos.set( 3, 1, 0 );
//quat.setFromAxisAngle( new THREE.Vector3( 0, 0, 1 ), 30 * Math.PI / 180 );
//const obstacle = createParalellepiped( 10, 1, 4, 0, pos, quat, new THREE.MeshPhongMaterial( color: 0x606060 ) );
//obstacle.castShadow = true;
//obstacle.receiveShadow = true;



function processGeometry( bufGeometry )

// Ony consider the position values when merging the vertices
const posOnlyBufGeometry = new THREE.BufferGeometry();
posOnlyBufGeometry.setAttribute( position, bufGeometry.getAttribute( position ) );
posOnlyBufGeometry.setIndex( bufGeometry.getIndex() );

// Merge the vertices so the triangle soup is converted to indexed triangles
const indexedBufferGeom = BufferGeometryUtils.mergeVertices( posOnlyBufGeometry );

// Create index arrays mapping the indexed vertices to bufGeometry vertices
mapIndices( bufGeometry, indexedBufferGeom );



function isEqual( x1, y1, z1, x2, y2, z2 )

const delta = 0.000001;
return Math.abs( x2 - x1 ) < delta &&
Math.abs( y2 - y1 ) < delta &&
Math.abs( z2 - z1 ) < delta;



function mapIndices( bufGeometry, indexedBufferGeom )

// Creates ammoVertices, ammoIndices and ammoIndexAssociation in bufGeometry

const vertices = bufGeometry.attributes.position.array;
const idxVertices = indexedBufferGeom.attributes.position.array;
const indices = indexedBufferGeom.index.array;

const numIdxVertices = idxVertices.length / 3;
const numVertices = vertices.length / 3;

bufGeometry.ammoVertices = idxVertices;
bufGeometry.ammoIndices = indices;
bufGeometry.ammoIndexAssociation = [];

for ( let i = 0; i < numIdxVertices; i ++ )

const association = [];
bufGeometry.ammoIndexAssociation.push( association );

const i3 = i * 3;

for ( let j = 0; j < numVertices; j ++ )

const j3 = j * 3;
if ( isEqual( idxVertices[ i3 ], idxVertices[ i3 + 1 ], idxVertices[ i3 + 2 ],
vertices[ j3 ], vertices[ j3 + 1 ], vertices[ j3 + 2 ] ) )

association.push( j3 );









function createSoftVolume( bufferGeom, mass, pressure )

processGeometry( bufferGeom );

const volume = new THREE.Mesh( bufferGeom, new THREE.MeshPhongMaterial( color: 0xFFFFFF ) );
volume.castShadow = true;
volume.receiveShadow = true;
volume.frustumCulled = false;
scene.add( volume );

textureLoader.load( "textures/colors.png", function ( texture )

volume.material.map = texture;
volume.material.needsUpdate = true;

);

// Volume physic object

const volumeSoftBody = softBodyHelpers.CreateFromTriMesh(
physicsWorld.getWorldInfo(),
bufferGeom.ammoVertices,
bufferGeom.ammoIndices,
bufferGeom.ammoIndices.length / 3,
true );

const sbConfig = volumeSoftBody.get_m_cfg();
sbConfig.set_viterations( 40 );
sbConfig.set_piterations( 40 );

// Soft-soft and soft-rigid collisions
sbConfig.set_collisions( 0x11 );

// Friction
sbConfig.set_kDF( 0.1 );
// Damping
sbConfig.set_kDP( 0.01 );
// Pressure
sbConfig.set_kPR( pressure );
// Stiffness
volumeSoftBody.get_m_materials().at( 0 ).set_m_kLST( 0.9 );
volumeSoftBody.get_m_materials().at( 0 ).set_m_kAST( 0.9 );

volumeSoftBody.setTotalMass( mass, false );
Ammo.castObject( volumeSoftBody, Ammo.btCollisionObject ).getCollisionShape().setMargin( margin );
physicsWorld.addSoftBody( volumeSoftBody, 1, - 1 );
volume.userData.physicsBody = volumeSoftBody;
// Disable deactivation
volumeSoftBody.setActivationState( 4 );

softBodies.push( volume );



function createParalellepiped( sx, sy, sz, mass, pos, quat, material )

const threeObject = new THREE.Mesh( new THREE.BoxBufferGeometry( sx, sy, sz, 1, 1, 1 ), material );
const shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
shape.setMargin( margin );

createRigidBody( threeObject, shape, mass, pos, quat );

return threeObject;



function createRigidBody( threeObject, physicsShape, mass, pos, quat )

threeObject.position.copy( pos );
threeObject.quaternion.copy( quat );

const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
transform.setRotation( new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ) );
const motionState = new Ammo.btDefaultMotionState( transform );

const localInertia = new Ammo.btVector3( 0, 0, 0 );
physicsShape.calculateLocalInertia( mass, localInertia );

const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
const body = new Ammo.btRigidBody( rbInfo );

threeObject.userData.physicsBody = body;

scene.add( threeObject );

if ( mass > 0 )

rigidBodies.push( threeObject );

// Disable deactivation
body.setActivationState( 4 );



physicsWorld.addRigidBody( body );

return body;



function initInput()

window.addEventListener( pointerdown, function ( event )

if ( ! clickRequest )

mouseCoords.set(
( event.clientX / window.innerWidth ) * 2 - 1,
- ( event.clientY / window.innerHeight ) * 2 + 1
);

clickRequest = true;



, false );



//点击时创建一个球
function processClick()

if ( clickRequest )
console.log("点击界面!");
clickRequest=false;
return;
raycaster.setFromCamera( mouseCoords, camera );

// Creates a ball
const ballMass = 3;
const ballRadius = 0.4;

const ball = new THREE.Mesh( new THREE.SphereBufferGeometry( ballRadius, 18, 16 ), ballMaterial );
ball.castShadow = true;
ball.receiveShadow = true;
const ballShape = new Ammo.btSphereShape( ballRadius );
ballShape.setMargin( margin );
pos.copy( raycaster.ray.direction );
pos.add( raycaster.ray.origin );
quat.set( 0, 0, 0, 1 );
const ballBody = createRigidBody( ball, ballShape, ballMass, pos, quat );
ballBody.setFriction( 0.5 );

pos.copy( raycaster.ray.direction );
pos.multiplyScalar( 14 );
ballBody.setLinearVelocity( new Ammo.btVector3( pos.x, pos.y, pos.z ) );

clickRequest = false;





function onWindowResize()

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );



function animate()

requestAnimationFrame( animate );

render();
stats.update();



function render()

const deltaTime = clock.getDelta();

updatePhysics( deltaTime );

processClick();

renderer.render( scene, camera );



function updatePhysics( deltaTime )

// Step world
physicsWorld.stepSimulation( deltaTime, 10 );

// Update soft volumes
for ( let i = 0, il = softBodies.length; i < il; i ++ )

const volume = softBodies[ i ];
const geometry = volume.geometry;
const softBody = volume.userData.physicsBody;
const volumePositions = geometry.attributes.position.array;
const volumeNormals = geometry.attributes.normal.array;
const association = geometry.ammoIndexAssociation;
const numVerts = association.length;
const nodes = softBody.get_m_nodes();
for ( let j = 0; j < numVerts; j ++ )

const node = nodes.at( j );
const nodePos = node.get_m_x();
const x = nodePos.x();
const y = nodePos.y();
const z = nodePos.z();
const nodeNormal = node.get_m_n();
const nx = nodeNormal.x();
const ny = nodeNormal.y();
const nz = nodeNormal.z();

const assocVertex = association[ j ];

for ( let k = 0, kl = assocVertex.length; k < kl; k ++ )

let indexVertex = assocVertex[ k ];
volumePositions[ indexVertex ] = x;
volumeNormals[ indexVertex ] = nx;
indexVertex ++;
volumePositions[ indexVertex ] = y;
volumeNormals[ indexVertex ] = ny;
indexVertex ++;
volumePositions[ indexVertex ] = z;
volumeNormals[ indexVertex ] = nz;





geometry.attributes.position.needsUpdate = true;
geometry.attributes.normal.needsUpdate = true;



// Update rigid bodies
for ( let i = 0, il = rigidBodies.length; i < il; i ++ )

const objThree = rigidBodies[ i ];
const objPhys = objThree.userData.physicsBody;
const ms = objPhys.getMotionState();
if ( ms )

ms.getWorldTransform( transformAux1 );
const p = transformAux1.getOrigin();
const q = transformAux1.getRotation();
objThree.position.set( p.x(), p.y(), p.z() );
objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );







//创建立方体标识基站
function createBaseStation()
for (var i=0;i<2;i++)
var geometry = new THREE.CubeGeometry(20, 20, 20);//创建一个立方体
var material = new THREE.MeshBasicMaterial(color: 0x0084ff);//填充的材质
var cube = new THREE.Mesh(geometry, material);//网格绘制
cube.position.x=-Math.ceil(Math.random()*10)*100;
cube.position.z=-Math.ceil(Math.random()*10)*100;
cube.name="cube_"+i.toString();
scene.add(cube);//场景添加网格

createSVG();
//createSky();


//创建天空
function createSky()
var skyBoxGeometry = new THREE.BoxGeometry( 5000, 5000, 5000 );

var texture = new THREE.TextureLoader().load("./01Images_Test/sky.png");

var skyBoxMaterial = new THREE.MeshBasicMaterial( map:texture, side: THREE.DoubleSide );

var skyBox = new THREE.Mesh( skyBoxGeometry, skyBoxMaterial );

scene.add(skyBox);

//控制移动 ,更改位置
function testMoveCube()
var object0 = scene.getObjectByName( "cube_0" );
// object0.translateX(object0.position.x+1).step(duration:1000);
// object0.translateZ(object0.position.z+1).step(duration:1000);

//相对上次位置移动多少 (已测试)
//object0.translateX(10);
//object0.translateZ(10);

//相对世界坐标原点 ,重新设置位置 (已测试)
object0.position.set(object0.position.x+10,object0.position.y,object0.position.z);

//var object1 = scene.getObjectByName( "cube_1" );
//object0.translateX(10);
//object0.translateZ(10);

var object1 = scene.getObjectByName("main_mode");
object1.position.z=object1.position.z-100;
object1.rotateY(Math.PI/2);
for(var i=0;i<100;i++)
(function(i)
var timeoutInfo=setTimeout(function()
console.log(i);

object1.position.set(object1.position.x + i, object1.position.y, object1.position.z);
console.log(object1.position.x);
, (i + 1) * 1000);
)(i)

if(object1.position.x>1000)
window.clearInterval(timeoutInfo);
alert("超出抵达世界边际点位置,已停止!");




//创建SVG底图
function createSVG()
// tietu();
// return;
var url = ./01Images_Test/5.svg;
loadSVG(url);


function loadSVG( url )

//

//scene = new THREE.Scene();
//scene.background = new THREE.Color( 0xffffff );



//const helper = new THREE.GridHelper( 160, 10 );
//helper.rotation.x = Math.PI / 2;
//scene.add( helper );

//

const loader = new SVGLoader();

loader.load( url, function ( data )

const paths = data.paths;

const group = new THREE.Group();
//group.scale.multiplyScalar( 0.25 );
//group.position.x = - 200;
group.position.y = 0;
// group.scale.y *= - 1;
//group.rotateX(8.09);
group.rotation.x = Math.PI /2;

for ( let i = 0; i < paths.length; i ++ )

const path = paths[ i ];

const fillColor = path.userData.style.fill;
if ( guiData.drawFillShapes && fillColor !== undefined && fillColor !== none )

const material = new THREE.MeshBasicMaterial(
color: new THREE.Color().setStyle( fillColor ),
opacity: path.userData.style.fillOpacity,
transparent: path.userData.style.fillOpacity < 1,
side: THREE.DoubleSide,
depthWrite: false,
wireframe: guiData.fillShapesWireframe
);

const shapes = path.toShapes( true );

for ( let j = 0; j < shapes.length; j ++ )

const shape = shapes[ j ];

const geometry = new THREE.ShapeBufferGeometry( shape );
const mesh = new THREE.Mesh( geometry, material );

group.add( mesh );





const strokeColor = path.userData.style.stroke;

if ( guiData.drawStrokes && strokeColor !== undefined && strokeColor !== none )

const material = new THREE.MeshBasicMaterial(
color: new THREE.Color().setStyle( strokeColor ),
opacity: path.userData.style.strokeOpacity,
transparent: path.userData.style.strokeOpacity < 1,
side: THREE.DoubleSide,
depthWrite: false,
wireframe: guiData.strokesWireframe
);

for ( let j = 0, jl = path.subPaths.length; j < jl; j ++ )

const subPath = path.subPaths[ j ];

const geometry = SVGLoader.pointsToStroke( subPath.getPoints(), path.userData.style );

if ( geometry )

const mesh = new THREE.Mesh( geometry, material );

group.add( mesh );









scene.add( group );

);



//地面贴图
function tietu()
var floorTexture = new THREE.ImageUtils.loadTexture( ./01Images_Test/testmain.png );
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set(1,1 );
// DoubleSide: render texture on both sides of mesh
var floorMaterial = new THREE.MeshBasicMaterial( map: floorTexture, side: THREE.DoubleSide );
var floorGeometry = new THREE.PlaneGeometry(1500,1500, 1, 1);
var floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.y = 0.1; //-0.1;
floor.rotation.x = Math.PI /2;


//floor.rotation.x = Math.PI / 2.9;

scene.add(floor);


// 生成三维文字
//字体转换原因:
//
// 在官方提供的字库里没有中文的
// 解决方法:
//    在 C:\\Windows\\Fonts 下找打一个有中文字符的字库,文件格式为TTF
// 去 facetype.js 转换成json
function create3DText()
//var textLoad = new THREE.FontLoader().load(fonts/helvetiker_regular.typeface.json,function(font)
var textLoad = new THREE.FontLoader().load(fonts/STFangsong_Regular.json,function(font)
var txtGeo = new THREE.TextGeometry(测试人,
font: font,
size: 20,
height: 0.9,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.1,
bevelSize: 0.05,
bevelSegments: 3
);
var txtMater = new THREE.MeshBasicMaterial(color: 0x0000ff);
var txtMesh = new THREE.Mesh(txtGeo,txtMater);
txtMesh.position.set(-2,2.3,-0.4);
scene.add(txtMesh);
);


// 加载模型
(function()

//const loader = new GLTFLoader().setPath( ./models/gltf/DamagedHelmet/glTF/ );
//loader.load( DamagedHelmet.gltf, function ( gltf )
const loader = new GLTFLoader().setPath( ./01Images_Test/ );
loader.load( miner.gltf, function ( gltf )
gltf.scene.traverse( function ( child )

child.name="main_mode";

if ( child.isMesh )

// TOFIX RoughnessMipmapper seems to be broken with WebGL 2.0
// roughnessMipmapper.generateMipmaps( child.material );



);
//gltf.userData.name="main_mode";
scene.add( gltf.scene );
//roughnessMipmapper.dispose();
render();

create3DText();
// 调用动画
//var mixer = new THREE.AnimationMixer( gltf.scene.children[2] );
//mixer.clipAction( gltf.animations[ 0 ] ).setDuration( 1 ).play();
//mixers.push( mixer );

);
)()


//创建基站数据
function createStationData()
$.get()



</script>
</body>
</html>

 点击移动按钮时人物改变朝向,朝着X轴方向出发。

使用Three.js

以上是关于使用Three.js 心得系列一 如何创建三维网格三使用Three.js 心得系列三 Three.js 如何加载GlTF文件的主要内容,如果未能解决你的问题,请参考以下文章

Three.js建模基础

Three.js 进阶之旅:新春特典-Rabbit craft go 🐇

Three.JS提升学习5:从外部加载几何体

Three.js教程:第一个3D场景

如何在 THREE.JS 上创建自定义网格?

#yyds干货盘点#愚公系列2022年12月 微信小程序-three.js绘制球体