PyOpenGL - 如何加载在 mtl 文件中定义了颜色的 obj 文件

Posted

技术标签:

【中文标题】PyOpenGL - 如何加载在 mtl 文件中定义了颜色的 obj 文件【英文标题】:PyOpenGL - How load obj file that has color defined in the mtl file 【发布时间】:2021-03-08 22:32:24 【问题描述】:

我有两个文件,drone2.obj 和drone2.mtl。我相信 .mtl 文件使用 RGB 颜色代码作为颜色,而不是使用 .jpg/.png 纹理。这是使用 3D Builder 和 PyOpenGL 加载drone2.obj 时的样子:

我已经阅读了几个教程(Youtube 频道 sentdex、atibyte、The Cherno)。我还尝试了网站 learnopengl.com、opengl-tutorial.org 和 codeloop.com。我也用过 Udemy。这些都没有具体说明如何加载使用 .mtl 文件中定义的 RGB 颜色的模型。我不确定要使用哪些关键字来查找有关如何在 OpenGL 中为 .obj 文件加载这些 .mtl RGB 值的教程。

有人可以帮我在 PyOpenGL 中正确加载 .obj/.mtl 颜色吗?这是我的代码和 .mtl 值:

import glfw
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader
import pyrr
from ObjLoader import ObjLoader


vertex_src = """
# version 330

layout(location = 0) in vec3 a_position;
layout(location = 1) in vec2 a_texture;
layout(location = 2) in vec3 a_normal;

uniform mat4 model;
uniform mat4 projection;
uniform mat4 view;

out vec2 v_texture;

void main()

    gl_Position = projection * view * model * vec4(a_position, 1.0);
    v_texture = a_texture;

"""

fragment_src = """
# version 330

in vec2 v_texture;

out vec4 out_color;

uniform sampler2D s_texture;

void main()

    out_color = texture(s_texture, v_texture);

"""


# glfw callback functions
def window_resize(window, width, height):
    glViewport(0, 0, width, height)
    projection = pyrr.matrix44.create_perspective_projection_matrix(45, width / height, 0.1, 100)
    glUniformMatrix4fv(proj_loc, 1, GL_FALSE, projection)


# initializing glfw library
if not glfw.init():
    raise Exception("glfw can not be initialized!")

# creating the window
window = glfw.create_window(640, 480, "My OpenGL window", None, None)

# check if window was created
if not window:
    glfw.terminate()
    raise Exception("glfw window can not be created!")

# set window's position
glfw.set_window_pos(window, 400, 200)

# set the callback function for window resize
glfw.set_window_size_callback(window, window_resize)

# make the context current
glfw.make_context_current(window)

# load here the 3d meshes

drone_indices, drone_buffer = ObjLoader.load_model("drone2.obj")

shaderObj = compileProgram(compileShader(vertex_src, GL_VERTEX_SHADER), compileShader(fragment_src, GL_FRAGMENT_SHADER))

# VAO and VBO
VAO = glGenVertexArrays(2)
VBO = glGenBuffers(2)

# VAO
glBindVertexArray(VAO[0])
# Vertex Buffer Object
glBindBuffer(GL_ARRAY_BUFFER, VBO[0])
glBufferData(GL_ARRAY_BUFFER, drone_buffer.nbytes, drone_buffer, GL_STATIC_DRAW)
# vertices
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, drone_buffer.itemsize * 8, ctypes.c_void_p(0))
# textures
glEnableVertexAttribArray(1)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, drone_buffer.itemsize * 8, ctypes.c_void_p(12))
# normals
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, drone_buffer.itemsize * 8, ctypes.c_void_p(20))
glEnableVertexAttribArray(2)

glUseProgram(shaderObj)
glClearColor(0, 0.1, 0.1, 1)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

projection = pyrr.matrix44.create_perspective_projection_matrix(45, 640 / 480, 0.1, 100)
#drone_pos = pyrr.matrix44.create_from_translation(pyrr.Vector3([0, -5, -10]))
drone_pos = pyrr.matrix44.create_from_translation(pyrr.Vector3([0, 0, 0]))

# eye, target, up
view = pyrr.matrix44.create_look_at(pyrr.Vector3([0, 0, 8]), pyrr.Vector3([0, 0, 0]), pyrr.Vector3([0, 1, 0]))

model_loc = glGetUniformLocation(shaderObj, "model")
proj_loc = glGetUniformLocation(shaderObj, "projection")
view_loc = glGetUniformLocation(shaderObj, "view")

glUniformMatrix4fv(proj_loc, 1, GL_FALSE, projection)
glUniformMatrix4fv(view_loc, 1, GL_FALSE, view)

# the main application loop
while not glfw.window_should_close(window):
    glfw.poll_events()

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    rot_y = pyrr.Matrix44.from_y_rotation(0.8 * glfw.get_time())
    model = pyrr.matrix44.multiply(rot_y, drone_pos)

    # draw the drone character
    glBindVertexArray(VAO[0])
    glUniformMatrix4fv(model_loc, 1, GL_FALSE, model)
    glDrawArrays(GL_TRIANGLES, 0, len(drone_indices))

    glfw.swap_buffers(window)

# terminate glfw, free up allocated resources
glfw.terminate()

以下是 .mtl 值:

# Blender MTL File: 'drone2.blend'
# Material Count: 6

