import os os.environ['__NV_PRIME_RENDER_OFFLOAD'] = '1' os.environ['__GLX_VENDOR_LIBRARY_NAME'] = 'nvidia' import numpy as np from OpenGL.GL import * from OpenGL.GL import shaders from OpenGL.GLU import * import pygame
# 버텍스 쉐이더 VERTEX_SHADER = """ #version 330 core layout(location = 0) in vec3 position; layout(location = 1) in vec3 normal;
uniform mat4 model; uniform mat4 view; uniform mat4 projection;
out vec3 FragPos; out vec3 Normal;
void main() { FragPos = vec3(model * vec4(position, 1.0)); Normal = mat3(transpose(inverse(model))) * normal; gl_Position = projection * view * model * vec4(position, 1.0); } """
# 프래그먼트 쉐이더 수정 FRAGMENT_SHADER = """ #version 330 core in vec3 FragPos; in vec3 Normal;
uniform vec3 lightPos; uniform vec3 viewPos; uniform vec3 lightColor; uniform vec3 objectColor;
out vec4 FragColor;
void main() { // 주변광 세기 증가 float ambientStrength = 0.4; vec3 ambient = ambientStrength * lightColor; // 분산광 (디퓨즈) vec3 norm = normalize(Normal); vec3 lightDir = normalize(lightPos - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = diff * lightColor; // 반사광 (스페큘러) 추가 float specularStrength = 0.5; vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); vec3 specular = specularStrength * spec * lightColor; vec3 result = (ambient + diffuse + specular) * objectColor; FragColor = vec4(result, 1.0); } """
class ModernModelViewer: def __init__(self, model_path): pygame.init() # OpenGL 설정 pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLEBUFFERS, 1) pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLESAMPLES, 4) pygame.display.gl_set_attribute(pygame.GL_DEPTH_SIZE, 24) pygame.display.gl_set_attribute(pygame.GL_STENCIL_SIZE, 8) self.display = pygame.display.set_mode((800, 600), pygame.OPENGL | pygame.DOUBLEBUF | pygame.HWSURFACE) pygame.display.set_caption("3D Model Viewer (Modern OpenGL)") # 모델 데이터 로드 self.vertices, self.faces, self.normals = self.load_obj(model_path) # 변환 행렬 초기화 self.model_matrix = np.identity(4, dtype=np.float32) self.view_matrix = np.identity(4, dtype=np.float32) self.projection_matrix = self.perspective(45, 800/600, 0.1, 100.0) # 카메라/뷰어 상태 self.camera_pos = np.array([0.0, 2.0, 5.0], dtype=np.float32) # z축 거리를 늘리고 y축으로 올립니다 self.rotation = np.array([30.0, 0.0, 0.0]) # x축으로 30도 회전하여 모델을 내려다보게 합니다 self.scale = 1.0 # 마우스 제어 관련 속성 추가 self.dragging = False self.prev_mouse_pos = None self.mouse_sensitivity = 0.8 # 쉐이더 프로그램 생성 self.shader = self.create_shader_program() # VAO, VBO 설정 self.setup_vertex_buffer() # FPS 제어 self.clock = pygame.time.Clock() self.target_fps = 144
def perspective(self, fovy, aspect, near, far): """원근 투영 행렬 생성""" f = 1.0 / np.tan(np.radians(fovy) / 2.0) return np.array([ [f/aspect, 0.0, 0.0, 0.0], [0.0, f, 0.0, 0.0], [0.0, 0.0, (far+near)/(near-far), 2*far*near/(near-far)], [0.0, 0.0, -1.0, 0.0] ], dtype=np.float32)
def create_shader_program(self): """쉐이더 프로그램 컴파일 및 링크""" try: vertex_shader = shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER) print("Vertex shader compiled successfully") fragment_shader = shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER) print("Fragment shader compiled successfully") shader_program = shaders.compileProgram(vertex_shader, fragment_shader) print("Shader program linked successfully") # 쉐이더 프로그램 검증 glValidateProgram(shader_program) validation_status = glGetProgramiv(shader_program, GL_VALIDATE_STATUS) print(f"Shader program validation status: {validation_status}") if validation_status != GL_TRUE: print(glGetProgramInfoLog(shader_program)) return shader_program except Exception as e: print(f"Error creating shader program: {str(e)}") raise
def load_obj(self, filename): """OBJ 파일 로드 및 법선 벡터 계산""" vertices = [] faces = [] normals = [] try: with open(filename, 'r') as f: for line in f: if line.startswith('v '): parts = line.split() if len(parts) >= 4: # v x y z 형식인지 확인 vertices.append([float(parts[1]), float(parts[2]), float(parts[3])]) elif line.startswith('f '): face = [] parts = line.split()[1:] # 첫 번째 'f' 제외 for part in parts: # 빈 줄이나 잘못된 형식 건너뛰기 if '/' in part: vertex_index = part.split('/')[0] if vertex_index: face.append(int(vertex_index) - 1) if len(face) >= 3: # 최소 3개의 유효한 인덱스가 있는지 확인 faces.append(face) if not vertices or not faces: raise ValueError("No valid vertices or faces found in the file") vertices = np.array(vertices, dtype=np.float32) faces = np.array(faces, dtype=np.int32) # 모델 중심을 원점으로 이동 center = (vertices.max(axis=0) + vertices.min(axis=0)) / 2 vertices -= center # 모델 크기를 1로 정규화 scale = np.max(np.abs(vertices)) if scale != 0: vertices /= scale # 버텍스 법선 계산 normals = np.zeros_like(vertices) for face in faces: if len(face) >= 3: # 안전 검사 추가 v1, v2, v3 = vertices[face[0]], vertices[face[1]], vertices[face[2]] normal = np.cross(v2 - v1, v3 - v1) norm = np.linalg.norm(normal) if norm > 1e-6: normal = normal / norm normals[face] += normal # 법선 벡터 정규화 norms = np.linalg.norm(normals, axis=1) norms[norms == 0] = 1 normals = normals / norms[:, np.newaxis] print(f"Loaded model with {len(vertices)} vertices and {len(faces)} faces") return vertices, faces, normals except Exception as e: print(f"Error loading OBJ file: {str(e)}") print(f"Current working directory: {os.getcwd()}") print(f"Trying to load file: {filename}") return np.array([]), np.array([]), np.array([])
def update_matrices(self): """변환 행렬 업데이트""" # 모델 행렬 model = np.identity(4, dtype=np.float32) # 회전 적용 rx = self.rotation_matrix(self.rotation[0], [1, 0, 0]) ry = self.rotation_matrix(self.rotation[1], [0, 1, 0]) rz = self.rotation_matrix(self.rotation[2], [0, 0, 1]) model = model.dot(rx).dot(ry).dot(rz) self.model_matrix = model # 뷰 행렬 target = np.array([0.0, 0.0, 0.0]) up = np.array([0.0, 1.0, 0.0]) self.view_matrix = self.look_at(self.camera_pos, target, up)
def rotation_matrix(self, angle, axis): """회전 행렬 생성""" angle = np.radians(angle) axis = np.array(axis) / np.linalg.norm(axis) a = np.cos(angle / 2.0) b, c, d = -axis * np.sin(angle / 2.0) return np.array([ [a*a+b*b-c*c-d*d, 2*(b*c-a*d), 2*(b*d+a*c), 0], [2*(b*c+a*d), a*a+c*c-b*b-d*d, 2*(c*d-a*b), 0], [2*(b*d-a*c), 2*(c*d+a*b), a*a+d*d-b*b-c*c, 0], [0, 0, 0, 1] ], dtype=np.float32)
def look_at(self, eye, target, up): """뷰 행렬 생성""" eye = np.array(eye, dtype=np.float32) target = np.array(target, dtype=np.float32) up = np.array(up, dtype=np.float32)
# z axis = eye - target (normalized) z = eye - target z = z / np.linalg.norm(z)
# x axis = up cross z (normalized) x = np.cross(up, z) if np.sum(x) == 0: # 'up' 벡터와 'z' 벡터가 평행한 경우 처리 x = np.array([1.0, 0.0, 0.0], dtype=np.float32) else: x = x / np.linalg.norm(x)
# y axis = z cross x (normalized) y = np.cross(z, x) y = y / np.linalg.norm(y)
translation = np.array([ [1, 0, 0, -eye[0]], [0, 1, 0, -eye[1]], [0, 0, 1, -eye[2]], [0, 0, 0, 1] ], dtype=np.float32)
rotation = np.array([ [x[0], x[1], x[2], 0], [y[0], y[1], y[2], 0], [z[0], z[1], z[2], 0], [0, 0, 0, 1] ], dtype=np.float32)
return rotation.dot(translation)
def setup_vertex_buffer(self): """VAO와 VBO 설정""" try: self.vao = glGenVertexArrays(1) glBindVertexArray(self.vao) # 모든 버텍스 데이터를 하나의 배열로 준비 vertices = [] for face in self.faces: for vertex_id in face: vertex = self.vertices[vertex_id] normal = self.normals[vertex_id] vertices.extend([*vertex, *normal]) # [x,y,z, nx,ny,nz] vertices = np.array(vertices, dtype=np.float32) self.vertex_count = len(self.faces) * 3 # VBO 생성 및 데이터 업로드 self.vbo = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, self.vbo) glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW) # vertex positions glEnableVertexAttribArray(0) glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 24, None) # vertex normals glEnableVertexAttribArray(1) glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12)) # 디버그 출력 print(f"Vertex count: {self.vertex_count}") print(f"Vertex data size: {vertices.nbytes} bytes")
except Exception as e: print(f"Error in setup_vertex_buffer: {str(e)}") raise
def render_model(self): try: glClearColor(0.1, 0.1, 0.1, 1.0) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glEnable(GL_DEPTH_TEST) glEnable(GL_MULTISAMPLE) glEnable(GL_LINE_SMOOTH) glEnable(GL_POLYGON_SMOOTH) glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST) glUseProgram(self.shader) self.update_matrices() # 조명 설정 glUniform3f(glGetUniformLocation(self.shader, "lightPos"), 3.0, 5.0, 3.0) glUniform3f(glGetUniformLocation(self.shader, "viewPos"), *self.camera_pos) glUniform3f(glGetUniformLocation(self.shader, "lightColor"), 1.0, 1.0, 0.95) glUniform3f(glGetUniformLocation(self.shader, "objectColor"), 0.9, 0.9, 0.9) # 행렬 전송 glUniformMatrix4fv(glGetUniformLocation(self.shader, "model"), 1, GL_TRUE, self.model_matrix) glUniformMatrix4fv(glGetUniformLocation(self.shader, "view"), 1, GL_TRUE, self.view_matrix) glUniformMatrix4fv(glGetUniformLocation(self.shader, "projection"), 1, GL_TRUE, self.projection_matrix) # 모델 렌더링 glBindVertexArray(self.vao) glDrawArrays(GL_TRIANGLES, 0, self.vertex_count) pygame.display.flip() except Exception as e: print(f"Render error: {str(e)}")
def run(self): """메인 렌더링 루프""" while True: try: # 이벤트 처리 for event in pygame.event.get(): if event.type == pygame.QUIT: return elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: return # 마우스 입력 처리 추가 elif event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: # 왼쪽 버튼 self.dragging = True self.prev_mouse_pos = pygame.mouse.get_pos() elif event.type == pygame.MOUSEBUTTONUP: if event.button == 1: # 왼쪽 버튼 self.dragging = False elif event.type == pygame.MOUSEMOTION: if self.dragging: x, y = pygame.mouse.get_pos() if self.prev_mouse_pos: dx = x - self.prev_mouse_pos[0] dy = y - self.prev_mouse_pos[1] self.rotation[2] -= dx * self.mouse_sensitivity # z축 회전으로 변경 self.rotation[0] -= dy * self.mouse_sensitivity # x축 유지 self.prev_mouse_pos = (x, y) self.render_model() self.clock.tick(self.target_fps) except Exception as e: print(f"Error during rendering: {str(e)}") break pygame.quit() # 리소스 정리 glDeleteVertexArrays(1, [self.vao]) glDeleteBuffers(1, [self.vbo]) glDeleteProgram(self.shader)
if __name__ == "__main__": import sys import os
if len(sys.argv) > 1: model_path = sys.argv[1] else: model_path = "12221_Cat_v1_l3.obj" # "Tree.obj" # 파일 존재 여부 확인 if not os.path.exists(model_path): print(f"Error: File '{model_path}' does not exist") sys.exit(1) try: viewer = ModernModelViewer(model_path) viewer.run() except Exception as e: print(f"Error: {str(e)}") |
Member discussion