Komischer Bug beim .obj-Laden

Hallo
Ich habe einen komischen Bug beim Laden von .obj Dateien.
Der Bug wirkt sich nicht darauf aus, wie es angezeigt wird.
Es handelt sich um ein Objekt mit 8 Punkten


# Blender v2.74 (sub 0) OBJ File: ''
# www.blender.org
mtllib barren.mtl
o Cube_Cube.001
v -0.311052 0.217910 0.798047
v -0.456289 -0.014817 0.925725
v -0.456289 -0.014817 -0.925042
v 0.312115 0.217910 0.798047
v 0.456697 -0.014817 0.925725
v 0.313673 0.218541 -0.798578
v 0.456697 -0.014817 -0.925042
v -0.311052 0.217910 -0.797382
vn 0.848400 -0.529400 -0.000000
vn 0.000000 -0.481000 -0.876700
vn -0.852900 -0.522100 -0.001000
vn 0.000000 -0.480900 0.876800
vn 0.000000 1.000000 0.000000
vn 0.000600 -1.000000 -0.000200
vn 0.000000 -1.000000 -0.000000
vn 0.000500 -1.000000 -0.000200
vn -0.849400 -0.527700 -0.000000
vn 0.002200 -0.475400 0.879700
vn 0.001000 -1.000000 -0.000400
usemtl None
s 1
f 1//1 2//1 3//1
f 1//2 4//2 5//2
f 4//3 6//3 7//3
f 8//4 3//4 7//4
f 2//5 5//5 7//5
f 4//6 1//7 8//8
f 8//1 1//1 3//1
f 2//2 1//2 5//2
f 5//9 4//9 7//9
f 6//10 8//10 7//10
f 3//5 2//5 7//5
f 6//11 4//6 8//8

Aber wenn ich das Objekt lade und die Punkte ausgebe sind es 28 Punkte und es gibt welche, die in der .obj Datei nicht existieren:


-0.311052 0.11791 0.798047
-0.456289 -0.114817 0.925725
-0.456289 -0.114817 -0.925042
-0.311052 0.11791 0.798047
0.312115 0.11791 0.798047
0.456697 -0.114817 0.925725
0.312115 0.11791 0.798047
0.313673 0.118541 -0.798578
0.456697 -0.114817 -0.925042
-0.311052 0.11791 -0.797382
-0.456289 -0.114817 -0.925042
0.456697 -0.114817 -0.925042
-0.456289 -0.114817 0.925725
0.456697 -0.114817 0.925725
0.456697 -0.114817 -0.925042
0.312115 0.11791 0.798047
-0.311052 0.11791 0.798047
-0.311052 0.11791 -0.797382
-0.311052 0.11791 -0.797382
-0.456289 -0.114817 0.925725
0.456697 -0.114817 0.925725
0.312115 0.11791 0.798047
0.456697 -0.114817 -0.925042
0.313673 0.118541 -0.798578
-0.311052 0.11791 -0.797382
0.456697 -0.114817 -0.925042
-0.456289 -0.114817 -0.925042
0.313673 0.118541 -0.798578

