Three.js笔记 - 渲染器、相机、光源

156 阅读12分钟

image.png

前言

我们先从一个简单的场景开始,让你对 threejs 有个简单的认识。

image.png

上面场景很简单,有一个灰色的平面,上面是一个红色的立方体和一个蓝色的小球,还有一个三维的轴线,它的实现代码如下:

// 定义用来展示场景的div盒子
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        // 创建一个场景,它将承载我们所有的元素,例如渲染对象、相机和灯光等
        var scene = new THREE.Scene();

        // 创建一个相机,它定义了我们正在看的地方
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        // 创建一个renderer渲染器,用来执行渲染工作
        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColorHex();
        renderer.setClearColor(new THREE.Color(0xEEEEEE));
        // 设置渲染的大小
        renderer.setSize(window.innerWidth, window.innerHeight);

        // 在屏幕上显示辅助轴线,也就是xyz轴
        // 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
        var axes = new THREE.AxisHelper(20);
        // 把轴线加入到场景中去
        scene.add(axes);

        // new一个平面几何体
        var planeGeometry = new THREE.PlaneGeometry(60, 20);
        // new一个基础材质
        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.y = 0;
        plane.position.z = 0;

        // 把平面添加到场景中
        scene.add(plane);

        // 创建一个立方体对象
        var cubeGeometry = new THREE.BoxGeometry(4, 4, 4
        // 设置立方体的材质,wireframe表示使用线框
        var cubeMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
        var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);

        // 同样设置立方体的定位位置
        cube.position.x = -4;
        cube.position.y = 3;
        cube.position.z = 0;

        // 把立方体添加到场景中
        scene.add(cube);

        // 创建一个球体对象
        var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
        // 设置球体表面的材质,
        var sphereMaterial = new THREE.MeshBasicMaterial({color: 0x7777ff, wireframe: true});
        var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);

        // 设置球体的定位位置
        sphere.position.x = 20;
        sphere.position.y = 4;
        sphere.position.z = 2;

        // 添加球到场景中
        scene.add(sphere);

        // 将相机定位并指向场景的中心
        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);

        // 将渲染器的输出添加到 最开始定义的div里
        document.getElementById("WebGL-output").appendChild(renderer.domElement);

        // 执行渲染操作
        renderer.render(scene, camera);
    }
    init();

</script>

总结一下

  1. 定义承载输出的html元素
  2. 创建场景
  3. 创建渲染对象,并把对象加入到场景中
  4. 创建渲染器
  5. 创建相机
  6. 相机对焦,指明要拍摄场景中的哪个位置
  7. 使用渲染器执行渲染,并把结果展示到第一步定义的html元素中

下面我们会详细的针对renderer,camera,sence等等进行具体的介绍。

WebGLRenderer

three.js中内置了很多渲染器,在实际项目中用什么渲染器可以根据需求来选择。在这里我们只介绍最常用的WebGLRenderer

WebGLRenderer可以创建一个WebGL 渲染器,用于将场景渲染到HTML页面上。那什么是WebGL呢? 这里借用维基百科的回答:

WebGL是一种JavaScript API,用于在不使用插件的情况下在任何兼容的网页浏览器中呈现交互式2D和3D图形。WebGL完全集成到浏览器的所有网页标准中,可将影像处理和效果的GPU加速使用方式当做网页Canvas的一部分。WebGL元素可以加入其他HTML元素之中并与网页或网页背景的其他部分混合。

总结一下就是 WebGL是一种可以调用GPU,在浏览器中直接渲染2D和3D图形的技术。

那WebGL和Threejs有什么区别呢?

three.js 是以 WebGL 为基础的库,封装了一些3D渲染需求中重要的工具方法与渲染方法。WebGL门槛相对较高,Three.js 对 WebGL 提供的接口进行了友好的封装。

我们直接上代码:

    // create a render and set the size
    var webGLRenderer = new THREE.WebGLRenderer();
    webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
    webGLRenderer.setSize(window.innerWidth, window.innerHeight);
    webGLRenderer.shadowMapEnabled = true;

    webGLRenderer.render(scene, camera);

解释下上述代码的作用:

  1. new 一个 WebGLRenderer 实例。
  2. 设置颜色及其透明度。
  3. 设置大小
  4. 允许在场景中使用阴影贴图。
  5. 执行渲染操作

