ThreeJs入门36-WebGL视图篇:多摄像机、多视图、多角度摄影

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

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

我们经常玩游戏的朋友,应该都知道,我们在游戏中,一个窗口中,会有多个子窗口,在子窗口中显示场景不同角度的动画。例如小地图,就是一个很好的例子。 image.png

  • 这幅图中,红框部分就是通过不同视图的渲染来做到的,它综合使用了透视相机和正投影相机。
  • 被红框圈住的部分,就是正投影相机绘制出来的,因为它的大小永远不会变化。
  • 通过前面的内容,我们已经了解了怎么设置是图,这一节我们来深入这一部分内容,这样以后遇到同类问题,就能很好地顺藤摸瓜,不至于没有思路。

0. 效果展示

  • 首先我们先来看下最终的效果展示,如图:

image.png

  • 图中,有3个画布,3个画布里面分别显示了一个场景的不同区域。在图形学中,我们通常将这种显示画布叫做视口(viewport)
  • 效果demo

有阴影出现?

  • 我们demo中的多买难题的各个面的颜色是不一样的,有一点渐变。多面体底部是有一些阴影的,我们移动鼠标就可以看到阴影。

怎么推断光源情况?

  • 从多买难题表面呈现的光照情况来看,可以看出有一个灯光。
  • 多面体上没有聚光的效果,所以没有聚光灯
  • 也不是每个地方都有一样的光照,所有也不太可能是环境光
  • 最后推断出应该是方向光。

1. 构建静态界面

根据上面效果描述,我们先来创建静态页面 image.png

  • 我们需要三个canvas容器,这三个容器分别代表3个画布。
<div id="container">
    <canvas id="canvas1"></canvas>
    <canvas id="canvas2"></canvas>
    <canvas id="canvas3"></canvas>
</div>
复制代码
#canvas1, #canvas2, #canvas3 {
    position: relative;
    display: block;
    border: 1px solid red  ;  // 边框红色
}

#canvas1 {
    width: 300px;
    height: 200px;
}

#canvas2 {
    width: 400px;
    height: 100px;
    left: 150px;
}

#canvas3 {
    width: 200px;
    height: 300px;
    left: 75px;
}     
复制代码

2. 初始化视图

image.png

3. 初始化场景和灯光

4. 定义阴影面

  • 运行本示例,查看效果,在球的地步可以看到一块阴影,这个阴影是怎么实现的呢?

image.png

  • 我们这里的阴影不是通过光照生成的,而是简单的使用一个像阴影的贴图,贴到一个平面上生成的。这也是一个中生成阴影的方法。
  • 这里要实现阴影的效果,我们需要一个像阴影的纹理,一种带有这种纹理材质和一个带有这种材质的平面。生成像阴影一样的平面,然后放到多面体(球)的下方。
  • 我们可以通过canvas画布的绘制来生成阴影,因为是在浏览器中,所以这个图形肯定是适合three.js的
// 创建画布
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);
复制代码

实现效果如下: image.png

  • 画布做好以后,我们将画布转为纹理
var shadowTexture = new THREE.CanvasTexture( canvas );
复制代码
  • CanvasTexture可以从canvas画布中创建一个纹理。然后由纹理定义材质,代码如下:
var 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);
}
复制代码

这样就生成了3个平面阴影,并将其放置在了场景中。有了阴影,还差像球体一样的二十面体,下面,我们就来添加二十面体。

5. 生成二十面体,并给每个顶点赋予不同的颜色

  • 我们20面体的每个面都不是纯色的。显示在面上的颜色都是渐变色。这种渐变色是将三角形的每一个顶点赋予不同的颜色值形成的。
// 20面体的半径是200单位
var radius = 200;

// 生成3个20面体
var geometry1 = new THREE.IcosahedronBufferGeometry( radius, 1 );

