ThreeJs入门37-WebGL视图篇:类似3Dmax的多视图显示

「这是我参与2022首次更文挑战的第41天,活动详情查看:2022首次更文挑战

示例代码采用three.js-r73版本: github.com/mrdoob/thre…

我们上节讲解了多摄像机、多视图、多角度摄影,对于多视图我们还需要了解一些其他的东西。类似于3Dmax的多视图显示又应该如何做呢?我们一起来看下吧。

3Dmax的多视图

  • 如果你有用过3Dmax,那么你对于多视图是会比较熟悉的。看下图

aa18972bd40735faad72ed9e9f510fb30f240812.jpg

  • 在我们的3Dmax软件上显示一个物体,但是我们显示了4个窗口,分别显示了物体的正面、侧面、斜面、透视和实体效果。
  • 我们通过three.js也能实现类似的效果,下面我们就开始吧。

效果展示

  • 我们在场景中添加了3个20面体和对应的阴影。通过设置摄像机不同的位置,来达到类似于3Dmax的效果

image.png

  • 图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轴看向原点,正对屏幕
复制代码

image.png

图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中摆放了从右往左看的摄像机

image.png

图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中摆放了一个向下看的摄像机

image.png

图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中摆放了一个向下看的摄像机

image.png

初始化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);

复制代码
  • 如果只使用一个渲染器,四个容器中添加这个渲染器,那么会造成只有最后一个容器添加了这个渲染器,也就是如下效果

image.png

渲染函数

  • 我们需要让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)
}
复制代码

codepen示例代码

おすすめ

転載: juejin.im/post/7069173560073256996