Blender V4.5
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
8
9#include "BKE_image.hh"
10#include "BKE_main.hh"
11#include "BKE_node.hh"
13
14#include "BLI_math_vector.h"
15#include "BLI_path_utils.hh"
16#include "BLI_string.h"
17
18#include "DNA_material_types.h"
19#include "DNA_node_types.h"
20
21#include "NOD_shader.h"
22
23#include "obj_export_mtl.hh"
24#include "obj_import_mtl.hh"
25
26#include "CLG_log.h"
27static CLG_LogRef LOG = {"io.obj"};
28
29namespace blender::io::obj {
30
36 const char *socket_id,
37 Span<float> value,
38 bNode *r_node)
39{
40 BLI_assert(r_node);
41 bNodeSocket *socket{bke::node_find_socket(*r_node, SOCK_IN, socket_id)};
42 BLI_assert(socket && socket->type == property_type);
43 switch (property_type) {
44 case SOCK_FLOAT: {
45 BLI_assert(value.size() == 1);
46 static_cast<bNodeSocketValueFloat *>(socket->default_value)->value = value[0];
47 break;
48 }
49 case SOCK_RGBA: {
50 /* Alpha will be added manually. It is not read from the MTL file either. */
51 BLI_assert(value.size() == 3);
52 copy_v3_v3(static_cast<bNodeSocketValueRGBA *>(socket->default_value)->value, value.data());
53 static_cast<bNodeSocketValueRGBA *>(socket->default_value)->value[3] = 1.0f;
54 break;
55 }
56 case SOCK_VECTOR: {
57 BLI_assert(value.size() == 3);
59 value.data());
60 break;
61 }
62 default: {
63 BLI_assert(0);
64 break;
65 }
66 }
67}
68
69static Image *load_image_at_path(Main *bmain, const std::string &path, bool relative_paths)
70{
71 Image *image = BKE_image_load_exists(bmain, path.c_str());
72 if (!image) {
73 CLOG_WARN(&LOG, "Cannot load image file: '%s'", path.c_str());
74 return nullptr;
75 }
76 CLOG_INFO(&LOG, 1, "Loaded image from: '%s'", path.c_str());
77 if (relative_paths) {
80 }
81 return image;
82}
83
84static Image *create_placeholder_image(Main *bmain, const std::string &path)
85{
86 const float color[4] = {0, 0, 0, 1};
87 Image *image = BKE_image_add_generated(bmain,
88 1,
89 1,
90 BLI_path_basename(path.c_str()),
91 24,
92 false,
94 color,
95 false,
96 false,
97 false);
98 STRNCPY(image->filepath, path.c_str());
99
100 /* Ensure that we are not marked as a generated image and clear any buffers created so far. */
101 image->source = IMA_SRC_FILE;
102 image->type = IMA_TYPE_IMAGE;
104
105 return image;
106}
107
108static Image *load_texture_image(Main *bmain, const MTLTexMap &tex_map, bool relative_paths)
109{
110 Image *image = nullptr;
111
112 /* Remove quotes. */
113 std::string image_path{tex_map.image_path};
114 auto end_pos = std::remove(image_path.begin(), image_path.end(), '"');
115 image_path.erase(end_pos, image_path.end());
116
117 /* First try treating texture path as relative. */
118 std::string tex_path{tex_map.mtl_dir_path + image_path};
119 image = load_image_at_path(bmain, tex_path, relative_paths);
120 if (image != nullptr) {
121 return image;
122 }
123 /* Then try using it directly as absolute path. */
124 image = load_image_at_path(bmain, image_path, relative_paths);
125 if (image != nullptr) {
126 return image;
127 }
128 /* Try replacing underscores with spaces. */
129 std::string no_underscore_path{image_path};
130 std::replace(no_underscore_path.begin(), no_underscore_path.end(), '_', ' ');
131 if (!ELEM(no_underscore_path, image_path, tex_path)) {
132 image = load_image_at_path(bmain, no_underscore_path, relative_paths);
133 if (image != nullptr) {
134 return image;
135 }
136 }
137 /* Try taking just the basename from input path. */
138 std::string base_path{tex_map.mtl_dir_path + BLI_path_basename(image_path.c_str())};
139 if (base_path != tex_path) {
140 image = load_image_at_path(bmain, base_path, relative_paths);
141 if (image != nullptr) {
142 return image;
143 }
144 }
145
146 image = create_placeholder_image(bmain, tex_path);
147 return image;
148}
149
150/* Nodes are arranged in columns by type, with manually placed x coordinates
151 * based on node widths. */
152const float node_locx_texcoord = -880.0f;
153const float node_locx_mapping = -680.0f;
154const float node_locx_image = -480.0f;
155const float node_locx_normalmap = -200.0f;
156const float node_locx_bsdf = 0.0f;
157const float node_locx_output = 280.0f;
158
159/* Nodes are arranged in rows; one row for each image being used. */
160const float node_locy_top = 300.0f;
161const float node_locy_step = 300.0f;
162
163/* Add a node of the given type at the given location. */
164static bNode *add_node(bNodeTree *ntree, int type, float x, float y)
165{
166 bNode *node = bke::node_add_static_node(nullptr, *ntree, type);
167 node->location[0] = x;
168 node->location[1] = y;
169 return node;
170}
171
172static void link_sockets(bNodeTree *ntree,
173 bNode *from_node,
174 const char *from_node_id,
175 bNode *to_node,
176 const char *to_node_id)
177{
178 bNodeSocket *from_sock{bke::node_find_socket(*from_node, SOCK_OUT, from_node_id)};
179 bNodeSocket *to_sock{bke::node_find_socket(*to_node, SOCK_IN, to_node_id)};
180 BLI_assert(from_sock && to_sock);
181 bke::node_add_link(*ntree, *from_node, *from_sock, *to_node, *to_sock);
182}
183
184static void set_bsdf_socket_values(bNode *bsdf, Material *mat, const MTLMaterial &mtl_mat)
185{
186 const int illum = mtl_mat.illum_mode;
187 bool do_highlight = false;
188 bool do_tranparency = false;
189 bool do_reflection = false;
190 bool do_glass = false;
191 /* See https://wikipedia.org/wiki/Wavefront_.obj_file for possible values of illum. */
192 switch (illum) {
193 case -1:
194 case 1:
195 /* Base color on, ambient on. */
196 break;
197 case 2: {
198 /* Highlight on. */
199 do_highlight = true;
200 break;
201 }
202 case 3: {
203 /* Reflection on and Ray trace on. */
204 do_reflection = true;
205 break;
206 }
207 case 4: {
208 /* Transparency: Glass on, Reflection: Ray trace on. */
209 do_glass = true;
210 do_reflection = true;
211 do_tranparency = true;
212 break;
213 }
214 case 5: {
215 /* Reflection: Fresnel on and Ray trace on. */
216 do_reflection = true;
217 break;
218 }
219 case 6: {
220 /* Transparency: Refraction on, Reflection: Fresnel off and Ray trace on. */
221 do_reflection = true;
222 do_tranparency = true;
223 break;
224 }
225 case 7: {
226 /* Transparency: Refraction on, Reflection: Fresnel on and Ray trace on. */
227 do_reflection = true;
228 do_tranparency = true;
229 break;
230 }
231 case 8: {
232 /* Reflection on and Ray trace off. */
233 do_reflection = true;
234 break;
235 }
236 case 9: {
237 /* Transparency: Glass on, Reflection: Ray trace off. */
238 do_glass = true;
239 do_reflection = false;
240 do_tranparency = true;
241 break;
242 }
243 default: {
244 CLOG_WARN(&LOG,
245 "Material illum value '%d' is not supported by the Principled BSDF shader.",
246 illum);
247 break;
248 }
249 }
250 /* Approximations for trying to map obj/mtl material model into
251 * Principled BSDF: */
252 /* Specular: average of Ks components. */
253 float specular = (mtl_mat.spec_color[0] + mtl_mat.spec_color[1] + mtl_mat.spec_color[2]) / 3;
254 if (specular < 0.0f) {
255 specular = do_highlight ? 1.0f : 0.0f;
256 }
257 /* Roughness: map 0..1000 range to 1..0 and apply non-linearity. */
258 float roughness;
259 if (mtl_mat.spec_exponent < 0.0f) {
260 roughness = do_highlight ? 0.0f : 1.0f;
261 }
262 else {
263 float clamped_ns = std::max(0.0f, std::min(1000.0f, mtl_mat.spec_exponent));
264 roughness = 1.0f - sqrt(clamped_ns / 1000.0f);
265 }
266 /* Metallic: average of `Ka` components. */
267 float metallic = (mtl_mat.ambient_color[0] + mtl_mat.ambient_color[1] +
268 mtl_mat.ambient_color[2]) /
269 3;
270 if (do_reflection) {
271 if (metallic < 0.0f) {
272 metallic = 1.0f;
273 }
274 }
275 else {
276 metallic = 0.0f;
277 }
278
279 float ior = mtl_mat.ior;
280 if (ior < 0) {
281 if (do_tranparency) {
282 ior = 1.0f;
283 }
284 if (do_glass) {
285 ior = 1.5f;
286 }
287 }
288 float alpha = mtl_mat.alpha;
289 if (do_tranparency && alpha < 0) {
290 alpha = 1.0f;
291 }
292
293 /* PBR values, when present, override the ones calculated above. */
294 if (mtl_mat.roughness >= 0) {
295 roughness = mtl_mat.roughness;
296 }
297 if (mtl_mat.metallic >= 0) {
298 metallic = mtl_mat.metallic;
299 }
300
301 float3 base_color = mtl_mat.color;
302 if (base_color.x >= 0 && base_color.y >= 0 && base_color.z >= 0) {
303 set_property_of_socket(SOCK_RGBA, "Base Color", {base_color, 3}, bsdf);
304 /* Viewport shading uses legacy r,g,b base color. */
305 mat->r = base_color.x;
306 mat->g = base_color.y;
307 mat->b = base_color.z;
308 }
309
311 set_property_of_socket(SOCK_FLOAT, "Emission Strength", {1.0f}, bsdf);
312 }
313
314 float3 emission_color = mtl_mat.emission_color;
315 if (emission_color.x >= 0 && emission_color.y >= 0 && emission_color.z >= 0) {
316 float emission_strength = fmax(emission_color.x, fmax(emission_color.y, emission_color.z));
317 if (emission_strength > 1.0f) {
318 /* For colors brighter than 1.0, change color to be in 0..1 range, and set emission
319 * strength accordingly. */
321 SOCK_RGBA, "Emission Color", {emission_color / emission_strength, 3}, bsdf);
322 set_property_of_socket(SOCK_FLOAT, "Emission Strength", {emission_strength}, bsdf);
323 }
324 else {
325 set_property_of_socket(SOCK_RGBA, "Emission Color", {emission_color, 3}, bsdf);
326 set_property_of_socket(SOCK_FLOAT, "Emission Strength", {1.0f}, bsdf);
327 }
328 }
329
330 set_property_of_socket(SOCK_FLOAT, "Specular IOR Level", {specular}, bsdf);
331 set_property_of_socket(SOCK_FLOAT, "Roughness", {roughness}, bsdf);
332 mat->roughness = roughness;
333 set_property_of_socket(SOCK_FLOAT, "Metallic", {metallic}, bsdf);
334 mat->metallic = metallic;
335 /* Some files have `Ni 0`, ignore those values. */
336 if (ior > 0.0f) {
337 set_property_of_socket(SOCK_FLOAT, "IOR", {ior}, bsdf);
338 }
339 if (alpha != -1) {
340 set_property_of_socket(SOCK_FLOAT, "Alpha", {alpha}, bsdf);
341 }
342 if (do_tranparency || (alpha >= 0.0f && alpha < 1.0f)) {
345 }
346
347 if (mtl_mat.sheen >= 0) {
348 set_property_of_socket(SOCK_FLOAT, "Sheen Weight", {mtl_mat.sheen}, bsdf);
349 }
350 if (mtl_mat.cc_thickness >= 0) {
351 /* Clearcoat used to include an implicit 0.25 factor, so stay compatible to old versions. */
352 set_property_of_socket(SOCK_FLOAT, "Coat Weight", {0.25f * mtl_mat.cc_thickness}, bsdf);
353 }
354 if (mtl_mat.cc_roughness >= 0) {
355 set_property_of_socket(SOCK_FLOAT, "Coat Roughness", {mtl_mat.cc_roughness}, bsdf);
356 }
357 if (mtl_mat.aniso >= 0) {
358 set_property_of_socket(SOCK_FLOAT, "Anisotropic", {mtl_mat.aniso}, bsdf);
359 }
360 if (mtl_mat.aniso_rot >= 0) {
361 set_property_of_socket(SOCK_FLOAT, "Anisotropic Rotation", {mtl_mat.aniso_rot}, bsdf);
362 }
363
364 /* Transmission: average of transmission color. */
365 float transmission = (mtl_mat.transmit_color[0] + mtl_mat.transmit_color[1] +
366 mtl_mat.transmit_color[2]) /
367 3;
368 if (transmission >= 0) {
369 set_property_of_socket(SOCK_FLOAT, "Transmission Weight", {transmission}, bsdf);
370 }
371}
372
373static void add_image_textures(Main *bmain,
374 bNodeTree *ntree,
375 bNode *bsdf,
376 Material *mat,
377 const MTLMaterial &mtl_mat,
378 bool relative_paths)
379{
380 float node_locy = node_locy_top;
381 for (int key = 0; key < int(MTLTexMapType::Count); ++key) {
382 const MTLTexMap &value = mtl_mat.texture_maps[key];
383 if (!value.is_valid()) {
384 /* No Image texture node of this map type can be added to this material. */
385 continue;
386 }
387
388 Image *image = load_texture_image(bmain, value, relative_paths);
389 if (image == nullptr) {
390 continue;
391 }
392
393 bNode *image_node = add_node(ntree, SH_NODE_TEX_IMAGE, node_locx_image, node_locy);
394 BLI_assert(image_node);
395 image_node->id = &image->id;
396 static_cast<NodeTexImage *>(image_node->storage)->projection = value.projection_type;
397
398 /* Add normal map node if needed. */
399 bNode *normal_map = nullptr;
400 if (key == int(MTLTexMapType::Normal)) {
401 normal_map = add_node(ntree, SH_NODE_NORMAL_MAP, node_locx_normalmap, node_locy);
402 const float bump = std::max(0.0f, mtl_mat.normal_strength);
403 set_property_of_socket(SOCK_FLOAT, "Strength", {bump}, normal_map);
404 }
405
406 /* Add UV mapping & coordinate nodes only if needed. */
407 if (value.translation != float3(0, 0, 0) || value.scale != float3(1, 1, 1)) {
408 bNode *texcoord = add_node(ntree, SH_NODE_TEX_COORD, node_locx_texcoord, node_locy);
409 bNode *mapping = add_node(ntree, SH_NODE_MAPPING, node_locx_mapping, node_locy);
410 set_property_of_socket(SOCK_VECTOR, "Location", {value.translation, 3}, mapping);
411 set_property_of_socket(SOCK_VECTOR, "Scale", {value.scale, 3}, mapping);
412
413 link_sockets(ntree, texcoord, "UV", mapping, "Vector");
414 link_sockets(ntree, mapping, "Vector", image_node, "Vector");
415 }
416
417 if (normal_map) {
418 link_sockets(ntree, image_node, "Color", normal_map, "Color");
419 link_sockets(ntree, normal_map, "Normal", bsdf, "Normal");
420 }
421 else if (key == int(MTLTexMapType::Alpha)) {
422 link_sockets(ntree, image_node, "Alpha", bsdf, tex_map_type_to_socket_id[key]);
425 }
426 else {
427 link_sockets(ntree, image_node, "Color", bsdf, tex_map_type_to_socket_id[key]);
428 }
429
430 /* Next layout row: goes downwards on the screen. */
431 node_locy -= node_locy_step;
432 }
433}
434
436 const MTLMaterial &mtl_mat,
437 Material *mat,
438 bool relative_paths)
439{
441 nullptr, &mat->id, "Shader Nodetree", ntreeType_Shader->idname);
442
445
446 set_bsdf_socket_values(bsdf, mat, mtl_mat);
447 add_image_textures(bmain, ntree, bsdf, mat, mtl_mat, relative_paths);
448 link_sockets(ntree, bsdf, "BSDF", output, "Surface");
450
451 return ntree;
452}
453
454} // 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:877
#define SH_NODE_TEX_IMAGE
#define SH_NODE_BSDF_PRINCIPLED
#define SH_NODE_TEX_COORD
#define SH_NODE_NORMAL_MAP
#define SH_NODE_OUTPUT_MATERIAL
#define SH_NODE_MAPPING
#define BLI_assert(a)
Definition BLI_assert.h:46
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
int BLI_path_normalize(char *path) ATTR_NONNULL(1)
bool void BLI_path_rel(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1)
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
#define ELEM(...)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
#define CLOG_INFO(clg_ref, level,...)
Definition CLG_log.h:179
@ IMA_SRC_FILE
@ IMA_TYPE_IMAGE
@ IMA_GENTYPE_BLANK
@ MA_BM_BLEND
@ MA_BL_HIDE_BACKFACE
@ SOCK_OUT
@ SOCK_IN
eNodeSocketDatatype
@ SOCK_VECTOR
@ SOCK_FLOAT
@ SOCK_RGBA
struct blender::bke::bNodeTreeType * ntreeType_Shader
constexpr const T * data() const
Definition BLI_span.hh:215
constexpr int64_t size() const
Definition BLI_span.hh:252
#define sqrt
#define output
#define LOG(severity)
Definition log.h:32
bNodeTree * node_tree_add_tree_embedded(Main *bmain, ID *owner_id, StringRefNull name, StringRefNull idname)
Definition node.cc:4375
bNodeSocket * node_find_socket(bNode &node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:2864
bNode * node_add_static_node(const bContext *C, bNodeTree &ntree, int type)
Definition node.cc:3804
bNodeLink & node_add_link(bNodeTree &ntree, bNode &fromnode, bNodeSocket &fromsock, bNode &tonode, bNodeSocket &tosock)
Definition node.cc:4087
void node_set_active(bNodeTree &ntree, bNode &node)
Definition node.cc:4996
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)
VecBase< float, 3 > float3
char filepath[1024]
short source
void * default_value
float location[2]
struct ID * id
void * storage
const MTLTexMap & tex_map_of_type(MTLTexMapType key) const
MTLTexMap texture_maps[int(MTLTexMapType::Count)]
void * BKE_image_free_buffers
Definition stubs.c:35