Zum laden verwende ich den Code, der dem ein oder anderen schon aus “Indexberechnung bei einer “Schale”” bekannt sein könnte.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class OBJReader {

    private static final Charset CHARSET = Charset.forName("US-ASCII");

    private final List<Integer> indices = new ArrayList<Integer>();

    private int index = 0;

	List<Float> vertex = new ArrayList<Float>();

	List<Float> texCoords = new ArrayList<Float>();

	List<Float> normals = new ArrayList<Float>();


    public OBJReader(final InputStream is) {
        parse(is);

    }


    public final List<Integer> getIndices() {

        return Collections.unmodifiableList(indices);

    }



    private void parse(final InputStream is) {

        final List<Float> v = new ArrayList<Float>();
        final List<Float> vt = new ArrayList<Float>();
        final List<Float> vn = new ArrayList<Float>();
        final Map<String, Integer> f = new HashMap<String, Integer>();

        try (final BufferedReader br = new BufferedReader(new InputStreamReader(is, CHARSET))) {

            String line;
            while ((line = br.readLine()) != null) {

                if (line.contains("#"))
                    line = line.substring(0, line.indexOf("#"));

                line = line.trim();

                if (line.length() > 0) {

                    final String[] values = line.split(" ");

                    switch (values[0]) {

                    case "v":
                        parseV(v, values);
                        break;

                    case "vt":
                        parseVT(vt, values);
                        break;

                    case "vn":
                        parseVN(vn, values);
                        break;

                    case "f":
                        parseF(v, vt, vn, f, values);
                        break;

                    default:
                        break;

                    }

                }

            }

        } catch (final IOException e) {

            throw new RuntimeException(e);

        }

    }


    private void parseV(final List<Float> v, final String[] values) throws IOException {

        if (3 != values.length - 1)
            throw new IOException("unsupported file format (v dimension)");

        for (int i = 1; i <= 3; i++)
            v.add(Float.valueOf(values**));

    }


    private void parseVT(final List<Float> vt, final String[] values) throws IOException {

        if (2 != values.length - 1)
            throw new IOException("unsupported file format (vt dimension)");

        for (int i = 1; i <= 2; i++)
            vt.add(Float.valueOf(values**));

    }


    private void parseVN(final List<Float> vn, final String[] values) throws IOException {

        if (3 != values.length - 1)
            throw new IOException("unsupported file format (vn dimension)");

        for (int i = 1; i <= 3; i++)
            vn.add(Float.valueOf(values**));

    }


    private void parseF(final List<Float> v, final List<Float> vt, final List<Float> vn, final Map<String, Integer> f, final String[] values) throws IOException {

        if (3 != values.length - 1)
            throw new IOException("unsupported file format (only triangle faces supported)");

        for (int i = 1; i <= 3; i++) {

            if (!f.containsKey(values**))
                parseF(v, vt, vn, f, values**);

            indices.add(f.get(values**));

        }

    }


    private void parseF(final List<Float> v, final List<Float> vt, final List<Float> vn, final Map<String, Integer> f, final String value) throws IOException {

        final String[] values = value.split("/");

        if (3 != values.length)
            throw new IOException("unsupported file format (face components)");

        final int vID = Integer.valueOf(values[0]) - 1;
        for (int i = 0; i < 3; i++)
            vertex.add(v.get(vID * 3 + i));

        if(values[1].length() > 0){
        final int vtID = Integer.valueOf(values[1]) - 1;
        for (int i = 0; i < 2; i++)
            texCoords.add(vt.get(vtID * 2 + i));
        }

        final int vnID = Integer.valueOf(values[2]) - 1;
        for (int i = 0; i < 3; i++)
            normals.add(vn.get(vnID * 3 + i));

        f.put(value, index++);

    }


}

Wenn man das mal so direkt ausführt, mit

    public static void main(String[] args) throws FileNotFoundException
    {
        OBJReader o = new OBJReader(new FileInputStream("test.obj"));
        
        for (int i=0; i<o.getIndices().size()/3; i++)
        {
            System.out.println(o.getIndices().get(i*3+0)+", "+o.getIndices().get(i*3+1)+", "+o.getIndices().get(i*3+2));
        }
        for (int i=0; i<o.vertex.size()/3; i++)
        {
            System.out.println(o.vertex.get(i*3+0)+", "+o.vertex.get(i*3+1)+", "+o.vertex.get(i*3+2));
        }
    }

gibt er aus


0, 1, 2
3, 4, 5
6, 7, 8
9, 10, 11
12, 13, 14
15, 16, 17
18, 0, 2
19, 3, 5
20, 21, 22
23, 24, 25
26, 12, 14
27, 15, 17
-0.311052, 0.21791, 0.798047
-0.456289, -0.014817, 0.925725
-0.456289, -0.014817, -0.925042
-0.311052, 0.21791, 0.798047
0.312115, 0.21791, 0.798047
0.456697, -0.014817, 0.925725
0.312115, 0.21791, 0.798047
0.313673, 0.218541, -0.798578
0.456697, -0.014817, -0.925042
-0.311052, 0.21791, -0.797382
-0.456289, -0.014817, -0.925042
0.456697, -0.014817, -0.925042
-0.456289, -0.014817, 0.925725
0.456697, -0.014817, 0.925725
0.456697, -0.014817, -0.925042
0.312115, 0.21791, 0.798047
-0.311052, 0.21791, 0.798047
-0.311052, 0.21791, -0.797382
-0.311052, 0.21791, -0.797382
-0.456289, -0.014817, 0.925725
0.456697, -0.014817, 0.925725
0.312115, 0.21791, 0.798047
0.456697, -0.014817, -0.925042
0.313673, 0.218541, -0.798578
-0.311052, 0.21791, -0.797382
0.456697, -0.014817, -0.925042
-0.456289, -0.014817, -0.925042
0.313673, 0.218541, -0.798578

