Blender  V3.3
obj_import_mtl.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
7 #include "BKE_image.h"
8 #include "BKE_main.h"
9 #include "BKE_node.h"
10 
11 #include "BLI_map.hh"
12 #include "BLI_math_vector.h"
13 #include "BLI_path_util.h"
14 
15 #include "DNA_material_types.h"
16 #include "DNA_node_types.h"
17 
18 #include "NOD_shader.h"
19 
20 /* TODO: move eMTLSyntaxElement out of following file into a more neutral place */
21 #include "obj_export_io.hh"
22 #include "obj_import_mtl.hh"
24 
25 namespace blender::io::obj {
26 
31 static void set_property_of_socket(eNodeSocketDatatype property_type,
32  StringRef socket_id,
33  Span<float> value,
34  bNode *r_node)
35 {
36  BLI_assert(r_node);
37  bNodeSocket *socket{nodeFindSocket(r_node, SOCK_IN, socket_id.data())};
38  BLI_assert(socket && socket->type == property_type);
39  switch (property_type) {
40  case SOCK_FLOAT: {
41  BLI_assert(value.size() == 1);
42  static_cast<bNodeSocketValueFloat *>(socket->default_value)->value = value[0];
43  break;
44  }
45  case SOCK_RGBA: {
46  /* Alpha will be added manually. It is not read from the MTL file either. */
47  BLI_assert(value.size() == 3);
48  copy_v3_v3(static_cast<bNodeSocketValueRGBA *>(socket->default_value)->value, value.data());
49  static_cast<bNodeSocketValueRGBA *>(socket->default_value)->value[3] = 1.0f;
50  break;
51  }
52  case SOCK_VECTOR: {
53  BLI_assert(value.size() == 3);
54  copy_v4_v4(static_cast<bNodeSocketValueVector *>(socket->default_value)->value,
55  value.data());
56  break;
57  }
58  default: {
59  BLI_assert(0);
60  break;
61  }
62  }
63 }
64 
65 static Image *load_image_at_path(Main *bmain, const std::string &path, bool relative_paths)
66 {
67  Image *image = BKE_image_load_exists(bmain, path.c_str());
68  if (!image) {
69  fprintf(stderr, "Cannot load image file: '%s'\n", path.c_str());
70  return nullptr;
71  }
72  fprintf(stderr, "Loaded image from: '%s'\n", path.c_str());
73  if (relative_paths) {
74  BLI_path_rel(image->filepath, BKE_main_blendfile_path(bmain));
75  }
76  return image;
77 }
78 
79 static Image *create_placeholder_image(Main *bmain, const std::string &path)
80 {
81  const float color[4] = {0, 0, 0, 1};
83  32,
84  32,
85  BLI_path_basename(path.c_str()),
86  24,
87  false,
89  color,
90  false,
91  false,
92  false);
93  STRNCPY(image->filepath, path.c_str());
94  image->source = IMA_SRC_FILE;
95  return image;
96 }
97 
98 static Image *load_texture_image(Main *bmain, const tex_map_XX &tex_map, bool relative_paths)
99 {
100  Image *image = nullptr;
101 
102  /* First try treating texture path as relative. */
103  std::string tex_path{tex_map.mtl_dir_path + tex_map.image_path};
104  image = load_image_at_path(bmain, tex_path, relative_paths);
105  if (image != nullptr) {
106  return image;
107  }
108  /* Then try using it directly as absolute path. */
109  std::string raw_path{tex_map.image_path};
110  image = load_image_at_path(bmain, raw_path, relative_paths);
111  if (image != nullptr) {
112  return image;
113  }
114  /* Try removing quotes. */
115  std::string no_quote_path{tex_path};
116  auto end_pos = std::remove(no_quote_path.begin(), no_quote_path.end(), '"');
117  no_quote_path.erase(end_pos, no_quote_path.end());
118  if (no_quote_path != tex_path) {
119  image = load_image_at_path(bmain, no_quote_path, relative_paths);
120  if (image != nullptr) {
121  return image;
122  }
123  }
124  /* Try replacing underscores with spaces. */
125  std::string no_underscore_path{no_quote_path};
126  std::replace(no_underscore_path.begin(), no_underscore_path.end(), '_', ' ');
127  if (no_underscore_path != no_quote_path && no_underscore_path != tex_path) {
128  image = load_image_at_path(bmain, no_underscore_path, relative_paths);
129  if (image != nullptr) {
130  return image;
131  }
132  }
133  /* Try taking just the basename from input path. */
134  std::string base_path{tex_map.mtl_dir_path + BLI_path_basename(tex_map.image_path.c_str())};
135  if (base_path != tex_path) {
136  image = load_image_at_path(bmain, base_path, relative_paths);
137  if (image != nullptr) {
138  return image;
139  }
140  }
141 
142  image = create_placeholder_image(bmain, tex_path);
143  return image;
144 }
145 
147 {
149 }
150 
152  const MTLMaterial &mtl_mat,
153  Material *mat,
154  bool relative_paths)
155  : mtl_mat_(mtl_mat)
156 {
157  nodetree_.reset(ntreeAddTree(nullptr, "Shader Nodetree", ntreeType_Shader->idname));
158  bsdf_ = add_node_to_tree(SH_NODE_BSDF_PRINCIPLED);
159  shader_output_ = add_node_to_tree(SH_NODE_OUTPUT_MATERIAL);
160 
161  set_bsdf_socket_values(mat);
162  add_image_textures(bmain, mat, relative_paths);
163  link_sockets(bsdf_, "BSDF", shader_output_, "Surface", 4);
164 
165  nodeSetActive(nodetree_.get(), shader_output_);
166 }
167 
172 {
173  if (nodetree_) {
174  /* nodetree's ownership must be acquired by the caller. */
175  nodetree_.reset();
176  BLI_assert(0);
177  }
178 }
179 
181 {
182  /* If this function has been reached, we know that nodes and the nodetree
183  * can be added to the scene safely. */
184  return nodetree_.release();
185 }
186 
187 bNode *ShaderNodetreeWrap::add_node_to_tree(const int node_type)
188 {
189  return nodeAddStaticNode(nullptr, nodetree_.get(), node_type);
190 }
191 
192 std::pair<float, float> ShaderNodetreeWrap::set_node_locations(const int pos_x)
193 {
194  int pos_y = 0;
195  bool found = false;
196  while (true) {
197  for (Span<int> location : node_locations) {
198  if (location[0] == pos_x && location[1] == pos_y) {
199  pos_y += 1;
200  found = true;
201  }
202  else {
203  found = false;
204  }
205  }
206  if (!found) {
207  node_locations.append({pos_x, pos_y});
208  return {pos_x * node_size_, pos_y * node_size_ * 2.0 / 3.0};
209  }
210  }
211 }
212 
213 void ShaderNodetreeWrap::link_sockets(bNode *from_node,
214  StringRef from_node_id,
215  bNode *to_node,
216  StringRef to_node_id,
217  const int from_node_pos_x)
218 {
219  std::tie(from_node->locx, from_node->locy) = set_node_locations(from_node_pos_x);
220  std::tie(to_node->locx, to_node->locy) = set_node_locations(from_node_pos_x + 1);
221  bNodeSocket *from_sock{nodeFindSocket(from_node, SOCK_OUT, from_node_id.data())};
222  bNodeSocket *to_sock{nodeFindSocket(to_node, SOCK_IN, to_node_id.data())};
223  BLI_assert(from_sock && to_sock);
224  nodeAddLink(nodetree_.get(), from_node, from_sock, to_node, to_sock);
225 }
226 
227 void ShaderNodetreeWrap::set_bsdf_socket_values(Material *mat)
228 {
229  const int illum = mtl_mat_.illum;
230  bool do_highlight = false;
231  bool do_tranparency = false;
232  bool do_reflection = false;
233  bool do_glass = false;
234  /* See https://wikipedia.org/wiki/Wavefront_.obj_file for possible values of illum. */
235  switch (illum) {
236  case -1:
237  case 1:
238  /* Base color on, ambient on. */
239  break;
240  case 2: {
241  /* Highlight on. */
242  do_highlight = true;
243  break;
244  }
245  case 3: {
246  /* Reflection on and Ray trace on. */
247  do_reflection = true;
248  break;
249  }
250  case 4: {
251  /* Transparency: Glass on, Reflection: Ray trace on. */
252  do_glass = true;
253  do_reflection = true;
254  do_tranparency = true;
255  break;
256  }
257  case 5: {
258  /* Reflection: Fresnel on and Ray trace on. */
259  do_reflection = true;
260  break;
261  }
262  case 6: {
263  /* Transparency: Refraction on, Reflection: Fresnel off and Ray trace on. */
264  do_reflection = true;
265  do_tranparency = true;
266  break;
267  }
268  case 7: {
269  /* Transparency: Refraction on, Reflection: Fresnel on and Ray trace on. */
270  do_reflection = true;
271  do_tranparency = true;
272  break;
273  }
274  case 8: {
275  /* Reflection on and Ray trace off. */
276  do_reflection = true;
277  break;
278  }
279  case 9: {
280  /* Transparency: Glass on, Reflection: Ray trace off. */
281  do_glass = true;
282  do_reflection = false;
283  do_tranparency = true;
284  break;
285  }
286  default: {
287  std::cerr << "Warning! illum value = " << illum
288  << "is not supported by the Principled-BSDF shader." << std::endl;
289  break;
290  }
291  }
292  /* Approximations for trying to map obj/mtl material model into
293  * Principled BSDF: */
294  /* Specular: average of Ks components. */
295  float specular = (mtl_mat_.Ks[0] + mtl_mat_.Ks[1] + mtl_mat_.Ks[2]) / 3;
296  if (specular < 0.0f) {
297  specular = do_highlight ? 1.0f : 0.0f;
298  }
299  /* Roughness: map 0..1000 range to 1..0 and apply non-linearity. */
300  float roughness;
301  if (mtl_mat_.Ns < 0.0f) {
302  roughness = do_highlight ? 0.0f : 1.0f;
303  }
304  else {
305  float clamped_ns = std::max(0.0f, std::min(1000.0f, mtl_mat_.Ns));
306  roughness = 1.0f - sqrt(clamped_ns / 1000.0f);
307  }
308  /* Metallic: average of Ka components. */
309  float metallic = (mtl_mat_.Ka[0] + mtl_mat_.Ka[1] + mtl_mat_.Ka[2]) / 3;
310  if (do_reflection) {
311  if (metallic < 0.0f) {
312  metallic = 1.0f;
313  }
314  }
315  else {
316  metallic = 0.0f;
317  }
318 
319  float ior = mtl_mat_.Ni;
320  if (ior < 0) {
321  if (do_tranparency) {
322  ior = 1.0f;
323  }
324  if (do_glass) {
325  ior = 1.5f;
326  }
327  }
328  float alpha = mtl_mat_.d;
329  if (do_tranparency && alpha < 0) {
330  alpha = 1.0f;
331  }
332 
333  float3 base_color = {mtl_mat_.Kd[0], mtl_mat_.Kd[1], mtl_mat_.Kd[2]};
334  if (base_color.x >= 0 && base_color.y >= 0 && base_color.z >= 0) {
335  set_property_of_socket(SOCK_RGBA, "Base Color", {base_color, 3}, bsdf_);
336  /* Viewport shading uses legacy r,g,b base color. */
337  mat->r = base_color.x;
338  mat->g = base_color.y;
339  mat->b = base_color.z;
340  }
341 
342  float3 emission_color = {mtl_mat_.Ke[0], mtl_mat_.Ke[1], mtl_mat_.Ke[2]};
343  if (emission_color.x >= 0 && emission_color.y >= 0 && emission_color.z >= 0) {
344  set_property_of_socket(SOCK_RGBA, "Emission", {emission_color, 3}, bsdf_);
345  }
347  set_property_of_socket(SOCK_FLOAT, "Emission Strength", {1.0f}, bsdf_);
348  }
349  set_property_of_socket(SOCK_FLOAT, "Specular", {specular}, bsdf_);
350  set_property_of_socket(SOCK_FLOAT, "Roughness", {roughness}, bsdf_);
351  mat->roughness = roughness;
352  set_property_of_socket(SOCK_FLOAT, "Metallic", {metallic}, bsdf_);
353  mat->metallic = metallic;
354  if (ior != -1) {
355  set_property_of_socket(SOCK_FLOAT, "IOR", {ior}, bsdf_);
356  }
357  if (alpha != -1) {
358  set_property_of_socket(SOCK_FLOAT, "Alpha", {alpha}, bsdf_);
359  }
360  if (do_tranparency || (alpha >= 0.0f && alpha < 1.0f)) {
361  mat->blend_method = MA_BM_BLEND;
362  }
363 }
364 
365 void ShaderNodetreeWrap::add_image_textures(Main *bmain, Material *mat, bool relative_paths)
366 {
367  for (const Map<const eMTLSyntaxElement, tex_map_XX>::Item texture_map :
368  mtl_mat_.texture_maps.items()) {
369  if (!texture_map.value.is_valid()) {
370  /* No Image texture node of this map type can be added to this material. */
371  continue;
372  }
373 
374  bNode *image_texture = add_node_to_tree(SH_NODE_TEX_IMAGE);
375  BLI_assert(image_texture);
376  Image *image = load_texture_image(bmain, texture_map.value, relative_paths);
377  if (image == nullptr) {
378  continue;
379  }
380  image_texture->id = &image->id;
381  static_cast<NodeTexImage *>(image_texture->storage)->projection =
382  texture_map.value.projection_type;
383 
384  /* Add normal map node if needed. */
385  bNode *normal_map = nullptr;
386  if (texture_map.key == eMTLSyntaxElement::map_Bump) {
387  normal_map = add_node_to_tree(SH_NODE_NORMAL_MAP);
388  const float bump = std::max(0.0f, mtl_mat_.map_Bump_strength);
389  set_property_of_socket(SOCK_FLOAT, "Strength", {bump}, normal_map);
390  }
391 
392  /* Add UV mapping & coordinate nodes only if needed. */
393  if (texture_map.value.translation != float3(0, 0, 0) ||
394  texture_map.value.scale != float3(1, 1, 1)) {
395  bNode *mapping = add_node_to_tree(SH_NODE_MAPPING);
396  bNode *texture_coordinate = add_node_to_tree(SH_NODE_TEX_COORD);
397  set_property_of_socket(SOCK_VECTOR, "Location", {texture_map.value.translation, 3}, mapping);
398  set_property_of_socket(SOCK_VECTOR, "Scale", {texture_map.value.scale, 3}, mapping);
399 
400  link_sockets(texture_coordinate, "UV", mapping, "Vector", 0);
401  link_sockets(mapping, "Vector", image_texture, "Vector", 1);
402  }
403 
404  if (normal_map) {
405  link_sockets(image_texture, "Color", normal_map, "Color", 2);
406  link_sockets(normal_map, "Normal", bsdf_, "Normal", 3);
407  }
408  else if (texture_map.key == eMTLSyntaxElement::map_d) {
409  link_sockets(image_texture, "Alpha", bsdf_, texture_map.value.dest_socket_id, 2);
410  mat->blend_method = MA_BM_BLEND;
411  }
412  else {
413  link_sockets(image_texture, "Color", bsdf_, texture_map.value.dest_socket_id, 2);
414  }
415  }
416 }
417 } // namespace blender::io::obj
struct Image * BKE_image_load_exists(struct Main *bmain, const char *filepath)
struct Image * BKE_image_add_generated(struct 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 struct Main *bmain) ATTR_NONNULL()
#define SH_NODE_BSDF_PRINCIPLED
Definition: BKE_node.h:1164
#define SH_NODE_OUTPUT_MATERIAL
Definition: BKE_node.h:1101
void ntreeFreeEmbeddedTree(struct bNodeTree *ntree)
Definition: node.cc:3112
struct bNodeLink * nodeAddLink(struct bNodeTree *ntree, struct bNode *fromnode, struct bNodeSocket *fromsock, struct bNode *tonode, struct bNodeSocket *tosock)
Definition: node.cc:2296
struct bNodeSocket * nodeFindSocket(const struct bNode *node, eNodeSocketInOut in_out, const char *identifier)
#define SH_NODE_MAPPING
Definition: BKE_node.h:1088
struct bNodeTree * ntreeAddTree(struct Main *bmain, const char *name, const char *idname)
Definition: node.cc:2674
struct bNode * nodeAddStaticNode(const struct bContext *C, struct bNodeTree *ntree, int type)
Definition: node.cc:2151
void nodeSetActive(struct bNodeTree *ntree, struct bNode *node)
Definition: node.cc:3644
#define BLI_assert(a)
Definition: BLI_assert.h:46
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])
const char * BLI_path_basename(const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT
Definition: path_util.c:1653
void BLI_path_rel(char *file, const char *relfile) ATTR_NONNULL()
Definition: path_util.c:450
#define STRNCPY(dst, src)
Definition: BLI_string.h:483
@ IMA_GENTYPE_BLANK
@ IMA_SRC_FILE
@ MA_BM_BLEND
@ SOCK_OUT
@ SOCK_IN
eNodeSocketDatatype
@ SOCK_VECTOR
@ SOCK_FLOAT
@ SOCK_RGBA
struct bNodeTreeType * ntreeType_Shader
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Sky Generate a procedural sky texture Noise Generate fractal Perlin noise Wave Generate procedural bands or rings with noise Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks SH_NODE_TEX_COORD
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 Bright Control the brightness and contrast of the input color Vector Map an input vectors to used to fine tune the interpolation of the input 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 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
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block SH_NODE_TEX_IMAGE
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 Bright Control the brightness and contrast of the input color Vector Map an input vectors to used to fine tune the interpolation of the input 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 a color
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value SH_NODE_NORMAL_MAP
constexpr const T * data() const
Definition: BLI_span.hh:203
constexpr int64_t size() const
Definition: BLI_span.hh:240
constexpr const char * data() const
ShaderNodetreeWrap(Main *bmain, const MTLMaterial &mtl_mat, Material *mat, bool relative_paths)
OperationNode * node
depth_tx normal_tx diffuse_light_tx specular_light_tx volume_light_tx environment_tx ambient_occlusion_tx aov_value_tx in_weight_img image(1, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "out_weight_img") .image(3
bool remove(void *owner, const AttributeIDRef &attribute_id)
static Image * load_image_at_path(Main *bmain, const std::string &path, bool relative_paths)
static Image * create_placeholder_image(Main *bmain, const std::string &path)
static void set_property_of_socket(eNodeSocketDatatype property_type, StringRef socket_id, Span< float > value, bNode *r_node)
static Image * load_texture_image(Main *bmain, const tex_map_XX &tex_map, bool relative_paths)
static const pxr::TfToken ior("ior", pxr::TfToken::Immortal)
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal)
static const pxr::TfToken specular("specular", pxr::TfToken::Immortal)
#define min(a, b)
Definition: sort.c:35
Definition: BKE_main.h:121
char idname[64]
Definition: BKE_node.h:375
float locy
struct ID * id
float locx
void * storage
Map< const eMTLSyntaxElement, tex_map_XX > texture_maps
const tex_map_XX & tex_map_of_type(const eMTLSyntaxElement key) const
float z
float y
float x
float max