前言:
WebGL可以直接使用显卡资源从JS创建三维场景;可用Three.js优化该复杂过程。
"构建骨架"
理论知识:
我们分别需要Three.js和TrackballControls.js两个文件作为支持库。
THREE中需要场景、相机和渲染器来构建三维元素。
scene - 场景是一个容器,用于保存、跟踪所要渲染的物体和使用的光源。
scene是渲染物体的基础。
对于物体对象的基本操作个人概括为:1、创建物体的尺寸 2、添加样式-颜色/透明度/材质 3、将尺寸和样式融合并“覆盖原物体” 4、设置物体的“外方位元素” 5、添加到场景中
camera - 相机将决定哪些东西会被渲染(可视)
renderer - 渲染场景和相机视角, renderer.render(scene, camera);
最后将渲染挂载到div标签上,告诉渲染器使用指定摄像机渲染场景
接下来我们实现下表中的对象
sphereGeometry方法
效果:
由于设置了旋转角,默认z轴垂直平面向外。
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
/* 确保容器填满整个视口 */
#webgl-output {
width: 100%;
height: 100%;
display: block;
}
</style>
<script type="text/javascript" charset="UTF-8" src="./libs/three.js"></script>
<script type="text/javascript" charset="UTF-8" src="./libs/TrackballControls.js"></script>
</head>
<body>
<!-- 添加一个容器元素 -->
<div id="webgl-output"></div>
<script>
const init = () =>{
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
// 设置背景颜色
renderer.setClearColor(new THREE.Color(0xEEEEEE));
// 设置渲染区域的大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 创建坐标轴
var axes = new THREE.AxisHelper(20);
scene.add(axes);
// 创建平面
var planeGeometry = new THREE.PlaneGeometry(60, 20);
// 创建材质
var planeMaterial = new THREE.MeshBasicMaterial({color: 0xcccccc});
// 组装
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
// 平面旋转
plane.rotation.x = -0.5 * Math.PI;
// 平面位置
plane.position.x = 15;
// plane.position.set(15, 0, 0);
// 添加到场景
scene.add(plane);
// 创建立方体
var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
var cubeMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(-4, 3, 0);
scene.add(cube)
// 创建球体
var sphereGeometry = new THREE.SphereGeometry(4,20,20)
var sphereMaterial = new THREE.MeshBasicMaterial({color: 0x7777ff, wireframe: true});
var sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.set(20, 4, 2);
scene.add(sphereMesh);
// 设置相机位置
camera.position.set(-30, 40, 30);
camera.lookAt(scene.position);
// 将渲染器添加到dom
document.getElementById('webgl-output').appendChild(renderer.domElement);
// 渲染
renderer.render(scene, camera);
}
init();
</script>
</body>
</html>
"添加羽毛"
添加材质、光源、阴影效果。
理论知识:
通过castShadow属性设置,THREE.js阴影效果被开启,并使用shadow.mapSize,shadow.camera.far,shadow.camera.near来控制阴影精细化程度。
注意光源的显示必须要合适的材料,效果才能显示,eg:MeshBasicMaterial材料无效果
可以改为MeshLambertMaterial、MeshPhysicalMaterial、MeshStandardMaterial、MeshPhongMaterail(已废弃)
阴影贴图大小仅为512x512,阴影贴图必须是2的幂
告诉渲染器需要阴影效果 renderer.shadowMap.enabled = true;
此时仍然无光影效果,需要明确指定谁投射阴影,谁接受阴影。
注意需要对光源的强度和随距离衰减情况进行修改,否则材质显示为黑色。
plane.recieveShadow = true
cube.castShadow = true
sphere.castShadow = true
定义产生阴影的光源,并不是所有的光源可以产生阴影,通过Three.SpotLight定义的光源能够产生阴影。下述代码渲染阴影。
spotLight.castShadow = true
效果:
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
/* 确保容器填满整个视口 */
#webgl-output {
width: 100%;
height: 100%;
display: block;
overflow: hidden;
}
</style>
<script type="module">
import * as THREE from '../build/three.module.js';
window.THREE = THREE;
</script>
</head>
<body>
<!-- 添加一个容器元素 -->
<div id="Stats-output">
<div id="webgl-output"></div>
</div>
<script type="module">
const init = () => {
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
// 设置背景颜色
renderer.setClearColor(new THREE.Color(0x000000));
// 设置渲染区域的大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 创建坐标轴
var axes = new THREE.AxesHelper(20);
scene.add(axes);
// 创建平面
var planeGeometry = new THREE.PlaneGeometry(60, 20);
// 创建材质
var planeMaterial = new THREE.MeshBasicMaterial({color: 0xecb514, side: THREE.DoubleSide});
// 组装-创建格网
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
// 平面旋转
plane.rotation.x = -0.5 * Math.PI;
// 平面位置
plane.position.set(15, 0, 0);
plane.receiveShadow = true;
// 添加到场景
scene.add(plane);
// 创建立方体
var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000, emissive: 0x000000, side: THREE.DoubleSide});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(-4, 3, 0);
cube.castShadow = true;
scene.add(cube);
// 创建球体
var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff, emissive: 0x000000, side: THREE.DoubleSide});
var sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.set(20, 4, 2);
sphereMesh.castShadow = true;
scene.add(sphereMesh);
// 设置相机位置
camera.position.set(-30, 40, 30);
camera.lookAt(cube.position); // 改为看向立方体
// 光照
// 聚光灯
const spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(-40, 60, -10);
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.camera.visible = false; // 不显示阴影相机
spotLight.castShadow = true;
spotLight.shadow.camera.far = 130;
spotLight.shadow.camera.near = 40;
spotLight.shadow.camera.fov = 50; // 设置阴影相机的视场角
spotLight.intensity = 5; // 聚光灯强度
spotLight.target = cube; // 设置聚光灯的目标
spotLight.decay = 0.06; // 衰减
scene.add(spotLight);
// 添加阴影效果
renderer.setClearColor(new THREE.Color(0x000000));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 设置更高的阴影贴图分辨率
renderer.shadowMap.width = 2048; // 减少锯齿效果
renderer.shadowMap.height = 2048;
renderer.shadowMap.enabled = true;
// 将渲染器添加到dom
document.getElementById('webgl-output').appendChild(renderer.domElement);
}
window.onload = init;
</script>
</body>
</html>
"初试羽翼"
让场景中的物体动起来。
requestAnimationFrame()
稳定而连续的渲染场景
// 将渲染器添加到dom
document.getElementById('webgl-output').appendChild(renderer.domElement);
// 渲染循环
function renderScene() {
requestAnimationFrame(renderScene);
renderer.render(scene, camera);
}
renderScene()
添加帧数显示
代码:
// 添加帧数显示
function initStates(type){
var panelType = (typeof type !== 'undefined' && type) && (!isNaN(parseInt(type))) ? parseInt(type) : 0;
var stats = new Stats();
stats.showPanel(panelType);
document.getElementById('Stats-output').appendChild(stats.dom);
return stats;
}
旋转立方体
效果:
代码:
// 立方体旋转
cube.rotation.x += 0.02;
cube.rotation.y += 0.02;
cube.rotation.z += 0.02;
弹跳球
效果:
代码:
const step = 0
// 小球运动
step += 0.04;
sphereMesh.position.x = 20 + (10 * (Math.cos(step)));
sphereMesh.position.y = 2 + (10 * Math.abs(Math.sin(step)));
GUI简化调试流程
代码:
// 创建待GUI控制的对象
var controls = new function(){
this.rotationSpeed = 0.02;
this.bouncingSpeed = 0.03;
}
// 创建控制器
var gui = new dat.GUI();
gui.add(controls, 'rotationSpeed', 0, 0.5);
gui.add(controls, 'bouncingSpeed', 0, 0.5);