Und schon das zweite Face scheint ja nicht zu stimmen.

Moin,

@Marco13 , wo ist den da der Fehler im zweiten Face?

In seiner Ursprünglichen Form sollte der Code fehlerfrei laufen (er erwartet aber vt). Ka, wo da jetzt überall “Verbesserungen” vorgenommen wurden. Ich hab nur das von Marco ausgegebene zweite Face überflogen und konnte keinen Fehler finden…

Gruß
Fancy

@Fancy Vielleicht hab’ ich da was nicht ausreichend nachvollzogen, aber in der Eingabedatei steht
f 1//2 4//2 5//2
und die Ausgabe ist
3, 4, 5
Ich hätte da eher
0, 3, 4
erwartet…!?

Der reindiziert die Vertices direkt:

1//1 => 0
2//1 => 1
3//1 => 2
1//2 => 3
4//2 => 4
5//2 => 5

1//1 und 1//2 lassen sich in OpenGL ja nicht auf denselben Index abbilden. Erst wenn (v/vt/vn) gleich sind (als Tupel / da als String hust), nutzt der wieder denselben Index. Sollte imho doch so passen?

Gruß
Fancy

Ah OK… ich hatte das als extra Methode gemacht… kann jetzt auf die Schnelle aber nicht nachvollziehen, was da jetzt schiefgeht.

Ich auch nicht, vielleicht liegt es auch an etwas das nicht hier im Thread steht. :wink:

Wenn ich das mit meinem unverändertem Code ausführe, erhalte ich diese Werte:

0, 1, 2
3, 4, 5
6, 7, 8
9, 10, 11
12, 13, 14
15, 16, 17
18, 0, 2
19, 3, 5
20, 21, 22
23, 24, 25
26, 12, 14
27, 15, 17
-0.311052, 0.21791, 0.798047
-0.456289, -0.014817, 0.925725
-0.456289, -0.014817, -0.925042
-0.311052, 0.21791, 0.798047
0.312115, 0.21791, 0.798047
0.456697, -0.014817, 0.925725
0.312115, 0.21791, 0.798047
0.313673, 0.218541, -0.798578
0.456697, -0.014817, -0.925042
-0.311052, 0.21791, -0.797382
-0.456289, -0.014817, -0.925042
0.456697, -0.014817, -0.925042
-0.456289, -0.014817, 0.925725
0.456697, -0.014817, 0.925725
0.456697, -0.014817, -0.925042
0.312115, 0.21791, 0.798047
-0.311052, 0.21791, 0.798047
-0.311052, 0.21791, -0.797382
-0.311052, 0.21791, -0.797382
-0.456289, -0.014817, 0.925725
0.456697, -0.014817, 0.925725
0.312115, 0.21791, 0.798047
0.456697, -0.014817, -0.925042
0.313673, 0.218541, -0.798578
-0.311052, 0.21791, -0.797382
0.456697, -0.014817, -0.925042
-0.456289, -0.014817, -0.925042
0.313673, 0.218541, -0.798578

Das scheinen dieselben Werte zu sein die Marco schon gepostet hat.

Optisch sieht das so aus:

Gruß
Fancy

Ja, du bekommst also auch 28 Punkte raus, das ist ja so komisch. Bei mir wird’s auch richtig angezeigt, aber wieso werden 8 Punkte in der Datei plötzlich zu 28?

Du hast zwar 8 Raumkoordinaten (v) aber 11 Normale (vn). Ein Vertex ist ein Punkt (oder auch eine Datenstruktur) die aus einer Folge von (-hier-) v/vn besteht. Zwei Punkte die zwar dieselben Raumkoordinaten haben, aber unterschiedliche Normale, sind auch zwei unterschiedliche Vertices.

