Three.js的突破
Three.js只是炫,没有想象中的那么难
什么是webgl,什么是three.js
webgl是大部分浏览器直接支持的一种3D绘图标准。three.js在它的基础上进行了进一步的封装和简化开发开发过程。
three.js主要三个对象,一个行为
三个对象主要是场景(模型和灯光),相机(观察场景的视角),渲染器(场景渲染输出的目标),行为就是渲染。
three.js完整运行流程:
111077-20170424125001006-1547749106.png
本文中所需要的js全部都可以在官网例子中找到https://github.com/mrdoob/three.js/archive/master.zip
1.场景(Scene)
场景有两个参数 1参是模型,2参是灯光
模型
模型有两个参数 1参是几何模型,2参是材质
//网格模型 两个参数 1 几何模型 2材质var geometry = new THREE.BoxGeometry(100,100,100); //几何模型var material = new THREE.MeshLambertMaterial({color:0xff0000}); //材质var mesh = new THREE.Mesh(geometry,material); scene.add(mesh); //网格添加到场景当中
这里的模型可以添加多个!
灯光
var light = new THREE.DirectionalLight(0xffffff); // light.position.set(30,500,800); //设置光源位置 light.castShadow = true; light.position.set( 300, 200, 800 ); light.decay = 2; light.penumbra = 0.2; light.shadow.mapSize.width = 1024; light.shadow.mapSize.height = 1024; scene.add(light); //光源添加到场景之中
2.相机(Camera)
//相机对象 透视相机 var camera = new THREE.PerspectiveCamera(40,800/600,1,2000); camera.position.set(200,200,200) //设置相机的位置 camera.lookAt(scene.position); //相机朝向中心
3.渲染器(Renderer)
//设置渲染器 var renderer = new THREE.WebGLRenderer(); renderer.setSize(800,600); //设置渲染器的大小
4.将渲染器添加到文档之中
//渲染器添加到文档之中 document.body.appendChild(renderer.domElement); renderer.render(scene,camera); //渲染2个参数 1渲染的场景对象 2渲染的相机
这个我们就可以看到一个简单three的项目啦。
不过这个项目,它没有监听一些事件,比如说鼠标滚轮事件,拖动事件,three为我们提供了好多控制器(Controls),我们只用引用这个控制器,并且调用它, 就OK啦
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/OrbitControls.js" type="text/javascript" charset="utf-8"></script> function render(){ renderer.render(scene,camera); } //相机控制是场景交互和动画的基础 var controls = new THREE.OrbitControls(camera); //创建相机控制 2个参数 1是我们的相机 2可以忽略默认是文档 controls.addEventListener('change',render);
加上这些就可以监听事件啦。
5.添加3D模型
这里的3d文件指的是obj文件,就是一个人或者一个车子,一个房子,我们首先要引入一个加载器
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/OBJLoader.js" type="text/javascript" charset="utf-8"></script> //添加obj模型 var loader = new THREE.OBJLoader(); loader.load('img/man.obj',function(dogObject){ dogObject.scale.set(40,40,40); //缩放 dogObject.position.y = 50; dogObject.position.y = -30; // dogObject.rotateX(110); dogObject.rotateY(120); scene.add(dogObject); renderer.render(scene,camera); })
这样就把一个简单的3D模型放到浏览器中啦,当然你也可以添加多个3D模型。还有一点要注意的是加载文件是一个异步的行为,所以加载完之后一定要重新再渲染一次。或许你添加完之后,发现这个3D模型是没有材质的,材质可以看做皮肤,这个模型表面是什么都没有的。那继续往下看
6.添加材质
首先说下问题,添加一个obj的材质之前使用的是OBJMTLLoader.js,但是71以上的three版本,已经移除了这个组件,找啦好久找了一个新的解决方法,引入OBJLoader.js MTLLoader.js DDSLoader.js这3个js
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/OBJLoader.js" type="text/javascript" charset="utf-8"></script> <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/MTLLoader.js" type="text/javascript" charset="utf-8"></script> <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/DDSLoader.js"></script> var onProgress = function ( xhr ) { if ( xhr.lengthComputable ) { var percentComplete = xhr.loaded / xhr.total * 100; console.log( Math.round(percentComplete, 2) + '% downloaded' ); } }; var onError = function ( xhr ) { }; THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader() ); var mtlLoader = new THREE.MTLLoader(); mtlLoader.setPath( './img/' ); //设置我们需要加载的mtl文件路径 mtlLoader.load( 'man.mtl', function( material ) { //这里加载我们需要的文件名 material.preload(); var objLoader = new THREE.OBJLoader(); objLoader.setMaterials( material ); //材质,也可自定义 objLoader.setPath( './img/' ); //设置要加载的obj文件的路径 objLoader.load( 'man.obj', function ( object ) { //加载obj文件 object.position.z = 1; //这里设置我们的素材相对于原来的大小以及旋转缩放等 object.position.y = -0.5; object.scale.x = 300; object.scale.y = 300; object.scale.z = 300; object.rotation.x = 4.5; object1 = object; //这里是对素材设置阴影的操作 for(var k in object.children){ //由于我们的素材并不是看上去的一个整体,所以需要进行迭代 //对其中的所有孩子都设置接收阴影以及投射阴影 //才能看到阴影效果 object.children[k].castShadow = true; //设置该对象可以产生阴影 object.children[k].receiveShadow = true; //设置该对象可以接收阴影 } scene.add( object1 ); renderer.render(scene,camera); }, onProgress, onError ); });
然后只要调整路径和文件名称就OK啦。
7.添加天空盒
什么是天空盒,给一张图吧,会给人身临其境的效果,感觉身处在这个3维空间里
20170802095554275.jpg
//天空盒 var path = 'img/'; var format = '.png'; var urls = [ path + 'px' + format,path + 'nx' + format, path + 'py' + format,path + 'ny' + format, path + 'pz' + format,path + 'nz' + format, ] var textureCube = THREE.ImageUtils.loadTextureCube(urls); var meterial = new THREE.MeshBasicMaterial({ color:0xffffff, envMap:textureCube }) var shader = THREE.ShaderLib['cube']; shader.uniforms['tCube'].value = textureCube; var material = new THREE.ShaderMaterial({ fragmentShader:shader.fragmentShader, vertexShader:shader.vertexShader, uniforms:shader.uniforms, depthWrite:false, side:THREE.BackSide }), mesh = new THREE.Mesh(new THREE.BoxGeometry(1000,1000,1000),material); scene.add(mesh);
加入这些就OK啦。这些东西蛮好理解的, 就不一一作解释啦。最后给大家丢个全部代码吧!
8.事件
three的事件,因为它是个canvas,不像svg可以拿到元素,three的事件只能拿坐标去判断你点击的是什么模型,当然这个three是有组件去判断的
/**********************************事件***************************************/ var objects=[]; var raycaster = new THREE.Raycaster(); var mouse = new THREE.Vector2(); //监听全局点击事件,通过ray检测选中哪一个object document.addEventListener("mousedown", (event) => { event.preventDefault(); mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1; mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); scene.children.forEach(child => { if (child instanceof THREE.Mesh) {//根据需求判断哪些加入objects,也可以在生成object的时候push进objects objects.push(child) } }) var intersects = raycaster.intersectObjects(objects); if (intersects.length > 0) { console.log(intersects[0]) try{ if(ifCubeIdArr.indexOf(intersects[0].object.uuid) != -1){ if(intersects[ 0 ].object.material.color.b == 1 && intersects[ 0 ].object.material.color.g == 1 && intersects[ 0 ].object.material.color.r == 1){ intersects[ 0 ].object.material.color.set( 0xff0000); }else{ intersects[ 0 ].object.material.color.set( 0xffffff); } renderer.render(scene,camera); } objects = []; }catch(e){ objects = []; } } }, false)
它有一个很严重的问题,困扰了我一段时间,就是我只想给一个模型添加事件,而不是给所有的模型都添加事件,官网看了一段时间,没有找到好的解决方法。后来自己写js解决这个问题,思路:在创建这个模型的时候会有一个uuid,保存这个uuid,放在一个数组。并且在点击事件的时候,去检索这个数组。
9.添加文字
var fontLoader = new THREE.FontLoader(); //导入字体,设定字体,这里的话,你们找对自己的字体路径,可能和我的不一样的!!下载的three.js包里面examples/fonts里面有字体 fontLoader.load('js/optimer_regular.typeface.json',function(font){ for(let q = 0; q <arrobj.length;q++ ){ // 文字 var g = new THREE.TextGeometry(q,{ // 设定文字字体, font:font, //尺寸 size:1, //厚度 height:0.1, }); //计算边界,暂时不用管 g.computeBoundingBox(); //3D文字材质 var m = new THREE.MeshBasicMaterial({color:0xffffff}); //通过网格模型去添加 并且修改网格模型的位置 var mesh = new THREE.Mesh(g,m); mesh.position.x = arrobj[q].x - 3; mesh.position.y = arrobj[q].y + 2; mesh.position.z = arrobj[q].z - 1.1; // mesh.rotation.x = 0; // mesh.rotation.y = Math.PI * 2; // 加入到场景中 scene.add(mesh); } });
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/jquery-1.7.1.min.js" type="text/javascript" charset="utf-8"></script> <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/three.min.js" type="text/javascript" charset="utf-8"></script> <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/OrbitControls.js" type="text/javascript" charset="utf-8"></script> <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/OBJLoader.js" type="text/javascript" charset="utf-8"></script> <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/MTLLoader.js" type="text/javascript" charset="utf-8"></script> <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/DDSLoader.js"></script> <title></title> </head> <style> body{ margin: 0; overflow: hidden; } #percent { position: absolute; width: 200px; height: 20px; color: red; text-align: center; } </style> <body> <div id="percent"></div> <div id="WebGL-output"> </div> <script> var animatea; // 修改过 OrbitControls.js 源码 676行 $(function(){ var scene = new THREE.Scene();//场景构建 var camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,2000);//相机构建 var renderer = new THREE.WebGLRenderer();//渲染器构建 renderer.setClearColor(0xEEEEEE); renderer.setSize(window.innerWidth,window.innerHeight); renderer.shadowMapEnabled = true;//激活阴影// //构建一个坐标轴// var axes = new THREE.AxisHelper(20);// scene.add(axes); /****************************底座********************************/ var planeGeometry = new THREE.PlaneGeometry(40,20); //底座大小 var planeMaterial = new THREE.MeshBasicMaterial({color:0xcccccc}); var planeMaterial = new THREE.MeshLambertMaterial({color:0xffffff});//转换对光源有渲染的材质 var plane = new THREE.Mesh(planeGeometry,planeMaterial); plane.rotation.x = -0.5*Math.PI; plane.position.x = 15; plane.position.y = 0; plane.position.x = 0; scene.add(plane); plane.receiveShadow = true; //添加灯光 var spotLight = new THREE.SpotLight(0xffffff); spotLight.position.set(-10,20,10); spotLight.castShadow = true; scene.add(spotLight); //渲染视图视角 camera.position.x = -30; camera.position.y = 20; camera.position.z = 30; camera.lookAt(scene.position) var arrobj = [{ x:0,y:-1,z:0 },{ x:0,y:-1,z:5 },{ x:5,y:-1,z:0 },{ x:10,y:-1,z:0 },{ x:12,y:-1,z:0 },{ x:14,y:-1,z:0 },{ x:16,y:-1,z:0 },{ x:18,y:-1,z:0 },{ x:20,y:-1,z:0 },{ x:22,y:-1,z:0 },{ x:23,y:-1,z:0 },{ x:24,y:-1,z:0 },{ x:26,y:-1,z:0 },{ x:28,y:-1,z:0 },{ x:30,y:-1,z:0 },{ x:32,y:-1,z:0 },{ x:34,y:-1,z:0 }] var onProgress = function(xhr) { if (xhr.lengthComputable) { var percentComplete = xhr.loaded / xhr.total * 100; var percent = document.getElementById("percent"); percent.innerText = Math.round(percentComplete, 2) + '% 已经加载'; } }; for(let i = 0; i <arrobj.length;i++ ){ var onError = function(xhr) {}; var mtlLoader = new THREE.MTLLoader(); mtlLoader.setPath('./img/'); mtlLoader.load('22.mtl', function(materials) { materials.preload(); var objLoader = new THREE.OBJLoader(); objLoader.setMaterials(materials); objLoader.setPath('./img/'); objLoader.load('22.obj', function(object) { console.log(object); object.position.y = arrobj[i].y; object.position.x = arrobj[i].x; object.position.z = arrobj[i].z; // object.position.x = 5; // object.position.z = 5;// objectqi.rotation.x = 4.5; object.scale.set(0.001, 0.001, 0.001); scene.add(object); renderer.render(scene,camera); }, onProgress, onError); }); } // // 事件// //声明raycaster和mouse变量// var raycaster = new THREE.Raycaster();// var mouse = new THREE.Vector2();// // renderer.domElement. = function(e){// //通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1.// mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;// mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;// // // 通过鼠标点的位置和当前相机的矩阵计算出raycaster// raycaster.setFromCamera( mouse, camera );// // // 获取raycaster直线和所有模型相交的数组集合// var intersects = raycaster.intersectObjects( scene.children );// // console.log(intersects);// // //将所有的相交的模型的颜色设置为红色,如果只需要将第一个触发事件,那就数组的第一个模型改变颜色即可// for ( var i = 0; i < intersects.length; i++ ) {// // intersects[ i ].object.material.color.set( 0xff0000 );// // }// // } /*************************************字体********************************************/ var fontLoader = new THREE.FontLoader(); //导入字体,设定字体,这里的话,你们找对自己的字体路径,可能和我的不一样的!!下载的three.js包里面examples/fonts里面有字体 fontLoader.load('js/optimer_regular.typeface.json',function(font){ for(let q = 0; q <arrobj.length;q++ ){ // 文字 var g = new THREE.TextGeometry(q,{ // 设定文字字体, font:font, //尺寸 size:1, //厚度 height:0.1, }); //计算边界,暂时不用管 g.computeBoundingBox(); //3D文字材质 var m = new THREE.MeshBasicMaterial({color:0xffffff}); //通过网格模型去添加 并且修改网格模型的位置 var mesh = new THREE.Mesh(g,m); mesh.position.x = arrobj[q].x - 3; mesh.position.y = arrobj[q].y + 2; mesh.position.z = arrobj[q].z - 1.1; // mesh.rotation.x = 0; // mesh.rotation.y = Math.PI * 2; // 加入到场景中 scene.add(mesh); } }); /*************************************字体********************************************/ /*********************************设备上面的立方体************************************/ //因为不知道点击的是谁 所在在创建的时候保存uuid 点击的时候去检索 var ifCubeIdArr = []; for(let w = 0;w <arrobj.length;w++){ //网格模型 两个参数 1 几何模型 2材质 var geometryCube = new THREE.BoxGeometry(100,100,100); //几何模型 var materialCube = new THREE.MeshLambertMaterial({color:0xff0000}); //材质 var meshCube = new THREE.Mesh(geometryCube,materialCube); animatea = meshCube; meshCube.position.x = arrobj[w].x - 3; meshCube.position.y = arrobj[w].y + 3.5; meshCube.position.z = arrobj[w].z - 1.6; meshCube.scale.x = 0.01; meshCube.scale.y = 0.01; meshCube.scale.z = 0.01; scene.add(meshCube); //网格添加到场景当中 console.log(meshCube) ifCubeIdArr.push(meshCube.uuid); } var aa = true; function animate() { if(aa == true){ animatea.position.y += 3.5; //几何模型 aa = false; }else{ animatea.position.y -= 3.5; //几何模型 aa = true; } renderer.render(scene,camera); } animate(); setInterval( animate,500 ); /**********************************事件***************************************/ var objects=[]; var raycaster = new THREE.Raycaster(); var mouse = new THREE.Vector2(); //监听全局点击事件,通过ray检测选中哪一个object document.addEventListener("mousedown", (event) => { event.preventDefault(); mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1; mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); scene.children.forEach(child => { if (child instanceof THREE.Mesh) {//根据需求判断哪些加入objects,也可以在生成object的时候push进objects objects.push(child) } }) var intersects = raycaster.intersectObjects(objects); if (intersects.length > 0) { console.log(intersects[0]) try{ if(ifCubeIdArr.indexOf(intersects[0].object.uuid) != -1){ if(intersects[ 0 ].object.material.color.b == 1 && intersects[ 0 ].object.material.color.g == 1 && intersects[ 0 ].object.material.color.r == 1){ intersects[ 0 ].object.material.color.set( 0xff0000); }else{ intersects[ 0 ].object.material.color.set( 0xffffff); } renderer.render(scene,camera); } objects = []; }catch(e){ objects = []; } } }, false) /**********************************事件***************************************/ /************************************天空盒*************************************/ var path = 'img/'; var format = '.png'; var urls = [ path + 'px' + format,path + 'nx' + format, path + 'py' + format,path + 'ny' + format, path + 'pz' + format,path + 'nz' + format, ] var textureCube = THREE.ImageUtils.loadTextureCube(urls); var meterial = new THREE.MeshBasicMaterial({ color:0xffffff, envMap:textureCube }) var shader = THREE.ShaderLib['cube']; shader.uniforms['tCube'].value = textureCube; var material = new THREE.ShaderMaterial({ fragmentShader:shader.fragmentShader, vertexShader:shader.vertexShader, uniforms:shader.uniforms, depthWrite:false, side:THREE.BackSide }), meshTian = new THREE.Mesh(new THREE.BoxGeometry(1000,1000,1000),material); scene.add(meshTian); $("#WebGL-output").append(renderer.domElement)// renderScene();// function renderScene(){// requestAnimationFrame(renderScene);// renderer.render(scene,camera);// } function render(){ renderer.render(scene,camera); } //相机控制是场景交互和动画的基础 var controls = new THREE.OrbitControls(camera); //创建相机控制 2个参数 1是我们的相机 2可以忽略默认是文档 controls.addEventListener('change',render); }) </script> </body></html>
作者:会飞的猪l
链接:https://www.jianshu.com/p/070baa80ca02
共同学习,写下你的评论
评论加载中...
作者其他优质文章