Threejs从入门到入门
前言
threejs官网:https://threejs.org/
github各个版本:https://github.com/mrdoob/three.js/tags
版本更迭很快,我用的时候还是r90秒秒钟r91出来了
刚入门的时候可以看看这个教程:http://www.hewebgl.com/article/articledir/1
初级教程是免费的,中高级是收费的,写的不是很枯燥,也不用跟着敲代码,有个相机,渲染器,场景,光源的概念就可以了。它适配的版本是比较旧的,我当时用新版本跟着写有些demo无法完成,新手更谈不上根据版本之间的差别找到问题,慢慢就失去兴趣了。
我的这个教程用的是r90版本,它有个比较好的地方是,当你使用高版本的threejs却使用低版本的语法时,浏览器console会有警告告诉你新的写法是什么样的。
我的DEMO下载
演示地址: 太穷了,带宽很小,加载卡顿请谅解(哭泣)
相机转动做的比较仓促没有考虑细节,导致鼠标拖拽或是放大缩小卡顿,emmm小问题暂时不想改了
https://www.hugeoyzy.top/threejs/src/demo1.html
https://www.hugeoyzy.top/threejs/src/demo2.html
下载:https://download.csdn.net/download/u010588262/10288776
开发工具
因为我主要是做java开发,所以就用idea了,因为idea写js也是有提示的,所以还挺方便,就建个Static Web项目就可以了:
我的项目结构是这样的:
调试需要做两点配置:
1. 浏览器路径:
浏览器启动参数,因为threejs要加载本地的图片文件,obj文件之类的,看网上教程都说是不允许的,要部署在apache和tomcat之类的,我没试,看到说可以加启动参数就加了,调试没问题
–allow-file-access-from-files
调试的时候就在html文件上鼠标移到右上角,点击相应的浏览器图标就可以了
如果修改了html的话直接保存文件在浏览器刷新就能看到效果了,但是修改了js的话就要关闭页面重新点击浏览器图标。
官方DEMO(肥肠重要)
我入门最快的时候就是找到了官方DEMO之后
一个是:http://stemkoski.github.io/Three.js/#hello-world
这里的demo很入门,很好理解,页面最上方有github地址,可以去下载。唯一不好的是使用的版本很老。r60版本的,不过对于初学者一开始最重要的是理解大概的使用,不用纠结于最新版本。
还有一个是:https://threejs.org/examples/#webgl_animation_cloth
这里的demo相比第一个是比较高大上的,源代码就在你下载的threejs包里的examples目录下
代码讲解
这一块针对初学者中的初学者了,只要看过前言中的初级教程之后再看我的demo应该没问题了,demo的代码也比较整洁。
demo中的css,fonts,images和objs基本都是从官方demo中搞过来的,页面上引用的js分别是什么作用也做了注释了:
util.js是我把公用的东西放在一起了,要看懂util.js需要对threejs基本的组件有一些了解。
threejs的三围坐标是这样的,比如你现在对着电脑屏幕吧,把你屏幕的左下角想象成二维坐标原点,那往右就是x正方向,网上就是y正方向,从屏幕往外直盖脸的就是z正方向了。
最上面的src()是用来初始化场景,相机和渲染器的,最开始的textureLoader = new THREE.TextureLoader();是用来加载图片材质的,在大多数教程里用的都是ImageUtils来加载的,这个是新版本的改动,我说一下我对这仨玩意儿的理解吧
场景:就是一个黑盒子呗,把你要展示的东西全往里面装,地板啊,天花板啊,盒子啊,球啊,货架啊,三D字体啊全塞进去。为什么是黑盒子?因为你看不见它,也不看不见塞进去的东西。场景的初始化很简答就一句话scene = new THREE.Scene();
相机:相机可以在任意的位置,任意的角度”拍摄”黑盒子里的场景,相机的初始化稍微复杂一些。
var SCREEN_WIDTH = window.innerWidth; var SCREEN_HEIGHT = window.innerHeight; var VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 20000; camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR); scene.add(camera); camera.position.set(0, 600, 1000); cameraRadius = Math.sqrt(camera.position.x * camera.position.x + camera.position.z * camera.position.z); camera.up.set(0, 1, 0); camera.lookAt(new THREE.Vector3(0, 0, 0));
关于相机比较难理解的是position,up和lookAt的关系。你可看看这个视频http://www.hewebgl.com/video/2,虽然我看完了也没怎么看懂。
尝试解释一下吧。
现在,把你的手机打开摄像头,正对着屏幕,想象它就像单反一样上面右边有个快门,这就是threejs中相机的初始方向,就是从z轴正方向看向z轴负方向。position是什么意思呢,现在把手机往右平移,意思是position.x在增加。lookAt也很容易,就是相机往哪儿拍呗。比较难理解的是up,好现在把手机位置固定,只能旋转哦,也就是说position固定了,好了现在把手机旋转镜头对着屏幕左下角,也就是咱们的三维坐标原点,也就是说lookAt也固定了。up的作用就是你现在可以旋转手机了,保持镜头对着屏幕左下角的方向,手机的中心点位置不要变,旋转手机,拍出来的画面就会是正的,或者斜着的,或者上下倒过来的。随着你旋转手机快门的朝向也在变是吧,默认快门是对着Y轴正方向的,也就是(0,1,0)如果把手机颠倒,up就变成了(0,-1,0),拍出来的画面也就是上下颠倒的,因为你虽然可以倒着手机拍,但是看照片的时候得把手机正过来看,画面也就是反的咯。
渲染器
可以看到相机的初始化过程中没有与场景绑定,他们俩就像在两个平行世界一样,看似在一起,其实没在一起,需要用渲染器把他们俩关联起来,这样,相机就拍到了场景里的东西,再通过渲染器展示在浏览器上被我们看到了。Decetor是通过Detector.js引入进来的东西,用于判断浏览器兼容性的。
if (Detector.webgl) renderer = new THREE.WebGLRenderer({antialias: true}); else renderer = new THREE.CanvasRenderer(); renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); container = document.getElementById('ThreeJS'); container.appendChild(renderer.domElement);
以地板为例怎么搞一个东西放在场景里,CSDN的代码注释没有这么详细,写博客的时候加的,对不住只下资源不看文章的朋友。PlaneGeometry就是一个平面,SphereGeometry就是一个球,CubeGeometry就是一个立方体…….感兴趣的可以百度多了解一下其他组件。材质就是各种Material,也有很多种,有受光照影响的,有不受光照影响的。把组件和材质组合起来就是一个完整的物体啦,就是THREE.Mesh这个方法把他们组合起来,第一个参数是组件,第二个参数是材质,材质还可以传入一个数组,你可能觉得设置多个有啥意义啊一个盖住一个的,材质是可以设置透明度的,明白了吧。
function addfloor() { // 加载图片作为地板材质 var floorTexture = textureLoader.load('images/checkerboard.jpg'); // 沿x方向和Y方向都重复填充 floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping; // x方向和y方向各重复几次,不理解的话改改这个值看看效果就知道了 floorTexture.repeat.set(4, 4); // 将材质包装成表面材料,设置正反两面都要铺上 var floorMaterial = new THREE.MeshBasicMaterial({map: floorTexture, side: THREE.DoubleSide}); // 平面对象 var floorGeometry = new THREE.PlaneGeometry(1000, 1000, 1, 1); // 把对象和材料包装成Mesh floor = new THREE.Mesh(floorGeometry, floorMaterial); // 设置位置放入场景啦 floor.position.y = -0.5; floor.rotation.x = Math.PI / 2; scene.add(floor); }
others()方法里是一些可有可无的辅助,看注释就明白了。
animate()就是开始持续渲染,渲染就是通过renderer.render(scene, camera);把渲染器搞到的东西放在浏览器上给你看,持续渲染就是通过重复调用方法本身可以实现动画的效果requestAnimationFrame(animate);,除了这两个东西还调用了update();方法,update方法里就是慢慢的移动组件或者刷新其他需要更新得组件。requestAnimationFrame方法是比较平滑的动画,我之前想用Tween.js实现相机转动的效果,很卡顿,不流畅。
woodycube()就是封装了获得木箱,因为两个demo里都用到了木头箱子。
后面有空再说一下demo中的代码
2018年3月16日
更新了一下util.js中的代码,把鼠标点击选中事件和getMeshed方法从demo2中移到util里面了
点击事件
document.addEventListener("mousedown", onDocumentMouseDown, false); function onDocumentMouseDown(e) { e.preventDefault(); // 点击页面时停止相机旋转,因为相机旋转的时候无法选中物体,这个问题没有解决,有点麻烦 rotate = false; //将鼠标点击位置的屏幕坐标转成threejs中的标准坐标,具体解释见代码释义 var mouse = {}; mouse.x = (e.clientX / window.innerWidth) * 2 - 1; mouse.y = -(e.clientY / window.innerHeight) * 2 + 1; //新建一个三维单位向量 假设z方向就是0.5 //根据照相机,把这个向量转换到视点坐标系 var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5).unproject(camera); var sub = vector.sub(camera.position);= var param2 = sub.normalize(); var raycaster = new THREE.Raycaster(camera.position, param2); //射线和模型求交,选中一系列直线,getMeshs方法用于获得场景中可以被选中的所有Mesh var intersects = raycaster.intersectObjects(getMeshs()); if (intersects.length > 0) { // outlineMesh是物体边框组件,每次只会有一个物体被选中,就把这个东西定义为全局的 // 每次选中另一个物体之前先删除,相当于取消前一个物体的选中 scene.remove(outlineMesh); //选中第一个射线相交的物体 var SELECTED = intersects[0].object; var outlineMaterial2 = new THREE.MeshBasicMaterial({color: 0x00ff00, side: THREE.BackSide}); outlineMesh = new THREE.Mesh(SELECTED.geometry.clone(), outlineMaterial2); outlineMesh.position.set(SELECTED.position.x, SELECTED.position.y, SELECTED.position.z); outlineMesh.scale.multiplyScalar(1.05); scene.add(outlineMesh); } }