Bei einem Würfel hast Du z.B. auch 8 Eckpunkte aber 6 Normale. Jede Seite des Würfels braucht deshalb 4 Vertices macht 24 Vertices insgesamt. Und 36 Indices mit Dreiecken.

Bei deinem Gebilde gibt es noch mehr Normalen, so das es insgesamt eben 28 disjunkte Tupel (v/vn) gibt.

Beim Aufbau der Vertexattribute muss das berücksichtigt werden, da eben einige Raumkoordinaten mehrfach abgespeichert werden müssen. Das hängt mit dem zusammen was ich bereits in Beitrag #5 schrieb.

Gruß
Fancy

Das problem ist, ich habe da so einen Editor für 3D-Modelle gemacht, in den man Vorlagen einlesen können soll. Diese Vorlagen will ich natürlich mit Blender machen, aber das ist doof, wenn man vom Objekt jetzt beispielsweise einen Punkt löschen will, ist eine andere Version dieses Punktes ja noch vorhanden. Kann ich dieses Problem irgendwie umgehen?

Ich meine, ich kann ja auch ein komplett funktionsfähiges Würfel-Mesh aus 8 Punkten machen und brauche keine 28 dafür.Es muss doch möglich sein, das Blender Mesh mit nur 8 Koordinaten zu importieren, oder zu konvertieren, dass es nur noch 8 Koordinaten hat.

Etwas oberflächlich, in Ermangelung von Kenntnis deiner Strukturen: Eine Möglichkeit könnte sein, sich zu merken, welche Indizes denn nun „diesen“ Vertex repräsentieren. Also, ein Punkt (0,0,0) kommt vielleicht mit 3 verschiedenen Normalen vor, dann könnte man sich alle drei indizes merken an denen (0,0,0) mit den jeweiligen Normalen vorkommen.

Also müsste ich immer wenn ein Punkt gelöscht, oder verschoben wird, alle Punkte raussuchen, die die selben Koordinaten haben und alle löschen/verschieben.

Grob ja, aber ich würde das nicht über die Koordinaten machen. Du musst dir im Klaren darüber sein, dass ein “Mesh Vertex” VIEL (VIEL) mehr ist als nur ein Punkt im Raum. Selbst im oberflächlichsten Sinn ist ein “Mesh Vertex” eben ein Ding mit Position+Normale+Texturkoordinate+(Topologie…). Ob man diese Informationen nun “objektorientiert” speichert, oder OpenGL-Näher mit großen Datenfeldern und Indizes, die in diese Felder zeigen, macht konzeptuell keinen Unterschied. So gesehen geht es ja “nur” um die Frage, wie das Mesh in eine OpenGL-Repräsentation übersetzt wird. Das “nur” steht dabei aber in seeeehr dicken Anführungszeichen: Es gibt einen Grund, warum sich … alle (!?) … damit so schwer tun, eine Mesh-Repräsentation zu finden, die sowohl für Rendering als auch für Modifikationen gut geeignet ist. Für’s Rendering braucht man eben rohe Buffer. Große Daten am Stück. Positionen, Normalen, Indizes. Für Manipulationen bieten sich andere Datenstrukturen an, HalfEdge & Co. Dummerweise sind diese beiden Repräsentationen sehr unterschiedlich und kaum unter einen Hut zu bringen: Wenn man in einem Buffer einen Vertex (bzw. dessen Position(!)) löschen will, ändert sich nicht nur die Buffergröße, sondern auch ALLE indizes, die auf die darauffolgenden Positionseinträge verwiesen haben.

Man kann da sehr weit gehen und eine Wissenschaft draus machen, und z.B. bei https://en.wikipedia.org/wiki/Polygon_mesh#Representations zu lesen anfangen. Als Einstieg, nur aus Neugier: Du hast ein Mesh mit Dreiecken


0,1,2
1,2,3
0,1,4

und löschst jetzt Vertex 3. Was passiert? Ein Dreieck fällt weg. Die Dreiecksliste muss also aktualisiert werden, und entält danach nur noch


0,1,2
0,1,4

Und wenn du Vertex 0 gelöscht hättest? Dann wären ZWEI Dreiecke weggefallen, und es wäre


1,2,3

