「这是我参与2022首次更文挑战的第41天,活动详情查看:2022首次更文挑战」
示例代码采用three.js-r73版本: github.com/mrdoob/thre…
我们上节讲解了多摄像机、多视图、多角度摄影,对于多视图我们还需要了解一些其他的东西。类似于3Dmax的多视图显示又应该如何做呢?我们一起来看下吧。
3Dmax的多视图
- 如果你有用过3Dmax,那么你对于多视图是会比较熟悉的。看下图
- 在我们的3Dmax软件上显示一个物体,但是我们显示了4个窗口,分别显示了物体的正面、侧面、斜面、透视和实体效果。
- 我们通过three.js也能实现类似的效果,下面我们就开始吧。
效果展示
- 我们在场景中添加了3个20面体和对应的阴影。通过设置摄像机不同的位置,来达到类似于3Dmax的效果
- 图1:正面看的效果
- 图2:侧面看的效果
- 图3:上面看的效果
- 图4:斜面看的效果
- 下面讲解的时候会以图1234来进行说明做的是哪一部分的内容
场景渲染方式
- 我们的渲染方式其实有两种:
- 多个相机、多个渲染器、单个场景
- 上一节讲述的就是这个渲染方式
- 多个相机、多个渲染器、多个场景
- 这一节我们要讲述的渲染方式
- 多个相机、多个渲染器、单个场景
- 实现方式
- 通过一个场景,放入3个20面体
- 把场景添加到4个容器中,这个时候我们不设置摄像机或者设置一个摄像机,4个容器展示效果一样
- 摆放4个不同位置的摄像机,就可以显示出不同的效果了
静态界面
- 我们先把html界面布局好
- 我们准备4个容器,用来布局我们的场景
<div id="container">
<div id="container1"></div>
<div id="container2"></div>
<div id="container3"></div>
<div id="container4"></div>
</div>
复制代码
body {
color: #808080;
font-family: Monospace;
font-size: 13px;
text-align: center;
background-color: #fff;
margin: 0px;
overflow: hidden;
}
#container {
position: relative;
}
#container1,
#container2,
#container3,
#container4 {
position: absolute;
border: 1px solid red;
}
#container1 {
width: 500px;
height: 250px;
}
#container2 {
width: 500px;
height: 250px;
left: 520px;
}
#container3 {
width: 500px;
height: 250px;
top: 270px;
}
#container4 {
width: 500px;
height: 250px;
top: 270px;
left: 520px;
}
复制代码
初始化视图
- 我们需要初始化4个画布,来放置我们场景中不同摄像机的位置
function initView() {
container1 = document.getElementById("container1");
container2 = document.getElementById("container2");
container3 = document.getElementById("container3");
container4 = document.getElementById("container4");
}
复制代码
初始化场景
- 我们初始化一个场景,用来放我们的物体、摄像机、灯光等
function initScene() {
scene = new THREE.Scene();
}
复制代码
初始化灯光
- 初始化一个环境光,添加到场景中
function initLight() {
light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 0, 1).normalize();
scene.add(light);
}
复制代码
初始化摄像机
- 我们使用的摄像机都是透视投影
- 设置远近距离
const near = 1
const far = 10000
复制代码
图1摄像机
- 图1中需要的摄像机,是正对屏幕往里看的效果
camera1 = new THREE.PerspectiveCamera(45, 500 / 250, near, far);
camera1.setViewOffset(500, 250, 0, 0, 500, 250)
camera1.position.z = 1800 // z轴看向原点,正对屏幕
复制代码
图2摄像机
- 图2需要的摄像机,是从侧面看的效果
camera2 = new THREE.PerspectiveCamera(45, 500 / 250, near, far);
camera2.setViewOffset(500, 250, 0, 0, 500, 250)
camera2.position.x = 1800 // x轴看向原点,从球的侧面往左看
复制代码
- 类似于在图1中摆放了从右往左看的摄像机
图3摄像机
- 图3需要的摄像机,是从上往下看
- 这里我们调整y的位置时,也需要调整快门的方向,保证快门和y轴平行
camera3 = new THREE.PerspectiveCamera(45, 500 / 250, near, far);
camera3.setViewOffset(500, 250, 0, 0, 500, 250)
camera3.position.y = 1800 // y轴看向原点,从球的上面往下看
camera3.up.set(0, 0, 1) // 默认为(0,1,0),调整快门的方向,和y轴平行
复制代码
- 类似于在图1中摆放了一个向下看的摄像机
图4摄像机
- 图3需要的摄像机,是从右下角往左上角看
camera4 = new THREE.PerspectiveCamera(45, 500 / 250, near, far);
camera4.setViewOffset(500, 250, 0, 0, 500, 250)
// 斜着看物体
camera4.position.x = 300
camera4.position.z = 800
复制代码
- 类似于在图1中摆放了一个向下看的摄像机
初始化20面几何体
- 上一节课我们已经实现过了,这一节不做过多介绍,直接看代码
var shadowMaterial;
function createShadowByCanvas() {
// 创建画布
var canvas = document.createElement("canvas");
// 设置画布大小宽128,高128
canvas.width = 128;
canvas.height = 128;
// 得到画布的可画类context
var context = canvas.getContext("2d");
// 创建一个放射性渐变,渐变从画布的中心开始,到以canvas.width/2为半径的圆结束。如果不明白可以参考:http://www.w3school.com.cn/htmldom/met_canvasrenderingcontext2d_createradialgradient.asp
var gradient = context.createRadialGradient(
canvas.width / 2,
canvas.height / 2,
0,
canvas.width / 2,
canvas.height / 2,
canvas.width / 2
);
// 在离画布中心canvas.width*0.1的位置添加一种颜色,
gradient.addColorStop(0.1, "rgba(210,210,210,1)");
// 在画布渐变的最后位置添加一种颜色,
gradient.addColorStop(1, "rgba(255,255,255,1)");
// 填充方式就是刚才创建的渐变填充
context.fillStyle = gradient;
// 实际的在画布上绘制渐变。
context.fillRect(0, 0, canvas.width, canvas.height);
// 将画布转为纹理
var shadowTexture = new THREE.CanvasTexture(canvas);
shadowTexture.needsUpdate = true;
// 将纹理添加到材质中
shadowMaterial = new THREE.MeshBasicMaterial({ map: shadowTexture });
}
function initShadowPlane() {
// 定义一个宽和高都是300的平面,平面内部没有出现多个网格。
var shadowGeo = new THREE.PlaneGeometry(300, 300, 1, 1);
// 构建一个Mesh网格,位置在(0,-250,0),即y轴下面250的距离。
mesh = new THREE.Mesh(shadowGeo, shadowMaterial);
mesh.position.y = -250;
// 围绕x轴旋转-90度,这样竖着的平面就横着了。阴影是需要横着的。
mesh.rotation.x = -Math.PI / 2;
scene.add(mesh);
// 第二个平面阴影
mesh = new THREE.Mesh(shadowGeo, shadowMaterial);
mesh.position.x = -400;
mesh.position.y = -250;
mesh.rotation.x = -Math.PI / 2;
scene.add(mesh);
// 第三个平面阴影
mesh = new THREE.Mesh(shadowGeo, shadowMaterial);
mesh.position.x = 400;
mesh.position.y = -250;
mesh.rotation.x = -Math.PI / 2;
scene.add(mesh);
}
// 初始化物体
function initObject() {
// 20面体的半径是200单位
var radius = 200;
// 生成3个20面体
var geometry1 = new THREE.IcosahedronGeometry(radius, 1);
// 由于场景中有3个20面体,所以这里通过clone函数复制3个。clone函数将生产一模一样的一个几何体。
var geometry2 = geometry1.clone();
var geometry3 = geometry1.clone();
var faceIndices = ['a', 'b', 'c', 'd'];
var color, f1, f2, f3, p, n, vertexIndex;
for (var i = 0; i < geometry1.faces.length; i++) {
f1 = geometry1.faces[i];
f2 = geometry2.faces[i];
f3 = geometry3.faces[i];
n = (f1 instanceof THREE.Face3) ? 3 : 4;
for (var j = 0; j < n; j++) {
vertexIndex = f1[faceIndices[j]];
p = geometry1.vertices[vertexIndex];
color = new THREE.Color(0xffffff);
color.setHSL((p.y / radius + 1) / 2, 1.0, 1.0);
f1.vertexColors[j] = color;
color = new THREE.Color(0xffffff);
color.setHSL(0.0, (p.y / radius + 1) / 2, 1.0);
f2.vertexColors[j] = color;
color = new THREE.Color(0xffffff);
color.setHSL(0.125 * vertexIndex / geometry1.vertices.length, 1.0, 1.0);
f3.vertexColors[j] = color;
}
}
var materials = [
new THREE.MeshLambertMaterial({ color: 0xffffff, shading: THREE.FlatShading, vertexColors: THREE.VertexColors }),
new THREE.MeshBasicMaterial({ color: 0x000000, shading: THREE.FlatShading, wireframe: true, transparent: true })
];
/* --几何体1-- */
group1 = THREE.SceneUtils.createMultiMaterialObject( geometry1, materials );
group1.position.x = -400;
group1.rotation.x = -1.87;
scene.add( group1 );
/* --几何体2-- */
group2 = THREE.SceneUtils.createMultiMaterialObject( geometry2, materials );
group2.position.x = 400;
group2.rotation.x = 0;
scene.add( group2 );
/* --几何体3-- */
group3 = THREE.SceneUtils.createMultiMaterialObject( geometry3, materials );
group3.position.x = 0;
group3.rotation.x = 0;
scene.add( group3 );
}
复制代码
- 我们上一节是使用r93版本实现的20面几何体,这一节我们使用r73版本实现的几何体,由于r73版本没有
IcosahedronBufferGeometry
这个方法,所以我们通过IcosahedronGeometry
方法来实现。 - 这里也用到了
THREE.SceneUtils.createMultiMaterialObject
方法,这个方法可以把多种材质的数组,添加到一个几何体中
初始化渲染器
- 我们需要4个渲染器,添加到4个容器中
renderer1 = new THREE.WebGLRenderer({ antialias: true });
renderer1.setSize(500, 250);
renderer2 = new THREE.WebGLRenderer({ antialias: true });
renderer2.setSize(500, 250);
renderer3 = new THREE.WebGLRenderer({ antialias: true });
renderer3.setSize(500, 250);
renderer4 = new THREE.WebGLRenderer({ antialias: true });
renderer4.setSize(500, 250);
container1.appendChild(renderer1.domElement);
container2.appendChild(renderer2.domElement);
container3.appendChild(renderer3.domElement);
container4.appendChild(renderer4.domElement);
复制代码
- 如果只使用一个渲染器,四个容器中添加这个渲染器,那么会造成只有最后一个容器添加了这个渲染器,也就是如下效果
渲染函数
- 我们需要让4个摄像机都看向场景,不然的话可能摄像机就看不到我们的物体了。
function animation() {
render()
requestAnimationFrame(animation);
}
function render() {
camera1.lookAt(scene.position)
camera2.lookAt(scene.position)
camera3.lookAt(scene.position)
camera4.lookAt(scene.position)
renderer1.render(scene, camera1)
renderer2.render(scene, camera2)
renderer3.render(scene, camera3)
renderer4.render(scene, camera4)
}
复制代码