在 new WebGLRenderer实例时,支持传入很多参数,感兴趣的可以查看官方 链接

Camera

摄像机决定了屏幕上哪些东西需要渲染,你可以理解为只有摄像机对着的地方才会被Threejs渲染出来。

  • lookAt

将摄像机聚焦在指定点上

camera.lookAt(new THREE.Vector3(x,y,z));
camera.lookAt(scene.position);
// 指向场景中的特定对象
camera.lookAt(mesh.position);

PerspectiveCamera 透视投影摄像机

距离camera越远,物体被渲染的就越小,更接近真实世界。

image.png new THREE.PerpectiveCamera接收的参数如下:

  • fov

视场,代表摄像机能够看到的那部分场景。

人类有180°的视场,而有些鸟类有360°的视场。由于电脑不能完全显示我们能看到的景象,所以一般会选择一小块区域,在游戏中一般设为60-90°, 推荐的默认值为50。

  • aspect

长宽比,代表渲染结果横向尺寸与纵向尺寸的比值。

由于使用显示器作为输出界面,所以一般选择使用显示器窗口的长宽比。推荐的默认值为 window.innerWidth / window.innerHeight

  • near

近面距离,该属性代表从距离摄像机多近的距离开始渲染。

通常这个值会设置的尽量小,从而能够渲染从摄像机位置可以看到的所有物体。推荐默认值为0.1。

  • far

远面距离,该属性代表摄像机从它所属位置能够看多远。

如果这个值设置得较小,那么场景中有一部分不会被渲染,如果设置的过大,会影响渲染的性能。推荐的默认值为1000。

const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);  

其他常用属性如:

  • zoom

变焦,设置该属性可以放大和缩小场景。

如果这个值设置得小于1,则缩小场景。如果设置的大于1,则放大场景。如果设为负数,场景则上下颠倒。

camera.zoom = 0.8;
// 当camera属性变更后,必须执行该方法应用变更
camera.updateProjectionMatrix();

OrthographicCamera 正交投影摄像机

物体所有物体被渲染出来的尺寸是一致的

image.png new THREE.PerpectiveCamera时,构造函数接收的参数如下:

  • left 可视范围的左边界
  • right 可视范围的右边界
  • top 可视范围的上边界
  • bottom 可视范围的下边界
  • near 近面距离 同透视投影相机
  • far 远面距离 同透视投影相机
const camera = new THREE.OrthographicCamera(window.innerWidth / -16, window.innerWidth / 16, window.innerHeight / 16, window.innerHeight / -16, -200, 500);

常用属性

  • zoom 变焦 同透视投影相机

Scene

scene是一个场景图,里面包含所有图形场景的必要信息,如相机,光源,对象以及渲染所需要的其他对象。

场景图顾名思义,不仅仅是一个对象数组,还包含了场景图树形结构中的所有节点。每个添加到场景中的对象,甚至包含sence本身,都是继承自一个名为THREE.Object3D的对象。

一些常用方法:

  • add
  • remove
  • getObjectByName 通过名字获取场景中的对象
  • traverse 该方法接受一个function,该func会在每一个scene内的子对象上执行,包括子对象的子对象,直到最深层。
  • overrideMaterial 强制覆盖scene中所有物体的材质

一些常用属性:

  • children 属性 包含所有加入到场景中的对象
  • fog 属性 用来设置雾化效果

Light 光源

光源决定材质如何显示,以及用于产生阴影效果。

WebGL本身并不支持光源,如果不使用Three.js,你需要自己写WebGL着色程序来模拟光源。

不可投射阴影光源

THREE.AmbientLight 基本光源

该光源的颜色将会叠加到场景现有物体的颜色上。

请注意图中的阴影是由于同时还使用了SpotLight导致的,并不是AmbientLight的效果。

image.png

const ambientLight = new THREE.AmbientLight('#0c0c0c');
scene.add(ambientLight);
// 如果要在初始化后更改颜色,需要重新new一个THREE.Color('xxx')对象
// 或者调用原有color实例的方法修改值,如color.set(value)
ambientLight.color = new THREE.Color('new color');

