Hi,
kurze, aber erschreckend lange
Mein Problem tritt jetzt auf, wenn ich von diesem Thread aus eine neue vaoID generieren will.
Für das generelle Erstellen und laden der Objekte (Terrain oder Modell) verwende ich meine Klasse BackgroundMeshLoader.
BackgroundMeshLoader:
Loader:
BackgroundTerrainLoader-Methode(s.u.):
Beim Laden von Terrains werden die Heightmaps geladen und direkt in Koordinaten und Normalen aufgeteilt, dann BlendMaps geladen und in ein Terrain Objekt gepackt.
Es wird dann nur die Methode BackgroundMeshLoader.loadToVAO(Daten); aufgerufen.
Beim Laden von Modellen verwende ich die Methode loadOBJModell().
Soooo...
Die Terrains lade ich zuerst.
Die Methode GL30.glGenVertexArrays() erstellt IDs von 1 bis 169.
Wenn ich DANACH (-> Terrainladen ist fertig) die Modelle lade, beginnt die Methode wieder bei 1.
Das Ergebnis davon ist, dass ich, wenn ich genügend Vertices malen lasse, eine untexturiertes Terrain sehe (schwarz).
Er überschreibt also, wie eingestellt, bereits gespeicherte Daten nicht.
Aber warum fängt er jetzt wieder bei 1 an?
Bin auch offen für Anregungen zum generellen Aufbau usw.
Danke im Vorraus,
eMmiE
kurze, aber erschreckend lange
Ich sitze jetzt schon länger an einem Projekt, bei dem es darum geht, eine funktionsfähige 3D Engine zu erzeugen.
Das Ganze probiere ich mit der LWJGL
Dabei erzeuge ich VAOs/VBOs für jedes 3D-Objekt der Welt (momentan für Terrains [aus Heightmaps geladen und texturiert und mit Shadern versehen] kein Problem).
Pro Terrain 1 Heightmap, an Höhlen und so einen Unfug habe ich noch nicht gedacht...
Ich kann die Terrains ohne Probleme anzeigen.
Hierzu habe ich diverse Threads (via Runnable) am Laufen, die, während das Programm läuft, im Hintergrund die Objekte aus dem Speicher werfen und neue reinladen.
[Threads habe ich für Texturenverwaltung und Meshverwaltung(-> auch für Terrains) und Terrainmanagement, ausserdem für das Vordergrundfenster, damit ich close requests mitbekomme]
Die Umsetzung mit Runnable deswegen, weil ich mir davon erhoffe, dass das Programm im Vordergrund ohne größere FPS-Drops weiterlaufen kann, ich die Objekte, die ich NEU anzeigen möchte (bspw. beim Terrainwechsel), schon im Speicher habe.
Aktuell mache ich es so, dass ich 13*13 Terrains im Speicher habe und davon einen Ausschnitt (weniger als 11*11) anzeige.
Beim Terrainwechsel lade ich dann eine ganze Reihe (20) nach und werfe das Gegenstück dazu (andere Seite) aus dem Speicher raus.
Das funktioniert auch soweit ganz ok.
Das Problem ist jetzt, dass ich für die Entitäten (Rehe, Dinos, Glibberwürfel usw. -> Sachen mit KI) einen Hintergrundthread brauche, der deren Modelle, wenn die benötigt werden, in den Speicher reinlädt und auch wieder rausschmeißt.
Derselbe Thread soll dann auch die KI übernehmen.
Performance allerdings ist erstmal nicht mein Hauptziel, sondern nur, dass es funktioniert.
Das Ganze probiere ich mit der LWJGL
Dabei erzeuge ich VAOs/VBOs für jedes 3D-Objekt der Welt (momentan für Terrains [aus Heightmaps geladen und texturiert und mit Shadern versehen] kein Problem).
Pro Terrain 1 Heightmap, an Höhlen und so einen Unfug habe ich noch nicht gedacht...
Ich kann die Terrains ohne Probleme anzeigen.
Hierzu habe ich diverse Threads (via Runnable) am Laufen, die, während das Programm läuft, im Hintergrund die Objekte aus dem Speicher werfen und neue reinladen.
[Threads habe ich für Texturenverwaltung und Meshverwaltung(-> auch für Terrains) und Terrainmanagement, ausserdem für das Vordergrundfenster, damit ich close requests mitbekomme]
Die Umsetzung mit Runnable deswegen, weil ich mir davon erhoffe, dass das Programm im Vordergrund ohne größere FPS-Drops weiterlaufen kann, ich die Objekte, die ich NEU anzeigen möchte (bspw. beim Terrainwechsel), schon im Speicher habe.
Aktuell mache ich es so, dass ich 13*13 Terrains im Speicher habe und davon einen Ausschnitt (weniger als 11*11) anzeige.
Beim Terrainwechsel lade ich dann eine ganze Reihe (20) nach und werfe das Gegenstück dazu (andere Seite) aus dem Speicher raus.
Das funktioniert auch soweit ganz ok.
Das Problem ist jetzt, dass ich für die Entitäten (Rehe, Dinos, Glibberwürfel usw. -> Sachen mit KI) einen Hintergrundthread brauche, der deren Modelle, wenn die benötigt werden, in den Speicher reinlädt und auch wieder rausschmeißt.
Derselbe Thread soll dann auch die KI übernehmen.
Performance allerdings ist erstmal nicht mein Hauptziel, sondern nur, dass es funktioniert.
Mein Problem tritt jetzt auf, wenn ich von diesem Thread aus eine neue vaoID generieren will.
Für das generelle Erstellen und laden der Objekte (Terrain oder Modell) verwende ich meine Klasse BackgroundMeshLoader.
BackgroundMeshLoader:
Code:
package loader;
//hier stehen nur imports
public class BackgroundMeshLoader extends Loader {
private static List<String> toLoad;
//Die rel. Pfade, die geladen werden sollen
private static List<String> toUnload;
//Die rel. Pfade, die entladen werden sollen
private static Map<String, Integer[]> map;
//<rel. Pfade, {vaoID, indices-Anzahl}>
private static Map<Integer, Integer[]> vaoTOvboIDs;
//VBOs für Modellkoordinaten, Normalenvektoren und Texturkoordinaten
private boolean appeared = false;
//Test
//Siehe Runnable
private boolean go;
private boolean ready;
private boolean interrupt;
private boolean running;
private SharedDrawable sd;
public BackgroundMeshLoader(SharedDrawable sd) {
this.go = false;
this.ready = true;
this.interrupt = false;
this.running = false;
this.sd = sd;
toLoad = new ArrayList<String>();
toUnload = new ArrayList<String>();
map = new HashMap<String, Integer[]>();
vaoTOvboIDs = new HashMap<Integer, Integer[]>();
}
public void load(String path) {
if (!toLoad.contains(path)) {
toLoad.add(path);
}
}
public void unload(String path) {
if (!toLoad.contains(path) && !toUnload.contains(path) && map.containsKey(path)) {
toUnload.add(path);
}
}
public void unload(int vaoID) {
if (vaoTOvboIDs.containsKey(vaoID)) {
GL30.glDeleteVertexArrays(vaoID);
for (Integer i : vaoTOvboIDs.get(vaoID)) {
GL15.glDeleteBuffers(i);
}
vaoTOvboIDs.remove(vaoID);
}
}
public void process() {
if (toLoad.size() > 0 | toUnload.size() > 0) {
ready = false;
this.go = true;
}
}
public boolean isReady() {
return this.ready;
}
public void start() {
new Thread(new Runnable() {
public void run() {
System.err.println(" - BACKGROUNDMESHLOADER started - ");
init();
System.err.println(" - BACKGROUNDMESHLOADER running - ");
running = true;
while( !interrupt ) {
go = false;
while( !go ) {
try {
Thread.sleep(10);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
if (interrupt) {
break;
}
}
for (String str : toUnload) {
System.out.println("unload "+ str);
int i = (int)map.get(str)[0];
GL30.glDeleteVertexArrays(i);
Integer[] vboIds = vaoTOvboIDs.get(i);
for (Integer integer : vboIds) {
GL15.glDeleteBuffers(integer);
}
map.remove(str);
}
for (String s : toLoad) {
System.out.println("load "+s);
try {
if (map.containsKey(s)) {
throw new IllegalArgumentException("Mesh already loaded");
}
//modelData -> VAO,Size
int[] modelData = loadOBJModel(s);
System.err.println("Der zugehörige VAO Index ist: "+modelData[0]);
map.put(s, new Integer[]{modelData[0], modelData[1]});
} catch (Exception io) {
io.printStackTrace();
}
}
clearLists();
ready = true;
}
running = false;
System.err.println(" - BACKGROUNDMESHLOADER finished - ");
}
private void init() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
sd.makeCurrent();
} catch (LWJGLException e) {
e.printStackTrace();
}
}
}).start();
}
public int loadToVAO(float[] positions, float[] textureCoords, float[] normals, int[] indices) {
int vaoID = createVAO();
if (vaoID == 1 && !appeared) {
appeared = true;
} else if (vaoID == 1 && appeared){
//throw new IllegalArgumentException("He really creates two ids with index 1");
}
Integer vbos[] = new Integer[4];
vbos[0] = (bindIndicesBuffer(indices));
vbos[1] = (storeDataInAttributeList(0,3,positions));
vbos[2] = (storeDataInAttributeList(1,2,textureCoords));
vbos[3] = (storeDataInAttributeList(2,3,normals));
unbindVAO();
vaoTOvboIDs.put(vaoID,vbos);
return vaoID;
}
//Laden eines Modells aus einer .obj-Datei
public int[] loadOBJModel(String fileName) {
FileReader fr = null;
//Pathing:
//models
//-buildings
//-objects
//-scene
//-test
try {
fr = new FileReader(new File("res/models/"+fileName+".obj"));
} catch (FileNotFoundException e) {
System.err.println("Couldnt find file: res/models/"+fileName+".obj");
e.printStackTrace();
}
BufferedReader br = new BufferedReader(fr);
String line;
List<Vector3f> vertices = new ArrayList<Vector3f>();
List<Vector2f> textures = new ArrayList<Vector2f>();
List<Vector3f> normals = new ArrayList<Vector3f>();
List<Integer> indices = new ArrayList<Integer>();
float[] verticesArray = null;
float[] normalsArray = null;
float[] textureArray = null;
int[] indicesArray = null;
try {
while(true) {
line = br.readLine();
String[] currentLine = line.split(" ");
if (line.startsWith("v ")) {
Vector3f vertex = new Vector3f(Float.parseFloat(currentLine[1]),Float.parseFloat(currentLine[2]),Float.parseFloat(currentLine[3]));
vertices.add(vertex);
} else if (line.startsWith("vt ")) {
Vector2f texturecoord = new Vector2f(Float.parseFloat(currentLine[1]),Float.parseFloat(currentLine[2]));
textures.add(texturecoord);
} else if (line.startsWith("vn ")) {
Vector3f normal = new Vector3f(Float.parseFloat(currentLine[1]),Float.parseFloat(currentLine[2]),Float.parseFloat(currentLine[3]));
normals.add(normal);
} else if (line.startsWith("f ")) {
textureArray = new float[vertices.size() * 2];
normalsArray = new float[vertices.size() * 3];
break;
}
}
while(line != null) {
if (!line.startsWith("f ")) {
line = br.readLine();
continue;
}
String[] currentLine = line.split(" ");
String[] vertex1 = currentLine[1].split("/");
String[] vertex2 = currentLine[2].split("/");
String[] vertex3 = currentLine[3].split("/");
processFVertex(vertex1,indices,textures,normals,textureArray,normalsArray);
processFVertex(vertex2,indices,textures,normals,textureArray,normalsArray);
processFVertex(vertex3,indices,textures,normals,textureArray,normalsArray);
line = br.readLine();
}
br.close();
} catch(Exception e) {
e.printStackTrace();
}
verticesArray = new float[vertices.size() * 3];
indicesArray = new int[indices.size()];
int vertexPointer = 0;
for (Vector3f vec : vertices) {
verticesArray[vertexPointer++] = vec.x;
verticesArray[vertexPointer++] = vec.y;
verticesArray[vertexPointer++] = vec.z;
}
for (int i = 0 ;i < indices.size();i++) {
indicesArray[i] = indices.get(i);
}
return new int[] {loadToVAO(verticesArray, textureArray, normalsArray, indicesArray),indices.size()};
}
//Datenarray verwerten
private void processFVertex(String[] vertexData, List<Integer> indices, List<Vector2f> textures, List<Vector3f> normals, float[] textureArray, float[] normalsArray) {
int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
indices.add(currentVertexPointer);
Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
textureArray[currentVertexPointer * 2] = currentTex.x;
textureArray[currentVertexPointer * 2 + 1] = 1 - currentTex.y;
Vector3f currentNorm = normals.get(Integer.parseInt(vertexData[2]) - 1);
normalsArray[currentVertexPointer * 3] = currentNorm.x;
normalsArray[currentVertexPointer * 3 + 1] = currentNorm.y;
normalsArray[currentVertexPointer * 3 + 2] = currentNorm.z;
}
public Map<String, Integer[]> getMeshIDMap() {
return map;
}
//Interrupten
public void stop() {
interrupt = true;
System.out.println("bgml interrupted");
}
public boolean isRunning() {
return this.running;
}
public void clearLists() {
toLoad.clear();
toUnload.clear();
}
//Listen löschen
public void clear() {
//clears VAOs and VBOs
Set<Integer> l = vaoTOvboIDs.keySet();
for (Integer i : l) {
GL30.glDeleteVertexArrays(i);
}
Collection<Integer[]> vboIds = vaoTOvboIDs.values();
for (Integer[] i : vboIds) {
for (Integer integer : i) {
GL15.glDeleteBuffers(integer);
}
}
vaoTOvboIDs.clear();
toLoad.clear();
toUnload.clear();
map.clear();
}
}
Loader:
Code:
package loader;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
public class Loader {
protected static int createVAO() {
//creates a new VAO
int vaoID = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoID);
return vaoID;
}
protected static void unbindVAO() {
GL30.glBindVertexArray(0);
}
protected static int storeDataInAttributeList(int attributeNumber, int coordinateSize, float[] data) {
int vboID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
FloatBuffer buffer = storeDataInFloatBuffer(data);
//-> static draw : gl now knows, we dont want to edit the stored data any more
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(attributeNumber, coordinateSize, GL11.GL_FLOAT, false, 0,0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
return vboID;
}
protected static int bindIndicesBuffer(int[] indices) {
int vboID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboID);
IntBuffer buffer = storeDataInIntBuffer(indices);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
return vboID;
}
private static IntBuffer storeDataInIntBuffer (int[] data) {
IntBuffer buffer = BufferUtils.createIntBuffer(data.length);
buffer.put(data);
buffer.flip();
return buffer;
}
private static FloatBuffer storeDataInFloatBuffer(float[] data) {
FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
buffer.put(data);
buffer.flip();
return buffer;
}
}
BackgroundTerrainLoader-Methode(s.u.):
Code:
public void load(int x, int z) {
if (isCurrentlyLoaded(x,z)) {
System.out.println("Tell me a lie");
return;
}
File f = new File("res/textures/terrains/"+x+"_"+z+"_h.png");
if (!f.exists()) {
System.err.println(f.getAbsolutePath());
System.err.println(x+ " | "+z+" nicht gefunden [FILE]");
throw new IllegalArgumentException("!!!");
}
BufferedImage image = null;
try {
image = ImageIO.read(f);
} catch (IOException io) {
io.printStackTrace();
}
//Lade die Hilfsdateien für die Normalen an den Kanten
if (wantToKnow) System.out.println("Für "+x+"|"+z+" geladen");
f = new File("res/textures/terrains/"+(x-1)+"_"+z+"_h.png");
BufferedImage hxmimage = null;
if (f.exists()) {
try {
hxmimage = ImageIO.read(f);
} catch (IOException io) {
io.printStackTrace();
}
}
f = new File("res/textures/terrains/"+(x+1)+"_"+z+"_h.png");
BufferedImage hxpimage = null;
if (f.exists()) {
try {
hxpimage = ImageIO.read(f);
} catch (IOException io) {
io.printStackTrace();
}
}
f = new File("res/textures/terrains/"+(x)+"_"+(z-1)+"_h.png");
BufferedImage hzmimage = null;
if (f.exists()) {
try {
hzmimage = ImageIO.read(f);
} catch (IOException io) {
io.printStackTrace();
}
}
f = new File("res/textures/terrains/"+(x)+"_"+(z+1)+"_h.png");
BufferedImage hzpimage = null;
if (f.exists()) {
try {
hzpimage = ImageIO.read(f);
} catch (IOException io) {
io.printStackTrace();
}
}
int VERTEX_COUNT = image.getHeight();
//image.getHeight() is usually 256
int count = VERTEX_COUNT * VERTEX_COUNT;
float[] vertices = new float[count * 3];
float[][] heights = new float[VERTEX_COUNT][VERTEX_COUNT];
float[] normals = new float[count * 3];
float[] textureCoords = new float[count * 2];
int[] indices = new int[6 * (VERTEX_COUNT - 1) * (VERTEX_COUNT - 1)];
int vertexPointer = 0;
for (int i = 0;i < VERTEX_COUNT;i++) {
for (int j = 0;j < VERTEX_COUNT;j++) {
vertices[vertexPointer * 3] = (float)j / ((float) VERTEX_COUNT - 1) * Terrain.SIZE;
float height = getHeight(j,i,image);
vertices[vertexPointer * 3 + 1] = height;
heights[j][i] = height;
vertices[vertexPointer * 3 + 2] = (float)i / ((float) VERTEX_COUNT - 1) * Terrain.SIZE;
Vector3f normal = calculateNormal(j,i,image,hxmimage,hxpimage,hzmimage,hzpimage);
normals[vertexPointer*3] = normal.x;
normals[vertexPointer*3 + 1] = normal.y;
normals[vertexPointer*3 + 2] = normal.z;
textureCoords[vertexPointer * 2] = (float)j / ((float)VERTEX_COUNT - 1);
textureCoords[vertexPointer * 2 + 1] = (float)i / ((float)VERTEX_COUNT - 1);
vertexPointer++;
}
}
vertexPointer = 0;
for (int gz = 0; gz < VERTEX_COUNT - 1; gz++) {
for (int gx = 0; gx < VERTEX_COUNT - 1;gx++) {
int topLeft = (gz * VERTEX_COUNT + gx);
int topRight = topLeft + 1;
int bottomLeft = (gz + 1) * VERTEX_COUNT + gx;
int bottomRight = bottomLeft + 1;
indices[vertexPointer++] = topLeft;
indices[vertexPointer++] = bottomLeft;
indices[vertexPointer++] = topRight;
indices[vertexPointer++] = topRight;
indices[vertexPointer++] = bottomLeft;
indices[vertexPointer++] = bottomRight;
}
}
TerrainTexturePack ttp = new TerrainTexturePack(null,null,null,null,null);
this.bgtl.load(new Texture("terrains/"+x+"_"+z+"_b",(byte) 0));
ttp.setBlendMapPath("terrains/"+x+"_"+z+"_b");
File file = new File("res/game/terrains/"+x+"_"+z+"_t.txt");
char[] content = new char[(int)file.length()];
try {
FileReader fr = new FileReader(file);
fr.read(content);
fr.close();
} catch (IOException io) {
io.printStackTrace();
}
String str = new String(content);
String[] strings = str.split(":");
if (!strings[0].equals("null")) {
ttp.setRTexturePath(strings[0]);
this.bgtl.load(new Texture(strings[0],(byte) 0));
} else {
ttp.setRTexturePath("NULL");
}
if (!strings[1].equals("null")) {
ttp.setGTexturePath(strings[1]);
this.bgtl.load(new Texture(strings[1],(byte) 0));
} else {
ttp.setGTexturePath("NULL");
}
if (!strings[2].equals("null")) {
ttp.setBTexturePath(strings[2]);
this.bgtl.load(new Texture(strings[2],(byte) 0));
} else {
ttp.setBTexturePath("NULL");
}
if (!strings[3].equals("null")) {
ttp.setBackgroundTexturePath(strings[3]);
this.bgtl.load(new Texture(strings[3],(byte) 0));
}
int vaoID = this.bgml.loadToVAO(vertices,textureCoords,normals,indices);
System.err.println("Terrain VAO ID: "+vaoID);
this.allTerrains.add(new Terrain(x,z,ttp,vaoID,heights));
}
Beim Laden von Terrains werden die Heightmaps geladen und direkt in Koordinaten und Normalen aufgeteilt, dann BlendMaps geladen und in ein Terrain Objekt gepackt.
Es wird dann nur die Methode BackgroundMeshLoader.loadToVAO(Daten); aufgerufen.
Beim Laden von Modellen verwende ich die Methode loadOBJModell().
Soooo...
Die Terrains lade ich zuerst.
Die Methode GL30.glGenVertexArrays() erstellt IDs von 1 bis 169.
Wenn ich DANACH (-> Terrainladen ist fertig) die Modelle lade, beginnt die Methode wieder bei 1.
Das Ergebnis davon ist, dass ich, wenn ich genügend Vertices malen lasse, eine untexturiertes Terrain sehe (schwarz).
Er überschreibt also, wie eingestellt, bereits gespeicherte Daten nicht.
Aber warum fängt er jetzt wieder bei 1 an?
Bin auch offen für Anregungen zum generellen Aufbau usw.
Danke im Vorraus,
eMmiE