// 从20面体中的顶点数组(geometry1.attributes.position)中获得顶点的个数,目的是为每个顶点设置一个颜色属性
var count = geometry1.attributes.position.count;
// 为几何体设置一个颜色属性,颜色属性的属性名必须是‘color’,这样Three.js才认识。
geometry1.addAttribute( 'color', new THREE.BufferAttribute( new Float32Array( count * 3 ), 3 ) );

// 由于场景中有3个20面体,所以这里通过clone函数复制3个。clone函数将生产一模一样的一个几何体。
var geometry2 = geometry1.clone();
var geometry3 = geometry1.clone();

// 这里声明一个临时的颜色值,待会用来作为中间结果。
var color = new THREE.Color();

// 获得3个几何体的位置属性数组
var positions1 = geometry1.attributes.position;
var positions2 = geometry2.attributes.position;
var positions3 = geometry3.attributes.position;

// 获得3个几何体的颜色属性数组
var colors1 = geometry1.attributes.color;
var colors2 = geometry2.attributes.color;
var colors3 = geometry3.attributes.color;

// 使用HSL颜色空间来设置颜色。下文将简单介绍这种颜色空间。
// 为每个顶点设置一种颜色
for ( var i = 0; i < count; i ++ ) {

    color.setHSL( ( positions1.getY( i ) / radius + 1 ) / 2, 1.0, 0.5 );
    colors1.setXYZ( i, color.r, color.g, color.b );

    color.setHSL( 0, ( positions2.getY( i ) / radius + 1 ) / 2, 0.5 );
    colors2.setXYZ( i, color.r, color.g, color.b );

    color.setRGB( 1, 0.8 - ( positions3.getY( i ) / radius + 1 ) / 2, 0 );
    colors3.setXYZ( i, color.r, color.g, color.b );

}
复制代码

6. 创建让平面和线框同时显示的物体

  • 20面体比较特殊,不但显示了表面,而且显示了表面周围的线框。这在three.js引擎中是不支持这种同时显示平面和线框的。
  • 为了实现这个效果,我们可以创建2个不同材质的相同物体,来近似的模拟这种效果。
var material = new THREE.MeshPhongMaterial( {
    color: 0xffffff,
    flatShading: true,
    vertexColors: THREE.VertexColors,
    shininess: 0
} );

var wireframeMaterial = new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true, transparent: true } );
var mesh = new THREE.Mesh( geometry1, material );
var wireframe = new THREE.Mesh( geometry1, wireframeMaterial );
mesh.add( wireframe );
mesh.position.x = - 400;
mesh.rotation.x = - 1.87;
scene.add( mesh );
复制代码
  • 这段代码用不同的材质,绘制了2个网格模型,这2个网格模型使用了同样的几何体对象(geometry)。带有线框的效果如下:

image.png

7. 初始化渲染器

renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( fullWidth, fullHeight );
复制代码

8. 渲染函数

  • 我们需要将3个视图的结果渲染出来
function animate() {

    for ( var i = 0; i < views.length; ++i ) {

        views[ i ].render();

    }

    requestAnimationFrame( animate );

}
复制代码

9. 让场景随鼠标移动

  • 我们通过监听鼠标的移动,来控制相机的位置。这里我们来实时的得到鼠标的位置(mouseX,mouseY)。
document.addEventListener('mousemove', onDocumentMouseMove, false );
function onDocumentMouseMove( event ) {
    mouseX = ( event.clientX - windowHalfX );
    mouseY = ( event.clientY - windowHalfY );
}
复制代码
  • 这里对event.ClientX和event.ClientY进行了一些处理,event.ClientX和event.ClientY的原点是窗口左上角,通过减去窗口一半的距离。让mouseX和mouseY,当鼠标在窗口中心点的时候,其值为0,这样原点就好像移动到了窗口的中心位置。

动态效果展示

动图.gif codepen示例代码

猜你喜欢

转载自juejin.im/post/7068679665317216293