从接触 WebGL,到入门 three.js,到各种修修改改,到活动上线,前后耗费了14个工作日。而后耗时3天又上了一个版本,整个十二月也就过去了。如果以后项目中没有用到这货估计也不会再接触了吧,安心搞自己乱七八糟的东西比较开心,写点东西纪念一下咯。
WebGL 是什么
WebGL 是一种 3D 绘图标准,允许把 JavaScript 和 OpenGL ES 2.0 结合在一起,为 Canvas 提供硬件3D加速渲染。一句话概括,通过 WebGL,可以在页端展示3D场景
three.js 是什么
three.js 是一个基于 Javascript 能够简化 WebGL 开发的库。通过 three.js 可以极大地提高开发效率,同时降低了开发门槛,就算不熟悉图形学也可以通过 three.js 愉快地玩耍了
如何愉快地玩耍
暂时忘记 WebGL 那些复杂的概念,基于 three.js,直接抛个简单的示例 jsbin
scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000); camera.position.z = 1000; geometry = new THREE.BoxGeometry(200, 200, 200); material = new THREE.MeshLambertMaterial({ color: 0x66ccff }); mesh = new THREE.Mesh(geometry, material); scene.add(mesh); light = new THREE.DirectionalLight(0xffffff, 0.8); light.position.set(0, 0, 300); scene.add(light); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x333333); document.body.appendChild(renderer.domElement);
|
代码不多,可以看到的东西是创建了一个立方体,添加了光照,还有阴影。简单粗暴的文档如下,直接看文档方便一些
然后单独说一下模型,代码如下:
geometry = new THREE.BoxGeometry(200, 200, 200); material = new THREE.MeshLambertMaterial({ color: 0x66ccff }); mesh = new THREE.Mesh(geometry, material); scene.add(mesh);
|
geometry
即几何结构,包含了能够描述物体的三维模型的所有数据,具体到所有顶点坐标。这里使用的是 BoxGeometry,并提供了长宽高三个参数,three.js 会将其转换为顶点信息并提供给着色器,可在 geometry.vertices
看到
material
即材料,描述了物体的外表,具体到颜色、贴图、对光线的反射效果等。这里使用的是 MeshLambertMaterial,即兰伯特材质,表面能对光线产生均匀散射,继承自 Material,并提供了 color
参数。其他还可以提供纹理、透明度、叠加效果等参数,这里不具体巴拉
mesh
即网格模型,糅合了几何机构和材料在一起。将模型添加到已经创建的场景 scene
里面,摆好相机位置,渲染器 renderer
渲染场景,则可以在画布中看到添加的物体了。可以修改网格模型的坐标 position
、旋转 rotation
等参数
以上,其他有什么想法可以直接到官网找示例看源码如何实现。官方文档有部分对象没有提到,谷歌就是了。
坐标系
一张图片就够了吧,右手坐标系
踩过的坑
1. 移动端画面模糊,锯齿严重
移动端画面模糊,锯齿严重。跟着示例源码加了这样一句,发现没用。
renderer.setPixelRatio(window.devicePixelRatio);
|
继续查资料发现是 viewport
的问题。做这样的修改可以保证 Android 和 iOS 下显示清晰
<meta name="viewport" content="initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no" />
|
然后在锤子手机上又发现 viewport 出了问题,于是又做了这种奇怪的修改,保证了锤子手机能正确渲染。具体关于 viewport 之后再整理一下
<meta name="viewport" content="initial-scale=1,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no" />
|
2. 着色器控制
发现画面间歇性出现绘制异常,然后定位到自己写的着色器,有这部分代码
attribute float size; attribute float idx; uniform float timeline; ... void main() { ... if(length(mvPosition.xyz) < 400.0) { enlarge = (sin(timeline - idx) * 40.0); } else { enlarge = (sin(timeline - idx) * 10.0); } ... gl_Position = projectionMatrix * mvPosition; gl_PointSize = 1500.0 / length(mvPosition.xyz) * size + enlarge; }
|
在计算中,enlarge
可能是负值,导致最终计算的 gl_PointSize
也可能是负值,然后导致了绘制的点直接呈现在相机前。加个判断修正就可以啦
if(enlarge < 0.1) { enlarge = .0 - enlarge + 1.0; }
|
3. 兼容问题
第一次尝试 WebGL,用身边有限的测试机子跑了一遍感觉兼容性出乎意料的好,为了兼容到旧的 Android 手机,这里使用 three.js r71 版本。可是在项目临近上线前发现兼容问题远比预想的严重。从线上返回的数据看,有接近三分之一的用户手机或浏览器因为不支持 WebGL 或者对其兼容有问题,而访问到降级页面。如果有打算将 WebGL 运用到项目里的话,需要在一开始关注如何平稳退化
压缩构建 three.js
想在移动端上面使用 three.js 的话,重新构建打包剔除不必要的代码是必须的。在 github 上的源码也提供了重新构建的方法,各个平台都考虑到了的样子,然后使用 node 构建的话主要参数如下:
··· var parser = new argparse.ArgumentParser(); parser.addArgument( ['--include'], { action: 'append', required: true } ); parser.addArgument( ['--externs'], { action: 'append', defaultValue: ['./externs/common.js'] } ); parser.addArgument( ['--amd'], { action: 'storeTrue', defaultValue: false } ); parser.addArgument( ['--minify'], { action: 'storeTrue', defaultValue: false } ); parser.addArgument( ['--output'], { defaultValue: '../../build/three.js' } ); parser.addArgument( ['--sourcemaps'], { action: 'storeTrue', defaultValue: true } ); ···
|
可以根据需要调整 utils/build/includes
中的文件列表,减少打包的文件,然后重新构建就可以了。common
中,可以剔除的文件大概有没用到的 light
,loaders
,还有 shaders
的部分文件,其他经常会报错,然后 extras
就直接保留使用到的功能就行了,根据项目实际需要吧
node build.js --include common --include extras --amd --minify
|
几何函数
简单的速度曲线变化用几何函数去处理路线就可以了,当然处理的结果有时会比较生硬,可以去 tween.js 扒一些东西下来。这里列几个简单的几何函数
EasingFunctions = { linear: function (t) { return t }, easeInQuad: function (t) { return t*t }, easeOutQuad: function (t) { return t*(2-t) }, easeInOutQuad: function (t) { return t<.5 ? 2*t*t : -1+(4-2*t)*t }, easeInCubic: function (t) { return t*t*t }, easeOutCubic: function (t) { return (--t)*t*t+1 }, easeInOutCubic: function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }, easeInQuart: function (t) { return t*t*t*t }, easeOutQuart: function (t) { return 1-(--t)*t*t*t }, easeInOutQuart: function (t) { return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t }, easeInQuint: function (t) { return t*t*t*t*t }, easeOutQuint: function (t) { return 1+(--t)*t*t*t*t }, easeInOutQuint: function (t) { return t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t } }
|
参考资料
学习过程中帮助很大的一些网站,大大的感谢~
http://webglfundamentals.org/
http://www.cnblogs.com/yiyezhai/category/446753.html
http://wiki.jikexueyuan.com/project/webgl/
https://oncemore2020.github.io/blog/homogeneous/
http://csgrandeur.github.io/
http://threejs.org/