1. 效果图
2. 详细步骤
2.1 创建场景、相机、渲染器、光
- 这一步都是基础的创建,注意相机位置和配置数据,保证立方体在视景体内就好。
- 雾可加可不加,添加雾后立方体后方会部分模糊。
- 环境光和直线光也可以只添加一个,但不能全不添加,添加不同的光会影响立方体的亮度,可以自己尝试不同颜色不同类型的光。
- 渲染器的一些配置可参考官方文档
// 声明场景、渲染器、相机变量
let scene, renderer, camera;
// 获取窗口宽度高度
let ww = window.innerWidth,
wh = window.innerHeight;
// 调用初始化函数
init();
/**
*@method 初始化函数
*/
function init() {
// 创建场景
scene = new THREE.Scene();
// 创建雾 (颜色, 受影响最小距离, 受影响最大距离)
scene.fog = new THREE.Fog(0x050505, 2500, 3500);
// 创建相机
camera = new THREE.PerspectiveCamera(27, ww/wh, 1, 3500);
// 设置相机位置
camera.position.z = 3000;
// 添加环境光
scene.add(new THREE.AmbientLight(0xFFFFFF));
// 添加直线光
var light = new THREE.DirectionalLight(0xffffff, 0.5);
light.position.set(1, 1, 1);
scene.add(light);
// 创建渲染器
renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('container'),
antialias : false
});
renderer.setClearColor(scene.fog.color);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(ww, wh);
renderer.gammaInput = true;
renderer.gammaOutput = true;
}
2.2 创建立方体
每一步代码注释有相应解释,这里做详细讲解:
- var triangles = 160000; // 这里是声明了构成立方体的点数,这里的立方体是由小三角形构成的,所以这里就是构成立方体的小三角形的总数。
- var geometry = new THREE.BufferGeometry(); // 创建一个几何体,这里的BufferGeometry可以理解为自己去定义每一个点的 x y z 值,这些值按顺序放在一个数组里,比如 [1, 2, 3] 构成 x=1, y=2, z=3 的点,即数组中三个元素确定一个点。如果要确定一条 线呢,那么需要2个点,数组中需要6个元素。三角形由三个点构成,所以三个点需要9个元素。
- positions、normals、colors 就理解为长度为三角形数*9的数组,Float32Array和Array的区别看注释。为什么都 * 3 *3 呢,因为这里一个小三角形有3个点,所以是 *3, 每一个点有 x y z 三个值,所以 *3 *3。positions 就按顺序存储了每个点的 x y z 值;normals存法向量x y z 值;colors 存每一个点 r g b 值。
- 遍历创建三角形的时候,一次循环创建一个三角形,一个三角形9个元素,所以每次 +9
- 遍历中声明的 x y z 是每个三角形原点(理解为在这个点附近一定范围内创建三个点构成三角形),Math.random * n 即 0~800,-n2 即 -400 范围为 -400 ~ 400,也就是每一个原点的 x y z 值都在 -400 ~ 400 范围内,即以 (0, 0, 0) 为中心,800 为边长的正方体内。
- pA、pB、pC 为三角形三个点的位置(这里我们称ABC三点),ax ay az 为点 A x y z 值,Math.random()*d-d2 即-6~6,x y z 分别加这个值,表示原点上加或减6之内的值,同理生成点B、点C的x y z 值,由此随机生成三角形三点。
- 法向量的计算是三角形两边的向量积,Vector3 提供了一系列的方法。为什么要计算每一个点的法向量呢,因为物体表面和光照方向的夹角大小不同,那么强度也不一样,为了计算这个角度,首先要知道物体表面每个位置的法线方向。并且在网格模型 Mesh 中,物体都是由小三角形组成的,所以我们需要计算出小三角形三个顶点的法向量。
- 计算完成后将位置,法向量,颜色分别赋值给 positions、normals、colors。
- 通过:geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));
geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3)); 进行设置。
// 一共有多少个点
var triangles = 160000;
// 创建立方体
var geometry = new THREE.BufferGeometry();
/**
* Float32Array 其实就是数组, 在操作较大的阵列时,访问数据通常比普通数组更快
* Float32Array 创建数组必须使用构造函数, 参数为数组长度
* 这里的长度为显示的点数 * 9 (因为每一个点需要用9个值描述,后面会将)
*/
var positions = new Float32Array(triangles * 3 * 3);
var normals = new Float32Array(triangles * 3 * 3);
var colors = new Float32Array(triangles * 3 * 3);
var color = new THREE.Color();
// n 为立方体边长
var n = 800, n2 = n/2;
// d 为每个小三角形的每一个点距原点偏移的最大值
var d = 12, d2 = d/2;
// 三角形三个顶点的向量
var pA = new THREE.Vector3();
var pB = new THREE.Vector3();
var pC = new THREE.Vector3();
var cb = new THREE.Vector3();
var ab = new THREE.Vector3();
// 遍历所有点, i 每次 +9 保证每次循环操作一个点
for(var i = 0; i < positions.length; i += 9){
// 每一个点的原点位置, xyz 都在 -400 ~ 400 之间, 生成的点一定在边长为 800 的立方体内
var x = Math.random() * n - n2; // 随机 -400 ~ 400 的值
var y = Math.random() * n - n2; // 随机 -400 ~ 400 的值
var z = Math.random() * n - n2; // 随机 -400 ~ 400 的值
/**
* Math.random() * d 为 0 ~ 12, -d2 即为 -6 ~ 6,即三角形顶点 x 在原点基础上 -6 或 +6
*/
// 每一个点(小三角形)的点a 的 xyz, 因为随机, 所有每一个小三角形大小不一样, 只控制了最大值
var ax = x + Math.random() * d - d2;
var ay = y + Math.random() * d - d2;
var az = z + Math.random() * d - d2;
var bx = x + Math.random() * d - d2;
var by = y + Math.random() * d - d2;
var bz = z + Math.random() * d - d2;
var cx = x + Math.random() * d - d2;
var cy = y + Math.random() * d - d2;
var cz = z + Math.random() * d - d2;
// 小三角形点a坐标占 index 0-2
positions[i] = ax;
positions[i+1] = ay;
positions[i+2] = az;
// 小三角形点b坐标占 index 3-5
positions[i+3] = bx;
positions[i+4] = by;
positions[i+5] = bz;
// 小三角形点c坐标占 index 6-8
positions[i+6] = cx;
positions[i+7] = cy;
positions[i+8] = cz;
// 设置三角形的三个顶点
pA.set(ax, ay, az);
pB.set(bx, by, bz);
pC.set(cx, cy, cz);
// pC pB向量相减(三角形一条边)
cb.subVectors(pC, pB);
// A B向量相减(三角形一条边)
ab.subVectors(pA, pB);
// cb ab 向量积, 即同时垂直上面两条边的向量,即法向量
cb.cross(ab);
// 将法向量转为单位向量
cb.normalize();
//法向量的方向可以这样表示N(nx, ny, nz);
var nx = cb.x;
var ny = cb.y;
var nz = cb.z;
// 0-8 循环法向量 xyz
normals[i] = nx;
normals[i+1] = ny;
normals[i+2] = nz;
normals[i+3] = nx;
normals[i+4] = ny;
normals[i+5] = nz;
normals[i+6] = nx;
normals[i+7] = ny;
normals[i+8] = nz;
//颜色用rgb表示, rgb每一个分量取值范围0-1,vx,vy,vz分别对应rgb值。
/**
* x 点的 x 值, -400-400,立方体边长为 800, 所以 vx 为 -0.5 ~ 0.5,+0.5 后范围为 0 ~ 1
* 通过这样的计算, 相同的 xyz 颜色相同, 相近的 xyz 颜色相近
*/
var vx = (x/n) + 0.5;
var vy = (y/n) + 0.5;
var vz = (z/n) + 0.5;
// color 存该点的颜色
color.setRGB(vx, vy, vz);
//将三角形的三个顶点设为同样的颜色
colors[i] = color.r;
colors[i+1] = color.g;
colors[i+2] = color.b;
colors[i+3] = color.r;
colors[i+4] = color.g;
colors[i+5] = color.b;
colors[i+6] = color.r;
colors[i+7] = color.g;
colors[i+8] = color.b;
}
// 设置原点, 三角形, 颜色
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));
geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3));
// 计算外边界球
geometry.computeBoundingSphere();
// 创建立方体材质
var material = new THREE.MeshPhongMaterial({
color : 0xaaaaaa,
ambient : 0xaaaaaa,
specular : 0xffffff,
shininess : 250,
side : THREE.DoubleSide,
vertexColors : THREE.VertexColors
});
// 创建立方体物体并添加到场景
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
3. 完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>BufferGeometry 彩色三角形立方体</title>
<style type="text/css">
html, body {
margin: 0px;
padding: 0px;
height: 100%;
}
#container {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="container"></canvas>
</body>
<script type="text/javascript" src="../../js/build/three.js"></script>
<script type="text/javascript">
// 声明场景、渲染器、相机、立方体变量
let scene, renderer, camera, mesh;
// 窗口宽高
let ww = window.innerWidth,
wh = window.innerHeight;
// 初始化
init();
// 添加动画
animate();
/**
* @method 初始化函数
*/
function init(){
// 创建场景
scene = new THREE.Scene();
// 创建雾 (颜色, 受影响最小距离, 受影响最大距离)
scene.fog = new THREE.Fog(0x050505, 2500, 3500);
// 创建相机
camera = new THREE.PerspectiveCamera(27, ww/wh, 1, 3500);
// 设置相机位置
camera.position.z = 3000;
// 添加环境光
scene.add(new THREE.AmbientLight(0xFFFFFF));
// 添加直线光
var light = new THREE.DirectionalLight(0xffffff, 0.5);
light.position.set(1, 1, 1);
scene.add(light);
// 创建渲染器
renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('container'),
antialias : false
});
renderer.setClearColor( scene.fog.color);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(ww, wh);
renderer.gammaInput = true;
renderer.gammaOutput = true;
// 一共有多少个点
var triangles = 160000;
// 创建立方体
var geometry = new THREE.BufferGeometry();
/**
* Float32Array 其实就是数组, 在操作较大的阵列时,访问数据通常比普通数组更快
* Float32Array 创建数组必须使用构造函数, 参数为数组长度
* 这里的长度为显示的点数 * 9 (因为每一个点需要用9个值描述,后面会将)
*/
var positions = new Float32Array(triangles * 3 * 3);
var normals = new Float32Array(triangles * 3 * 3);
var colors = new Float32Array(triangles * 3 * 3);
var color = new THREE.Color();
// n 为立方体边长
var n = 800, n2 = n/2;
// d 为每个小三角形的每一个点距原点偏移的最大值
var d = 12, d2 = d/2;
// 三角形三个顶点的向量
var pA = new THREE.Vector3();
var pB = new THREE.Vector3();
var pC = new THREE.Vector3();
var cb = new THREE.Vector3();
var ab = new THREE.Vector3();
// 遍历所有点, i 每次 +9 保证每次循环操作一个点
for(var i = 0; i < positions.length; i += 9){
// 每一个点的原点位置, xyz 都在 -400 ~ 400 之间, 生成的点一定在边长为 800 的立方体内
var x = Math.random() * n - n2; // 随机 -400 ~ 400 的值
var y = Math.random() * n - n2; // 随机 -400 ~ 400 的值
var z = Math.random() * n - n2; // 随机 -400 ~ 400 的值
/**
* Math.random() * d 为 0 ~ 12, -d2 即为 -6 ~ 6,即三角形顶点 x 在原点基础上 -6 或 +6
*/
// 每一个点(小三角形)的点a 的 xyz, 因为随机, 所有每一个小三角形大小不一样, 只控制了最大值
var ax = x + Math.random() * d - d2;
var ay = y + Math.random() * d - d2;
var az = z + Math.random() * d - d2;
var bx = x + Math.random() * d - d2;
var by = y + Math.random() * d - d2;
var bz = z + Math.random() * d - d2;
var cx = x + Math.random() * d - d2;
var cy = y + Math.random() * d - d2;
var cz = z + Math.random() * d - d2;
// 小三角形点a坐标占 index 0-2
positions[i] = ax;
positions[i+1] = ay;
positions[i+2] = az;
// 小三角形点b坐标占 index 3-5
positions[i+3] = bx;
positions[i+4] = by;
positions[i+5] = bz;
// 小三角形点c坐标占 index 6-8
positions[i+6] = cx;
positions[i+7] = cy;
positions[i+8] = cz;
// 设置三角形的三个顶点
pA.set(ax, ay, az);
pB.set(bx, by, bz);
pC.set(cx, cy, cz);
// pC pB向量相减(三角形一条边)
cb.subVectors(pC, pB);
// A B向量相减(三角形一条边)
ab.subVectors(pA, pB);
// cb ab 向量积, 即同时垂直上面两条边的向量,即法向量
cb.cross(ab);
// 将法向量转为单位向量
cb.normalize();
//法向量的方向可以这样表示N(nx, ny, nz);
var nx = cb.x;
var ny = cb.y;
var nz = cb.z;
// 0-8 循环法向量 xyz
normals[i] = nx;
normals[i+1] = ny;
normals[i+2] = nz;
normals[i+3] = nx;
normals[i+4] = ny;
normals[i+5] = nz;
normals[i+6] = nx;
normals[i+7] = ny;
normals[i+8] = nz;
//颜色用rgb表示, rgb每一个分量取值范围0-1,vx,vy,vz分别对应rgb值。
/**
* x 点的 x 值, -400-400,立方体边长为 800, 所以 vx 为 -0.5 ~ 0.5,+0.5 后范围为 0 ~ 1
* 通过这样的计算, 相同的 xyz 颜色相同, 相近的 xyz 颜色相近
*/
var vx = (x/n) + 0.5;
var vy = (y/n) + 0.5;
var vz = (z/n) + 0.5;
// color 存该点的颜色
color.setRGB(vx, vy, vz);
//将三角形的三个顶点设为同样的颜色
colors[i] = color.r;
colors[i+1] = color.g;
colors[i+2] = color.b;
colors[i+3] = color.r;
colors[i+4] = color.g;
colors[i+5] = color.b;
colors[i+6] = color.r;
colors[i+7] = color.g;
colors[i+8] = color.b;
}
// 设置原点, 三角形, 颜色
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));
geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3));
// 计算外边界球
geometry.computeBoundingSphere();
// 创建立方体材质
var material = new THREE.MeshPhongMaterial({
color : 0xaaaaaa,
ambient : 0xaaaaaa,
specular : 0xffffff,
shininess : 250,
side : THREE.DoubleSide,
vertexColors : THREE.VertexColors
});
// 创建立方体物体并添加到场景
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight);
}
function animate(){
requestAnimationFrame(animate);
render();
}
function render(){
var time = Date.now() * 0.001;
mesh.rotation.x = time * 0.25;
mesh.rotation.y = time * 0.5;
renderer.render(scene, camera);
}
</script>
</html>