使用SVGRenderer(或以其他方式渲染球体)将图像渲染为Three.js中的节点 [英] Rendering an image as a node in Three.js with SVGRenderer (or otherwise rendering spheres)

查看:240
本文介绍了使用SVGRenderer(或以其他方式渲染球体)将图像渲染为Three.js中的节点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在SVG文档中有< circle> 元素,我应用了< radialGradient> 给出它是一个球体的错觉:

 < svg version =1.1id =sphere_svgxmlns = http://www.w3.org/2000/svgxmlns:xlink =http://www.w3.org/1999/xlinkx =0pxy =0pxwidth =640pxheight =640pxviewBox =0 0 640 640enable-background =new 0 0 640 640xml:space =preserve> 
< defs>
< radialGradient id =sphere_gradientcx =292.3262cy =287.4077r =249.2454fx =147.7949fy =274.5532gradientTransform =matrix(1.0729 0 0 1.0729 -23.3359 -23.3359 )gradientUnits =userSpaceOnUse>
< stop id =sphere_gradient_0offset =0style =stop-color:#F37D7F/>
< stop id =sphere_gradient_1offset =0.4847style =stop-color:#ED1F24/>
< stop id =sphere_gradient_2offset =1style =stop-color:#7E1416/>
< / radialGradient>
< / defs>
< circle fill =url(#sphere_gradient)cx =320cy =320r =320/>
< / svg>

它看起来像这样:





来自three.js作为实验的基础,我尝试了几种不同的技术并且没有太多运气。我希望有tri.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_pa​​rticle_count = 25;
for(var i = 0; i< red_pa​​rticle_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);
}

我的视图框中没有节点显示。



我接下来尝试的是一个 THREE.Sprite 对象,使用 canvg THREE.Texture 例程:

  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_pa​​rticle_count = 25;
for(var p = 0; p< red_pa​​rticle_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),
精灵。 position.z = 0.9 *(Math.random() - 0.5),
sprite.scale.set(0.1,0.1,0.1);
scene.add(sprite);
}

这稍微好一点,因为我得到的是白色不透明的盒子否则会出现在渲染的视图框中。



第三次尝试创建一个< svg> 来嵌套在父SVG节点内,其中包含一个可引用的< radialGradient> ,其id为 #sphere_gradient

  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_pa​​rticle_count = 25;
for(var i = 0; i< red_pa​​rticle_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 不要更改最终结果。



有趣的是,如果我从 url(#sphere_gradient)更改 fill 属性红色,我得到一个大圆圈,主要在我的视图框外渲染,它没有附加到场景中(它不会与我父母中的其他元素一起旋转)场景,就像一个立方体的两侧)。



是否有一种(工作和高效)方式使用<$在空间中绘制球体或圆形球状粒子c $ c> SVGRenderer in three.js?

解决方案

SVG中的大多数属性都在null中名称空间如此

  red_node.setAttributeNS('http://www.w3.org/2000/svg','height', '10'); 
red_node.setAttributeNS('http://www.w3.org/2000/svg','width','10');

正确写为

  red_node.setAttribute('height','10'); 
red_node.setAttribute('width','10');

FWIW

  red_node.setAttributeNS('http://www.w3.org/1999/xlink','href',red_png_data); 

理想情况下应写为

  red_node.setAttributeNS('http://www.w3.org/1999/xlink','xlink:href',red_png_data); 

虽然在这种情况下你的原始表格适用于大多数UAs。



在最后一个例子中,stop元素必须在SVG名称空间中,即

  var stop0 = document。 createElementNS(null,stop); 

应该是

  var stop0 = document.createElementNS(xmlns,stop); 

根本不绘制没有停止的渐变,这就是为什么在改变之前你什么都看不到的原因填充为红色。



SVG有一个画家模型。事物按照它们在文件中出现的顺序绘制。如果你想要其他东西,你需要将其放在文件中。


I have a <circle> element in an SVG document, to which I apply a <radialGradient> to give the illusion of it being a sphere:

<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>

It looks something like this:

JSFiddle

I can render this in a three.js WebGLRenderer container by using Gabe Lerner's canvg library:

/* 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);

So far, so good. I can even programmatically modify the gradient and render different categories of particles:

What I would like to do is now switch over from WebGLRenderer to using an SVGRenderer, so that I can allow the end user to set the desired orientation and then export a vector image (SVG, or converted to PDF on the back end) that can be used for publication-quality work.

Using the SVG sandbox example from three.js as the basis for experimentation, I have tried a couple different techniques and have not had much luck. I'm hoping someone with experience with three.js may have some suggestions.

My first attempt was to use canvg to render the SVG into a PNG image, and then apply that to an <image> node:

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);
}

No nodes show up in my viewbox.

The next thing I tried was a THREE.Sprite object, using canvg and THREE.Texture routines:

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);
}

This was slightly better, in that I get white, opaque boxes where the spheres would otherwise appear in the rendered viewbox.

A third attempt was made to create an <svg> to nest within the parent SVG node, which contains a reference-able <radialGradient> with the id #sphere_gradient:

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);
}

No nodes are rendered. Adjustments of the <circle> element's r, cx or cy do not change the end result.

Interestingly, if I change the fill attribute from url(#sphere_gradient) to red, I get a large circle mostly rendered outside my viewbox, which is not attached to the scene (it does not rotate with other elements in my parent scene, like the sides of a cube).

Is there a (working and performant) way to draw spheres or rounded, sphere-like particles in space using a SVGRenderer in three.js?

解决方案

Most attributes in SVG are in the null namespace so

red_node.setAttributeNS('http://www.w3.org/2000/svg', 'height', '10');
red_node.setAttributeNS('http://www.w3.org/2000/svg', 'width', '10');

is correctly written as

red_node.setAttribute('height', '10');
red_node.setAttribute('width', '10');

FWIW

red_node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', red_png_data);

should ideally be written as

red_node.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', red_png_data);

although in this case your original form will work in most UAs.

In the final example the stop elements must be in the SVG namespace i.e.

var stop0 = document.createElementNS(null, "stop");

should be

var stop0 = document.createElementNS(xmlns, "stop");

A gradient without stops is not drawn at all so that's why you don't see anything till you change the fill to red.

SVG has a painters model. Things are drawn in the order they occur in the file. If you want something to go on top of something else you need to place it later in the file.

这篇关于使用SVGRenderer(或以其他方式渲染球体)将图像渲染为Three.js中的节点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