如果你尝试在前言示例的代码中加入光源,那你可能要失望了,即使你加入了光源,也不会有任何效果,因为在前言示例中,我们物体的材质使用了 MeshBasicMaterial,而这种材质不会对光源产生反应。

THREE.PointLight 点光源

从空间的一点向所有方向发射光线。

image.png

const light = new THREE.PointLight( 0xff0000, 1, 100 );
light.position.set( 50, 50, 50 );
scene.add( light );

构造函数:

  • color - (可选参数)) 十六进制光照颜色。 默认值 0xffffff (白色)。

  • intensity - (可选参数) 光照强度。 默认值 1。

  • distance - 表示从光源到光照强度为0的位置距离。 当设置为14时,光线强度在距离为14的地方慢慢会减少为0,当设置为0时,光不会随着距离的增加而减弱。默认值 0。

  • decay - 沿着光照距离的衰退量。默认值 1。 在 physically correct 模式中,decay = 2

常用属性:

  • position 光源在场景中的位置

  • visible 是否可见

THREE.HemisphereLight 更加自然的光源

这是一种特殊的光源,可以通过模拟反光面和光线微弱的天空来创建更加自然的室外光线。

image.png 常用属性:

  • groundColor 从地面发出的光线的颜色

  • color 从天空发出的光线的颜色

  • intensity 光线照射的强度

创建一个自然光源

var hemiLight = new THREE.HemisphereLight(0x0000ff, 0x00ff00, 0.6);
hemiLight.position.set(0, 500, 0);
scene.add(hemiLight);
THREE.AreaLight 平面光源

这种光源可以指定散发光线的平面,而不是一个点。

image.png AreaLight不在标准的Three.js库中,而在它的扩展库中。如果要使用AreaLight,就不能使用THREE.WebGLRenderer对象了,因为AreaLight是一种非常复杂的光源,它会给普通的THREE.WebGLRenderer带来严重的性能损失。 一般使用THREE.WebGLDeferredRenderer对象,它可以更好的处理复杂的光照(或者大量的光源)。

var areaLight = new THREE.AreaLight(0xff0000, 3);
areaLight.position.set(-10, 10, -35);
areaLight.rotation.set(-Math.PI / 2, 0, 0);
areaLight.width = 4;
areaLight.height = 9.9;
scene.add(areaLight);

可以投射阴影光源

THREE.SpotLight 聚光光源

这种光源有聚光效果,类似台灯或者手电筒。从特定的一点发射锥形的光线。

image.png 常用属性:

  • angle 角度 光源发射出的光束的宽度,单位是弧度,默认值为Math.PI / 3
  • castShadow 投影 如果设置为true,光源就会产生阴影,同时要注意场景中要投射阴影的对象,也要设置castShadow为true
  • color 光源颜色
  • distance 表示从光源到光照强度为0的位置距离。当设置为14时,光线强度在距离为14的地方慢慢会减少为0,当设置为0时,光不会随着距离的增加而减弱。默认值 0。
  • exponent 光强度衰减指数 该属性决定了光线强度递减的速度。SpotLight 光源发射的光线强度随着光源距离的增加而减弱,使用低值,从光源发出的光线将到达远处看到物体,高值则光线仅能到达非常接近光源的物体。
  • intensity 光源照射的强度 默认值为1,为2代表着两倍强度
  • onlyShadow 设为true则代表着仅展示阴影,不会在场景中添加任何光照。
  • position 光源在场景中的位置
  • shadowCameraNear 投影近点 从距离光源的哪一个位置开始可以生成阴影,默认值为50
  • shadowCameraFar 投影远点 到距离光源的哪一个位置内可以生成阴影,默认值5000
  • shadowCameraFov 投影视场, 用于生成阴影的视场有多大,默认值5
  • shadowCameraVisible 如果该属性设为true,可以看到光源在哪里以及如何生成阴影的,类似于开启了debug模式。默认值为false。
  • shadowDarkness 阴影暗度 定义了阴影渲染的暗度,在场景渲染之后无法更改,默认值为0.5。
  • shadowMapWidth和shadowMapHeight 决定了有多少像素用来生成阴影。当阴影有锯齿状边缘时,可以增加这个值。场景渲染之后无法更改,默认值为0.5.
  • target 指向目标 使用target属性,可以将SpotLight光源指向场景中特定对象或位置。 此属性需要一个THREE.Object3D对象,如THREE.Mesh。
  • visible 是否可见,默认为true
  • shadowBias 偏移阴影位置 如果使用非常薄的对象时,遇到阴影失真或者看起来很怪,可以尝试将该属性设为很小的值,如0.01。此属性默认值为0。

