通过Web3D|基于WebGL的Three.js框架|入门篇和Web3D|基于WebGL的Three.js框架|Mesh基础篇等一系列的文章,我们已经了解了用Three.js开发3D的基础知识,那么你有没有想过
怎么将一张SVG的矢量图拉伸,显示成3D效果呢?
探索
还记得Web3D|基于WebGL的Three.js框架|Mesh基础篇中最后提到的ExtrudeGeometry和ExtrudeBufferGeometry吗?没错,我们就是需要用它。
知道了核心点之后,我们需要理清楚,怎么将SVG中的元素,一步一步的转成Three中的元素。
- 首先,SVG是一个标准的XML文件,所以我们可以很容易地解析其中的元素。
- 其次,我们需要用到Three中专门用来定制2D图形的一些元素,ShapePath和Shape。也就是说,我们需要将SVG的元素,转成Three中的ShapePath,然后再切成Shape。
- 最后,我们可以通过ExtrudeGeometry或ExtrudeBufferGeometry将我们的2D图形拉伸成3D图形。
具体步骤
一,加载SVG文件,并转成ShapePath
加载SVG并转成ShapePath,可以用到THREE.SVGLoader来实现:
var loader = new THREE.SVGLoader(); loader.load( SVG_URL, //SVG文件的URL function (shapePaths) { // 处理加载的ShapePath,转Shape,创建Mesh等。 }, // 加载进度 function (xhr) { console.log((xhr.loaded / xhr.total * 100) + '% loaded'); }, // 异常处理 function (error) { console.log('An error happened' + error); } );
SVGLoader做了哪些事情:
- 遍历SVG DOM。
- 识别的SVG标签:path,rect,polygon,polyline,circle,ellipse和line。 SVG标签加载限制:首先会从各个节点中读取fill值,只有fill值不为空或不透明才会加载。
function isVisible( style ) { return style.fill !== 'none' && style.fill !== 'transparent'; }
- 只有这些标签可识别,并且可见,SVGLoader才会创建完全对应的ShapePath。
重要的一点:由于这个SVGLoader只能读取标准的SVG文件。 通常情况下,在Adobe Illustrator中创建的SVG文件不能直接被加载,需要做一下转换。File -> Export -> Export As… -> 在打开的对话框中选择SVG格式 -> Export。
二,创建ExtrudeBufferGeometry
通过THREE.SVGLoader加载SVG格式的图片,会自动将一些标准格式的SVG图形转成ShapePath。接下来就要将这些ShapePath转成Three的Shape然后加到Scene中显示。
1. ShapePath转Shape
var shapes = path.toShapes(true, false);
2. 拉伸Shape,创建ExtrudeBufferGeometry
var bufferGeometry = new THREE.ExtrudeBufferGeometry(shapes, {depth: depth});
注意这里的参数depth就是你要拉伸的值,也就是Z轴方向的高度。
3. 通过ShapePath中加载的SVG图形的颜色创建Material
var material = new THREE.MeshBasicMaterial({color: color});
简单情况下,我们可以创建一个基础的Material去加载SVG中的颜色属性。
4. 创建Mesh加入Scene中显示
var mesh = new THREE.Mesh(geometry, material);
三,一些小技巧以及特殊处理
1. 文本处理
文本处理在3D中特别麻烦,尤其是中文的处理。虽然在设计SVG的时候可以将文本转成Path,这些在浏览器中显示不会有什么问题。但是在Three中,万一一个文本所对应的path生成的Shape中,有一些的depth(拉伸)值设置的不一样,文本就会显示成很诡异的样子。 所以可以建议将所有的文本值过滤掉。参考使用Sprite去实现。
2. 拉伸高度
SVG中有些图会默认先在底层创建一个rect,然后将所有的图再画在这个rect上。就像我们的室内地图的地板一样。 在做拉伸的时候,必须要用很小的depth值去渲染这些大面积的图形,或者直接过滤掉。
3. Shape合并处理
- 一个ShapePath通常可以生成多个Shape,我们可以遍历这个Shape数组,将Shape一个一个创建ExtrudeBufferGeometry和Mesh加入到Scene中,也可以将这些所有的Shape作为一个整体去创建ExtrudeBufferGeometry和Mesh加入到Scene中。当然了,创建的Mesh越多,WebGL渲染的性能就越差。为了提高性能,通常将一个ShapePath对应的所有的Shape创建成一个ExtrudeBufferGeometry去实现。
- 通过Geometry类可以取到BBox,进而取得center和size值。
4. 中心点设置
- Scene加入的所有图形,其位置(positions)默认都是在(0,0,0)点,也就是Renderer的中心。
- 我们通常会使用地板(或SVG中最底层能包含所有图形的那个图形)来确定移动的多少。详细请参考下一小节。
5. 和原始的SVG图片方向保持一致。
由于SVG中的坐标点事基于计算机坐标系统(自上而下,从左往右)的原则,而3D中(Three也是一样)使用的是前面提到过的右手坐标系统,由此可见,X轴基本一致,但Y轴是完全相反的。所以首先要做的,就是把X轴旋转180°。
将X轴旋转180°,使得Y坐标的值能够保持一致。
mesh.rotation.x = Math.PI;
由于我们将X轴旋转了180°,导致Z坐标的值反了过去,Z坐标的值,也就是我们设定的拉伸值(depth),所以我们需要将Z坐标的值向相反的方向拉回来。
mesh.translateZ(-depth - 1);
移动X坐标的值使得整个SVG渲染图到原点。
mesh.translateX(offsetX);
移动Y坐标的值使得整个SVG渲染图到原点。
mesh.translateY(offsetY);
计算X轴和Y轴的偏移量。 X轴和Y轴的偏移量,可以通过原始SVG图片的ViewBox来确定,但这种方式作出来不太精确。我暂时使用的方法是遍历所有的ShapePath(这些ShapePath是SVGLoader中加载得来的)中的Shape,计算出一个最大的能包含所有图形的矩形区域,然后再进行X轴和Y轴的位移。
6. 使用Group来批量设置
var box = new THREE.Box3(); box.setFromObject(group); var center = box.getCenter(); for(var i=0;i<group.children.length;i++) { group.children[i].rotation.z = Math.PI; group.children[i].rotation.y = Math.PI; group.children[i].position.x = -center.x; group.children[i].position.y = center.y; }
因为所有的模型都加到了一个Group中,我们可以通过Group的中心点来设置每一个模型的旋转和位移,但这段代码还不太完善。需要将所有图形的depth通过Z轴拉回来。
7. 通过Sprite来添加浮标或装饰
var iconSprite = new THREE.Sprite(new THREE.SpriteMaterial({map: new THREE.TextureLoader().load(path)})); iconSprite.position.copy(intersected.point); iconSprite.positionZ = iconSprite.positionZ + 150; iconSprite.scale.x = iconSprite.scale.y = 20;//100; scene.add(iconSprite);
- Path是用来在Sprite上显示的图片的路径。
- 位置设置中interescted是通过raycaster找到的鼠标点击时的模型。
- 其它的设置和Geometry Mesh的设置一样。
总结
- SVG虽说是标准的XML,但也有很多自定义的格式在里面,如果有解析不到的元素,或需要扩展属性,我们需要去重写SVGLoader实现。
- SVG中可能会有一些元素通过图片是看不到的,或是重叠的,我们在加载的时候,最好是能够过滤掉一些没有必要的东西。
- SVG在设计的时候,中文为了能够正确的显示字体,一般都会转成path,这在通过Three拉伸的时候效果会很差,建议过滤掉,然后用Sprite来显示。
- 具体开发中,一定要注意SVG的坐标和Three的坐标转换,更多的信息可以参考我的系列文章:Web3D|基于WebGL的Three.js框架|坐标转换篇
本文暂时没有评论,来添加一个吧(●'◡'●)