Open GL 中体素模型的纹理映射
Texture mapping on voxel model in Open GL
我正在 YouTube 上使用 LWJGL 关注一个关于 Open GL 的视频系列,到目前为止,我已经成功渲染 3d 模型并使用 obj 格式正确地对它们进行纹理处理。我想在我的游戏中使用基于体素的模型,所以我进入了软件 MagicaVoxel,导出了一个带有 obj 格式的纹理示例,但纹理映射不正确。确实,有些颜色似乎正确映射,但其他面具有整个纹理。
这是预期结果的图片:
实际结果:
我认为问题在于纹理和 opengl 对其进行插值的方式,纹理是一条 1*256 行的颜色,而在 obj 文件中,只有所需的颜色放在 uv 坐标上。
我做了一个更简单的例子来帮助理解发生了什么:只有 3 个相互对齐的立方体,以及一个具有 3 种不同颜色的 3 像素长的纹理,这是 obj 文件的代码,纹理是太小了,看不见,但实际上只有 3 个彩色像素。
# normals
vn -1 0 0
vn 1 0 0
vn 0 0 1
vn 0 0 -1
vn 0 -1 0
vn 0 1 0
# texcoords
vt 0.25 0.5
vt 0.5 0.5
vt 0.75 0.5
# verts
v -0.1 0 0
v -0.1 0 -0.1
v -0.1 0.1 0
v -0.1 0.1 -0.1
v 0.2 0 0
v 0.2 0 -0.1
v 0.2 0.1 0
v 0.2 0.1 -0.1
v -0.1 0 0
v -0.1 0.1 0
v 0 0 0
v 0 0.1 0
v 0.1 0 0
v 0.1 0.1 0
v 0.2 0 0
v 0.2 0.1 0
v -0.1 0 -0.1
v -0.1 0.1 -0.1
v 0 0 -0.1
v 0 0.1 -0.1
v 0.1 0 -0.1
v 0.1 0.1 -0.1
v 0.2 0 -0.1
v 0.2 0.1 -0.1
v -0.1 0 0
v 0 0 0
v 0.1 0 0
v 0.2 0 0
v -0.1 0 -0.1
v 0 0 -0.1
v 0.1 0 -0.1
v 0.2 0 -0.1
v -0.1 0.1 0
v 0 0.1 0
v 0.1 0.1 0
v 0.2 0.1 0
v -0.1 0.1 -0.1
v 0 0.1 -0.1
v 0.1 0.1 -0.1
v 0.2 0.1 -0.1
# faces
f 3/2/1 2/2/1 1/2/1
f 4/2/1 2/2/1 3/2/1
f 5/1/2 6/1/2 7/1/2
f 7/1/2 6/1/2 8/1/2
f 11/2/3 10/2/3 9/2/3
f 12/2/3 10/2/3 11/2/3
f 13/3/3 12/3/3 11/3/3
f 14/3/3 12/3/3 13/3/3
f 15/1/3 14/1/3 13/1/3
f 16/1/3 14/1/3 15/1/3
f 17/2/4 18/2/4 19/2/4
f 19/2/4 18/2/4 20/2/4
f 19/3/4 20/3/4 21/3/4
f 21/3/4 20/3/4 22/3/4
f 21/1/4 22/1/4 23/1/4
f 23/1/4 22/1/4 24/1/4
f 29/2/5 26/2/5 25/2/5
f 30/3/5 27/3/5 26/3/5
f 30/2/5 26/2/5 29/2/5
f 31/1/5 28/1/5 27/1/5
f 31/3/5 27/3/5 30/3/5
f 32/1/5 28/1/5 31/1/5
f 33/2/6 34/2/6 37/2/6
f 34/3/6 35/3/6 38/3/6
f 37/2/6 34/2/6 38/2/6
f 35/1/6 36/1/6 39/1/6
f 38/3/6 35/3/6 39/3/6
f 39/1/6 36/1/6 40/1/6
如您所见,对于每个面,为 3 个顶点拾取的 3 个 UV 坐标 是相同的,但在 OpenGL 中,这是结果(它们应该是红色、蓝色和黄色):
这是我的 OBJ 文件 reader 代码(在 Java 中),我调用它来创建要渲染的 vao 和内容:
public static RawModel loadObjModel(String fileName, Loader loader) {
FileReader fr = null;
try {
fr = new FileReader(new File("res/" + fileName + ".obj"));
} catch (FileNotFoundException e) {
System.err.println("Couldn't load file!");
e.printStackTrace();
}
BufferedReader reader = 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 = reader.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 texture = new Vector2f(Float.parseFloat(currentLine[1]),
Float.parseFloat(currentLine[2]));
textures.add(texture);
} 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 = reader.readLine();
continue;
}
String[] currentLine = line.split(" ");
String[] vertex1 = currentLine[1].split("/");
String[] vertex2 = currentLine[2].split("/");
String[] vertex3 = currentLine[3].split("/");
processVertex(vertex1,indices,textures,normals,textureArray,normalsArray);
processVertex(vertex2,indices,textures,normals,textureArray,normalsArray);
processVertex(vertex3,indices,textures,normals,textureArray,normalsArray);
line = reader.readLine();
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
verticesArray = new float[vertices.size()*3];
indicesArray = new int[indices.size()];
int vertexPointer = 0;
for(Vector3f vertex:vertices){
verticesArray[vertexPointer++] = vertex.x;
verticesArray[vertexPointer++] = vertex.y;
verticesArray[vertexPointer++] = vertex.z;
}
for(int i=0;i<indices.size();i++){
indicesArray[i] = indices.get(i);
}
return loader.loadToVAO(verticesArray, indicesArray, textureArray);
}
private static void processVertex(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;
}
这是我的片段着色器:
#version 400 core
in vec2 pass_textureCoords;
out vec4 out_colour;
uniform sampler2D textureSampler;
void main(void){
out_colour = texture(textureSampler,pass_textureCoords);
}
这是我的顶点着色器:
#version 400 core
in vec3 position;
in vec2 textureCoords;
out vec2 pass_textureCoords;
uniform mat4 transformationMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
void main(void){
gl_Position = projectionMatrix * viewMatrix * transformationMatrix * vec4(position.xyz,1.0);
pass_textureCoords = textureCoords;
}
这是我每帧调用的渲染方法:
public void render(Entity entity,StaticShader shader) {
TexturedModel texturedModel = entity.getTexturedModel();
RawModel model = texturedModel.getRawModel();
GL30.glBindVertexArray(model.getVaoID());
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
Matrix4f transformationMatrix = Maths.createTransformationMatrix(entity.getPosition(), entity.getRotation(), entity.getScale());
shader.loadTransformationMatrix(transformationMatrix);
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texturedModel.getTexture().getID());
GL11.glDrawElements(GL11.GL_TRIANGLES, model.getVertexCount(),GL11.GL_UNSIGNED_INT,0);
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL30.glBindVertexArray(0);
}
我不明白我错过了什么,而且它已经适用于具有大纹理的搅拌机模型:
您的 Wavefront OBJ 文件加载器实际上只在非常特殊的情况下工作,即当任何两个顶点不共享纹理坐标或法线时,因此 v、vn 和 vt 规范具有 1:1 对应关系彼此。
不过,通常情况并非如此。撇开你的 Wavefront OBJ 文件加载器也仅在所有 'f' 行都在所有 v、vt 和 vn 行之后才起作用这一事实(情况并非总是如此),你仍然有一些其他问题。
因此,目前的主要问题是您假设 'v' 和 'vt' 行之间存在 1:1 对应关系,但事实并非如此。通常,您不能简单地使用 'v' 索引(通过 'f' 行中的第一个 '/' 分隔值指定)作为 OpenGL 元素缓冲区索引,因为 OpenGL 只有一个元素索引,统一索引到位置、纹理和法线数组,而 Wavefront OBJ 文件格式具有三个不同的索引,每个索引分别用于位置、纹理和法线。
所以,你应该做的是rethink/rewrite你的Wavefront OBJ文件加载器,这样它基本上收集所有位置(v),纹理坐标(vt)和法线(vn)信息和然后,每当遇到面 (f) 时,将指定索引处(在 'f' 行中)的位置、纹理和法线信息附加到最终结果缓冲区中。您不能简单地使用从 'f' 行的位置索引派生的单个索引。
您要么根本不使用任何索引,要么只使用连续递增索引作为 OpenGL 元素缓冲区。
但首先,我强烈建议阅读 Wavefront OBJ 文件格式的实际规范,例如:http://paulbourke.net/dataformats/obj/
我正在 YouTube 上使用 LWJGL 关注一个关于 Open GL 的视频系列,到目前为止,我已经成功渲染 3d 模型并使用 obj 格式正确地对它们进行纹理处理。我想在我的游戏中使用基于体素的模型,所以我进入了软件 MagicaVoxel,导出了一个带有 obj 格式的纹理示例,但纹理映射不正确。确实,有些颜色似乎正确映射,但其他面具有整个纹理。
这是预期结果的图片:
实际结果:
我认为问题在于纹理和 opengl 对其进行插值的方式,纹理是一条 1*256 行的颜色,而在 obj 文件中,只有所需的颜色放在 uv 坐标上。
我做了一个更简单的例子来帮助理解发生了什么:只有 3 个相互对齐的立方体,以及一个具有 3 种不同颜色的 3 像素长的纹理,这是 obj 文件的代码,纹理是太小了,看不见,但实际上只有 3 个彩色像素。
# normals
vn -1 0 0
vn 1 0 0
vn 0 0 1
vn 0 0 -1
vn 0 -1 0
vn 0 1 0
# texcoords
vt 0.25 0.5
vt 0.5 0.5
vt 0.75 0.5
# verts
v -0.1 0 0
v -0.1 0 -0.1
v -0.1 0.1 0
v -0.1 0.1 -0.1
v 0.2 0 0
v 0.2 0 -0.1
v 0.2 0.1 0
v 0.2 0.1 -0.1
v -0.1 0 0
v -0.1 0.1 0
v 0 0 0
v 0 0.1 0
v 0.1 0 0
v 0.1 0.1 0
v 0.2 0 0
v 0.2 0.1 0
v -0.1 0 -0.1
v -0.1 0.1 -0.1
v 0 0 -0.1
v 0 0.1 -0.1
v 0.1 0 -0.1
v 0.1 0.1 -0.1
v 0.2 0 -0.1
v 0.2 0.1 -0.1
v -0.1 0 0
v 0 0 0
v 0.1 0 0
v 0.2 0 0
v -0.1 0 -0.1
v 0 0 -0.1
v 0.1 0 -0.1
v 0.2 0 -0.1
v -0.1 0.1 0
v 0 0.1 0
v 0.1 0.1 0
v 0.2 0.1 0
v -0.1 0.1 -0.1
v 0 0.1 -0.1
v 0.1 0.1 -0.1
v 0.2 0.1 -0.1
# faces
f 3/2/1 2/2/1 1/2/1
f 4/2/1 2/2/1 3/2/1
f 5/1/2 6/1/2 7/1/2
f 7/1/2 6/1/2 8/1/2
f 11/2/3 10/2/3 9/2/3
f 12/2/3 10/2/3 11/2/3
f 13/3/3 12/3/3 11/3/3
f 14/3/3 12/3/3 13/3/3
f 15/1/3 14/1/3 13/1/3
f 16/1/3 14/1/3 15/1/3
f 17/2/4 18/2/4 19/2/4
f 19/2/4 18/2/4 20/2/4
f 19/3/4 20/3/4 21/3/4
f 21/3/4 20/3/4 22/3/4
f 21/1/4 22/1/4 23/1/4
f 23/1/4 22/1/4 24/1/4
f 29/2/5 26/2/5 25/2/5
f 30/3/5 27/3/5 26/3/5
f 30/2/5 26/2/5 29/2/5
f 31/1/5 28/1/5 27/1/5
f 31/3/5 27/3/5 30/3/5
f 32/1/5 28/1/5 31/1/5
f 33/2/6 34/2/6 37/2/6
f 34/3/6 35/3/6 38/3/6
f 37/2/6 34/2/6 38/2/6
f 35/1/6 36/1/6 39/1/6
f 38/3/6 35/3/6 39/3/6
f 39/1/6 36/1/6 40/1/6
如您所见,对于每个面,为 3 个顶点拾取的 3 个 UV 坐标 是相同的,但在 OpenGL 中,这是结果(它们应该是红色、蓝色和黄色):
这是我的 OBJ 文件 reader 代码(在 Java 中),我调用它来创建要渲染的 vao 和内容:
public static RawModel loadObjModel(String fileName, Loader loader) {
FileReader fr = null;
try {
fr = new FileReader(new File("res/" + fileName + ".obj"));
} catch (FileNotFoundException e) {
System.err.println("Couldn't load file!");
e.printStackTrace();
}
BufferedReader reader = 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 = reader.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 texture = new Vector2f(Float.parseFloat(currentLine[1]),
Float.parseFloat(currentLine[2]));
textures.add(texture);
} 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 = reader.readLine();
continue;
}
String[] currentLine = line.split(" ");
String[] vertex1 = currentLine[1].split("/");
String[] vertex2 = currentLine[2].split("/");
String[] vertex3 = currentLine[3].split("/");
processVertex(vertex1,indices,textures,normals,textureArray,normalsArray);
processVertex(vertex2,indices,textures,normals,textureArray,normalsArray);
processVertex(vertex3,indices,textures,normals,textureArray,normalsArray);
line = reader.readLine();
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
verticesArray = new float[vertices.size()*3];
indicesArray = new int[indices.size()];
int vertexPointer = 0;
for(Vector3f vertex:vertices){
verticesArray[vertexPointer++] = vertex.x;
verticesArray[vertexPointer++] = vertex.y;
verticesArray[vertexPointer++] = vertex.z;
}
for(int i=0;i<indices.size();i++){
indicesArray[i] = indices.get(i);
}
return loader.loadToVAO(verticesArray, indicesArray, textureArray);
}
private static void processVertex(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;
}
这是我的片段着色器:
#version 400 core
in vec2 pass_textureCoords;
out vec4 out_colour;
uniform sampler2D textureSampler;
void main(void){
out_colour = texture(textureSampler,pass_textureCoords);
}
这是我的顶点着色器:
#version 400 core
in vec3 position;
in vec2 textureCoords;
out vec2 pass_textureCoords;
uniform mat4 transformationMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
void main(void){
gl_Position = projectionMatrix * viewMatrix * transformationMatrix * vec4(position.xyz,1.0);
pass_textureCoords = textureCoords;
}
这是我每帧调用的渲染方法:
public void render(Entity entity,StaticShader shader) {
TexturedModel texturedModel = entity.getTexturedModel();
RawModel model = texturedModel.getRawModel();
GL30.glBindVertexArray(model.getVaoID());
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
Matrix4f transformationMatrix = Maths.createTransformationMatrix(entity.getPosition(), entity.getRotation(), entity.getScale());
shader.loadTransformationMatrix(transformationMatrix);
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texturedModel.getTexture().getID());
GL11.glDrawElements(GL11.GL_TRIANGLES, model.getVertexCount(),GL11.GL_UNSIGNED_INT,0);
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL30.glBindVertexArray(0);
}
我不明白我错过了什么,而且它已经适用于具有大纹理的搅拌机模型:
您的 Wavefront OBJ 文件加载器实际上只在非常特殊的情况下工作,即当任何两个顶点不共享纹理坐标或法线时,因此 v、vn 和 vt 规范具有 1:1 对应关系彼此。 不过,通常情况并非如此。撇开你的 Wavefront OBJ 文件加载器也仅在所有 'f' 行都在所有 v、vt 和 vn 行之后才起作用这一事实(情况并非总是如此),你仍然有一些其他问题。 因此,目前的主要问题是您假设 'v' 和 'vt' 行之间存在 1:1 对应关系,但事实并非如此。通常,您不能简单地使用 'v' 索引(通过 'f' 行中的第一个 '/' 分隔值指定)作为 OpenGL 元素缓冲区索引,因为 OpenGL 只有一个元素索引,统一索引到位置、纹理和法线数组,而 Wavefront OBJ 文件格式具有三个不同的索引,每个索引分别用于位置、纹理和法线。 所以,你应该做的是rethink/rewrite你的Wavefront OBJ文件加载器,这样它基本上收集所有位置(v),纹理坐标(vt)和法线(vn)信息和然后,每当遇到面 (f) 时,将指定索引处(在 'f' 行中)的位置、纹理和法线信息附加到最终结果缓冲区中。您不能简单地使用从 'f' 行的位置索引派生的单个索引。 您要么根本不使用任何索引,要么只使用连续递增索引作为 OpenGL 元素缓冲区。
但首先,我强烈建议阅读 Wavefront OBJ 文件格式的实际规范,例如:http://paulbourke.net/dataformats/obj/