我在SVG文档中有一个<circle>
元素,我为其应用了一个<radialGradient>
来营造球体的假象:
<svg version="1.1" id="sphere_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="640px" height="640px" viewBox="0 0 640 640" enable-background="new 0 0 640 640" xml:space="preserve">
<defs>
<radialGradient id="sphere_gradient" cx="292.3262" cy="287.4077" r="249.2454" fx="147.7949" fy="274.5532" gradientTransform="matrix(1.0729 0 0 1.0729 -23.3359 -23.3359)" gradientUnits="userSpaceOnUse">
<stop id="sphere_gradient_0" offset="0" style="stop-color:#F37D7F"/>
<stop id="sphere_gradient_1" offset="0.4847" style="stop-color:#ED1F24"/>
<stop id="sphere_gradient_2" offset="1" style="stop-color:#7E1416"/>
</radialGradient>
</defs>
<circle fill="url(#sphere_gradient)" cx="320" cy="320" r="320"/>
</svg>
它看起来像这样:
我可以使用Gabe Lerner的canvg
库在三.js WebGLRenderer
容器中呈现它:
/* sphere_asset is a div containing the svg element */
var red_svg_html = new String($('#sphere_asset').html());
var red_svg_canvas = document.createElement("canvas");
canvg(red_svg_canvas, red_svg_html);
var red_svg_texture = new THREE.Texture(red_svg_canvas);
var red_particles = new THREE.Geometry();
var red_particle_material = new THREE.PointCloudMaterial({
map: red_svg_texture,
transparent: true,
size: 0.15,
alphaTest: 0.10
});
var red_particle_count = 25;
for (var p = 0; p < red_particle_count; p++) {
var pX = 0.9 * (Math.random() - 0.5),
pY = 0.9 * (Math.random() - 0.5),
pZ = 0.9 * (Math.random() - 0.5),
red_particle = new THREE.Vector3(pX, pY, pZ);
red_particles.vertices.push(red_particle);
}
var red_particle_system = new THREE.PointCloud(red_particles, red_particle_material);
scene.add(red_particle_system);
目前为止,一切都很好。我甚至可以以编程方式修改渐变并呈现不同类别的粒子:
现在我想要做的是从WebGLRenderer
切换到使用SVGRenderer
,这样我就可以允许最终用户设置所需的方向,然后导出矢量图像(SVG或在后端转换为PDF),可用于出版质量的工作。
使用three.js的SVG sandbox示例作为实验基础,我尝试了几种不同的技术,但没有取得太大的成功。我希望有经验的three.js用户能提供一些建议。
我的第一次尝试是使用canvg
将SVG渲染成PNG图像,然后应用于一个<image>
节点:
var red_svg_html = new String($('#sphere_asset').html());
var red_svg_canvas = document.createElement("canvas");
canvg(red_svg_canvas, red_svg_html);
var red_png_data = red_svg_canvas.toDataURL('image/png');
var red_node = document.createElementNS('http://www.w3.org/2000/svg', 'image');
red_node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', red_png_data);
red_node.setAttributeNS('http://www.w3.org/2000/svg', 'height', '10');
red_node.setAttributeNS('http://www.w3.org/2000/svg', 'width', '10');
var red_particle_count = 25;
for (var i = 0; i < red_particle_count; i++) {
var object = new THREE.SVGObject(red_node.cloneNode());
object.position.x = 0.9 * (Math.random() - 0.5);
object.position.y = 0.9 * (Math.random() - 0.5);
object.position.z = 0.9 * (Math.random() - 0.5);
scene.add(object);
}
我的视窗中没有显示任何节点。
接下来我尝试使用canvg
和THREE.Texture
例程创建 THREE.Sprite
对象:
var red_svg_html = new String($('#sphere_asset').html());
var red_svg_canvas = document.createElement("canvas");
canvg(red_svg_canvas, red_svg_html);
var red_svg_texture = new THREE.Texture(red_svg_canvas);
red_svg_texture.needsUpdate = true;
var red_sprite = THREE.ImageUtils.loadTexture(red_png_data);
var red_particle_count = 25;
for (var p = 0; p < red_particle_count; p++) {
var material = new THREE.SpriteMaterial( {
map: red_svg_texture,
transparent: true,
size: 0.15,
alphaTest: 0.10
});
var sprite = new THREE.Sprite( material );
sprite.position.x = 0.9 * (Math.random() - 0.5),
sprite.position.y = 0.9 * (Math.random() - 0.5),
sprite.position.z = 0.9 * (Math.random() - 0.5),
sprite.scale.set(0.1, 0.1, 0.1);
scene.add(sprite);
}
这次稍微好一些,因为在渲染视图框中,球体所在的位置出现了白色不透明的方框。
第三次尝试创建一个<svg>
嵌套在父级SVG节点中,其中包含可引用的id为#sphere_gradient
的<radialGradient>
:
var xmlns = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(xmlns, 'svg');
svg.setAttributeNS(null, 'version', '1.1');
svg.setAttributeNS(null, 'x', '0px');
svg.setAttributeNS(null, 'y', '0px');
svg.setAttributeNS(null, 'width', '640px');
svg.setAttributeNS(null, 'height', '640px');
svg.setAttributeNS(null, 'viewBox', '0 0 640 640');
svg.setAttributeNS(null, 'enable-background', 'new 0 0 640 640');
var defs = document.createElementNS(xmlns, "defs");
var radialGradient = document.createElementNS(xmlns, "radialGradient");
radialGradient.setAttributeNS(null, "id", "sphere_gradient");
radialGradient.setAttributeNS(null, "cx", "292.3262");
radialGradient.setAttributeNS(null, "cy", "287.4077");
radialGradient.setAttributeNS(null, "r", "249.2454");
radialGradient.setAttributeNS(null, "fx", "147.7949");
radialGradient.setAttributeNS(null, "fy", "274.5532");
radialGradient.setAttributeNS(null, "gradientTransform", "matrix(1.0729 0 0 1.0729 -23.3359 -23.3359)");
radialGradient.setAttributeNS(null, "gradientUnits", "userSpaceOnUse");
var stop0 = document.createElementNS(null, "stop");
stop0.setAttributeNS(null, "offset", "0");
stop0.setAttributeNS(null, "stop-color", "#f37d7f");
radialGradient.appendChild(stop0);
var stop1 = document.createElementNS(null, "stop");
stop1.setAttributeNS(null, "offset", "0.4847");
stop1.setAttributeNS(null, "stop-color", "#ed1f24");
radialGradient.appendChild(stop1);
var stop2 = document.createElementNS(null, "stop");
stop2.setAttributeNS(null, "offset", "1");
stop2.setAttributeNS(null, "stop-color", "#7e1416");
radialGradient.appendChild(stop2);
defs.appendChild(radialGradient);
svg.appendChild(defs);
var red_circle = document.createElementNS(xmlns, "circle")
red_circle.setAttribute('fill', 'url(#sphere_gradient)');
red_circle.setAttribute('r', '320');
red_circle.setAttribute('cx', '320');
red_circle.setAttribute('cy', '320');
svg.appendChild(red_circle);
var red_particle_count = 25;
for (var i = 0; i < red_particle_count; i++) {
var object = new THREE.SVGObject(svg.cloneNode(true));
object.position.x = 0.85 * (Math.random() - 0.5);
object.position.y = 0.85 * (Math.random() - 0.5);
object.position.z = 0.85 * (Math.random() - 0.5);
scene.add(object);
}
没有任何节点被渲染出来。调整<circle>
元素的r
、cx
或cy
属性并不会改变最终结果。
有趣的是,如果我将fill
属性从url(#sphere_gradient)
更改为red
,我会得到一个大圆,大部分渲染在我的视口之外,它与场景没有连接(它不会像立方体的侧面那样随着父场景中的其他元素旋转)。
是否有一种(工作且性能良好的)方法可以使用three.js中的SVGRenderer
绘制空间中的球形或圆角的类球状粒子?
href
属性修改为xlink:href
并没有改变最终结果(即,仍然没有渲染任何节点)。 - Alex Reynolds<svg>
元素的转换没有被计算到场景中,所以其中包含的<circle>
元素保持固定在其位置,即使对其父级<svg>
应用了转换。 - Alex Reynolds<defs>
组移动到SVGRenderer
代码中,使其成为根级别的<svg>
的一部分,但是我遇到了渲染问题,其中<circle>
元素没有按顺序绘制,这破坏了深度感的错觉。具体来说,靠近相机的<circle>
元素首先被绘制,然后才是离相机更远的另一个元素。结果是,靠近相机的元素(应该在前景中)被渲染在更远的元素下面。旋转场景会破坏深度感的错觉。 - Alex Reynolds