| |
|
|
- Seite 1 - |
|
HofK | Auf einen heißen Tipp von IF hin, habe ich mir mal three.js [...] angeschaut. Da [...] (ganz unten) die ersten Resultate. |
|
|
| |
|
|
| |
|
- Seite 6 - |
|
|
HofK | Der Fehler war leicht zu beheben, man kann unmittelbar auf das zuvor mit .addGroup ( push ) abgelegte Element über den Index zugreifen.
Bei der Korrektur habe ich auch noch gleich die Indexrechnung optimiert. Bei der Erstellung der Befehle ist es einfacher zu überschauen, wenn man den arithmetischen Ausdruck immer unmittelbar an der Stelle hat.
Abgesehen von internen Optimierungen von Javascript, ist es aber sinnvoll wiederkehrende Indizes einmal vorab zu bestimmen.
Im Codeabschnitt wird dies und die Korrektur deutlich.
Beim Test mit der Funktion material cover: 0.2 + 0.3 *( 1 + Math.sin( t + u ) ) , ( siehe [...] korrigiert ) zeigt sich bei z.B. 50 Segmenten, dass BufferGeometry nicht flotter ist als Geometry! Beim Test mit unmittelbarer Definition im Code ist bei 72 Segmenten Geometry sogar schneller!
Wie das, es soll doch effizienter sein.
Nehmen wir der Einfachheit halber 100 x 100 Segmente an.
Rechnet man nach, stellt man fest, das Geometry indexed arbeitet und dann gut 10000 Ecken mit zu berechnenden Koordinaten und 20000 Dreiecke (faces) hat (nicht gewaffelt). Diese greifen auf die Koordinaten zurück.
Die non-indexed BufferGeometry benötigt aber für jedes Dreieck drei Koordinaten, auch wenn sich diese mehrfach ( bis zu sechs mal bei nicht gewaffelt, 8 mal bei gewaffelt) wiederholen. Es ergeben sich 60000 Koordinaten!
In einer Anfrage, ob indexed oder non- indexed effektiver ist kam die Antwort, dass man dies nicht allgemein sagen kann sondern je Gebilde testen muss. In meinem Fall müssen immer alle Berechnungen durchgeführt werden, egal ob eine komplizierte Funktion angegeben wird oder nur die konstante Default- Funktion benutzt wird.
Also bleibt mir nichts anderes übrig, als dann auch noch non-indexed BufferGeometry in Angriff zu nehmen.
Dann kann man die drei Varianten gut vergleichen. |
|
|
| |
|
|
|
HofK | Fast geschafft, die drei Varianten stehen ab sofort zum Test zur Verfügung. [...]
Allerdings gibt es noch einen Unterschied bei den Normalen. Bei Geometry mit bottom oder top führt die intern benutzte three.js Funktion .computeVertexNormals() dazu, dass am unteren/oberen Rand eine schräge Normale entsteht und der Rand dann bei smooth shading auch "glatt" ist.
Nach diesem Vorbild habe ich bei non-indexed BufferGeometry die Normalen auch mit eigener Berechnung realisiert. Hier ist die Funktion .computeVertexNormals() nicht anwendbar.
Bei indexedBuffer Geometry führt die Funktion dazu, dass am Rand und der Naht zwei Normalen entstehen.
Das liegt daran, das bottom und top extra vertices haben (wegen der uv-map nötig). Damit bleiben Rand / Naht auch bei smooth ziemlich flat.
Ich werde diese Normalen nun noch zusammenführen. UPDATE: siehe 27.06.2017 neue Version - neue Normalen! - siehe 06.07.2017
Der Code ist so gestaltet, dass man die nicht benötigten Varianten sehr einfach löschen kann. Es sind komplette Blöcke am Ende der Haupt-Funktionen create, morphVertices und morphFaces.
Die Auswahl bei BufferGeometry geschieht intern durch eine neue Eigenschaft
indexed,// indexed or non indexed BufferGeometry Indexed ist default.
Wenn der Rest erledigt ist und der Code noch ein wenig optimiert, werde ich nach einigen Tests die Sache dann auf GitHub hochladen und bei [...] offiziell mitteilen.
Auch die Effizienz möchte ich vorher noch testen. |
|
|
| |
|
|
|
p.specht
| Faszinierend, was da schon alles geht! Weiter so!!! |
|
|
| XProfan 11Computer: Gerät, daß es in Mikrosekunden erlaubt, 50.000 Fehler zu machen, zB 'daß' statt 'das'... | 15.06.2017 ▲ |
|
|
|
|
HofK | ... Ich werde diese Normalen nun noch zusammenführen. ...
Das war jetzt doch aufwändiger als gedacht!
Die Zusammenführung ist von der Systematik her problematisch und eventuell insgesamt aufwändiger, als die Normalen komplett selbst zu berechnen. Deshalb habe ich bei indexed BufferGeometry .computeVertexNormals() gestrichen und "gerechnet".
Diese "Indexschlacht" war wegen der Nähte heftiger als bei non-indexed BufferGeometry, wo einheitlich nur isolierte Dreiecke zusammengeflickt werden.
Wie bekommt man diese ganzen Indizes in den Griff?
Bei solchen Sachen habe ich immer einige Blatt kariertes Papier auf dem Tisch. Darauf entstehen dann mit Bleistift und Radiergummi schrittweise die "Entwicklungsunterlagen".
Als ich mit THREEf begonnen habe entstand dieses Blatt:
Da sind noch geklammerte und gestrichene Erstentwürfe sichtbar. Auch eine abgearbeitete "to do"- Liste. Das Schema ist rechts unten deutlich sichtbar
Etwas verzwickt war die Sache mit dem entrollen (unroll). Da musste ein zweites Blatt her:
Für Geometry hat das ausgereicht, aber als ich dann bei non-indexed BufferGeometry mit den Dreiecken durcheinander gekommen bin, habe ich die Sache mal ganz ordentlich konstruiert. Bottom und Top sind halbwegs angedeutet, die paar Knoten und Dreiecke kann man an den Fingern dazuzählen. Von dem 3x3 Entwurf aus lässt sich die Sache gut verallgemeinern und testen.
Die Zuordnung der Dreiecke (faces) zu den Knoten (vertex) ist recht verzwickt. Ich brauche das, um die Normalen zu berechnen. Da musste nochmal ein Blatt Papier her um die Sache festzuhalten.
Nun gibt es bei BufferGeometry noch ein Problem mit dem Mittelpunkt von Boden und Deckel. Wenn nicht circOpen, gehen die Randpunkte noch doppelt in die Rechnung ein. Bei nur drei Radiussegmenten ist die Verschiebung des Mittelpunktes ganz deutlich. Das läßt sich aber recht leicht beheben.
Dann noch einige Tests und eventuelle Optimierungen und fertig ist es. Wenn nicht jemand noch Fehler findet. [...] Gern testen und mitteilen.
UPDATE: siehe 06.07.2017 |
|
|
| |
|
|
|
HofK | Der Mittelpunkt von Boden und Deckel ist nun bei indexed BufferGeometry korrekt.
Auch ein Fehler bei den Normalen war noch zu korrigieren. Wenn circOpen, darf das letzte face am unteren Rand und das erste face am oberen Rand nicht gedoppelt werden.
So wurde aus einfachem
if ( i === 0 ) vFace.push( fLeftPos ); // face double ...
if ( i === 0 && ( !g.circOpen || ( g.circOpen && j !== rs ) ) ) vFace.push( fLeftPos ); // face double ...
War ja auch wirklich zu primitiv dieses "gleich Null" .
Seit 22.06. ist die Revision 86 [...] von three.js aktuell. Da es diesmal keine offensichtlich für THREEf relevanten Änderungen gibt, konnte ich einfach die neue Version unterschieben.
Die geänderte Testversion weiterhin dort: [...]
UPDATE 06.07. siehe nächster und übernächster Beitrag |
|
|
| |
|
|
|
HofK | Da waren noch einige Kleinigkeiten zu stemmen.
Bei der zuerst erledigten Berechnung der Normalen bei non indexed BufferGeometry ließ die Systematik sehr zu wünschen übrig. Daher habe ich die Berechnung noch einmal nach dem Muster von indexed BufferGeometry umgestrickt.
Wer arbeitet macht Fehler und so tanzte hinterher eine einzige Normale an der vorletzten Position der Hülle aus der Reihe. Der Knoten zeigte zwei Normalen. Der Fehler war etwas heimtückisch, da er nicht dort entstand wo er zu vermuten wäre - das hat gedauert!
Nun ist es hoffentlich (fast) fehlerfrei.
Es gibt aber immer noch einen Unterschied bei den Normalen!
Die Berechnung mit .computeVertexNormals() bei Geometry ergibt bei nicht gewaffelt ( !waffled ) am oberen und unteren Rand abweichende Normalen.
Der Grund dafür ist, dass die Anzahl der faces rechts und links unterschiedlich ist ( 1 bzw. 2) und diese gewichtet eingehen. Bei der eigenen Berechnung für BufferGeometry habe ich dies durch Zeilen wie z.B.
vFace.push( fIdx * 3 ); if ( i === 0 ) vFace.push( fIdx * 3 ); // face double (equal number left and right)
ausgeglichen.
Eventuell mache ich mich später einmal daran und rechne auch für Geometry selbst.
In der Darstellung wireframe / cover kann man die zu den Normalen gehörenden faces gut erkennen.
Nun noch ein wenig testen - siehe [...] - dort auch ein Link zu einem vergleichenden Beispiel für die drei Geometrievarianten. Und dann für GitHub fertig machen. |
|
|
| |
|
|
|
HofK | ------ Die aktuelle Version von THREEf mit Geometry und BufferGeometry befindet sich jetzt auf [...] .
Die Testversion behält ihren Platz [...] .
Zur Sicherheit habe ich die alte Version (nur Geometry, r85) komplett nach [...] verschoben.
Auf GitHub [...] habe ich das entsprechend realisiert, die alte Version befindet sich vollständig im Verzeichnis /THREEf_old.
Fehlt noch die Ankündigung bei discourse.threejs.org [...] |
|
|
| |
|
|
|
HofK | Die Ankündigung bei discourse.threejs.org [...] ist durch einen weiteren Eintrag bei [...] erfolgt.
_________________________________________________________
Nun kommt die große Überraschung:
Zitat aus docs / Geometrie [...]
"Mit Geometrien ist einfacher zu arbeiten als mit BufferGeometrien, da sie Attribute wie Eckpunkte, Flächen, Farben und so weiter direkt (anstatt in Puffern) speichern, aber sie sind in der Regel langsamer."
Weil es einfacher ist, habe ich das addon mit Geometry begonnen.
Mir wurde gesagt, ich sollte immer BufferGeometry nehmen.
Die Überraschung: Geometry ist die schnellste Variante!
Siehe [...] Wie kann man das erklären? Ich weiß nicht warum.
Kann sein, meine Berechnung der vertices, positions, faces, normals sind nicht effizient genug und die three.js internen Berechnungen von Geometry sind oft viel besser.
In Englisch [...] |
|
|
| |
|
|
|
HofK | HofK (08.07.2017)
Kann sein, meine Berechnung der vertices, positions, faces, normals sind nicht effizient genug und die three.js internen Berechnungen von Geometry sind oft viel besser. In Englisch [...]
Und da [...] sind auch Antworten. Geometry wird über kurz oder lang "abgeschafft", da lohnt es nicht sich damit intensiv zu befassen.
Ich finde es schade, da Anfänger es dann seeeeehr schwer haben werden sich mit three.js zu befassen.
Die BufferGeometry ist doch etwas verzwickt. Der Focus liegt aber wohl deutlich bei den professionellen Anwendern.
Ich bin nicht gerade ein Anfänger in der Programmierung, aber wenn es Geometry nicht gegeben hätte, wer weiß ob ich dann wirklich bei three.js eingestiegen wäre?
Auch jetzt habe ich es vorgezogen eine Erweiterung erst einmal mit Geometry zu testen. Wenn das klappt, kann ich es auf BufferGeometry übertragen.
Die Frage [...] brachte mich auf die Idee, es mal mit THREEf zu versuchen.
Es ist gar nicht mal viel Aufwand. Einziges Problem ist die Tatsache, dass unter Windows Systemen nur Linien der Stärke 1 dargestellt werden (siehe [...] ) und man das Material des Körpers etwas transparent halten muss, um die "quad lines" sauber darzustellen.
In der Funktion create() wird eine THREE.line erzeugt (statt Maschen also eine Linie) und alle Punkte werden erst einmal auf den Ursprung gelegt.
In der Funktion morphVertices( time ) wird dann die Linie in zwei Teilen über alle vertices gezogen. Einmal horizontal, einmal vertikal im Zickzack. Das gibt wieder eine schöne Indexrechnung und ein nicht vorzeigbares "Schmierblatt" als Entwurf dazu - viel Radiergummi verbraucht!
Es ist deutlich gekennzeichnet, was hinzugekommen ist. Wegen der Effizienz werde ich vorher auf quadline abfragen und zwei getrennte Blöcke anlegen, um ohne quadline nicht ständig Abfragen im Code zu haben. |
|
|
| |
|
|
|
HofK | Das ging noch einfacher als gedacht, auch BufferGeometry hat nun die Eigenschaften quadLine und quadColor.
Im erneuerten "Sandkasten" [...] (firefox!) kann man beide auswählen. Die quad line ist nur sinnvoll möglich mit der Eigenschaft circOpen. Deshalb wird diese Eigenschaft in THREEf automatisch gesetzt. Um das sichtbar zu machen, wird auch die Checkbox dann gesetzt.
Bei den weiteren Beispielen (further examples [...] - auch andere Browser) habe ich eine entsprechende Ergänzung vorgenommen.
Zu beachten ist, dass dem mesh die quad line gesondert hinzugefügt werden muss. In THREEf wird ja nur die Geometrie erzeugt / manipuliert.
|
|
|
| |
|
|
|
HofK | Bisher konnten die Nummerierung der Ecken (vertices) nur bei Geometry angezeigt werden. Diesen Mangel habe ich nun behoben.
Da aber non-indexed Buffer Geometry gar keine eigenen Ecken besitzt - Dreieckssuppe - musste ich auf die intern in einem array vertexPositions gespeicherten Daten zurückgreifen.
Dort sind die zu einer Ecke gehörenden Dreiecke - genauer die Position eine Ecke des Dreiecks - gespeichert.
Von diesen Einträgen nehme ich jeweils immer das erste Element.
Für Ecke n:
vertexNumbers[ n ].position.set( ... .vertexPositions[ n ][ 0 ] ...
Bei [...] ist die neue Möglichkeit nun verfügbar.
Bei discourse.threejs.org mitgeteilt und auf GitHub gespeichert. |
|
|
| |
|
|
|
HofK | HofK (08.07.2017)
Kann sein, meine Berechnung der vertices, positions, faces, normals sind nicht effizient genug und die three.js internen Berechnungen von Geometry sind oft viel besser.
Um das eventuell zu klären, habe ich einen ganz simplen Test fabriziert.
Der Code ist knapp und bis auf die tatsächlich notwendigen Unterschiede für Geometry und BufferGeometry identisch.
<!DOCTYPE html>
<!-- *** speedtest simple ***
/**
* @author hofk / https://sandbox.threejs.hofk.de/
*/
-->
<html lang="de">
<head>
<title> speedtest simple </title>
<meta charset="utf-8" />
</head>
<body>
segment sount;
<input type="text" size="5" id="hs" value="100" > /
<input type="text" size="5" id="rs" value="100" > - chose:
<input type="radio" name="geom" id="Geometry" checked="checked" > Geometry
<input type="radio" name="geom" id="BufferGeometry" > indexed BufferGeometry
<button type="button" id="show"> -> show mesh </button>
</body>
<script src="three.min.86.js"></script>
<script src="OrbitControls.js"></script>
<script src="THREEx.WindowResize.js"></script>
<script>
'use strict'
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200000 );
camera.position.set( 400, 600, 1000 );
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xeeeeee, 1 );
var container = document.createElement('div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement );
THREEx.WindowResize( renderer, camera );
var controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
var clock = new THREE.Clock( true );
var t;// time
var g;// Geometry or BufferGeometry
var mesh;
var hs;// height segment count ( y direction)
var rs;// radial segment count (here in test x direction)
var hss;// hs + 1
var rss;// rs + 1
var vertexCount;
var vIdx;// vertex Index
var faceCount;
var fIdx;// face index
var j0, j1;// j index
var a, b1, c1, c2;// vertex indices, b2 equals c1
var ni, nj;// relative counter variable
var posIdx;// position Index
var x, y, z;// position coordinates
var materialSegment;
var showGeo = false;
// materials
var uvTex = new THREE.TextureLoader().load( "uvgrid01.png" );
var waterlilyTex = new THREE.TextureLoader().load( "waterlily.png" );
// var earthTex = new THREE.TextureLoader().load( "earth_nasa_map_900.png" );
var side = THREE.DoubleSide;
var materials = [
// material index:
new THREE.MeshBasicMaterial( { transparent: true, opacity: 0.5, side: side } ),// 0 transparent
new THREE.MeshBasicMaterial( { map: uvTex, side: side } ),// 1 uv grid
new THREE.MeshPhongMaterial( { color: 0xff0000, emissive: 0xff0000, side: side, } ),// 2 red
new THREE.MeshPhongMaterial( { color: 0x00ff00, emissive: 0x00ff00, side: side, } ),// 3 green
new THREE.MeshPhongMaterial( { color: 0x0000ff, emissive: 0x0000ff, side: side, } ),// 4 blue
new THREE.MeshPhongMaterial( { color: 0xffff00, emissive: 0xffff00, side: side, } ),// 5 yellow
new THREE.MeshPhongMaterial( { color: 0xff00ff, emissive: 0xff00ff, side: side, } ),// 6 mgenta
new THREE.MeshPhongMaterial( { color: 0x00ffff, emissive: 0x00ffff, side: side, } ),// 7 cyan
new THREE.MeshBasicMaterial( { map: waterlilyTex, side: side } ),// 8 photo waterlily (free)
new THREE.MeshPhongMaterial( { color: 0x7755ff, emissive: 0x4433dd, side: side } ),// 9 color
new THREE.MeshPhongMaterial( { color: 0x444444, emissive: 0x333333, side: side } )// 10 grey
];
// var material = new THREE.MeshBasicMaterial( { color: 0x880088, side: side, wireframe: true } );
// var material = new THREE.MeshBasicMaterial( { map: earthTex, side: side } );
document.getElementById( "show" ).onclick = showNewMesh;
animate();
// ......................
function showNewMesh() {
if ( mesh ) {
scene.remove( mesh );
g.dispose();
showGeo = false;
}
hs = Math.floor( document.getElementById( "hs" ).value );
rs = Math.floor( document.getElementById( "rs" ).value );
hss = hs + 1;
rss = rs + 1;
vertexCount = hss * rss;
faceCount = hs * rs * 2;
// Geometry or BufferGeometry
if ( document.getElementById( "Geometry" ).checked ) g = new THREE.Geometry();
if ( document.getElementById( "BufferGeometry" ).checked ) g = new THREE.BufferGeometry();
// mesh
mesh = new THREE.Mesh( g, materials );
scene.add( mesh );
//configure
if ( g.isGeometry ) {
for ( var i = 0; i < vertexCount; i ++ ) {
g.vertices.push( new THREE.Vector3( 0, 0, 0 ) );
}
}
if ( g.isBufferGeometry ) {
var idxCount = 0;
g.faceIndices = new Uint32Array( faceCount * 3 );
g.vertices = new Float32Array( vertexCount * 3 );
g.setIndex( new THREE.BufferAttribute( g.faceIndices, 1 ) );
g.addAttribute('position', new THREE.BufferAttribute( g.vertices, 3 ).setDynamic( true ) );
for ( var f = 0, p = 0; f < faceCount; f ++, p += 3 ) {
g.addGroup( p, 3, 0 );// write group for multi material
}
}
for ( var j = 0; j < rs; j ++ ) {
j0 = hss * j;
j1 = hss * ( j + 1 );
for ( var i = 0; i < hs; i ++ ) {
// 2 faces / segment, 3 vertex indices
a = j0 + i;
b1 = j1 + i;// right-bottom
c1 = j1 + 1 + i;
// b2 = j1 + 1 + i; =c1 // left-top
c2 = j0 + 1 + i;
if ( g.isGeometry ) {
g.faces.push( new THREE.Face3( a, b1, c1 ) );// right-bottom
g.faces.push( new THREE.Face3( a, c1, c2 ) );// left-top
}
if ( g.isBufferGeometry ) {
g.faceIndices[ idxCount ] = a;// right-bottom
g.faceIndices[ idxCount + 1 ] = b1;
g.faceIndices[ idxCount + 2 ] = c1;
g.faceIndices[ idxCount + 3 ] = a;// left-top
g.faceIndices[ idxCount + 4 ] = c1,
g.faceIndices[ idxCount + 5 ] = c2;
idxCount += 6;
}
}
}
showGeo = true;// start animation
}
function move( t ) {
if ( g.isGeometry ) g.verticesNeedUpdate = true;
if ( g.isBufferGeometry ) g.attributes.position.needsUpdate = true;
g.groupsNeedUpdate = true;// to change materialIndex for multi material
for ( var j = 0; j < rss; j ++ ) {
nj = j / rs;
y = 400 * nj;// calculate y
for ( var i = 0; i < hss; i ++ ) {
ni = i / hs;
x = 400 * ni;// calculate x
z = 400 * Math.sin( t + ni + nj );// calculate z
vIdx = hss * j + i;
if ( g.isGeometry ) g.vertices[ vIdx ].set( x, y, z );
if ( g.isBufferGeometry ) {
posIdx = vIdx * 3;
g.vertices[ posIdx ] = x;
g.vertices[ posIdx + 1 ] = y;
g.vertices[ posIdx + 2 ] = z;
}
}
}
for ( var j = 0; j < rs ; j ++ ) {
for ( var i = 0; i < hs; i ++ ) {
materialSegment = Math.floor( 5 * ( 1 + Math.cos( 0.2 * t + i * i + 2 * j ) ) );// calculate material
fIdx = 2 * hs * j + 2 * i;
if ( g.isGeometry ) {
g.faces[ fIdx ].materialIndex = materialSegment;
g.faces[ fIdx + 1 ].materialIndex = materialSegment;
}
if ( g.isBufferGeometry ) {
g.groups[ fIdx ].materialIndex = materialSegment;
g.groups[ fIdx + 1 ].materialIndex = materialSegment;
}
}
}
}
function animate() {
requestAnimationFrame( animate );
t = clock.getElapsedTime();
if ( showGeo ) move( t );
renderer.render( scene, camera );
controls.update();
}
</script>
</html>
Auch hier ist aber Geometry schneller.
Speicherverbrauch und Geschwindigkeit stehen oft in einem umgekehrten Verhältnis. Wenn nicht noch interne Optimierungen vorgenommen werden, muss man dann für solche Sachen statt zur einfachen Geometry zu noch komplizierteren Dingen als BufferGeometry greifen.
Den Test kann man selbst unter [...] absolvieren.
|
|
|
| |
|
|