做有态度的前端团队

网易FEG前端团队

Threejs学习笔记(一) 基础篇

基本概念

此学习笔记主要记录使用threejs的制作http://sqace.163.com网站中用到的API和相关知识点。
一个完整的3D环境包含以下元素:

1.场景(Scene):是物体、光源等元素的容器,
2.相机(Camera):控制视角的位置、范围以及视觉焦点的位置,一个3D环境中只能存在一个相机
3.物体对象(Mesh):包括二维物体(点、线、面)、三维物体、粒子
4.光源(Light):包括全局光、平行光、点光源
5.渲染器(Renderer):指定渲染方式,如webGL\canvas2D\Css2D\Css3D等。
6.控制器(Control): 相机控件,可通过键盘、鼠标控制相机的移动

场景(Scene)

物体、光源、控制器的添加必须使用secen.add(object)添加到场景中才能渲染出来。
一个3D项目中可同时存在多个scene,通过切换render的scene来切换显示场景


var scene = new THREE.Scene();
var mesh=scene.getObjectByName("sky");//获取场景中name=sky的物体;

相机(Camera)

基本概念

相机根据投影方式分两种:正交投影相机和透视投影相机

正交投影相机:THREE.OrthographicCamera(left, right, top, bottom, near, far),大小不因远近而变化
透视投影相机:THREE.PerspectiveCamera(fov, aspect, near, far),遵循近大远小的空间规则

一般情况下,我们使用的是透视投影相机,其参数为:

fov: 垂直方向夹角
aspect:可视区域长宽比 width/height
near:渲染区域离摄像机最近的距离
far:渲染区域离摄像机最远的距离,3仅在距离摄像机near和far间的区域会被渲染到 canvas中

65f5665d-3d7f-42b9-a3f8-22d4f46bab18.png

相机有两个重要的参数:

camera.position:控制相机在整个3D环境中的位置(取值为3维坐标对象-THREE.Vector3(x,y,z))
camera.lookAt:控制相机的焦点位置,决定相机的朝向(取值为3维坐标对象-THREE.Vector3(x,y,z))

以下代码创建一个垂直夹角为45度、渲染区域为距离镜头1px到1000000px的透视投影相机,设置其位置为x:500px,y:0,z:500px,镜头朝向空间坐标轴原点x:0,y:0,z:0


var camera = new THREE.PerspectiveCamera( 45, 1920 / 1000, 1, 1000000 );
camera.position.set(500, 0, 500);
camera.lookAt(new THREE.Vector3(0,0,0));

主要应用

  • 1.设置相机焦点位置为原点坐标或某物体的位置坐标:camera.lookAt(new THREE.Vector3(0,0,0)),循环改变camera.position的位置,可以实现围绕物体旋转 360度观看物体的动画。

  • 2.同时循环设置camera.lookAt和camera.position,可以实现以第一人称视角在空间自由移动的动画

物体(Mesh)

一个完整的物体对象mesh包括形状Geometry和材质Material

  • Mesh:三维物体,包括Geometry、Material,设置其name属性可以通过scene.getObjectByName(name)获取该物体对象;
  • Geometry:包括平面Plane、圆形Circle、立方体Cube、球体Sphere、圆柱Cylinder、多面体Polyhedron等,拥有以下属性:
  • Material:包括基础材质MeshBasicMaterial,深度材质MeshDepthMaterial、法向材质MeshNormalMaterial、面材质MeshFacelMaterial、朗柏材质MeshLambertMaterial、phone材质MeshPhongMaterial、着色器材质ShaderMaterial

以下代码以创建name为sky一个立方体为例,使用6张图片作为材质贴到立方体内部

var path = "resource/sky/";
var format = '.jpg';
var urls = [
        path + 'px' + format, path + 'nx' + format,
        path + 'py' + format, path + 'ny' + format,
        path + 'pz' + format, path + 'nz' + format
    ];