newmtl drakgray
Ns 225.000000
Ka 0.800000 0.800000 0.800000
Kd 0.067358 0.067358 0.067358
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 3

newmtl flare
Ns 323.999994
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2

newmtl gray
Ns 56.250000
Ka 0.200000 0.200000 0.200000
Kd 0.229457 0.229457 0.229457
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 3

newmtl red
Ns 323.999994
Ka 1.000000 1.000000 1.000000
Kd 0.287604 0.005644 0.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2

newmtl silver
Ns 506.250000
Ka 1.000000 1.000000 1.000000
Kd 0.815288 0.815288 0.815288
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 3

newmtl white
Ns 81.000006
Ka 0.500000 0.500000 0.500000
Kd 0.668079 0.668079 0.668079
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 3

这就是drone2.obj 开头的样子:

# Blender v2.82 (sub 7) OBJ File: 'drone2.blend'
# www.blender.org
mtllib drone2.mtl
o Cylinder
v 0.848355 0.230875 -0.375419
v 0.921437 0.252606 -0.360946
<...>

【问题讨论】:

IIRC 颜色在 Kd 中,所以只需将其用作使用材质设置的面的颜色 ... 【参考方案1】:
import pygame
from OpenGL.GL import *

def MTL(filename):
    contents = 
    mtl = None
    for line in open(filename, "r"):
        if line.startswith('#'): continue
        values = line.split()
        if not values: continue
        if values[0] == 'newmtl':
            mtl = contents[values[1]] = 
        elif mtl is None:
            raise ValueError("mtl file doesn't start with newmtl stmt")
        elif values[0] == 'map_Kd':
            # load the texture referred to by this declaration
            mtl[values[0]] = values[1]
            surf = pygame.image.load(mtl['map_Kd'])
            image = pygame.image.tostring(surf, 'RGBA', 1)
            ix, iy = surf.get_rect().size
            texid = mtl['texture_Kd'] = glGenTextures(1)
            glBindTexture(GL_TEXTURE_2D, texid)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                GL_LINEAR)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                GL_LINEAR)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ix, iy, 0, GL_RGBA,
                GL_UNSIGNED_BYTE, image)
            
        else:
            mtl[values[0]] = map(float, values[1:])
    return contents

class OBJ:
    def __init__(self, filename, swapyz=False):
        """Loads a Wavefront OBJ file. """
        self.vertices = []
        self.normals = []
        self.texcoords = []
        self.faces = []

        material = None
        for line in open(filename, "r"):
            if line.startswith('#'): continue
            values = line.split()
            if not values: continue
            if values[0] == 'v':
                v = list(map(lambda x: float(x), values[1:4]))
                if swapyz:
                    v = v[0], v[2], v[1]
                self.vertices.append(v)
            elif values[0] == 'vn':
                v = list(map(lambda x: float(x), values[1:4]))
                if swapyz:
                    v = v[0], v[2], v[1]
                self.normals.append(v)
            elif values[0] == 'vt':
                self.texcoords.append(list(map(lambda x: float(x), values[1:3])))
            elif values[0] in ('usemtl', 'usemat'):
                material = values[1]
            elif values[0] == 'mtllib':
                self.mtl = MTL(values[1])
            elif values[0] == 'f':
                face = []
                texcoords = []
                norms = []
                for v in values[1:]:
                    w = v.split('/')
                    face.append(int(w[0]))
                    if len(w) >= 2 and len(w[1]) > 0:
                        texcoords.append(int(w[1]))
                    else:
                        texcoords.append(0)
                    if len(w) >= 3 and len(w[2]) > 0:
                        norms.append(int(w[2]))
                    else:
                        norms.append(0)
                self.faces.append((face, norms, texcoords, material))

        self.gl_list = glGenLists(1)
        glNewList(self.gl_list, GL_COMPILE)
        glEnable(GL_TEXTURE_2D)
        glFrontFace(GL_CCW)
        for face in self.faces:
            vertices, normals, texture_coords, material = face
            
            c = list(self.mtl[material]["Kd"])
            if len(c) == 0:
                c = [1,1,1]
            else:
                self.mtl[material]["Kd"] = c
            mtl = self.mtl[material]
            if 'texture_Kd' in mtl:
                # use diffuse texmap
                glBindTexture(GL_TEXTURE_2D, mtl['texture_Kd'])
            else:
                # just use diffuse colour
                glColor(c[0], c[1], c[2])

            glBegin(GL_POLYGON)
            for i in range(len(vertices)):
                if normals[i] > 0:
                    glNormal3fv(self.normals[normals[i] - 1])
                if texture_coords[i] > 0:
                    glTexCoord2fv(self.texcoords[texture_coords[i] - 1])
                glVertex3fv(self.vertices[vertices[i] - 1])
            glEnd()
        glDisable(GL_TEXTURE_2D)
        glEndList()

见here

【讨论】:

以上是关于PyOpenGL - 如何加载在 mtl 文件中定义了颜色的 obj 文件的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法使用 obj 文件中的路径加载 mtl?

使用assimp加载mtl颜色

更改材质后再次加载原始纹理(.mtl)

WebGL入门(四十三)-WebGL加载OBJ-MTL三维模型

three.js obj 和 mtl 文件用阴影呈现黑色

实现在3D模型上加载2D图片,3D模型用obj+mtl格式的文件实现的