Class: Bestguigui::Render3D::ObjModel
- Inherits:
-
Object
- Object
- Bestguigui::Render3D::ObjModel
- Includes:
- OpenGL
- Defined in:
- lib/bestguigui/render_3d/obj_model.rb
Overview
Modèle 3D chargé depuis un fichier .obj. Export "Wavefront .obj"
standard, ce que Blender et la plupart des logiciels 3D savent
produire. Gère les objets/groupes en o ou g, avec ou sans
normales, triangulés (une face = 3 sommets).
La texture est trouvée dans cet ordre :
1. Le .obj référence un .mtl (`mtllib`) → une texture par matériau
(`usemtl`/`map_Kd`).
2. `texture:` est passé explicitement → toute la géométrie utilise
cette texture (pratique quand plusieurs modèles partagent une
même feuille de texture, ex: gfx/textures.png pour tout le jeu).
3. Sinon, une image du même nom que le .obj à côté de lui
(car.obj -> car.png/.jpg) est cherchée et utilisée.
4. Si rien de tout ça ne donne de texture, erreur explicite plutôt
qu'un crash cryptique au premier appel à #draw.
La géométrie est aplatie en un Mesh (VBO) par matériau (ou un seul Mesh si texture unique) au chargement — #draw ne fait plus que positionner/tourner et dessiner ces Mesh déjà en mémoire GPU, plutôt que de reconstruire des triangles en immediate mode à chaque frame.
desk = Bestguigui::Render3D::ObjModel.new("gfx/models/desk.obj", transparent: true)
desk.draw(camera, x, y, z, rotation_y)
car = Bestguigui::Render3D::ObjModel.new("gfx/car.obj", texture: shared_texture)
car.draw(camera, x, y, z, rotation_y, scale: 16) # .obj à l'échelle native de l'outil 3D
lighting: true bascule sur un éclairage Gouraud (Shader.gouraud,
voir Bestguigui::Render3D::Light pour régler direction/couleur) —
look "PS1/FF7", faces à l'ombre assombries plutôt qu'un rendu unlit
plat. Utilise les normales du .obj (vn) si présentes ; sinon calcule
une normale par face (aspect facetté, cohérent avec un rendu bas-poly).
hero = Bestguigui::Render3D::ObjModel.new("gfx/hero.obj", texture: tex, lighting: true)
Constant Summary collapse
- IMAGE_EXTENSIONS =
%w[.png .jpg .jpeg].freeze
Instance Method Summary collapse
-
#draw(camera, x = 0, y = 0, z = 0, rotation_y = 0, scale: 1) ⇒ Object
scale:est utile quand le .obj a été exporté à l'échelle native de l'outil 3D (souvent des coordonnées ~0.01-1 dans Blender) plutôt qu'à l'échelle du monde du jeu — 1 par défaut, ne change rien si le modèle est déjà à la bonne taille. -
#initialize(obj_path, transparent: false, texture: nil, lighting: false, shader: nil) ⇒ ObjModel
constructor
A new instance of ObjModel.
Constructor Details
#initialize(obj_path, transparent: false, texture: nil, lighting: false, shader: nil) ⇒ ObjModel
Returns a new instance of ObjModel.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/bestguigui/render_3d/obj_model.rb', line 42 def initialize(obj_path, transparent: false, texture: nil, lighting: false, shader: nil) @transparent = transparent @lighting = lighting @shader = shader || (lighting ? Shader.gouraud : Shader.default) @groups = {} # material/groupe => Mesh (mode multi-matériaux) @mesh = nil # mode texture unique vertices, tex_coords, normals = [], [], [] faces = {} materials = nil current = "default" faces[current] = { material: nil, triangles: [] } File.readlines(obj_path).each do |line| infos = line.chomp.split(" ") case infos[0] when "mtllib" materials = MaterialCollection.new(File.join(File.dirname(obj_path), infos[1])) when "v" vertices.push infos.drop(1).map(&:to_f) when "vt" tex_coords.push infos.drop(1).map(&:to_f) when "vn" normals.push infos.drop(1).map(&:to_f) when "o", "g" if infos.size > 1 current = infos.drop(1).join(" ") faces[current] ||= { material: nil, triangles: [] } end when "usemtl" faces[current][:material] = infos[1] when "f" # Chaque référence est "v/vt" ou "v/vt/vn" (parfois juste "v"). triangle = infos[1..3].map { |v| v.split("/").map { |i| i.to_i - 1 } } faces[current][:triangles].push triangle end end @materials = materials @texture = texture || (materials.nil? ? find_sibling_texture(obj_path) : nil) if materials.nil? && @texture.nil? raise "Bestguigui::Render3D::ObjModel: no texture found for #{obj_path} — " \ "add a mtllib to the .obj, pass texture:, or put a same-named " \ "image next to it (#{File.basename(obj_path, ".*")}.png)." end bake(faces, vertices, tex_coords, normals) end |
Instance Method Details
#draw(camera, x = 0, y = 0, z = 0, rotation_y = 0, scale: 1) ⇒ Object
scale: est utile quand le .obj a été exporté à l'échelle native
de l'outil 3D (souvent des coordonnées ~0.01-1 dans Blender) plutôt
qu'à l'échelle du monde du jeu — 1 par défaut, ne change rien si le
modèle est déjà à la bonne taille.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/bestguigui/render_3d/obj_model.rb', line 97 def draw(camera, x = 0, y = 0, z = 0, rotation_y = 0, scale: 1) model = Mat4.translation(x, y, z) * Mat4.rotation_y(rotation_y) * Mat4.scaling(scale, scale, scale) @shader.use @shader.set_mat4("u_model", model) @shader.set_mat4("u_view", camera.view_matrix) @shader.set_mat4("u_projection", camera.projection_matrix) @shader.set_bool("u_alpha_test", @transparent) @shader.set_vec3("u_tint", 1, 1, 1) @shader.set_int("u_texture", 0) if @lighting @shader.set_vec3("u_light_dir", *Light.direction) @shader.set_vec3("u_light_color", *Light.color) @shader.set_float("u_ambient", Light.ambient) end if @texture @texture.bind @mesh.draw else @groups.each do |material, mesh| @materials.texture_for(material).bind mesh.draw end end end |