var materials = []; 
for (var i = 0; i < urls.length; ++i) {
    var loader = new THREE.TextureLoader();
    loader.setCrossOrigin( this.crossOrigin );
    var texture = loader.load( urls[i], function(){}, undefined, function(){} );
    materials.push(new THREE.MeshBasicMaterial({
        map: texture, 
        overdraw: true,
        side: THREE.BackSide,
        //transparent: true,
        //needsUpdate:true
    })
    ); 
} 
var cube = new THREE.Mesh(new THREE.CubeGeometry(9000,9000,9000), new THREE.MeshFaceMaterial(materials)); 
cube.name="sky";
scene.add(cube);

记住以下几点:

  • 1.物体mesh的大小是由构成三维物体形状的顶点坐标mesh.geometry.vertices和缩放级别mesh.scale(vector3)决定的,初始化创建物体时由传入的参数确定了形状顶点坐标mesh.geometry.vertices,后面需要修改物体大小使用mesh.scale进行缩放
  • 2.物体的透明度是材质的透明度属性mesh.material.opacity决定的,若需要设置透明度,需将材质的是否支持半透明属性mesh.material.transparent设置为true
  • 3.在页面运行期间需要改变物体的材质或属性需要同步设置mesh.material.needsUpdate=true
  • 4.若物体使用了THREE.MultiMaterial材质(如上面的例子如果多张图作为贴图材质),则会将所有材质放到mesh.meterials数组中,修改材质需要对每一个材质mesh.materials.material进行单独修改
  • 5.修改mesh.rotationmesh.positionmesh.visible可设置物体旋转角度、位置、可见性。注意将物体的visible从false改成true,可能会造成画面卡顿

光源(Light)

全局光:THREE.AmbientLight,影响整个scene的光源,一般是为了弱化阴影或调整整体色调,可设置光照颜色,以颜色的明度确定光源亮度
平行光:THREE.DirectionalLight,模拟类似太阳的光源,所有被照射的区域亮度是一致的,可设置光照颜色、光照方向(通过向量确定方向),以颜色的明度确定光源亮度
点光源:THREE.PointLight:单点发光,照射所有方向,可设置光照强度,光照半径和光颜色

以下分别在 scene中添加了全局光,1个平行光及5个点光源,项目中平行光作为主要光源、点光源主要用于暗部补光。


var ambient = new THREE.AmbientLight( 0xcccccc );
scene.add( ambient );
var directionalLight = new THREE.DirectionalLight( 0xcccccc );
directionalLight.position.set( -2, 9, 1).normalize();//设置平行光方向
scene.add( directionalLight );
var pointlight = new THREE.PointLight(0xffffff, 1, 3000); 
pointlight.position.set(0, 0, 2000);//设置点光源位置
scene.add( pointlight );
var pointlight2 = new THREE.PointLight(0xffffff, 1, 2000);
pointlight2.position.set(-1000, 1000, 1000);
scene.add( pointlight2 );
var pointlight_back = new THREE.PointLight(0xffffff, 1, 2000); 
pointlight_back.position.set(0, 0, -2000);
scene.add( pointlight_back );
var pointlight_left = new THREE.PointLight(0xffffff, 1, 2000); 
pointlight_left.position.set( -2000, 0,0);
scene.add( pointlight_left );
var pointlight_right = new THREE.PointLight(0xffffff, 1, 2000); 
pointlight_right.position.set(0,  -2000, 0);
scene.add( pointlight_right );

渲染器(Renderer)

一般情况下我们使用的是WebGL渲染器,


var renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );//设置canvas的像素比为当前设备的屏幕像素比,避免高分屏下模糊
renderer.setSize( _container.offsetWidth, _container.offsetHeight );//设置渲染器大小,即canvas画布的大小
container.appendChild( renderer.domElement );//在页面中添加canvas

控制器(Controls)

