【问题标题】:Rendering an image as a node in Three.js with SVGRenderer (or otherwise rendering spheres)使用 SVGRenderer(或其他渲染球体)在 Three.js 中将图像渲染为节点
【发布时间】:2015-02-22 21:19:45
【问题描述】:

我在一个 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>

看起来像这样:

JSFiddle

我可以使用Gabe Lerner's canvg library 在三个.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 example 作为实验的基础,我尝试了几种不同的技术,但运气不佳。希望有过three.js经验的朋友给点建议。

我的第一次尝试是使用 canvg 将 SVG 渲染为 PNG 图像,然后将其应用于 &lt;image&gt; 节点:

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

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

接下来我尝试的是THREE.Sprite 对象,使用canvgTHREE.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_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);
}

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

第三次尝试创建一个 &lt;svg&gt; 嵌套在父 SVG 节点中,其中包含一个可引用的 &lt;radialGradient&gt;,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);
}

没有节点被渲染。调整&lt;circle&gt; 元素的rcxcy 不会改变最终结果。

有趣的是,如果我将fill 属性从url(#sphere_gradient) 更改为red,我会得到一个大圆圈,大部分呈现在我的视图框之外,它没有附加到场景中(它不会与我的其他元素一起旋转)父场景,例如立方体的侧面)。

在three.js 中使用SVGRenderer 在空间中绘制球体或圆形、类似球体的粒子是否有(有效且高效的)方法?

【问题讨论】:

    标签: javascript svg three.js webgl


    【解决方案1】:

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

    虽然在这种情况下,您的原始表单可以在大多数 UA 中使用。

    在最后一个示例中,停止元素必须位于 SVG 命名空间中,即

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

    应该是

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

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

    SVG 有一个画家模型。事物是按照它们在文件中出现的顺序绘制的。如果您希望某些内容放在其他内容之上,则需要稍后将其放置在文件中。

    【讨论】:

    • 感谢您的建议。将href 属性修改为xlink:href 并没有改变最终结果(,仍然没有渲染任何节点)。
    • 好的,谢谢 — 命名空间更改有助于现在应用渐变!但是,看起来应用于此嵌套&lt;svg&gt; 元素的变换并未计算到场景中,因此包含在其中的&lt;circle&gt; 元素保持固定在其位置,即使将变换应用于其父&lt;svg&gt; 也是如此。
    • 我已经成功地将&lt;defs&gt; 组移动到SVGRenderer 代码中,使其成为最根&lt;svg&gt; 的一部分,但是我遇到了&lt;circle&gt; 元素所在的渲染问题没有“按顺序”绘制,这打破了深度的错觉。具体来说,首先绘制一个靠近相机的&lt;circle&gt; 元素,然后再绘制另一个远离相机的元素。结果是更靠近相机的元素(并且应该在前景中)被渲染在更远的元素下方。旋转场景打破了深度错觉。
    【解决方案2】:

    在您的第一次尝试中,如果您添加 snap svg 库,您可以从 canvg 获取 svg 元素并将其包装到 snap 结构中(作为 snap 的纸张),然后使用 paper.toString()为您的 svg 文件生成 svg 代码的方法。

    var paper = Snap("#... ");        //this wraps the svg element from the canvg onto snap structure 
    console.log( paper.toString());   // this gives you the svg code!
    

    编辑:或者您可以使用 fabric.js 将画布转换为 svg:

    var svg = canvas.toSVG();
    

    或者你可以使用 canvas2svg 库。

    【讨论】:

    • 我已经有了 SVG。我会再次从 SVG 转换为 PNG 到 SVG 吗?
    • 您已经有了初始的 svg ,而不是更新后的。我认为可以帮助您的步骤如下:您有一个 svg 然后将其添加到画布以添加额外内容,然后从画布您需要获取更新的 svg。您的最后一部分无法获取更新的 svg。这不是您想要简化流程的原因吗?!
    • 我不清楚你所说的“更新”是什么意思。 SVG 是独立的;我已经将它作为字符串引入:var red_svg_html = new String($('#sphere_asset').html());
    • BY updated svg 我的意思是您希望您的用户导出的 svg。”我现在想做的是从 WebGLRenderer 切换到使用 SVGRenderer,这样我就可以允许最终用户设置所需的方向,然后导出矢量图像......”使用 SVGRenderer 你不能做你想做的事。它是有限的。因此,您需要使用 webGLrenderer,然后使用我提到的库之一,将画布的所需方向导出到 svg。
    • 你能在 jsbin 中提出你的第三种方法吗,我想我有答案!
    【解决方案3】:

    这是您需要添加到 SVGRenerer.js 以解决渲染粒子问题的内容:

    function renderParticle( v1, element, material, scene ) {
    
    
            _svgNode = getCircleNode( _circleCount++ );
            _svgNode.setAttribute( 'cx', v1.x );
            _svgNode.setAttribute( 'cy', v1.y );
            _svgNode.setAttribute( 'r', element.scale.x * _svgWidthHalf );
    
            if ( material instanceof THREE.ParticleCircleMaterial ) {
    
                if ( _enableLighting ) {
    
                    _color.r = _ambientLight.r + _directionalLights.r + _pointLights.r;
                    _color.g = _ambientLight.g + _directionalLights.g + _pointLights.g;
                    _color.b = _ambientLight.b + _directionalLights.b + _pointLights.b;
    
                    _color.r = material.color.r * _color.r;
                    _color.g = material.color.g * _color.g;
                    _color.b = material.color.b * _color.b;
    
                    _color.updateStyleString();
    
                } else {
    
                    _color = material.color;
    
                }
    
                _svgNode.setAttribute( 'style', 'fill: ' + _color.__styleString );
    
            }
    
            _svg.appendChild( _svgNode );
    
    
        }
    

    【讨论】:

    • 谢谢,但正如之前的评论中提到的,我在自定义 SVGRenderer 时遇到了两个问题: 1. 我无法轻松分配以编程方式生成的径向渐变,因此我被任何渐变所困扰我创造。 2. 最终结果不会渲染正确的 3D 视图,因为靠近相机的元素不会渲染在远离相机的元素之上,并且没有正确缩放。旋转粒子显示了它是如何“破裂”的。目前尚不清楚如何重新排序 &lt;circle&gt; 元素以渲染正确的 3D 场景。这两个问题都会破坏交易。
    • 您能否提供一个指向您现在正在使用的 SVGRender.js 文件的链接?
    • 好的,请改用这个文件,如果它有效,请告诉我:dl.dropboxusercontent.com/u/140225334/SVGRenderer.js
    • 它的使用导致第17行出现如下错误:TypeError: undefined is not a constructor (evaluating 'new THREE.Rectangle()')
    • 好的,您可以在 jsfiddle 或 jsbin 中分享您的设置,或者通过电子邮件将其发送给我为您检查。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-04
    • 1970-01-01
    • 2017-06-02
    • 2017-11-16
    • 2016-10-21
    • 2017-10-06
    相关资源
    最近更新 更多