经过2个多月的紧张开发,SpriteJS的3D扩展终于完工啦。
SpriteJS的渲染能力终于从2D进入3D领域
那么SpriteJS究竟和现在流行的一些3D渲染库,比如ThreeJS、Babylon.js、claygl等库相比,用起来又有什么不一样的地方呢?
SpriteJS的3D扩展的底层是基于Ogl的,这是一个比较轻量级的库,它的作者给它的定位是:
OGL is a small, effective WebGL library aimed at developers who like minimal layers of abstraction, and are comfortable creating their own shaders.
这样的库非常适合SpriteJS的二次封装,打包出来的代码足够小。
SpriteJS的3D扩展延续SpriteJS 2D几乎一样的API,而这样的API与HTML DOM的API的契合度很高。这也意味着,使用d3等对DOM API友好的库操作SpriteJS的3D元素会非常方便!另外前端工程师学习和使用上,上手门槛比较低,掌握之后也容易高效率地开发。
const {Scene} = spritejs; const {Cube, shaders} = spritejs.ext3d; const container = document.getElementById('container'); const scene = new Scene({container}); const layer = scene.layer3d('fglayer', { camera: { fov: 35, // Field of view }, }); layer.camera.attributes.pos = [3, 3, 5]; const program = layer.createProgram({ ...shaders.NORMAL_GEOMETRY, cullFace: null, }); const cube = new Cube(program, { colors: 'red red blue blue green green', }); layer.append(cube); layer.setOrbit(); // 开启旋转控制
上面很简单的代码创建了一个可用鼠标和触屏交互的立方体:
熟悉SpriteJS的2D API的同学一定会发现这个代码几乎和2D的没有什么区别,只不过把layer换成了layer3d,而且创建3D元素的时候要传入对应的program对象而已。
SpriteJS提供了内置的一些shader供一般的应用使用,从而让我们能快速创建3D物体,不用纠结于细节。
SpriteJS还内置了一些几何体元素,包括:
Plane 平面元素
Cube 立方体
Cylinder 圆柱体
Sphere 球体
这些几何元素都有自己的属性,我们可以像操作2D的Sprite元素一样给它们赋属性值或改变他们的属性值。
另外SpriteJS还提供了这些几何体的基类Mesh3d对象,给这个对象传一组顶点数据进去即可创建和渲染我们想要绘制的任何几何体。
const {Scene} = spritejs; const {Mesh3d, shaders} = spritejs.ext3d; const container = document.getElementById('container'); const scene = new Scene({ container, width: 600, height: 600, }); const layer = scene.layer3d('fglayer', { camera: { fov: 35, z: 5, }, }); const program = layer.createProgram({ ...shaders.NORMAL_GEOMETRY, cullFace: null, }); const p = 1 / Math.sqrt(2); const model = { position: [ -1, 0, -p, 1, 0, -p, 0, 1, p, -1, 0, -p, 1, 0, -p, 0, -1, p, 1, 0, -p, 0, -1, p, 0, 1, p, -1, 0, -p, 0, 1, p, 0, -1, p], }; const sprite = new Mesh3d(program, { model, colors: 'red blue green orange', }); layer.append(sprite); sprite.animate([ {rotateY: 0}, {rotateY: 360}, ], { duration: 7000, iterations: Infinity, }); sprite.animate([ {rotateZ: 0}, {rotateZ: 360}, ], { duration: 17000, iterations: Infinity, }); sprite.animate([ {rotateX: 0}, {rotateX: -360}, ], { duration: 11000, iterations: Infinity, }); layer.setOrbit();
与其他3D库类似,SpriteJS支持光照、阴影等效果,而且用起来更简单!
/* globals dat */ const {Scene, Color} = spritejs; const {Cube, shaders} = spritejs.ext3d; const container = document.getElementById('container'); const scene = new Scene({container}); const layer = scene.layer3d('fglayer', { ambientColor: '#ff000080', directionalLight: [1, 0, 0, 0.5], pointLightColor: 'blue', pointLightPosition: [5, 3, 6], camera: { fov: 35, // 相机的视野 pos: [3, 3, 5], // 相机的位置 }, }); const camera = layer.camera; camera.lookAt([0, 0, 0]); const program = layer.createProgram({ ...shaders.NORMAL_GEOMETRY, cullFace: null, }); const cube = new Cube(program, { colors: 'grey', }); layer.append(cube); layer.setOrbit();
SpriteJS支持环境光、方向光和点光源。
SpriteJS也支持方便地绘制阴影效果:
const {Scene} = spritejs; const {Camera, Mesh3d, Plane, shaders} = spritejs.ext3d; const container = document.getElementById('container'); const scene = new Scene({ container, displayRatio: 2, }); const layer = scene.layer3d('fglayer', { camera: { fov: 35, }, }); layer.camera.attributes.pos = [5, 4, 10]; layer.setOrbit(); const light = new Camera(layer.gl, { left: -3, right: 3, bottom: -3, top: 3, near: 1, far: 20, }); light.attributes.pos = [3, 10, 3]; light.lookAt([0, 0, 0]); const shadow = layer.createShadow({light}); const texture = layer.createTexture('https://p2.ssl.qhimg.com/t01155feb9a795bdd05.jpg'); const model = layer.loadModel('https://s0.ssl.qhres.com/static/0baccc5ad3cd5b8c.json'); const program = layer.createProgram({ ...shaders.TEXTURE_WITH_SHADOW, cullFace: null, texture, }); const plane = new Mesh3d(program, {model}); window.plane = plane; layer.append(plane); const waterTexture = layer.createTexture('https://p0.ssl.qhimg.com/t01db936e50ab52f10a.jpg'); const program2 = layer.createProgram({ ...shaders.TEXTURE_WITH_SHADOW, cullFace: null, texture: waterTexture, }); const ground = new Plane(program2, { rotateX: 90, scale: 6, y: -3, }); layer.append(ground); shadow.add(plane); shadow.add(ground); layer.setShadow(shadow); layer.tick((t) => { // A bit of plane animation if(plane) { plane.attributes.z = Math.sin(t * 0.001); plane.attributes.rotateX = Math.sin(t * 0.001 + 2) * 18; plane.attributes.rotateY = Math.sin(t * 0.001 - 4) * -18; } });
我们可以使用与ThreeJS兼容的模型数据渲染各种3D模型,甚至绘制视频。
const {Scene} = spritejs; const {Mesh3d, Cube, shaders} = spritejs.ext3d; const container = document.getElementById('container'); const scene = new Scene({ container, displayRatio: 2, }); const layer = scene.layer3d('fglayer', { camera: { fov: 45, }, }); layer.camera.attributes.pos = [3, 1.5, 4]; layer.camera.lookAt([1, 0.2, 0]); const texture = layer.createTexture('https://p5.ssl.qhimg.com/t0110e2451e92de6193.jpg'); const program = layer.createProgram({ ...shaders.NORMAL_TEXTURE, texture, }); const model = layer.loadModel('https://s5.ssl.qhres.com/static/f545f86e6da07b9d.json'); const mesh = new Mesh3d(program, {model}); layer.append(mesh); const videoTexture = layer.createTexture({ generateMipmaps: false, width: 1024, height: 512, }); const video = document.createElement('video'); video.crossOrigin = 'anonymous'; video.src = 'https://s4.ssl.qhres.com/static/a2fa8e8634dd1ccb.mp4'; video.loop = true; video.muted = true; video.play(); const videoProgram = layer.createProgram({ ...shaders.NORMAL_TEXTURE, texture: videoTexture, cullFace: null, }); const videoMesh = new Cube(videoProgram, { width: 1.78, height: 1, depth: 1.78, }); videoMesh.attributes.pos = [0, 0.5, -4]; videoMesh.attributes.scale = 1.5; layer.append(videoMesh); layer.tick((t) => { // Attach video and/or update texture when video is ready if(video.readyState >= video.HAVE_ENOUGH_DATA) { if(!videoTexture.image) videoTexture.image = video; videoTexture.needsUpdate = true; } if(mesh) mesh.attributes.rotateY -= 0.3; videoMesh.attributes.rotateY += 0.2; });
还可以随便玩全景地图:
const {Scene} = spritejs; const {Sphere, shaders} = spritejs.ext3d; const container = document.getElementById('container'); const scene = new Scene({ container, displayRatio: 2, }); const layer = scene.layer3d('fglayer', { camera: { fov: 45, }, }); layer.camera.attributes.pos = [0, 0, 8]; const texture = layer.createTexture('https://p5.ssl.qhimg.com/t01e4a8428b9cc12bca.jpg'); const program = layer.createProgram({ ...shaders.GEOMETRY_WITH_TEXTURE, texture, // Need inside of sphere to be visible cullFace: null, }); const sphere = new Sphere(program, {radius: 1, widthSegments: 64}); layer.append(sphere); const skyBox = sphere.cloneNode(); skyBox.attributes.scale = 10; layer.append(skyBox); layer.setOrbit();
在线演示spritejs.org
SpriteJS 3D扩展还有RenderTarget、Post(后期处理通道)等黑科技,可以玩一些非常有意思的效果:
性能方面,SpriteJS的3D扩展也是很优秀的,渲染数千元素直到密集物体恐惧症,帧率轻松达到60fps毫无压力。
更何况还有GPGPU这样的黑科技,甚至能渲染数万至数十万的粒子:
只要能想到,没有做不到。只要有创意用SpriteJS的3D扩展可以愉快地写出各种惊艳的效果来~
关于奇舞周刊
《奇舞周刊》是360公司专业前端团队「奇舞团」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。