SpriteJS的3D渲染能力 Up, Up, Up!
前言
本期由@月影授权分享。
正文从这开始~~
SpriteJS 是一款由360奇舞团开源的跨终端 canvas 绘图框架,可以基于 canvas 快速绘制结构化 UI、动画和交互效果,并发布到任何拥有canvas环境的平台上(比如浏览器、小程序和node)。
经过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(); // 开启旋转控制
上面很简单的代码创建了一个可用鼠标和触屏交互的立方体:
在线demo:https://spritejs.org/demo/#/3d/basic
熟悉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();
在线demo:https://spritejs.org/demo/#/3d/geometry2
与其他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();
在线demo:https://spritejs.org/demo/#/3d/light
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;
}
});
在线demo:https://spritejs.org/demo/#/3d/shadow
我们可以使用与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;
});
在线demo:https://spritejs.org/demo/#/3d/video
还可以随便玩全景地图:
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();
在线demo:https://spritejs.org/demo/#/3d/skydome
SpriteJS 3D扩展还有RenderTarget、Post(后期处理通道)等黑科技,可以玩一些非常有意思的效果:
性能方面,SpriteJS的3D扩展也是很优秀的,渲染数千元素直到密集物体恐惧症,帧率轻松达到60fps毫无压力。
在线demo:https://spritejs.org/demo/#/benchmark/meshes3d
更何况还有GPGPU这样的黑科技,甚至能渲染数万至数十万的粒子:
只要能想到,没有做不到。只要有创意用SpriteJS的3D扩展可以愉快地写出各种惊艳的效果来~
项目:https://spritejs.org/#/
关于本文 作者:@月影 原文:https://zhuanlan.zhihu.com/p/103253115