From e38fdcc2c4113c96a303b563f0358aff1849d4db Mon Sep 17 00:00:00 2001 From: Thomas FORGIONE Date: Tue, 29 Nov 2016 15:40:30 +0100 Subject: [PATCH] Removed shaders, textures are now supported --- assets/shaders/shader.frag | 1 + assets/shaders/shader.vert | 2 + d3/model/basemodel.py | 129 ++++++++------------------- d3/model/mesh.py | 173 +++++++++++++++++++++++++++++++++++++ d3/model/obj.py | 12 ++- d3/model/ply.py | 7 +- viewer.py | 7 +- 7 files changed, 228 insertions(+), 103 deletions(-) create mode 100644 d3/model/mesh.py diff --git a/assets/shaders/shader.frag b/assets/shaders/shader.frag index d1fe7ef..6ea1f92 100644 --- a/assets/shaders/shader.frag +++ b/assets/shaders/shader.frag @@ -10,5 +10,6 @@ void main() { vec3 ambientFactor = ambientLight; vec3 lambertFactor = max(vec3(0.0,0.0,0.0), dot(directionnalLight, fNormal) * directionnalLightFactor); + vec4 texel = texture2D(tex, gl_TexCoord[0].xy); gl_FragColor = vec4(ambientFactor + lambertFactor, 1.0); } diff --git a/assets/shaders/shader.vert b/assets/shaders/shader.vert index 7cfea69..86558ff 100644 --- a/assets/shaders/shader.vert +++ b/assets/shaders/shader.vert @@ -1,6 +1,8 @@ varying vec3 fNormal; +varying vec4 fTexCoord; void main() { fNormal = gl_Normal; gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; } diff --git a/d3/model/basemodel.py b/d3/model/basemodel.py index 0a682e4..5b66cab 100644 --- a/d3/model/basemodel.py +++ b/d3/model/basemodel.py @@ -4,6 +4,8 @@ from math import sqrt from ..geometry import Vector +from .mesh import Material, MeshPart + Vertex = Vector Normal = Vertex TexCoord = Vertex @@ -43,22 +45,14 @@ class Face: self.c = FaceVertex().from_array(arr[2]) return self -class Material: - def __init__(self, name): - self.name = name - self.Ka = None - self.Kd = None - self.Ks = None - self.map_Kd = None - - class ModelParser: def __init__(self): self.vertices = [] self.normals = [] self.tex_coords = [] - self.faces = [] + self.parts = [] + self.current_part = None self.bounding_box = BoundingBox() self.center_and_scale = True self.vertex_vbo = None @@ -66,6 +60,10 @@ class ModelParser: self.normal_vbo = None self.path = None + def init_textures(self): + for part in self.parts: + part.init_texture() + def add_vertex(self, vertex): self.vertices.append(vertex) self.bounding_box.add(vertex) @@ -77,7 +75,12 @@ class ModelParser: self.normals.append(normal) def add_face(self, face): - self.faces.append(face) + if self.current_part is None or face.material != self.current_part.material: + self.current_part = MeshPart(self) + self.current_part.material = face.material + self.parts.append(self.current_part) + + self.current_part.add_face(face) def parse_line(self, string): pass @@ -90,11 +93,8 @@ class ModelParser: self.parse_line(line) def draw(self): - import OpenGL.GL as gl - gl.glColor3f(1.0,0.0,0.0) - if self.center_and_scale: center = self.bounding_box.get_center() scale = self.bounding_box.get_scale() / 2 @@ -102,104 +102,43 @@ class ModelParser: gl.glScalef(1/scale, 1/scale, 1/scale) gl.glTranslatef(-center.x, -center.y, -center.z) - if self.vertex_vbo is not None: - - self.vertex_vbo.bind() - gl.glEnableClientState(gl.GL_VERTEX_ARRAY); - gl.glVertexPointerf(self.vertex_vbo) - self.vertex_vbo.unbind() - - self.normal_vbo.bind() - gl.glEnableClientState(gl.GL_NORMAL_ARRAY); - gl.glNormalPointerf(self.normal_vbo) - self.normal_vbo.unbind() - - gl.glDrawArrays(gl.GL_TRIANGLES, 0, len(self.vertex_vbo.data) * 9) - - else: - - gl.glBegin(gl.GL_TRIANGLES) - for face in self.faces: - v1 = self.vertices[face.a.vertex] - v2 = self.vertices[face.b.vertex] - v3 = self.vertices[face.c.vertex] - - if face.a.normal is not None: - n1 = self.normals[face.a.normal] - n2 = self.normals[face.b.normal] - n3 = self.normals[face.c.normal] - - if face.a.normal is not None: - gl.glNormal3f(n1.x, n1.y, n1.z) - gl.glVertex3f(v1.x, v1.y, v1.z) - - if face.b.normal is not None: - gl.glNormal3f(n2.x, n2.y, n2.z) - gl.glVertex3f(v2.x, v2.y, v2.z) - - if face.c.normal is not None: - gl.glNormal3f(n3.x, n3.y, n3.z) - gl.glVertex3f(v3.x, v3.y, v3.z) - - gl.glEnd() + for part in self.parts: + part.draw() if self.center_and_scale: gl.glPopMatrix() def generate_vbos(self): - from OpenGL.arrays import vbo - from numpy import array - # Build VBO - v = [] - n = [] - t = [] - - for face in self.faces: - v1 = self.vertices[face.a.vertex] - v2 = self.vertices[face.b.vertex] - v3 = self.vertices[face.c.vertex] - v += [[v1.x, v1.y, v1.z], [v2.x, v2.y, v2.z], [v3.x, v3.y, v3.z]] - - if face.a.normal is not None: - n1 = self.normals[face.a.normal] - n2 = self.normals[face.b.normal] - n3 = self.normals[face.c.normal] - n += [[n1.x, n1.y, n1.z], [n2.x, n2.y, n2.z], [n3.x, n3.y, n3.z]] - - if face.a.tex_coord is not None: - t1 = self.tex_coords[face.a.tex_coord] - t2 = self.tex_coords[face.b.tex_coord] - t3 = self.tex_coords[face.c.tex_coord] - t += [[t1.x, t1.y], [t2.x, t2.y], [t3.x, t3.y]] - - self.vertex_vbo = vbo.VBO(array(v, 'f')) - self.normal_vbo = vbo.VBO(array(n, 'f')) - self.texture_vbo = vbo.VBO(array(t, 'f')) + for part in self.parts: + part.generate_vbos() def generate_vertex_normals(self): + self.normals = [Normal() for i in self.vertices] - for face in self.faces: - v1 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.b.vertex]) - v2 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.c.vertex]) - cross = Vertex.cross_product(v1, v2) - self.normals[face.a.vertex] += cross - self.normals[face.b.vertex] += cross - self.normals[face.c.vertex] += cross + for part in self.parts: + for face in part.faces: + v1 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.b.vertex]) + v2 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.c.vertex]) + cross = Vertex.cross_product(v1, v2) + self.normals[face.a.vertex] += cross + self.normals[face.b.vertex] += cross + self.normals[face.c.vertex] += cross for normal in self.normals: normal.normalize() - for face in self.faces: - face.a.normal = face.a.vertex - face.b.normal = face.b.vertex - face.c.normal = face.c.vertex + for part in self.parts: + for face in part.faces: + face.a.normal = face.a.vertex + face.b.normal = face.b.vertex + face.c.normal = face.c.vertex def generate_face_normals(self): - self.normals = [Normal() for i in self.faces] + self.normals = [Normal() for i in sum(list(map(lambda x: len(x), self.faces)))] - for (index, face) in enumerate(self.faces): + for (index, face) in enumerate(sum(self.faces, [])): v1 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.b.vertex]) v2 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.c.vertex]) diff --git a/d3/model/mesh.py b/d3/model/mesh.py new file mode 100644 index 0000000..8f048db --- /dev/null +++ b/d3/model/mesh.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python + +class Material: + def __init__(self, name): + self.name = name + self.Ka = None + self.Kd = None + self.Ks = None + self.map_Kd = None + self.id = None + + def init_texture(self): + + import OpenGL.GL as gl + + # Already initialized + if self.id is not None: + return + + # If no map_Kd, nothing to do + if self.map_Kd is None: + return + + try: + ix, iy, image = self.map_Kd.size[0], self.map_Kd.size[1], self.map_Kd.tobytes("raw", "RGBA", 0, -1) + except SystemError: + ix, iy, image = self.map_Kd.size[0], self.map_Kd.size[1], self.map_Kd.tobytes("raw", "RGBX", 0, -1) + + self.id = gl.glGenTextures(1) + + gl.glBindTexture(gl.GL_TEXTURE_2D, self.id) + gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT,1) + + gl.glTexImage2D( + gl.GL_TEXTURE_2D, 0, 3, ix, iy, 0, + gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, image + ) + + def bind(self): + from OpenGL import GL as gl + + gl.glEnable(gl.GL_TEXTURE_2D) + gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST) + gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST) + gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_DECAL) + gl.glBindTexture(gl.GL_TEXTURE_2D, self.id) + + def unbind(self): + pass + + +class MeshPart: + def __init__(self, parent): + self.parent = parent + self.material = None + self.vertex_vbo = None + self.tex_coord_vbo = None + self.normal_vbo = None + self.faces = [] + + def init_texture(self): + if self.material is not None: + self.material.init_texture() + + def add_face(self, face): + self.faces.append(face) + + def generate_vbos(self): + + from OpenGL.arrays import vbo + from numpy import array + + # Build VBO + v = [] + n = [] + t = [] + + for face in self.faces: + v1 = self.parent.vertices[face.a.vertex] + v2 = self.parent.vertices[face.b.vertex] + v3 = self.parent.vertices[face.c.vertex] + v += [[v1.x, v1.y, v1.z], [v2.x, v2.y, v2.z], [v3.x, v3.y, v3.z]] + + if face.a.normal is not None: + n1 = self.parent.normals[face.a.normal] + n2 = self.parent.normals[face.b.normal] + n3 = self.parent.normals[face.c.normal] + n += [[n1.x, n1.y, n1.z], [n2.x, n2.y, n2.z], [n3.x, n3.y, n3.z]] + + if face.a.tex_coord is not None: + t1 = self.parent.tex_coords[face.a.tex_coord] + t2 = self.parent.tex_coords[face.b.tex_coord] + t3 = self.parent.tex_coords[face.c.tex_coord] + t += [[t1.x, t1.y], [t2.x, t2.y], [t3.x, t3.y]] + + self.vertex_vbo = vbo.VBO(array(v, 'f')) + + if len(n) > 0: + self.normal_vbo = vbo.VBO(array(n, 'f')) + + if len(t) > 0: + self.tex_coord_vbo = vbo.VBO(array(t, 'f')) + + def draw(self): + + if self.material is not None: + self.material.bind() + + if self.vertex_vbo is not None: + self.draw_from_vbos() + else: + self.draw_from_arrays() + + if self.material is not None: + self.material.unbind() + + def draw_from_vbos(self): + + import OpenGL.GL as gl + + self.vertex_vbo.bind() + gl.glEnableClientState(gl.GL_VERTEX_ARRAY); + gl.glVertexPointerf(self.vertex_vbo) + self.vertex_vbo.unbind() + + if self.normal_vbo is not None: + self.normal_vbo.bind() + gl.glEnableClientState(gl.GL_NORMAL_ARRAY) + gl.glNormalPointerf(self.normal_vbo) + self.normal_vbo.unbind() + + if self.tex_coord_vbo is not None: + + if self.material is not None: + self.material.bind() + + self.tex_coord_vbo.bind() + gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY) + gl.glTexCoordPointerf(self.tex_coord_vbo) + self.tex_coord_vbo.unbind() + + gl.glDrawArrays(gl.GL_TRIANGLES, 0, len(self.vertex_vbo.data) * 9) + + def draw_from_arrays(self): + pass + +class Mesh: + def __init__(self): + self.vertices = [] + self.tex_coords = [] + self.normals = [] + self.parts = [] + self.current_material = None + self.current_part = None + + def draw(self): + for part in self.parts: + part.draw() + + def add_face(self, face): + if self.current_part is None or face.material != self.current_part.material: + self.current_part = MeshPart(self) + self.current_part.material = face.material + self.parts.append(self.current_part) + + self.current_part.add_face(face) + + def generate_vbos(self): + for part in self.parts: + part.generate_vbos() + + + diff --git a/d3/model/obj.py b/d3/model/obj.py index c3bad83..b40b2dc 100644 --- a/d3/model/obj.py +++ b/d3/model/obj.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 -from .basemodel import ModelParser, Exporter, Vertex, TexCoord, Normal, FaceVertex, Face, Material +from .basemodel import ModelParser, Exporter, Vertex, TexCoord, Normal, FaceVertex, Face +from .mesh import Material, MeshPart from functools import reduce import os.path import PIL.Image @@ -24,7 +25,7 @@ class OBJParser(ModelParser): split = split[1:] if first == 'usemtl': - self.current_material = split[0] + self.current_material = self.mtl[split[0]] elif first == 'mtllib': path = os.path.join(os.path.dirname(self.path), split[0]) self.mtl = MTLParser(self) @@ -73,7 +74,7 @@ class MTLParser: elif first == 'Ks': self.current_mtl.Ks = Vertex().from_array(split) elif first == 'map_Kd': - self.map_Kd = PIL.Image.open(os.path.join(os.path.dirname(self.parent.path), split[0])) + self.current_mtl.map_Kd = PIL.Image.open(os.path.join(os.path.dirname(self.parent.path), split[0])) def parse_file(self, path): @@ -82,6 +83,11 @@ class MTLParser: line = line.rstrip() self.parse_line(line) + def __getitem__(self, key): + for material in self.materials: + if material.name == key: + return material + class OBJExporter(Exporter): def __init__(self, model): diff --git a/d3/model/ply.py b/d3/model/ply.py index 1f08a12..a433c20 100644 --- a/d3/model/ply.py +++ b/d3/model/ply.py @@ -82,6 +82,9 @@ class PLYExporter(Exporter): super().__init__(model) def __str__(self): + + faces = sum([part.faces for part in self.model.parts], []) + # Header string = "ply\nformat ascii 1.0\ncomment Automatically gnerated by model-converter\n" @@ -90,7 +93,7 @@ class PLYExporter(Exporter): string += "property float32 x\nproperty float32 y\nproperty float32 z\n" # Types : faces - string += "element face " + str(len(self.model.faces)) + "\n" + string += "element face " + str(len(faces)) + "\n" string += "property list uint8 int32 vertex_indices\n" # End header @@ -100,7 +103,7 @@ class PLYExporter(Exporter): for vertex in self.model.vertices: string += str(vertex.x) + " " + str(vertex.y) + " " + str(vertex.z) + "\n" - for face in self.model.faces: + for face in faces: string += "3 " + str(face.a.vertex) + " " + str(face.b.vertex) + " " + str(face.c.vertex) + "\n" return string diff --git a/viewer.py b/viewer.py index 310410c..035775d 100755 --- a/viewer.py +++ b/viewer.py @@ -43,9 +43,10 @@ def main(args): running = True model = load_model(args.input) + model.init_textures() model.generate_vbos() - shader = DefaultShader() + # shader = DefaultShader() while running: for event in pg.event.get(): @@ -72,9 +73,9 @@ def main(args): gl.glPushMatrix() controls.apply() - shader.bind() + # shader.bind() model.draw() - shader.unbind() + # shader.unbind() gl.glPopMatrix() gl.glFlush()