Blender  V3.3
obj_export_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_node.h"
9 
10 #include "BLI_map.hh"
11 #include "BLI_math_vector.h"
12 #include "BLI_math_vector.hh"
13 #include "BLI_path_util.h"
14 
15 #include "DNA_material_types.h"
16 #include "DNA_node_types.h"
17 
18 #include "NOD_node_tree_ref.hh"
19 
20 #include "obj_export_mesh.hh"
21 #include "obj_export_mtl.hh"
22 
23 namespace blender::io::obj {
24 
28 static void copy_property_from_node(const eNodeSocketDatatype property_type,
29  const bNode *node,
30  const char *identifier,
31  MutableSpan<float> r_property)
32 {
33  if (!node) {
34  return;
35  }
36  bNodeSocket *socket{nodeFindSocket(node, SOCK_IN, identifier)};
37  BLI_assert(socket && socket->type == property_type);
38  if (!socket) {
39  return;
40  }
41  switch (property_type) {
42  case SOCK_FLOAT: {
43  BLI_assert(r_property.size() == 1);
44  bNodeSocketValueFloat *socket_def_value = static_cast<bNodeSocketValueFloat *>(
45  socket->default_value);
46  r_property[0] = socket_def_value->value;
47  break;
48  }
49  case SOCK_RGBA: {
50  BLI_assert(r_property.size() == 3);
51  bNodeSocketValueRGBA *socket_def_value = static_cast<bNodeSocketValueRGBA *>(
52  socket->default_value);
53  copy_v3_v3(r_property.data(), socket_def_value->value);
54  break;
55  }
56  case SOCK_VECTOR: {
57  BLI_assert(r_property.size() == 3);
58  bNodeSocketValueVector *socket_def_value = static_cast<bNodeSocketValueVector *>(
59  socket->default_value);
60  copy_v3_v3(r_property.data(), socket_def_value->value);
61  break;
62  }
63  default: {
64  /* Other socket types are not handled here. */
65  BLI_assert(0);
66  break;
67  }
68  }
69 }
70 
74 static void linked_sockets_to_dest_id(const bNode *dest_node,
76  StringRefNull dest_socket_id,
78 {
79  r_linked_sockets.clear();
80  if (!dest_node) {
81  return;
82  }
83  Span<const nodes::NodeRef *> object_dest_nodes = node_tree.nodes_by_type(dest_node->idname);
84  Span<const nodes::InputSocketRef *> dest_inputs = object_dest_nodes.first()->inputs();
85  const nodes::InputSocketRef *dest_socket = nullptr;
86  for (const nodes::InputSocketRef *curr_socket : dest_inputs) {
87  if (STREQ(curr_socket->bsocket()->identifier, dest_socket_id.c_str())) {
88  dest_socket = curr_socket;
89  break;
90  }
91  }
92  if (dest_socket) {
93  Span<const nodes::OutputSocketRef *> linked_sockets = dest_socket->directly_linked_sockets();
94  r_linked_sockets.resize(linked_sockets.size());
95  r_linked_sockets = linked_sockets;
96  }
97 }
98 
103  const int node_type)
104 {
105  for (const nodes::SocketRef *socket : sockets_list) {
106  const bNode *parent_node = socket->bnode();
107  if (parent_node->typeinfo->type == node_type) {
108  return parent_node;
109  }
110  }
111  return nullptr;
112 }
113 
114 /*
115  * From a texture image shader node, get the image's filepath.
116  * If packed image is found, only the file "name" is returned.
117  */
118 static std::string get_image_filepath(const bNode *tex_node)
119 {
120  if (!tex_node) {
121  return "";
122  }
123  Image *tex_image = reinterpret_cast<Image *>(tex_node->id);
124  if (!tex_image || !BKE_image_has_filepath(tex_image)) {
125  return "";
126  }
127 
128  if (BKE_image_has_packedfile(tex_image)) {
129  /* Put image in the same directory as the .MTL file. */
130  const char *filename = BLI_path_slash_rfind(tex_image->filepath) + 1;
131  fprintf(stderr,
132  "Packed image found:'%s'. Unpack and place the image in the same "
133  "directory as the .MTL file.\n",
134  filename);
135  return filename;
136  }
137 
138  char path[FILE_MAX];
139  BLI_strncpy(path, tex_image->filepath, FILE_MAX);
140 
141  if (tex_image->source == IMA_SRC_SEQUENCE) {
142  char head[FILE_MAX], tail[FILE_MAX];
143  unsigned short numlen;
144  int framenr = static_cast<NodeTexImage *>(tex_node->storage)->iuser.framenr;
145  BLI_path_sequence_decode(path, head, tail, &numlen);
146  BLI_path_sequence_encode(path, head, tail, numlen, framenr);
147  }
148 
149  return path;
150 }
151 
157 static const nodes::NodeRef *find_bsdf_node(const nodes::NodeTreeRef *nodetree)
158 {
159  if (!nodetree) {
160  return nullptr;
161  }
162  for (const nodes::NodeRef *node : nodetree->nodes_by_type("ShaderNodeOutputMaterial")) {
163  const nodes::InputSocketRef *node_input_socket0 = node->inputs()[0];
164  for (const nodes::OutputSocketRef *out_sock : node_input_socket0->directly_linked_sockets()) {
165  const nodes::NodeRef &in_node = out_sock->node();
166  if (in_node.typeinfo()->type == SH_NODE_BSDF_PRINCIPLED) {
167  return &in_node;
168  }
169  }
170  }
171  return nullptr;
172 }
173 
177 static void store_bsdf_properties(const nodes::NodeRef *bsdf_node,
178  const Material *material,
179  MTLMaterial &r_mtl_mat)
180 {
181  const bNode *bnode = nullptr;
182  if (bsdf_node) {
183  bnode = bsdf_node->bnode();
184  }
185 
186  /* If p-BSDF is not present, fallback to #Object.Material. */
187  float roughness = material->roughness;
188  if (bnode) {
189  copy_property_from_node(SOCK_FLOAT, bnode, "Roughness", {&roughness, 1});
190  }
191  /* Empirical approximation. Importer should use the inverse of this method. */
192  float spec_exponent = (1.0f - roughness);
193  spec_exponent *= spec_exponent * 1000.0f;
194 
195  float specular = material->spec;
196  if (bnode) {
197  copy_property_from_node(SOCK_FLOAT, bnode, "Specular", {&specular, 1});
198  }
199 
200  float metallic = material->metallic;
201  if (bnode) {
202  copy_property_from_node(SOCK_FLOAT, bnode, "Metallic", {&metallic, 1});
203  }
204 
205  float refraction_index = 1.0f;
206  if (bnode) {
207  copy_property_from_node(SOCK_FLOAT, bnode, "IOR", {&refraction_index, 1});
208  }
209 
210  float dissolved = material->a;
211  if (bnode) {
212  copy_property_from_node(SOCK_FLOAT, bnode, "Alpha", {&dissolved, 1});
213  }
214  const bool transparent = dissolved != 1.0f;
215 
216  float3 diffuse_col = {material->r, material->g, material->b};
217  if (bnode) {
218  copy_property_from_node(SOCK_RGBA, bnode, "Base Color", {diffuse_col, 3});
219  }
220 
221  float3 emission_col{0.0f};
222  float emission_strength = 0.0f;
223  if (bnode) {
224  copy_property_from_node(SOCK_FLOAT, bnode, "Emission Strength", {&emission_strength, 1});
225  copy_property_from_node(SOCK_RGBA, bnode, "Emission", {emission_col, 3});
226  }
227  mul_v3_fl(emission_col, emission_strength);
228 
229  /* See https://wikipedia.org/wiki/Wavefront_.obj_file for all possible values of `illum`. */
230  /* Highlight on. */
231  int illum = 2;
232  if (specular == 0.0f) {
233  /* Color on and Ambient on. */
234  illum = 1;
235  }
236  else if (metallic > 0.0f) {
237  /* Metallic ~= Reflection. */
238  if (transparent) {
239  /* Transparency: Refraction on, Reflection: ~~Fresnel off and Ray trace~~ on. */
240  illum = 6;
241  }
242  else {
243  /* Reflection on and Ray trace on. */
244  illum = 3;
245  }
246  }
247  else if (transparent) {
248  /* Transparency: Glass on, Reflection: Ray trace off */
249  illum = 9;
250  }
251  r_mtl_mat.Ns = spec_exponent;
252  if (metallic != 0.0f) {
253  r_mtl_mat.Ka = {metallic, metallic, metallic};
254  }
255  else {
256  r_mtl_mat.Ka = {1.0f, 1.0f, 1.0f};
257  }
258  r_mtl_mat.Kd = diffuse_col;
259  r_mtl_mat.Ks = {specular, specular, specular};
260  r_mtl_mat.Ke = emission_col;
261  r_mtl_mat.Ni = refraction_index;
262  r_mtl_mat.d = dissolved;
263  r_mtl_mat.illum = illum;
264 }
265 
269 static void store_image_textures(const nodes::NodeRef *bsdf_node,
271  const Material *material,
272  MTLMaterial &r_mtl_mat)
273 {
274  if (!material || !node_tree || !bsdf_node) {
275  /* No nodetree, no images, or no Principled BSDF node. */
276  return;
277  }
278  const bNode *bnode = bsdf_node->bnode();
279 
280  /* Normal Map Texture has two extra tasks of:
281  * - finding a Normal Map node before finding a texture node.
282  * - finding "Strength" property of the node for `-bm` option.
283  */
284 
286  r_mtl_mat.texture_maps.items()) {
288  const bNode *normal_map_node{nullptr};
289 
290  if (texture_map.key == eMTLSyntaxElement::map_Bump) {
291  /* Find sockets linked to destination "Normal" socket in P-BSDF node. */
292  linked_sockets_to_dest_id(bnode, *node_tree, "Normal", linked_sockets);
293  /* Among the linked sockets, find Normal Map shader node. */
294  normal_map_node = get_node_of_type(linked_sockets, SH_NODE_NORMAL_MAP);
295 
296  /* Find sockets linked to "Color" socket in normal map node. */
297  linked_sockets_to_dest_id(normal_map_node, *node_tree, "Color", linked_sockets);
298  }
299  else {
300  /* Skip emission map if emission strength is zero. */
301  if (texture_map.key == eMTLSyntaxElement::map_Ke) {
302  float emission_strength = 0.0f;
303  copy_property_from_node(SOCK_FLOAT, bnode, "Emission Strength", {&emission_strength, 1});
304  if (emission_strength == 0.0f) {
305  continue;
306  }
307  }
308  /* Find sockets linked to the destination socket of interest, in P-BSDF node. */
310  bnode, *node_tree, texture_map.value.dest_socket_id, linked_sockets);
311  }
312 
313  /* Among the linked sockets, find Image Texture shader node. */
314  const bNode *tex_node{get_node_of_type(linked_sockets, SH_NODE_TEX_IMAGE)};
315  if (!tex_node) {
316  continue;
317  }
318  const std::string tex_image_filepath = get_image_filepath(tex_node);
319  if (tex_image_filepath.empty()) {
320  continue;
321  }
322 
323  /* Find "Mapping" node if connected to texture node. */
324  linked_sockets_to_dest_id(tex_node, *node_tree, "Vector", linked_sockets);
325  const bNode *mapping = get_node_of_type(linked_sockets, SH_NODE_MAPPING);
326 
327  if (normal_map_node) {
329  SOCK_FLOAT, normal_map_node, "Strength", {&r_mtl_mat.map_Bump_strength, 1});
330  }
331  /* Texture transform options. Only translation (origin offset, "-o") and scale
332  * ("-o") are supported. */
333  copy_property_from_node(SOCK_VECTOR, mapping, "Location", {texture_map.value.translation, 3});
334  copy_property_from_node(SOCK_VECTOR, mapping, "Scale", {texture_map.value.scale, 3});
335 
336  texture_map.value.image_path = tex_image_filepath;
337  }
338 }
339 
341 {
342  BLI_assert(material != nullptr);
343  MTLMaterial mtlmat;
344  mtlmat.name = std::string(material->id.name + 2);
345  std::replace(mtlmat.name.begin(), mtlmat.name.end(), ' ', '_');
346  const nodes::NodeTreeRef *nodetree = nullptr;
347  if (material->nodetree) {
348  nodetree = new nodes::NodeTreeRef(material->nodetree);
349  }
350  const nodes::NodeRef *bsdf_node = find_bsdf_node(nodetree);
351  store_bsdf_properties(bsdf_node, material, mtlmat);
352  store_image_textures(bsdf_node, nodetree, material, mtlmat);
353  delete nodetree;
354  return mtlmat;
355 }
356 
357 } // namespace blender::io::obj
bool BKE_image_has_filepath(const struct Image *ima)
bool BKE_image_has_packedfile(const struct Image *image)
#define SH_NODE_BSDF_PRINCIPLED
Definition: BKE_node.h:1164
struct bNodeSocket * nodeFindSocket(const struct bNode *node, eNodeSocketInOut in_out, const char *identifier)
#define SH_NODE_MAPPING
Definition: BKE_node.h:1088
#define BLI_assert(a)
Definition: BLI_assert.h:46
MINLINE void mul_v3_fl(float r[3], float f)
MINLINE void copy_v3_v3(float r[3], const float a[3])
int BLI_path_sequence_decode(const char *string, char *head, char *tail, unsigned short *r_digits_len)
Definition: path_util.c:56
#define FILE_MAX
void BLI_path_sequence_encode(char *string, const char *head, const char *tail, unsigned short numlen, int pic)
Definition: path_util.c:123
const char * BLI_path_slash_rfind(const char *string) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT
Definition: path_util.c:1765
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t maxncpy) ATTR_NONNULL()
Definition: string.c:64
#define STREQ(a, b)
@ IMA_SRC_SEQUENCE
@ SOCK_IN
eNodeSocketDatatype
@ SOCK_VECTOR
@ SOCK_FLOAT
@ SOCK_RGBA
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
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
char filepath[1024]
short source
constexpr int64_t size() const
Definition: BLI_span.hh:511
constexpr T * data() const
Definition: BLI_span.hh:548
constexpr const T & first() const
Definition: BLI_span.hh:303
constexpr int64_t size() const
Definition: BLI_span.hh:240
constexpr const char * c_str() const
void resize(const int64_t new_size)
Definition: BLI_vector.hh:353
Span< const OutputSocketRef * > directly_linked_sockets() const
bNodeType * typeinfo() const
Span< const NodeRef * > nodes_by_type(StringRefNull idname) const
OperationNode * node
Material material
static const nodes::NodeRef * find_bsdf_node(const nodes::NodeTreeRef *nodetree)
static const bNode * get_node_of_type(Span< const nodes::OutputSocketRef * > sockets_list, const int node_type)
static std::string get_image_filepath(const bNode *tex_node)
static void linked_sockets_to_dest_id(const bNode *dest_node, const nodes::NodeTreeRef &node_tree, StringRefNull dest_socket_id, Vector< const nodes::OutputSocketRef * > &r_linked_sockets)
MTLMaterial mtlmaterial_for_material(const Material *material)
static void store_bsdf_properties(const nodes::NodeRef *bsdf_node, const Material *material, MTLMaterial &r_mtl_mat)
static void store_image_textures(const nodes::NodeRef *bsdf_node, const nodes::NodeTreeRef *node_tree, const Material *material, MTLMaterial &r_mtl_mat)
static void copy_property_from_node(const eNodeSocketDatatype property_type, const bNode *node, const char *identifier, MutableSpan< float > r_property)
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal)
static const pxr::TfToken specular("specular", pxr::TfToken::Immortal)
char name[66]
Definition: DNA_ID.h:378
struct bNodeTree * nodetree
int type
Definition: BKE_node.h:228
struct bNodeType * typeinfo
struct ID * id
void * storage
char idname[64]
Map< const eMTLSyntaxElement, tex_map_XX > texture_maps