Blender V4.3
obj_export_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_node.hh"
11#include "BKE_node_runtime.hh"
12
13#include "BLI_map.hh"
14#include "BLI_math_vector.h"
15#include "BLI_math_vector.hh"
16#include "BLI_path_utils.hh"
17#include "BLI_string.h"
18
19#include "DNA_material_types.h"
20#include "DNA_node_types.h"
21
22#include "obj_export_mesh.hh"
23#include "obj_export_mtl.hh"
24
25namespace blender::io::obj {
26
28 "Base Color",
29 "Metallic",
30 "Specular IOR Level",
31 "Roughness", /* Map specular exponent to roughness. */
32 "Roughness",
33 "Sheen Weight",
34 "Metallic", /* Map reflection to metallic. */
35 "Emission Color",
36 "Alpha",
37 "Normal",
38};
40 "array size mismatch");
41
45static void copy_property_from_node(const eNodeSocketDatatype property_type,
46 const bNode *node,
47 const char *identifier,
48 MutableSpan<float> r_property)
49{
50 if (!node) {
51 return;
52 }
53 const bNodeSocket *socket = bke::node_find_socket(
54 const_cast<bNode *>(node), SOCK_IN, identifier);
55 BLI_assert(socket && socket->type == property_type);
56 if (!socket) {
57 return;
58 }
59 switch (property_type) {
60 case SOCK_FLOAT: {
61 BLI_assert(r_property.size() == 1);
62 const bNodeSocketValueFloat *socket_def_value = static_cast<const bNodeSocketValueFloat *>(
63 socket->default_value);
64 r_property[0] = socket_def_value->value;
65 break;
66 }
67 case SOCK_RGBA: {
68 BLI_assert(r_property.size() == 3);
69 const bNodeSocketValueRGBA *socket_def_value = static_cast<const bNodeSocketValueRGBA *>(
70 socket->default_value);
71 copy_v3_v3(r_property.data(), socket_def_value->value);
72 break;
73 }
74 case SOCK_VECTOR: {
75 BLI_assert(r_property.size() == 3);
76 const bNodeSocketValueVector *socket_def_value = static_cast<const bNodeSocketValueVector *>(
77 socket->default_value);
78 copy_v3_v3(r_property.data(), socket_def_value->value);
79 break;
80 }
81 default: {
82 /* Other socket types are not handled here. */
83 BLI_assert(0);
84 break;
85 }
86 }
87}
88
92static void linked_sockets_to_dest_id(const bNode *dest_node,
93 const bNodeTree &node_tree,
94 const char *dest_socket_id,
95 Vector<const bNodeSocket *> &r_linked_sockets)
96{
97 r_linked_sockets.clear();
98 if (!dest_node) {
99 return;
100 }
101 Span<const bNode *> object_dest_nodes = node_tree.nodes_by_type(dest_node->idname);
102 Span<const bNodeSocket *> dest_inputs = object_dest_nodes.first()->input_sockets();
103 const bNodeSocket *dest_socket = nullptr;
104 for (const bNodeSocket *curr_socket : dest_inputs) {
105 if (STREQ(curr_socket->identifier, dest_socket_id)) {
106 dest_socket = curr_socket;
107 break;
108 }
109 }
110 if (dest_socket) {
111 Span<const bNodeSocket *> linked_sockets = dest_socket->directly_linked_sockets();
112 r_linked_sockets.resize(linked_sockets.size());
113 r_linked_sockets = linked_sockets;
114 }
115}
116
120static const bNode *get_node_of_type(Span<const bNodeSocket *> sockets_list, const int node_type)
121{
122 for (const bNodeSocket *socket : sockets_list) {
123 const bNode &parent_node = socket->owner_node();
124 if (parent_node.typeinfo->type == node_type) {
125 return &parent_node;
126 }
127 }
128 return nullptr;
129}
130
131/*
132 * From a texture image shader node, get the image's filepath.
133 * If packed image is found, only the file "name" is returned.
134 */
135static std::string get_image_filepath(const bNode *tex_node)
136{
137 if (!tex_node) {
138 return "";
139 }
140 Image *tex_image = reinterpret_cast<Image *>(tex_node->id);
141 if (!tex_image || !BKE_image_has_filepath(tex_image)) {
142 return "";
143 }
144
145 if (BKE_image_has_packedfile(tex_image)) {
146 /* Put image in the same directory as the `.MTL` file. */
147 const char *filename = BLI_path_basename(tex_image->filepath);
148 fprintf(stderr,
149 "Packed image found:'%s'. Unpack and place the image in the same "
150 "directory as the .MTL file.\n",
151 filename);
152 return filename;
153 }
154
155 char filepath[FILE_MAX];
156 STRNCPY(filepath, tex_image->filepath);
157
158 if (tex_image->source == IMA_SRC_SEQUENCE) {
159 char head[FILE_MAX], tail[FILE_MAX];
160 ushort numlen;
161 int framenr = static_cast<NodeTexImage *>(tex_node->storage)->iuser.framenr;
162 BLI_path_sequence_decode(filepath, head, sizeof(head), tail, sizeof(tail), &numlen);
163 BLI_path_sequence_encode(filepath, sizeof(filepath), head, tail, numlen, framenr);
164 }
165
166 return filepath;
167}
168
174static const bNode *find_bsdf_node(const bNodeTree *nodetree)
175{
176 if (!nodetree) {
177 return nullptr;
178 }
179 for (const bNode *node : nodetree->nodes_by_type("ShaderNodeOutputMaterial")) {
180 const bNodeSocket &node_input_socket0 = node->input_socket(0);
181 for (const bNodeSocket *out_sock : node_input_socket0.directly_linked_sockets()) {
182 const bNode &in_node = out_sock->owner_node();
183 if (in_node.typeinfo->type == SH_NODE_BSDF_PRINCIPLED) {
184 return &in_node;
185 }
186 }
187 }
188 return nullptr;
189}
190
194static void store_bsdf_properties(const bNode *bsdf_node,
195 const Material *material,
196 MTLMaterial &r_mtl_mat)
197{
198 float roughness = material->roughness;
199 if (bsdf_node) {
200 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Roughness", {&roughness, 1});
201 }
202 /* Empirical approximation. Importer should use the inverse of this method. */
203 float spec_exponent = (1.0f - roughness);
204 spec_exponent *= spec_exponent * 1000.0f;
205
206 float specular = material->spec;
207 if (bsdf_node) {
208 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Specular IOR Level", {&specular, 1});
209 }
210
211 float metallic = material->metallic;
212 if (bsdf_node) {
213 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Metallic", {&metallic, 1});
214 }
215
216 float refraction_index = 1.0f;
217 if (bsdf_node) {
218 copy_property_from_node(SOCK_FLOAT, bsdf_node, "IOR", {&refraction_index, 1});
219 }
220
221 float alpha = material->a;
222 if (bsdf_node) {
223 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Alpha", {&alpha, 1});
224 }
225 const bool transparent = alpha != 1.0f;
226
227 float3 diffuse_col = {material->r, material->g, material->b};
228 if (bsdf_node) {
229 copy_property_from_node(SOCK_RGBA, bsdf_node, "Base Color", {diffuse_col, 3});
230 }
231
232 float3 emission_col{0.0f};
233 float emission_strength = 0.0f;
234 if (bsdf_node) {
235 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Emission Strength", {&emission_strength, 1});
236 copy_property_from_node(SOCK_RGBA, bsdf_node, "Emission Color", {emission_col, 3});
237 }
238 mul_v3_fl(emission_col, emission_strength);
239
240 float sheen = -1.0f;
241 float coat = -1.0f;
242 float coat_roughness = -1.0f;
243 float aniso = -1.0f;
244 float aniso_rot = -1.0f;
245 float transmission = -1.0f;
246 if (bsdf_node) {
247 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Sheen Weight", {&sheen, 1});
248 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Coat Weight", {&coat, 1});
249 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Coat Roughness", {&coat_roughness, 1});
250 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Anisotropic", {&aniso, 1});
251 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Anisotropic Rotation", {&aniso_rot, 1});
252 copy_property_from_node(SOCK_FLOAT, bsdf_node, "Transmission Weight", {&transmission, 1});
253
254 /* Clearcoat used to include an implicit 0.25 factor, so stay compatible to old versions. */
255 coat *= 4.0f;
256 }
257
258 /* See https://wikipedia.org/wiki/Wavefront_.obj_file for all possible values of `illum`. */
259 /* Highlight on. */
260 int illum = 2;
261 if (specular == 0.0f) {
262 /* Color on and Ambient on. */
263 illum = 1;
264 }
265 else if (metallic > 0.0f) {
266 /* Metallic ~= Reflection. */
267 if (transparent) {
268 /* Transparency: Refraction on, Reflection: ~~Fresnel off and Ray trace~~ on. */
269 illum = 6;
270 }
271 else {
272 /* Reflection on and Ray trace on. */
273 illum = 3;
274 }
275 }
276 else if (transparent) {
277 /* Transparency: Glass on, Reflection: Ray trace off */
278 illum = 9;
279 }
280 r_mtl_mat.spec_exponent = spec_exponent;
281 if (metallic != 0.0f) {
282 r_mtl_mat.ambient_color = {metallic, metallic, metallic};
283 }
284 else {
285 r_mtl_mat.ambient_color = {1.0f, 1.0f, 1.0f};
286 }
287 r_mtl_mat.color = diffuse_col;
288 r_mtl_mat.spec_color = {specular, specular, specular};
289 r_mtl_mat.emission_color = emission_col;
290 r_mtl_mat.ior = refraction_index;
291 r_mtl_mat.alpha = alpha;
292 r_mtl_mat.illum_mode = illum;
293 r_mtl_mat.roughness = roughness;
294 r_mtl_mat.metallic = metallic;
295 r_mtl_mat.sheen = sheen;
296 r_mtl_mat.cc_thickness = coat;
297 r_mtl_mat.cc_roughness = coat_roughness;
298 r_mtl_mat.aniso = aniso;
299 r_mtl_mat.aniso_rot = aniso_rot;
300 r_mtl_mat.transmit_color = {transmission, transmission, transmission};
301}
302
306static void store_image_textures(const bNode *bsdf_node,
307 const bNodeTree *node_tree,
308 const Material *material,
309 MTLMaterial &r_mtl_mat)
310{
311 if (!material || !node_tree || !bsdf_node) {
312 /* No nodetree, no images, or no Principled BSDF node. */
313 return;
314 }
315
316 /* Normal Map Texture has two extra tasks of:
317 * - finding a Normal Map node before finding a texture node.
318 * - finding "Strength" property of the node for `-bm` option.
319 */
320
321 for (int key = 0; key < int(MTLTexMapType::Count); ++key) {
322 MTLTexMap &value = r_mtl_mat.texture_maps[key];
323 Vector<const bNodeSocket *> linked_sockets;
324 const bNode *normal_map_node{nullptr};
325
326 if (key == int(MTLTexMapType::Normal)) {
327 /* Find sockets linked to destination "Normal" socket in P-BSDF node. */
328 linked_sockets_to_dest_id(bsdf_node, *node_tree, "Normal", linked_sockets);
329 /* Among the linked sockets, find Normal Map shader node. */
330 normal_map_node = get_node_of_type(linked_sockets, SH_NODE_NORMAL_MAP);
331
332 /* Find sockets linked to "Color" socket in normal map node. */
333 linked_sockets_to_dest_id(normal_map_node, *node_tree, "Color", linked_sockets);
334 }
335 else {
336 /* Skip emission map if emission strength is zero. */
337 if (key == int(MTLTexMapType::Emission)) {
338 float emission_strength = 0.0f;
340 SOCK_FLOAT, bsdf_node, "Emission Strength", {&emission_strength, 1});
341 if (emission_strength == 0.0f) {
342 continue;
343 }
344 }
345 /* Find sockets linked to the destination socket of interest, in P-BSDF node. */
347 bsdf_node, *node_tree, tex_map_type_to_socket_id[key], linked_sockets);
348 }
349
350 /* Among the linked sockets, find Image Texture shader node. */
351 const bNode *tex_node{get_node_of_type(linked_sockets, SH_NODE_TEX_IMAGE)};
352 if (!tex_node) {
353 continue;
354 }
355 const std::string tex_image_filepath = get_image_filepath(tex_node);
356 if (tex_image_filepath.empty()) {
357 continue;
358 }
359
360 /* Find "Mapping" node if connected to texture node. */
361 linked_sockets_to_dest_id(tex_node, *node_tree, "Vector", linked_sockets);
362 const bNode *mapping = get_node_of_type(linked_sockets, SH_NODE_MAPPING);
363
364 if (normal_map_node) {
366 SOCK_FLOAT, normal_map_node, "Strength", {&r_mtl_mat.normal_strength, 1});
367 }
368 /* Texture transform options. Only translation (origin offset, "-o") and scale
369 * ("-o") are supported. */
370 copy_property_from_node(SOCK_VECTOR, mapping, "Location", {value.translation, 3});
371 copy_property_from_node(SOCK_VECTOR, mapping, "Scale", {value.scale, 3});
372
373 value.image_path = tex_image_filepath;
374 }
375}
376
378{
379 BLI_assert(material != nullptr);
380 MTLMaterial mtlmat;
381 mtlmat.name = std::string(material->id.name + 2);
382 std::replace(mtlmat.name.begin(), mtlmat.name.end(), ' ', '_');
383 const bNodeTree *nodetree = material->nodetree;
384 if (nodetree != nullptr) {
385 nodetree->ensure_topology_cache();
386 }
387
388 const bNode *bsdf_node = find_bsdf_node(nodetree);
389 store_bsdf_properties(bsdf_node, material, mtlmat);
390 store_image_textures(bsdf_node, nodetree, material, mtlmat);
391 return mtlmat;
392}
393
394} // namespace blender::io::obj
bool BKE_image_has_filepath(const Image *ima)
bool BKE_image_has_packedfile(const Image *image)
#define SH_NODE_TEX_IMAGE
Definition BKE_node.hh:930
#define SH_NODE_NORMAL_MAP
Definition BKE_node.hh:958
#define SH_NODE_MAPPING
Definition BKE_node.hh:900
#define BLI_STATIC_ASSERT(a, msg)
Definition BLI_assert.h:87
#define BLI_assert(a)
Definition BLI_assert.h:50
MINLINE void mul_v3_fl(float r[3], float f)
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
#define FILE_MAX
int BLI_path_sequence_decode(const char *path, char *head, size_t head_maxncpy, char *tail, size_t tail_maxncpy, unsigned short *r_digits_len)
Definition path_utils.cc:57
void BLI_path_sequence_encode(char *path, size_t path_maxncpy, const char *head, const char *tail, unsigned short numlen, int pic)
#define STRNCPY(dst, src)
Definition BLI_string.h:593
unsigned short ushort
#define ARRAY_SIZE(arr)
#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 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
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr T * data() const
Definition BLI_span.hh:540
constexpr const T & first() const
Definition BLI_span.hh:316
constexpr int64_t size() const
Definition BLI_span.hh:253
void resize(const int64_t new_size)
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
bNodeSocket * node_find_socket(bNode *node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:1829
const char * tex_map_type_to_socket_id[]
static const bNode * get_node_of_type(Span< const bNodeSocket * > sockets_list, const int node_type)
static std::string get_image_filepath(const bNode *tex_node)
static const bNode * find_bsdf_node(const bNodeTree *nodetree)
static void store_bsdf_properties(const bNode *bsdf_node, const Material *material, MTLMaterial &r_mtl_mat)
MTLMaterial mtlmaterial_for_material(const Material *material)
static void copy_property_from_node(const eNodeSocketDatatype property_type, const bNode *node, const char *identifier, MutableSpan< float > r_property)
static void store_image_textures(const bNode *bsdf_node, const bNodeTree *node_tree, const Material *material, MTLMaterial &r_mtl_mat)
static void linked_sockets_to_dest_id(const bNode *dest_node, const bNodeTree &node_tree, const char *dest_socket_id, Vector< const bNodeSocket * > &r_linked_sockets)
closure color sheen(normal N, float roughness) BUILTIN
char filepath[1024]
short source
void * default_value
bNodeTypeHandle * typeinfo
struct ID * id
void * storage
char idname[64]
MTLTexMap texture_maps[int(MTLTexMapType::Count)]