Blender V4.3
obj_import_mtl.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "BKE_image.hh"
10#include "BKE_main.hh"
11#include "BKE_node.hh"
12
13#include "BLI_math_vector.h"
14#include "BLI_path_utils.hh"
15#include "BLI_string.h"
16
17#include "DNA_material_types.h"
18#include "DNA_node_types.h"
19
20#include "NOD_shader.h"
21
22#include "obj_export_mtl.hh"
23#include "obj_import_mtl.hh"
24
25#include <iostream>
26
27namespace blender::io::obj {
28
34 const char *socket_id,
35 Span<float> value,
36 bNode *r_node)
37{
38 BLI_assert(r_node);
39 bNodeSocket *socket{bke::node_find_socket(r_node, SOCK_IN, socket_id)};
40 BLI_assert(socket && socket->type == property_type);
41 switch (property_type) {
42 case SOCK_FLOAT: {
43 BLI_assert(value.size() == 1);
44 static_cast<bNodeSocketValueFloat *>(socket->default_value)->value = value[0];
45 break;
46 }
47 case SOCK_RGBA: {
48 /* Alpha will be added manually. It is not read from the MTL file either. */
49 BLI_assert(value.size() == 3);
50 copy_v3_v3(static_cast<bNodeSocketValueRGBA *>(socket->default_value)->value, value.data());
51 static_cast<bNodeSocketValueRGBA *>(socket->default_value)->value[3] = 1.0f;
52 break;
53 }
54 case SOCK_VECTOR: {
55 BLI_assert(value.size() == 3);
56 copy_v4_v4(static_cast<bNodeSocketValueVector *>(socket->default_value)->value,
57 value.data());
58 break;
59 }
60 default: {
61 BLI_assert(0);
62 break;
63 }
64 }
65}
66
67static Image *load_image_at_path(Main *bmain, const std::string &path, bool relative_paths)
68{
69 Image *image = BKE_image_load_exists(bmain, path.c_str());
70 if (!image) {
71 fprintf(stderr, "Cannot load image file: '%s'\n", path.c_str());
72 return nullptr;
73 }
74 fprintf(stderr, "Loaded image from: '%s'\n", path.c_str());
75 if (relative_paths) {
76 BLI_path_rel(image->filepath, BKE_main_blendfile_path(bmain));
77 }
78 return image;
79}
80
81static Image *create_placeholder_image(Main *bmain, const std::string &path)
82{
83 const float color[4] = {0, 0, 0, 1};
84 Image *image = BKE_image_add_generated(bmain,
85 32,
86 32,
87 BLI_path_basename(path.c_str()),
88 24,
89 false,
91 color,
92 false,
93 false,
94 false);
95 STRNCPY(image->filepath, path.c_str());
96 image->source = IMA_SRC_FILE;
97 return image;
98}
99
100static Image *load_texture_image(Main *bmain, const MTLTexMap &tex_map, bool relative_paths)
101{
102 Image *image = nullptr;
103
104 /* Remove quotes. */
105 std::string image_path{tex_map.image_path};
106 auto end_pos = std::remove(image_path.begin(), image_path.end(), '"');
107 image_path.erase(end_pos, image_path.end());
108
109 /* First try treating texture path as relative. */
110 std::string tex_path{tex_map.mtl_dir_path + image_path};
111 image = load_image_at_path(bmain, tex_path, relative_paths);
112 if (image != nullptr) {
113 return image;
114 }
115 /* Then try using it directly as absolute path. */
116 image = load_image_at_path(bmain, image_path, relative_paths);
117 if (image != nullptr) {
118 return image;
119 }
120 /* Try replacing underscores with spaces. */
121 std::string no_underscore_path{image_path};
122 std::replace(no_underscore_path.begin(), no_underscore_path.end(), '_', ' ');
123 if (!ELEM(no_underscore_path, image_path, tex_path)) {
124 image = load_image_at_path(bmain, no_underscore_path, relative_paths);
125 if (image != nullptr) {
126 return image;
127 }
128 }
129 /* Try taking just the basename from input path. */
130 std::string base_path{tex_map.mtl_dir_path + BLI_path_basename(image_path.c_str())};
131 if (base_path != tex_path) {
132 image = load_image_at_path(bmain, base_path, relative_paths);
133 if (image != nullptr) {
134 return image;
135 }
136 }
137
138 image = create_placeholder_image(bmain, tex_path);
139 return image;
140}
141
142/* Nodes are arranged in columns by type, with manually placed x coordinates
143 * based on node widths. */
144const float node_locx_texcoord = -880.0f;
145const float node_locx_mapping = -680.0f;
146const float node_locx_image = -480.0f;
147const float node_locx_normalmap = -200.0f;
148const float node_locx_bsdf = 0.0f;
149const float node_locx_output = 280.0f;
150
151/* Nodes are arranged in rows; one row for each image being used. */
152const float node_locy_top = 300.0f;
153const float node_locy_step = 300.0f;
154
155/* Add a node of the given type at the given location. */
156static bNode *add_node(bNodeTree *ntree, int type, float x, float y)
157{
158 bNode *node = bke::node_add_static_node(nullptr, ntree, type);
159 node->locx = x;
160 node->locy = y;
161 return node;
162}
163
164static void link_sockets(bNodeTree *ntree,
165 bNode *from_node,
166 const char *from_node_id,
167 bNode *to_node,
168 const char *to_node_id)
169{
170 bNodeSocket *from_sock{bke::node_find_socket(from_node, SOCK_OUT, from_node_id)};
171 bNodeSocket *to_sock{bke::node_find_socket(to_node, SOCK_IN, to_node_id)};
172 BLI_assert(from_sock && to_sock);
173 bke::node_add_link(ntree, from_node, from_sock, to_node, to_sock);
174}
175
176static void set_bsdf_socket_values(bNode *bsdf, Material *mat, const MTLMaterial &mtl_mat)
177{
178 const int illum = mtl_mat.illum_mode;
179 bool do_highlight = false;
180 bool do_tranparency = false;
181 bool do_reflection = false;
182 bool do_glass = false;
183 /* See https://wikipedia.org/wiki/Wavefront_.obj_file for possible values of illum. */
184 switch (illum) {
185 case -1:
186 case 1:
187 /* Base color on, ambient on. */
188 break;
189 case 2: {
190 /* Highlight on. */
191 do_highlight = true;
192 break;
193 }
194 case 3: {
195 /* Reflection on and Ray trace on. */
196 do_reflection = true;
197 break;
198 }
199 case 4: {
200 /* Transparency: Glass on, Reflection: Ray trace on. */
201 do_glass = true;
202 do_reflection = true;
203 do_tranparency = true;
204 break;
205 }
206 case 5: {
207 /* Reflection: Fresnel on and Ray trace on. */
208 do_reflection = true;
209 break;
210 }
211 case 6: {
212 /* Transparency: Refraction on, Reflection: Fresnel off and Ray trace on. */
213 do_reflection = true;
214 do_tranparency = true;
215 break;
216 }
217 case 7: {
218 /* Transparency: Refraction on, Reflection: Fresnel on and Ray trace on. */
219 do_reflection = true;
220 do_tranparency = true;
221 break;
222 }
223 case 8: {
224 /* Reflection on and Ray trace off. */
225 do_reflection = true;
226 break;
227 }
228 case 9: {
229 /* Transparency: Glass on, Reflection: Ray trace off. */
230 do_glass = true;
231 do_reflection = false;
232 do_tranparency = true;
233 break;
234 }
235 default: {
236 std::cerr << "Warning! illum value = " << illum
237 << "is not supported by the Principled-BSDF shader." << std::endl;
238 break;
239 }
240 }
241 /* Approximations for trying to map obj/mtl material model into
242 * Principled BSDF: */
243 /* Specular: average of Ks components. */
244 float specular = (mtl_mat.spec_color[0] + mtl_mat.spec_color[1] + mtl_mat.spec_color[2]) / 3;
245 if (specular < 0.0f) {
246 specular = do_highlight ? 1.0f : 0.0f;
247 }
248 /* Roughness: map 0..1000 range to 1..0 and apply non-linearity. */
249 float roughness;
250 if (mtl_mat.spec_exponent < 0.0f) {
251 roughness = do_highlight ? 0.0f : 1.0f;
252 }
253 else {
254 float clamped_ns = std::max(0.0f, std::min(1000.0f, mtl_mat.spec_exponent));
255 roughness = 1.0f - sqrt(clamped_ns / 1000.0f);
256 }
257 /* Metallic: average of `Ka` components. */
258 float metallic = (mtl_mat.ambient_color[0] + mtl_mat.ambient_color[1] +
259 mtl_mat.ambient_color[2]) /
260 3;
261 if (do_reflection) {
262 if (metallic < 0.0f) {
263 metallic = 1.0f;
264 }
265 }
266 else {
267 metallic = 0.0f;
268 }
269
270 float ior = mtl_mat.ior;
271 if (ior < 0) {
272 if (do_tranparency) {
273 ior = 1.0f;
274 }
275 if (do_glass) {
276 ior = 1.5f;
277 }
278 }
279 float alpha = mtl_mat.alpha;
280 if (do_tranparency && alpha < 0) {
281 alpha = 1.0f;
282 }
283
284 /* PBR values, when present, override the ones calculated above. */
285 if (mtl_mat.roughness >= 0) {
286 roughness = mtl_mat.roughness;
287 }
288 if (mtl_mat.metallic >= 0) {
289 metallic = mtl_mat.metallic;
290 }
291
292 float3 base_color = mtl_mat.color;
293 if (base_color.x >= 0 && base_color.y >= 0 && base_color.z >= 0) {
294 set_property_of_socket(SOCK_RGBA, "Base Color", {base_color, 3}, bsdf);
295 /* Viewport shading uses legacy r,g,b base color. */
296 mat->r = base_color.x;
297 mat->g = base_color.y;
298 mat->b = base_color.z;
299 }
300
301 float3 emission_color = mtl_mat.emission_color;
302 if (emission_color.x >= 0 && emission_color.y >= 0 && emission_color.z >= 0) {
303 set_property_of_socket(SOCK_RGBA, "Emission Color", {emission_color, 3}, bsdf);
304 }
306 set_property_of_socket(SOCK_FLOAT, "Emission Strength", {1.0f}, bsdf);
307 }
308 set_property_of_socket(SOCK_FLOAT, "Specular IOR Level", {specular}, bsdf);
309 set_property_of_socket(SOCK_FLOAT, "Roughness", {roughness}, bsdf);
310 mat->roughness = roughness;
311 set_property_of_socket(SOCK_FLOAT, "Metallic", {metallic}, bsdf);
312 mat->metallic = metallic;
313 /* Some files have `Ni 0`, ignore those values. */
314 if (ior > 0.0f) {
315 set_property_of_socket(SOCK_FLOAT, "IOR", {ior}, bsdf);
316 }
317 if (alpha != -1) {
318 set_property_of_socket(SOCK_FLOAT, "Alpha", {alpha}, bsdf);
319 }
320 if (do_tranparency || (alpha >= 0.0f && alpha < 1.0f)) {
323 }
324
325 if (mtl_mat.sheen >= 0) {
326 set_property_of_socket(SOCK_FLOAT, "Sheen Weight", {mtl_mat.sheen}, bsdf);
327 }
328 if (mtl_mat.cc_thickness >= 0) {
329 /* Clearcoat used to include an implicit 0.25 factor, so stay compatible to old versions. */
330 set_property_of_socket(SOCK_FLOAT, "Coat Weight", {0.25f * mtl_mat.cc_thickness}, bsdf);
331 }
332 if (mtl_mat.cc_roughness >= 0) {
333 set_property_of_socket(SOCK_FLOAT, "Coat Roughness", {mtl_mat.cc_roughness}, bsdf);
334 }
335 if (mtl_mat.aniso >= 0) {
336 set_property_of_socket(SOCK_FLOAT, "Anisotropic", {mtl_mat.aniso}, bsdf);
337 }
338 if (mtl_mat.aniso_rot >= 0) {
339 set_property_of_socket(SOCK_FLOAT, "Anisotropic Rotation", {mtl_mat.aniso_rot}, bsdf);
340 }
341
342 /* Transmission: average of transmission color. */
343 float transmission = (mtl_mat.transmit_color[0] + mtl_mat.transmit_color[1] +
344 mtl_mat.transmit_color[2]) /
345 3;
346 if (transmission >= 0) {
347 set_property_of_socket(SOCK_FLOAT, "Transmission Weight", {transmission}, bsdf);
348 }
349}
350
351static void add_image_textures(Main *bmain,
352 bNodeTree *ntree,
353 bNode *bsdf,
354 Material *mat,
355 const MTLMaterial &mtl_mat,
356 bool relative_paths)
357{
358 float node_locy = node_locy_top;
359 for (int key = 0; key < int(MTLTexMapType::Count); ++key) {
360 const MTLTexMap &value = mtl_mat.texture_maps[key];
361 if (!value.is_valid()) {
362 /* No Image texture node of this map type can be added to this material. */
363 continue;
364 }
365
366 Image *image = load_texture_image(bmain, value, relative_paths);
367 if (image == nullptr) {
368 continue;
369 }
370
371 bNode *image_node = add_node(ntree, SH_NODE_TEX_IMAGE, node_locx_image, node_locy);
372 BLI_assert(image_node);
373 image_node->id = &image->id;
374 static_cast<NodeTexImage *>(image_node->storage)->projection = value.projection_type;
375
376 /* Add normal map node if needed. */
377 bNode *normal_map = nullptr;
378 if (key == int(MTLTexMapType::Normal)) {
379 normal_map = add_node(ntree, SH_NODE_NORMAL_MAP, node_locx_normalmap, node_locy);
380 const float bump = std::max(0.0f, mtl_mat.normal_strength);
381 set_property_of_socket(SOCK_FLOAT, "Strength", {bump}, normal_map);
382 }
383
384 /* Add UV mapping & coordinate nodes only if needed. */
385 if (value.translation != float3(0, 0, 0) || value.scale != float3(1, 1, 1)) {
386 bNode *texcoord = add_node(ntree, SH_NODE_TEX_COORD, node_locx_texcoord, node_locy);
387 bNode *mapping = add_node(ntree, SH_NODE_MAPPING, node_locx_mapping, node_locy);
388 set_property_of_socket(SOCK_VECTOR, "Location", {value.translation, 3}, mapping);
389 set_property_of_socket(SOCK_VECTOR, "Scale", {value.scale, 3}, mapping);
390
391 link_sockets(ntree, texcoord, "UV", mapping, "Vector");
392 link_sockets(ntree, mapping, "Vector", image_node, "Vector");
393 }
394
395 if (normal_map) {
396 link_sockets(ntree, image_node, "Color", normal_map, "Color");
397 link_sockets(ntree, normal_map, "Normal", bsdf, "Normal");
398 }
399 else if (key == int(MTLTexMapType::Alpha)) {
400 link_sockets(ntree, image_node, "Alpha", bsdf, tex_map_type_to_socket_id[key]);
403 }
404 else {
405 link_sockets(ntree, image_node, "Color", bsdf, tex_map_type_to_socket_id[key]);
406 }
407
408 /* Next layout row: goes downwards on the screen. */
409 node_locy -= node_locy_step;
410 }
411}
412
414 const MTLMaterial &mtl_mat,
415 Material *mat,
416 bool relative_paths)
417{
419 nullptr, &mat->id, "Shader Nodetree", ntreeType_Shader->idname);
420
421 bNode *bsdf = add_node(ntree, SH_NODE_BSDF_PRINCIPLED, node_locx_bsdf, node_locy_top);
423
424 set_bsdf_socket_values(bsdf, mat, mtl_mat);
425 add_image_textures(bmain, ntree, bsdf, mat, mtl_mat, relative_paths);
426 link_sockets(ntree, bsdf, "BSDF", output, "Surface");
427 bke::node_set_active(ntree, output);
428
429 return ntree;
430}
431
432} // namespace blender::io::obj
Image * BKE_image_load_exists(Main *bmain, const char *filepath, bool *r_exists=nullptr)
Image * BKE_image_add_generated(Main *bmain, unsigned int width, unsigned int height, const char *name, int depth, int floatbuf, short gen_type, const float color[4], bool stereo3d, bool is_data, bool tiled)
const char * BKE_main_blendfile_path(const Main *bmain) ATTR_NONNULL()
Definition main.cc:832
#define SH_NODE_TEX_IMAGE
Definition BKE_node.hh:930
#define SH_NODE_TEX_COORD
Definition BKE_node.hh:938
#define SH_NODE_NORMAL_MAP
Definition BKE_node.hh:958
#define SH_NODE_OUTPUT_MATERIAL
Definition BKE_node.hh:913
#define SH_NODE_MAPPING
Definition BKE_node.hh:900
#define BLI_assert(a)
Definition BLI_assert.h:50
sqrt(x)+1/max(0
MINLINE void copy_v4_v4(float r[4], const float a[4])
MINLINE void copy_v3_v3(float r[3], const float a[3])
void void void const char * BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
bool void BLI_path_rel(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1)
#define STRNCPY(dst, src)
Definition BLI_string.h:593
#define ELEM(...)
@ IMA_SRC_FILE
@ IMA_GENTYPE_BLANK
@ MA_BL_HIDE_BACKFACE
@ MA_BM_BLEND
@ SOCK_OUT
@ SOCK_IN
eNodeSocketDatatype
@ SOCK_VECTOR
@ SOCK_FLOAT
@ SOCK_RGBA
struct blender::bke::bNodeTreeType * ntreeType_Shader
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Brightness Control the brightness and contrast of the input color Vector Map input vector components with curves Camera Retrieve information about the camera and how it relates to the current shading point s position Clamp a value between a minimum and a maximum Vector Perform vector math operation Invert Invert a producing a negative Combine Generate a color from its and blue Hue Saturation Apply a color transformation in the HSV color model Specular Similar to the Principled BSDF node but uses the specular workflow instead of metallic
input_tx image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "preview_img") .compute_source("compositor_compute_preview.glsl") .do_static_compilation(true)
OperationNode * node
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
void node_set_active(bNodeTree *ntree, bNode *node)
Definition node.cc:3896
bNode * node_add_static_node(const bContext *C, bNodeTree *ntree, int type)
Definition node.cc:2642
bNodeLink * node_add_link(bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock, bNode *tonode, bNodeSocket *tosock)
Definition node.cc:2912
bNodeTree * node_tree_add_tree_embedded(Main *bmain, ID *owner_id, const char *name, const char *idname)
Definition node.cc:3239
bNodeSocket * node_find_socket(bNode *node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:1829
const char * tex_map_type_to_socket_id[]
const float node_locy_step
const float node_locx_normalmap
static void add_image_textures(Main *bmain, bNodeTree *ntree, bNode *bsdf, Material *mat, const MTLMaterial &mtl_mat, bool relative_paths)
const float node_locx_image
static bNode * add_node(bNodeTree *ntree, int type, float x, float y)
const float node_locx_mapping
const float node_locx_bsdf
static void set_bsdf_socket_values(bNode *bsdf, Material *mat, const MTLMaterial &mtl_mat)
static Image * create_placeholder_image(Main *bmain, const std::string &path)
bNodeTree * create_mtl_node_tree(Main *bmain, const MTLMaterial &mtl_mat, Material *mat, bool relative_paths)
const float node_locx_texcoord
static void set_property_of_socket(eNodeSocketDatatype property_type, const char *socket_id, Span< float > value, bNode *r_node)
const float node_locx_output
const float node_locy_top
static void link_sockets(bNodeTree *ntree, bNode *from_node, const char *from_node_id, bNode *to_node, const char *to_node_id)
static Image * load_image_at_path(Main *bmain, const std::string &path, bool relative_paths)
static Image * load_texture_image(Main *bmain, const MTLTexMap &tex_map, bool relative_paths)
struct ID * id
void * storage
const MTLTexMap & tex_map_of_type(MTLTexMapType key) const
MTLTexMap texture_maps[int(MTLTexMapType::Count)]