übrig geblieben. Hast du dir schon überlegt, wie du solche Änderungen abbilden willst?

Ja, dass die Indexe auch bearbeitet werden, habe ich schon implementiert, das funktioniert aber nur, mit jeweils einem Punkt an der gleichen Stelle.

Ich habe jetzt eine Methode gemacht, die die “richtigen” Koordinaten zählen soll. Also bei dem schon angeführten Beispiel 8. Nur gibt er 630 aus, was falsch ist.
Was stimmt denn da nicht?

public int countVertices(){
			ArrayList<Vector3f> realCoords = new ArrayList<Vector3f>();
			realCoords.add(new Vector3f(vertexBuffer.get(0), vertexBuffer.get(1), vertexBuffer.get(2)));
			for(int i = 0; i < vertexBuffer.capacity(); i+=3){
				Vector3f v = new Vector3f(vertexBuffer.get(i), vertexBuffer.get(i+1), vertexBuffer.get(i+2));
				for(int a = 0; a < realCoords.size(); a++){
					if(realCoords.get(a).x == v.x && realCoords.get(a).y == v.y && realCoords.get(a).z == v.z){
						break;
					}else{
						realCoords.add(v);
					}
				}
			}
			return realCoords.size();
		}

Hm. Spricht was dagegen, dort sowas einzufügen wie

System.out.println("Vergleiche mit anderen: "+v);
...
System.out.println("Vergleiche mit "+realCoords.get(a));
...
System.out.println("Sind nicht gleich, füge ein");
...
System.out.println("Ergebnis: "+realCoords);

!?

Ich habs jetzt richtig hingekriegt, danke!

public int countVertices(){
			ArrayList<Vector3f> realCoords = new ArrayList<Vector3f>();
			realCoords.add(new Vector3f(vertexBuffer.get(0), vertexBuffer.get(1), vertexBuffer.get(2)));
			for(int i = 0; i < vertexBuffer.capacity(); i+=3){
				Vector3f v = new Vector3f(vertexBuffer.get(i), vertexBuffer.get(i+1), vertexBuffer.get(i+2));
				System.out.println("Vergleiche mit anderen: "+v);
				boolean drinVorhanden = false;
				for(int a = 0; a < realCoords.size(); a++){
					System.out.println("Vergleiche mit "+realCoords.get(a));
					if(realCoords.get(a).x == v.x && realCoords.get(a).y == v.y && realCoords.get(a).z == v.z){
						drinVorhanden = true;
						break;
					}else{
						drinVorhanden = false;
					}
				}
				if(!drinVorhanden){
					System.out.println("Sind nicht gleich, füge ein");
					realCoords.add(v);
				}
			}
			System.out.println("Ergebnis: "+realCoords);
			return realCoords.size();
		}

Jetzt fehlt nur noch der andere Kram von wegen löschen, bewegen und so.

[OT]
Notfall:
Ich werkelte gerade an diesem Kram hier und plötzlich(evtl. nach Java Update) kam die Fehlermeldung, dass ich in meinem Fragmentshader nicht mehr gl_FragColor verwenden darf. Also schrieb ich folgendes(für den Shader der 2D GUI Elemente, die mit Ortho projeziert werden):


#version 150

uniform sampler2D texture;
uniform vec4 color;

//Das ist nicht wichtig, muss es aber als Input machen vom Vertex Shader aus
in vec3 cameraPosition;
in vec2 TexCoord;
in vec3 fragNormal;
in vec3 fragVert;
in mat4 modelMat;

out vec4 outColor;

void main() {
	vec4 surfaceColor = texture2D(texture, TexCoord);
    outColor = surfaceColor*color;
}

Mein Gui wird aber nicht mehr angezeigt(bzw. es ist unsichtbar)
[/OT]

Nicht verzagen, @Fancy fragen :smiley:

Man findet ja recht schnell was, …
opengl - How does the fragment shader know what variable to use for the color of a pixel? - Stack Overflow
… und gl_FragColor ist deprecated (aber bei 150 sollte es noch dabei sein - hab’ das versionsmapping da gerade nicht im Kopf).

Das ganze sollte aber mit Java nix zu tun haben. Hast du vielleicht ein Grafikkartentreiberupdate gemacht?