创建聚光光源:

var pointColor = '#ffffff';
var spotLight = new THREE.SpotLight(pointColor);
spotLight.position.set(-40, 60, -10);
spotLight.castShadow = true;
spotLight.shadowCameraNear = 2;
spotLight.shadowCameraFar = 200;
spotLight.shadowCameraFov = 30;
spotLight.target = plane;
spotLight.distance = 0;
spotLight.angle = 0.4;
scene.add(spotLight);
THREE.DirectionalLight  平行光

从这种光源散发出的光线可以看作是平行的,就像太阳光。不是从单个点发射光线了,而是从平面发射,光线彼此平行。

image.png 该光源与SpotLight的主要区别时:平行光不像聚光灯那样离目标越远越暗淡,被平行光照亮的整个区域接收到的光强度时一样的。也就是说场景里的光线照射区域不时锥形的,而是一个立方体,且立方体内的对象接收到的强度一样。

在立方体范围内所有对象都可以接收投影和阴影,与SpotLight相比,除了shadowCameraNear,shadowCameraFar外,还需要设置立方体的范围(SpotLight是设置angle角度),跟正交投影Camera类似,需要设置上下左右可视范围边界。即shadowCameraLeft,shadowCameraRight,shadowCameraTop,shadowCameraBottom

常用的属性与SpotLight相比,除了angle外基本一致。

var pointColor = "#ff5808";
var directionalLight = new THREE.DirectionalLight(pointColor);
directionalLight.position.set(-40, 60, -10);
directionalLight.castShadow = true;
directionalLight.shadowCameraNear = 2;
directionalLight.shadowCameraFar = 200;
directionalLight.shadowCameraLeft = -50;
directionalLight.shadowCameraRight = 50;
directionalLight.shadowCameraTop = 50;
directionalLight.shadowCameraBottom = -50;

directionalLight.distance = 0;
directionalLight.intensity = 0.5;
directionalLight.shadowMapHeight = 1024;
directionalLight.shadowMapWidth = 1024;

scene.add(directionalLight);

关于该光源还有一个shadowCascade属性,当需要使用directionalLight在一个很大的区域设置阴影时,这个属性可以帮助创建更好的阴影效果,如果将这个属性设为true,它会将阴影分裂成shadowCascadeCount指定的值,这将导致靠近摄像机会产生更具细节的阴影,远离摄像机视点的阴影细节更少。要使用这个选项,还需要设置一些shadowCascade相关的属性。

其它

THREE.LensFlare 镜头光晕效果

为场景中的光源添加镜头光晕效果

image.png 常用属性:

  • texture 纹理 就是一个图片,用来决定光晕的形状。
  • size 光晕尺寸(单位为像素)。
  • distance 从光源(0)到摄像机(1)的距离。
  • color 光晕的颜色。
  • blending 混合 由于可以为光晕提供多种材质,该属性决定了多种材质如何进行混合。默认的混合方式是 THREE.AdditiveBlending。
var textureFlare0 = THREE.ImageUtils.loadTexture("../assets/textures/lensflare/lensflare0.png");
var textureFlare3 = THREE.ImageUtils.loadTexture("../assets/textures/lensflare/lensflare3.png");
var flareColor = new THREE.Color(0xffaacc);
var lensFlare = new THREE.LensFlare(textureFlare0, 350, 0.0, THREE.AdditiveBlending, flareColor);

lensFlare.add(textureFlare3, 60, 0.6, THREE.AdditiveBlending);
lensFlare.add(textureFlare3, 70, 0.7, THREE.AdditiveBlending);
lensFlare.add(textureFlare3, 120, 0.9, THREE.AdditiveBlending);
lensFlare.add(textureFlare3, 70, 1.0, THREE.AdditiveBlending;
lensFlare.position.copy(spotLight.position);
 scene.add(lensFlare);