FlyControls:飞行控制,用键盘和鼠标控制相机的移动和转动
OrbitControls::轨道控制器,模拟轨道中的卫星,绕某个对象旋转平移,用键盘和鼠标控制相机位置
PointerLockControls:指针锁定,鼠标离开画布依然能被捕捉到鼠标交互,主要用于游戏
TrackballControls:轨迹球控制器,通过键盘和鼠标控制前后左右平移和缩放场景
TransformControls:变换物体控制器,可以通过鼠标对物体的进行拖放等操作

项目中使用的是轨道控制器OrbitControls,并限制了上下旋转的角度范围和滚轮控制相机离中心点的最大距离和最小距离


var controls = new THREE.OrbitControls(camera,_container);
    controls.maxPolarAngle=1.5;//上下两极的可视区域的最大角度
    controls.minPolarAngle=1;//上下两极的可视区域最小角度
    controls.enableDamping=true;//允许远近拉伸
    controls.enableKeys=false;//禁止键盘控制
    controls.enablePan=false;//禁止平移
    controls.dampingFactor = 0.1;//鼠标滚动一个单位时拉伸幅度
    controls.rotateSpeed=0.1;//旋转速度
//  controls.enabled = false;//禁用控制器
    controls.minDistance=1000;//离中心物体的最近距离
    controls.maxDistance=3000;//离中心物体的最远距离

示例:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
    <div id="space"></div>  
    <script   src="https://code.jquery.com/jquery-1.12.4.min.js"   integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="   crossorigin="anonymous"></script>
    <script src="../js/lib/threejs/three.js"></script>
    
    <script src="../js/lib/threejs/OrbitControls.js"></script>
    <script>
        

            var container, stats;

            var camera, scene, renderer;

            var mouseX = 0, mouseY = 0;

            var windowHalfX = window.innerWidth / 2;
            var windowHalfY = window.innerHeight / 2;


            init();
            animate();
            var mesh;

            function init() {

                container = document.getElementById("space")
                camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 5000 );
                camera.position.set(0, 0, 1500);
                
                scene = new THREE.Scene();

                var ambient = new THREE.AmbientLight( 0xffffff );
                scene.add( ambient );
                
                
                var directionalLight = new THREE.DirectionalLight( 0xffffff );
                directionalLight.position.set( -5, 5, 5).normalize();
                scene.add( directionalLight );

                var pointlight = new THREE.PointLight(0x63d5ff, 1, 200); 
                pointlight.position.set(0, 0, 200);
                scene.add( pointlight );                
                var pointlight2 = new THREE.PointLight(0xffffff, 1, 200); 
                pointlight2.position.set(-200, 200, 200);
                scene.add( pointlight2 );
                var pointlight3 = new THREE.PointLight(0xffffff, 1.5, 200); 
                pointlight3.position.set(-200, 200, 0);
                scene.add( pointlight3 );

                var controls = new THREE.OrbitControls(camera,container);
            //controls.maxPolarAngle=1.5;
            //controls.minPolarAngle=1;
            controls.enableDamping=true;
            controls.enableKeys=false;
            controls.enablePan=false;
            controls.dampingFactor = 0.1;
            controls.rotateSpeed=0.1;
    //      controls.enabled = false;
            //controls.minDistance=1000;
            //controls.maxDistance=3000;
                
                
                
                var path = "../resource/sky/";
                var format = '.jpg';
                var urls = [
                        path + 'px' + format, path + 'nx' + format,
                        path + 'py' + format, path + 'ny' + format,
                        path + 'pz' + format, path + 'nz' + format
                    ];
                var skyMaterials = []; 
                for (var i = 0; i < urls.length; ++i) {
                    var loader = new THREE.TextureLoader();
                    loader.setCrossOrigin( this.crossOrigin );
                    var texture = loader.load( urls[i], function(){}, undefined, function(){} );
                    
                    skyMaterials.push(new THREE.MeshBasicMaterial({
                        //map: THREE.ImageUtils.loadTexture(urls[i], {},function() { }), 
                        map: texture, 
                        overdraw: true,
                        side: THREE.BackSide,
                        //transparent: true,
                        //needsUpdate:true,
                        premultipliedAlpha: true
                        //depthWrite:true,
                        
        //              wireframe:false,
                    })
                    ); 
                    
                } 
                
                var cube = new THREE.Mesh(new THREE.CubeGeometry(500, 500,500), new THREE.MeshFaceMaterial(skyMaterials)); 
                cube.name="sky";
                scene.add(cube);
                renderer = new THREE.WebGLRenderer();
                renderer.setPixelRatio( window.devicePixelRatio );
                renderer.setSize( window.innerWidth, window.innerHeight );
                container.appendChild( renderer.domElement );

                document.addEventListener( 'mousemove', onDocumentMouseMove, false );
                window.addEventListener( 'resize', onWindowResize, false );

            }

            function onWindowResize() {

                windowHalfX = window.innerWidth / 2;
                windowHalfY = window.innerHeight / 2;

                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize( window.innerWidth, window.innerHeight );

            }

            function onDocumentMouseMove( event ) {

                mouseX = ( event.clientX - windowHalfX ) / 2;
                mouseY = ( event.clientY - windowHalfY ) / 2;

            }

            //

            function animate() {

                requestAnimationFrame( animate );
                render();

            }

            function render() {
                
//              camera.position.x += ( mouseX - camera.position.x ) ;
//              camera.position.y += ( mouseY - camera.position.y ) ;
                
                camera.lookAt( scene.position );

                renderer.render( scene, camera );

            }

    </script>
    </body>
