2014-12-24 6 views
12

Ho un elemento <circle> in un documento SVG, a cui si applicano una <radialGradient> per dare l'illusione di esso che è una sfera:rendering un'immagine come nodo Three.js con SVGRenderer (o altrimenti sfere di rendering)

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

sembra qualcosa di simile a questo:

red sphere

JSFiddle

I ca n rendono questo in un contenitore di WebGLRenderer Three.js utilizzando 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); 

Fin qui, tutto bene. Posso anche programmazione modificare il gradiente e rendering diverse categorie di particelle:

spheres

Cosa vorrei fare è ora passare da WebGLRenderer all'utilizzo di un SVGRenderer, in modo che possa consentire all'utente finale di impostare l'orientamento desiderato e quindi esportare un'immagine vettoriale (SVG o convertita in PDF sul back-end) che può essere utilizzata per lavori di qualità di pubblicazione.

Utilizzando il SVG sandbox example da three.js come base per la sperimentazione, ho provato un paio di tecniche diverse e non ho avuto molta fortuna. Spero che qualcuno con esperienza con three.js possa avere qualche suggerimento.

Il mio primo tentativo è stato quello di utilizzare canvg per rendere l'SVG un'immagine PNG in, e quindi applicare che ad un nodo <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); 
} 

Nessun nodo mostrano nel mio viewBox.

La prossima cosa che ho provato è stato un oggetto THREE.Sprite, utilizzando canvg e THREE.Texture routine:

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

Questo era un po 'meglio, nel senso che ho bianche, scatole opache in cui le sfere sarebbero altrimenti apparire nel viewBox reso .

Un terzo tentativo è stato fatto per creare un <svg> a nido all'interno del nodo padre SVG, che contiene un riferimento-grado <radialGradient> con l'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); 
} 

Nessun nodo sono resi. Le regolazioni degli elementi r, cx o cy dell'elemento <circle> non modificano il risultato finale.

È interessante notare che, se cambio l'attributo fillurl(#sphere_gradient)-red, ho un grande cerchio in gran parte reso fuori dalla mia viewBox, che non è attaccato alla scena (non ruota con altri elementi nella mia scena genitore, come il lati di un cubo).

Esiste un modo (funzionante e performante) per disegnare sfere o particelle di forma arrotondata nello spazio utilizzando uno SVGRenderer in three.js?

risposta

2

maggior parte degli attributi in SVG sono nello spazio dei nomi null quindi

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

sia correttamente scritto come

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

FWIW

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

dovrebbero idealmente essere scritto come

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

sebbene in questo caso il modulo originale funzioni nella maggior parte degli UA.

Nell'esempio finale gli elementi di arresto devono essere in formato SVG namespace cioè

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

dovrebbe essere

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

Un gradiente senza fermate non viene disegnato affatto così è per questo che non lo fai vedere qualsiasi cosa finché non si cambia il riempimento in rosso.

SVG ha un modello di pittori. Le cose sono disegnate nell'ordine in cui si verificano nel file. Se vuoi che qualcosa vada sopra qualcos'altro, devi metterlo più tardi nel file.

+0

Grazie per il suggerimento. La modifica dell'attributo 'href' in' xlink: href' non ha modificato il risultato finale (* i.e. *, nessuno dei nodi è reso, ancora). –

+0

Ok, grazie - la modifica dello spazio dei nomi aiuta a far applicare una sfumatura! Tuttavia, sembra che la trasformazione applicata a questo elemento '' nidificato non venga calcolata nella scena, quindi l'elemento' 'contenuto rimane fisso alla sua posizione, anche se una trasformazione viene applicata al suo elemento principale' '. –

+0

Ho avuto qualche successo nello spostamento del gruppo '' nel codice' SVGRenderer' in modo che faccia parte della '' ' principale - ma ottengo problemi di rendering in cui gli elementi '' non sono disegnati "in ordine", che rompe l'illusione della profondità. In particolare, un elemento '' che è più vicino alla telecamera viene disegnato per primo, prima di un altro elemento che è più lontano dalla telecamera. Il risultato è che l'elemento più vicino alla videocamera (e che dovrebbe essere in primo piano) viene invece visualizzato sotto l'elemento più lontano. Ruotando la scena si spezza l'illusione della profondità. –

0

Nel primo tentativo, se si aggiunge la libreria snap svg, è possibile ottenere gli elementi svg da canvg e avvolgerli in una struttura a scatto (come carta per lo snap) e quindi utilizzare il metodo paper.toString() per produrre il codice svg per il tuo file 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! 

Edit: Oppure si potrebbe usare fabric.js per convertire tela per SVG:

var svg = canvas.toSVG(); 

Oppure si potrebbe usare biblioteca canvas2svg.

+0

Ho già l'SVG. Sarei di nuovo convertito da SVG in PNG in SVG? –

+0

Hai già il tuo svg iniziale, non quello aggiornato.Qui sono i passaggi che penso possano aiutarti: hai un svg poi lo aggiungi ad una tela per aggiungerne altro, poi dalla tela hai bisogno di ottenere lo svg aggiornato . La tua ultima parte non funziona per ottenere lo svg aggiornato. Non è quello che vuoi fare per semplificare il tuo processo ?! – Bayan

+0

Non sono chiaro cosa intendi per "aggiornato". L'SVG è autonomo; Lo sto già introducendo come una stringa: 'var red_svg_html = new String ($ ('# sphere_asset'). Html());' –

0

Ecco ciò che è necessario per aggiungere ai SVGRenerer.js per risolvere il tuo problema con particelle di rendering:

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


    } 
+0

Grazie, ma come menzionato in un commento precedente, ci sono due problemi che ho avuto con la personalizzazione di SVGRenderer: 1. Non riesco ad assegnare facilmente un gradiente radiale generato da un programma, e quindi sono bloccato con qualsiasi gradiente creo. 2. Il risultato finale non rende una vista 3D corretta, in quanto gli elementi più vicini alla fotocamera non sono resi sopra gli elementi che sono più lontani dalla fotocamera e non sono ridimensionati correttamente. La rotazione delle particelle mostra come si "rompe". Non è chiaro come riordinare gli elementi '' 'per rendere una scena 3D corretta. Entrambi questi problemi sono contrari all'intervento. –

+0

Puoi fornire un collegamento al file SVGRender.js che stai utilizzando in questo momento? – Bayan

+0

https://dl.dropboxusercontent.com/u/31495717/three.svg-renderer.js –

Problemi correlati