</html>

手机阅读请扫描下方二维码:

已有 40 条评论

  1. [...]本文仅供技术分享。原文链接地址: http://feg.netease.com/archives/266.html[...]

  2. ru

    老哥你这版本有点旧了吧,有空能不能更新一下子,正在学习,希望少走弯路

  3. 1

    1 ali-40.gif ali-40.gif ali-41.gif ali-41.gif ali-42.gif ali-42.gif ali-43.gif ali-43.gif ali-44.gif ali-44.gif ali-45.gif ali-45.gif ali-46.gif ali-46.gif ali-47.gif ali-47.gif ali-48.gif ali-48.gif ali-49.gif ali-49.gif ali-50.gif ali-50.gif ali-51.gif ali-51.gif ali-52.gif ali-52.gif ali-53.gif ali-53.gif ali-54.gif ali-54.gif ali-55.gif ali-55.gif ali-56.gif ali-56.gif ali-57.gif ali-57.gif ali-58.gif ali-58.gif ali-59.gif ali-59.gif ali-60.gif ali-60.gif ali-61.gif ali-61.gif

  4. 1

    1 ali-40.gif ali-40.gif ali-41.gif ali-41.gif ali-42.gif ali-42.gif ali-43.gif ali-43.gif ali-44.gif ali-44.gif ali-45.gif ali-45.gif ali-46.gif ali-46.gif ali-47.gif ali-47.gif ali-48.gif ali-48.gif ali-49.gif ali-49.gif ali-50.gif ali-50.gif ali-51.gif ali-51.gif ali-52.gif ali-52.gif ali-53.gif ali-53.gif ali-54.gif ali-54.gif ali-55.gif ali-55.gif ali-56.gif ali-56.gif ali-57.gif ali-57.gif ali-58.gif ali-58.gif ali-59.gif ali-59.gif ali-60.gif ali-60.gif ali-61.gif ali-61.gif

  5. 1

    1

  6. 1

    1

  7. 1

    1

  8. 1

    1

  9. 1

    1

  10. 1

    1

  11. 1

    1

  12. 1

    1

  13. 1

    1

  14. 1

    1

  15. 1

    1

  16. 1

    1

  17. 1

    1

  18. 1

    1

  19. 1

    1

  20. 1

    1

  21. 1

    1

添加新评论

ali-40.gifali-41.gifali-42.gifali-43.gifali-44.gifali-45.gifali-46.gifali-47.gifali-48.gifali-49.gifali-50.gifali-51.gifali-52.gifali-53.gifali-54.gifali-55.gifali-56.gifali-57.gifali-58.gifali-59.gifali-60.gifali-61.gif