MDL SDK API nvidia_logo_transpbg.gif Up
Example for Material Distilling and Baking
[Previous] [Up] [Next]

This example introduces the distillation of mdl materials to a fixed target model and showcases how to bake material sub-expressions to a texture.

New Topics

  • Material Distilling
  • Baking material sub-expressions

Detailed Description

Material Distilling


MDL materials can be of arbitrary complexity. In some contexts like realtime environments it may however be desirable to deal with a fixed material model instead. The mi::neuraylib::IMdl_distiller_api provides a mechanism to transform MDL materials to a number of predefined target material models.
In this example we will show how to distill a material to a specific target model, how to extract sub-expressions from the resulting material and how to bake those sub-expressions into textures and constant values.
In create_material_instance we first load an mdl module and create a material instance. The material distilling requires our source material to be compiled, therefore we call compile_material_instance to build a compiled representation of it. We then acquire a pointer to the mi::neuraylib::IMdl_distiller_api interface. In create_distilled_material we call mi::neuraylib::IMdl_distiller_api::distill_material(), passing in the compiled material and the desired target model. The function returns the distilled material in compiled form. Now we need to investigate the distilled material and extract the information we need for further processing. This is done in setup_target_material. Here, we first lookup a pointer to the call expression associated with the surface.scattering sub-expression of the distilled material and extract the semantic of that function for later use. Depending of the target model, we set up a simple container storing material parameter names and associated parameter values. We then analyze the distilled material and extract material sub-expressions that match our parameters and collect them for later baking.

Baking material subexpressions


The function bake_target_material_inputs contains the code needed to bake the extracted sub-expressions to textures and constants. Here, we loop over the material container and check for each parameter if a bake path has been found. If so, we acquire a pointer to an mi::neuraylib::IBaker instance by calling mi::neuraylib::IMdl_distiller_api::create_baker() for the given bake-path. If the function mi::neuraylib::IBaker::is_uniform returns true the expression attached to the path is constant in the sense that it does not depend on texture coordinates. Therefore, it is sufficient to bake a constant value. We call mi::neuraylib::IBaker::bake_constant() and store the computed value in the material parameter struct.
In case of a varying bake path, we use the mi::neuraylib::IImage_api to create a canvas of the appropriate type, pass it to mi::neuraylib::IBaker::bake_texture() and store it in the material parameter structure. Back in main we call process_target_material. In this simple example we loop over all material parameters again, print some information to stdout and save the baked textures.

Example Source

Source Code Location: examples/mdl_sdk/distilling/example_distilling.cpp

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/distilling/example_distilling.cpp
//
// Introduces the distillation of mdl materials to a fixed target model
// and showcases how to bake material paths to a texture
#include <cassert>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <fstream>
#include <thread>
#include "example_shared.h"
#include "example_distilling_shared.h"
#include "utils/profiling.h"
using namespace mi::examples::profiling;
// Small struct used to store the result of a texture baking process
// of a material sub expression
struct Material_parameter
{
// Small struct used to store the UV tiles
// This approach assumes that there are no u/v transformations, otherwise it ends up baking the wrong u/v pairs
struct UVTile
{
UVTile(mi::Sint32 u, mi::Sint32 v)
:min_uv(u, v)
{}
std::pair<mi::Sint32, mi::Sint32> min_uv;
bool operator <(const UVTile & o) const
{
return min_uv < o.min_uv;
}
};
std::map<UVTile, mi::base::Handle<mi::neuraylib::ICanvas>> uv_tile_textures;
typedef void (Remap_func(mi::base::IInterface*));
std::string value_type;
std::string bake_path;
Remap_func* remap_func;
Material_parameter() : remap_func(nullptr)
{
}
Material_parameter(
const std::string& value_type,
Remap_func* func = nullptr)
: value_type(value_type)
, remap_func(func)
{
}
};
typedef std::map<std::string, Material_parameter> Material;
// Creates an instance of the given material.
mi::neuraylib::IFunction_call* create_material_instance(
const std::string& module_qualified_name,
const std::string& material_simple_name)
{
// Load the module.
mdl_impexp_api->load_module(transaction, module_qualified_name.c_str(), context);
if (!print_messages(context))
exit_failure("Loading module '%s' failed.", module_qualified_name.c_str());
// Get the database name for the module we loaded
mdl_factory->get_db_module_name(module_qualified_name.c_str()));
transaction->access<mi::neuraylib::IModule>(module_db_name->get_c_str()));
if (!module)
exit_failure("Failed to access the loaded module.");
// Attach the material name
std::string material_db_name
= std::string(module_db_name->get_c_str()) + "::" + material_simple_name;
material_db_name = mi::examples::mdl::add_missing_material_signature(
module.get(), material_db_name);
if (material_db_name.empty())
exit_failure("Failed to find the material %s in the module %s.",
material_simple_name.c_str(), module_qualified_name.c_str());
// Get the material definition from the database
transaction->access<mi::neuraylib::IFunction_definition>(material_db_name.c_str()));
if (!material_definition)
exit_failure("Accessing definition '%s' failed.", material_db_name.c_str());
// Create a material instance from the material definition with the default arguments.
mi::Sint32 result;
mi::neuraylib::IFunction_call* material_instance =
material_definition->create_function_call(0, &result);
if (result != 0)
exit_failure("Instantiating '%s' failed.", material_db_name.c_str());
return material_instance;
}
// Compiles the given material instance in the given compilation modes and stores
// it in the DB.
mi::neuraylib::ICompiled_material* compile_material_instance(
const mi::neuraylib::IFunction_call* material_instance,
bool class_compilation)
{
Timing timing("Compiling");
mi::Uint32 flags = class_compilation
// convert to target type SID_MATERIAL
mdl_factory->create_type_factory(transaction));
tf->get_predefined_struct(mi::neuraylib::IType_struct::SID_MATERIAL));
context->set_option("target_type", standard_material_type.get());
mi::neuraylib::ICompiled_material* compiled_material =
material_instance2->create_compiled_material(flags, context);
check_success(print_messages(context));
return compiled_material;
}
// Distills the given compiled material to the requested target model
// and returns it
const mi::neuraylib::ICompiled_material* create_distilled_material(
const mi::neuraylib::ICompiled_material* compiled_material,
const char* target_model)
{
mi::Sint32 result = 0;
distiller_api->distill_material(compiled_material, target_model, nullptr, &result));
check_success(result == 0);
distilled_material->retain();
return distilled_material.get();
}
// remap normal
void remap_normal(mi::base::IInterface* icanvas)
{
if(!canvas)
return;
// Convert normal values from the interval [-1.0,1.0] to [0.0, 1.0]
mi::base::Handle<mi::neuraylib::ITile> tile (canvas->get_tile());
mi::Float32* data = static_cast<mi::Float32*>(tile->get_data());
const mi::Uint32 n = canvas->get_resolution_x() * canvas->get_resolution_y() * 3;
for(mi::Uint32 i=0; i<n; ++i)
{
data[i] = (data[i] + 1.f) * 0.5f;
}
}
// simple roughness to glossiness conversion
void rough_to_gloss(mi::base::IInterface* ii)
{
if(canvas)
{
mi::base::Handle<mi::neuraylib::ITile> tile (canvas->get_tile());
mi::Float32* data = static_cast<mi::Float32*>(tile->get_data());
const mi::Uint32 n = canvas->get_resolution_x() * canvas->get_resolution_y();
for(mi::Uint32 i=0; i<n; ++i)
{
data[i] = 1.0f - data[i];
}
return;
}
if(value)
{
mi::get_value(value.get(), f);
value->set_value(1.0f - f);
}
}
// Setup material parameters according to target model and
// collect relevant bake paths
void setup_target_material(
const std::string& target_model,
Material& out_material)
{
Timing timing("Setup");
// Access surface.scattering function
lookup_call("surface.scattering", cm));
// ... and get its semantic
get_call_semantic(transaction, parent_call.get()));
if(target_model == "diffuse")
{
// The target model is supposed to be a diffuse reflection bsdf
check_success(semantic ==
// Setup diffuse material parameters
out_material["color"] = Material_parameter("Rgb_fp");
out_material["roughness"] = Material_parameter("Float32");
out_material["normal"] = Material_parameter("Float32<3>", remap_normal);
// Specify bake paths
out_material["color"].bake_path = "surface.scattering.tint";
out_material["roughness"].bake_path = "surface.scattering.roughness";
out_material["normal"].bake_path = "geometry.normal";
}
else if (target_model == "ue4" || target_model == "transmissive_pbr")
{
// Setup some UE4 material parameters
out_material["base_color"] = Material_parameter("Rgb_fp");
out_material["metallic"] = Material_parameter("Float32");
out_material["specular"] = Material_parameter("Float32");
out_material["roughness"] = Material_parameter("Float32");
out_material["normal"] = Material_parameter("Float32<3>", remap_normal);
out_material["clearcoat_weight"] = Material_parameter("Float32");
out_material["clearcoat_roughness"] = Material_parameter("Float32");
out_material["clearcoat_normal"] = Material_parameter("Float32<3>", remap_normal);
out_material["opacity"] = Material_parameter("Float32");
std::string path_prefix = "surface.scattering.";
bool is_transmissive_pbr = false;
if (target_model == "transmissive_pbr")
{
is_transmissive_pbr = true;
// insert parameters that only apply to transmissive_pbr
out_material["anisotropy"] = Material_parameter("Float32");
out_material["anisotropy_rotation"] = Material_parameter("Float32");
out_material["transparency"] = Material_parameter("Float32");
out_material["transmission_color"] = Material_parameter("Rgb_fp");
// uniform
out_material["attenuation_color"] = Material_parameter("Rgb_fp");
out_material["attenuation_distance"] = Material_parameter("Float32");
out_material["subsurface_color"] = Material_parameter("Rgb_fp");
out_material["volume_ior"] = Material_parameter("Rgb_fp");
// collect volume properties, they are guaranteed to exist
out_material["attenuation_color"].bake_path = "volume.absorption_coefficient.s.v.attenuation";
out_material["subsurface_color"].bake_path = "volume.absorption_coefficient.s.v.subsurface";
out_material["attenuation_distance"].bake_path = "volume.scattering_coefficient.s.v.distance";
out_material["volume_ior"].bake_path = "ior";
}
// Check for a clearcoat layer, first. If present, it is the outermost layer
{
// Setup clearcoat bake paths
out_material["clearcoat_weight"].bake_path = path_prefix + "weight";
out_material["clearcoat_roughness"].bake_path = path_prefix + "layer.roughness_u";
out_material["clearcoat_normal"].bake_path = path_prefix + "normal";
// Get clear-coat base layer
parent_call = lookup_call("base", cm, parent_call.get());
// Get clear-coat base layer semantic
semantic = get_call_semantic(transaction, parent_call.get());
// Extend path prefix
path_prefix += "base.";
}
// Check for a weighted layer. Sole purpose of this layer is the transportation of
// the under-clearcoat-normal. It contains an empty base and a layer with the
// actual material body
{
// Collect under-clearcoat normal
out_material["normal"].bake_path = path_prefix + "normal";
// Chain further
parent_call = lookup_call("layer", cm, parent_call.get());
semantic = get_call_semantic(transaction, parent_call.get());
path_prefix += "layer.";
}
// Check for a normalized mix. This mix combines the metallic and dielectric parts
// of the material
{
// The top-mix component is supposed to be a glossy bsdf
// Collect metallic weight
out_material["metallic"].bake_path = path_prefix + "components.value1.weight";
// And other metallic parameters
if (is_transmissive_pbr) {
out_material["roughness"].bake_path = path_prefix + "components.value1.component.roughness_u.s.r.roughness";
out_material["anisotropy"].bake_path = path_prefix + "components.value1.component.roughness_u.s.r.anisotropy";
out_material["anisotropy_rotation"].bake_path = path_prefix + "components.value1.component.roughness_u.s.r.rotation";
}
else
out_material["roughness"].bake_path = path_prefix + "components.value1.component.roughness_u";
// Base_color can be taken from any of the leaf-bsdfs. It is supposed to
// be the same.
out_material["base_color"].bake_path = path_prefix + "components.value1.component.tint";
// Chain further
parent_call = lookup_call(
"components.value0.component", cm, parent_call.get());
semantic = get_call_semantic(transaction, parent_call.get());
path_prefix += "components.value0.component.";
}
{
// Collect specular parameters
out_material["specular"].bake_path = path_prefix + "weight";
if (is_transmissive_pbr)
{
out_material["roughness"].bake_path = path_prefix + "layer.roughness_u.s.r.roughness";
out_material["anisotropy"].bake_path = path_prefix + "layer.roughness_u.s.r.anisotropy";
out_material["anisotropy_rotation"].bake_path = path_prefix + "layer.roughness_u.s.r.rotation";
}
else
{
out_material["roughness"].bake_path = path_prefix + "layer.roughness_u";
}
// Chain further
parent_call = lookup_call("base", cm, parent_call.get());
semantic = get_call_semantic(transaction, parent_call.get());
path_prefix += "base.";
}
{
check_success(is_transmissive_pbr);
out_material["transparency"].bake_path = path_prefix + "components.value1.weight";
out_material["transmission_color"].bake_path = path_prefix + "components.value1.component.tint";
// Chain further
parent_call = lookup_call("components.value0.component", cm, parent_call.get());
semantic = get_call_semantic(transaction, parent_call.get());
path_prefix += "components.value0.component.";
}
if(semantic ==
{
if(out_material["metallic"].bake_path.empty())
out_material["metallic"].value = create_value(transaction, "Float32", 1.0f);
if(out_material["roughness"].bake_path.empty())
out_material["roughness"].bake_path = path_prefix + "roughness_u";
if(out_material["base_color"].bake_path.empty())
out_material["base_color"].bake_path = path_prefix + "tint";
}
else if(semantic ==
{
if(out_material["base_color"].bake_path.empty())
out_material["base_color"].bake_path = path_prefix + "tint";
}
// Check for cutout-opacity
cm->lookup_sub_expression("geometry.cutout_opacity"));
if(cutout.is_valid_interface())
out_material["opacity"].bake_path = "geometry.cutout_opacity";
}
else if (target_model == "specular_glossy")
{
// Setup parameters for the specular - glossy material model
out_material["base_color"] = Material_parameter("Rgb_fp");
out_material["f0"] = Material_parameter("Rgb_fp");
out_material["f0_color"] = Material_parameter("Rgb_fp");
out_material["f0_refl"] = Material_parameter("Float32");
out_material["f0_weight"] = Material_parameter("Float32");
out_material["glossiness"] = Material_parameter("Float32", rough_to_gloss);
out_material["opacity"] = Material_parameter("Float32");
out_material["normal_map"] = Material_parameter("Float32<3>", remap_normal);
// Specular-glossy distillation can result in a diffuse bsdf, a glossy bsdf
// or a curve-weighted combination of both. Explicitly check the cases
// and save the corresponding bake paths.
switch(semantic)
{
out_material["base_color"].bake_path = "surface.scattering.tint";
out_material["f0_weight"].value = create_value(transaction, "Float32", 0.0f);
out_material["f0_color"].value = create_value(transaction, "Color", mi::Color(0.0f));
break;
out_material["f0_color"].bake_path = "surface.scattering.tint";
out_material["f0_refl"].value = create_value(transaction, "Float32", 1.0f);
out_material["f0_weight"].value = create_value(transaction, "Float32", 1.0f);
out_material["glossiness"].bake_path =
"surface.scattering.roughness_u"; // needs inversion
break;
out_material["base_color"].bake_path ="surface.scattering.base.tint";
out_material["f0_color"].bake_path = "surface.scattering.layer.tint";
out_material["f0_refl"].bake_path = "surface.scattering.normal_reflectivity";
out_material["f0_weight"].bake_path = "surface.scattering.weight";
out_material["glossiness"].bake_path =
"surface.scattering.layer.roughness_u"; // needs inversion
break;
default:
// unknown function, nothing to bake
break;
}
out_material["normal_map"].bake_path = "geometry.normal";
out_material["opacity"].bake_path = "geometry.cutout_opacity";
}
}
// If \p value is a texture, add all its u/v pairs to \p param.
mi::Size search_for_uv_textures(
const mi::neuraylib::IValue* value,
Material_parameter& param)
{
return 0;
const char* texture_name = value_texture->get_value();
if( !texture_name)
return 0;
transaction->access<mi::neuraylib::ITexture>( texture_name));
if( !texture)
return 0;
const char* image_name = texture->get_image();
if( !image_name)
return 0;
transaction->access<mi::neuraylib::IImage>( image_name));
if( !image)
return 0;
mi::Size count = 0;
mi::Size length = image->get_length();
for( mi::Size i = 0; i < length; ++i) {
mi::Size frame_length = image->get_frame_length( i);
count += frame_length;
for( mi::Size j = 0; j < frame_length; ++j) {
mi::Sint32 u = 0;
mi::Sint32 v = 0;
image->get_uvtile_uv( i, j, u, v);
param.uv_tile_textures[Material_parameter::UVTile(u, v)] = NULL;
}
}
return count;
}
// Adds u/v pairs of all found textures to \p param.
//
// Note that this simple traversal does not keeep track of referenced temporaries and parameters
// and traverses them once for each reference.
mi::Size search_for_uv_textures(
const mi::neuraylib::IExpression* expression,
Material_parameter& param)
{
switch( expression->get_kind()) {
mi::base::Handle<const mi::neuraylib::IValue> value( constant->get_value());
return search_for_uv_textures( transaction, value.get(), param);
}
direct_call->get_arguments());
mi::Size count = 0;
for( mi::Size i = 0; i < args->get_size(); ++i) {
count += search_for_uv_textures( transaction, arg.get(), cm, param);
}
return count;
}
mi::Size index = temporary_ref->get_index();
cm->get_temporary( index));
return search_for_uv_textures( transaction, temporary.get(), cm, param);
}
mi::Size index = parameter_ref->get_index();
cm->get_argument( index));
return search_for_uv_textures( transaction, parameter.get(), param);
}
break;
case mi::neuraylib::IExpression::EK_FORCE_32_BIT:
break;
}
assert( false);
return 0;
}
mi::Size search_for_uv_textures(
Material& out_material)
{
mi::Size count = 0;
for (Material::iterator it = out_material.begin(); it != out_material.end(); ++it)
{
Material_parameter& param = it->second;
// Do not attempt to bake empty paths
if (param.bake_path.empty())
continue;
cm->lookup_sub_expression(param.bake_path.c_str()));
count += search_for_uv_textures(transaction, expr.get(), cm, param);
}
return count;
}
// Constructs a material for the target model, extracts the bake paths relevant for this
// model from the compiled material and bakes those paths into textures or constant values
void bake_target_material_inputs(
mi::Uint32 baking_samples,
mi::Uint32 baking_resolution,
mi::Float32 min_u,
mi::Float32 max_u,
mi::Float32 min_v,
mi::Float32 max_v,
Material& out_material)
{
Timing timing("Baking");
for(Material::iterator it = out_material.begin();
it != out_material.end(); ++it)
{
Material_parameter& param = it->second;
// Do not attempt to bake empty paths
if(param.bake_path.empty())
continue;
// Create baker for current path
cm, param.bake_path.c_str(), baker_resource));
check_success(baker.is_valid_interface());
if(baker->is_uniform())
{
if(param.value_type == "Rgb_fp")
{
transaction->create<mi::IColor>());
value = v->get_interface<mi::IData>();
}
else if(param.value_type == "Float32<3>")
{
transaction->create<mi::IFloat32_3>());
value = v->get_interface<mi::IData>();
}
else if(param.value_type == "Float32")
{
transaction->create<mi::IFloat32>());
value = v->get_interface<mi::IData>();
}
else
{
std::cout << "Ignoring unsupported value type '" << param.value_type
<< "'" << std::endl;
continue;
}
// Bake constant value
mi::Sint32 result = baker->bake_constant(value.get());
check_success(result == 0);
if(param.remap_func)
param.remap_func(value.get());
param.value = value;
}
else
{
if (!param.uv_tile_textures.empty())
{
std::map<Material_parameter::UVTile, mi::base::Handle<mi::neuraylib::ICanvas>>::iterator it;
for (it = param.uv_tile_textures.begin(); it != param.uv_tile_textures.end(); it++)
{
// Create a canvas
image_api->create_canvas(param.value_type.c_str(), baking_resolution, baking_resolution));
// Bake texture
mi::Float32 min_u(mi::Float32(it->first.min_uv.first));
mi::Float32 max_u(min_u + 1);
mi::Float32 min_v(mi::Float32(it->first.min_uv.second));
mi::Float32 max_v(min_v + 1);
mi::Sint32 result = baker->bake_texture(canvas.get(), min_u, max_u, min_v, max_v, baking_samples);
check_success(result == 0);
if (param.remap_func)
param.remap_func(canvas.get());
it->second = canvas;
}
}
else
{
// Create a canvas
image_api->create_canvas(param.value_type.c_str(), baking_resolution, baking_resolution));
// Bake texture
mi::Sint32 result = baker->bake_texture(canvas.get(), min_u, max_u, min_v, max_v, baking_samples);
check_success(result == 0);
if (param.remap_func)
param.remap_func(canvas.get());
param.texture = canvas;
}
}
}
}
template <typename T, typename U>
void init_value(mi::neuraylib::ICanvas* canvas, mi::IData* value, T*& out_array, U& out_value)
{
if(canvas)
{
out_array =static_cast<T*>(tile->get_data());
}
else if(value)
{
mi::get_value(value, out_value);
}
}
void calculate_f0(mi::neuraylib::ITransaction* trans, Material& material)
{
// if refl_weight value exists and is zero, set f0 to zero, too
if(material["f0_weight"].value)
{
float v;
mi::get_value(material["f0_weight"].value.get(), v);
if(v==0.0f)
{
material["f0"].value = create_value(trans, "Color", mi::Color(0.0f));
material["f0"].texture = 0;
return;
}
}
mi::Uint32 rx = material["f0"].texture->get_resolution_x();
mi::Uint32 ry = material["f0"].texture->get_resolution_y();
mi::Color f0_color_value(0.0f);
mi::Float32 f0_weight_value = 0.0f;
mi::Float32 f0_refl_value = 0.0f;
mi::Float32_3* f0 = nullptr;
mi::Float32_3* f0_color = nullptr;
mi::Float32* f0_weight = nullptr;
mi::Float32* f0_refl = nullptr;
init_value(material["f0"].texture.get(), nullptr,
f0, /* dummy */ f0_color_value);
init_value(material["f0_color"].texture.get(), material["f0_color"].value.get(),
f0_color, f0_color_value);
init_value(material["f0_weight"].texture.get(), material["f0_weight"].value.get(),
f0_weight, f0_weight_value);
init_value(material["f0_refl"].texture.get(), material["f0_refl"].value.get(),
f0_refl, f0_refl_value);
const mi::Uint32 n =rx * ry;
for(mi::Uint32 i=0; i<n; ++i)
{
const mi::Float32 t = (f0_weight ? f0_weight[i] : f0_weight_value) *
(f0_refl ? f0_refl[i] : f0_refl_value);
f0[i][0] = (f0_color ? f0_color[i][0] : f0_color_value[0]) * t;
f0[i][1] = (f0_color ? f0_color[i][1] : f0_color_value[1]) * t;
f0[i][2] = (f0_color ? f0_color[i][2] : f0_color_value[2]) * t;
}
}
// Helper class to export canvases either sequentially or in parallel threads
class Canvas_exporter
{
bool m_in_parallel = true;
std::map<std::string/*filename*/, mi::base::Handle<const mi::neuraylib::ICanvas>> m_canvases;
public:
Canvas_exporter(bool parallel)
: m_in_parallel(parallel)
{
}
void add_canvas(const std::string& filename, const mi::neuraylib::ICanvas* canvas)
{
m_canvases[filename] = mi::base::make_handle_dup(canvas);
}
void do_export(mi::neuraylib::IMdl_impexp_api * mdl_impexp_api)
{
auto export_canvas = [mdl_impexp_api](const char* filename, const mi::neuraylib::ICanvas* canvas)
{
check_success(mdl_impexp_api->export_canvas(filename, canvas) == 0);
};
std::vector<std::thread> threads;
for (auto & canvas_file : m_canvases)
{
const char * filename(canvas_file.first.c_str());
const mi::neuraylib::ICanvas* canvas(canvas_file.second.get());
if (m_in_parallel)
{
threads.emplace_back(
std::thread(export_canvas, filename, canvas)
);
}
else
{
export_canvas(filename, canvas);
}
}
for (auto & t : threads)
{
t.join();
}
}
};
// Print some information about baked material parameters to the console and
// save the baked textures to disk
void process_target_material(
const std::string& target_model,
const std::string& material_name,
const Material& material,
bool save_baked_textures,
bool parallel,
{
Timing timing("Saving");
std::cout << "--------------------------------------------------------------------------------"
<< std::endl;
std::cout << "Material model: " << target_model << std::endl;
std::cout << "--------------------------------------------------------------------------------"
<< std::endl;
Canvas_exporter canvas_exporter(parallel);
for(Material::const_iterator it = material.begin();
it != material.end(); ++it)
{
const std::string& param_name = it->first;
const Material_parameter& param = it->second;
std::cout << "Parameter: '" << param_name << "': ";
if(param.bake_path.empty())
{
std::cout << " no matching bake path found in target material."
<< std::endl;
if(param.value)
std::cout << "--> value set to ";
if(param.texture)
std::cout << "--> calculated ";
}
else
std::cout << "path '"<< param.bake_path << "' baked to ";
if (!param.uv_tile_textures.empty())
{
// UV Tiles
std::cout << "texture" << (param.uv_tile_textures.size() > 1 ? "s:" : ":") << std::endl << std::endl;
std::map<Material_parameter::UVTile, mi::base::Handle<mi::neuraylib::ICanvas>>::const_iterator it;
for (it = param.uv_tile_textures.begin(); it != param.uv_tile_textures.end(); it++)
{
if (!it->second) // texture
continue;
if (save_baked_textures)
{
// write texture to disc
// these filenames match the UVTILE0 convention (independent of the convention used for the input)
// 0-based uv-tileset, expands to "_u"u"_v"v
std::stringstream file_name;
file_name << material_name << "-" << param_name << "_u" << it->first.min_uv.first << "_v" << it->first.min_uv.second << ".png";
canvas_exporter.add_canvas(file_name.str(), it->second.get());
std::cout << file_name.str() << std::endl;
}
else
{
std::cout << "<Not saved>" << std::endl;
}
}
}
else if (param.texture)
{
std::cout << "texture:" << std::endl << std::endl;
if (save_baked_textures)
{
// write texture to disc
std::stringstream file_name;
file_name << material_name << "-" << param_name << ".png";
canvas_exporter.add_canvas(file_name.str(), param.texture.get());
std::cout << file_name.str() << std::endl;
}
else
{
std::cout << "<Not saved>" << std::endl;
}
}
else if(param.value)
{
std::cout << "constant ";
if(param.value_type == "Rgb_fp")
{
param.value->get_interface<mi::IColor>());
color->get_value(c);
std::cout << "color ("
<< c.r << ", " << c.g << ", " << c.b << ")."<< std::endl << std::endl;
}
else if(param.value_type == "Float32")
{
param.value->get_interface<mi::IFloat32>());
value->get_value(v);
std::cout << "float " << v << "." << std::endl;
}
else if(param.value_type == "Float32<3>")
{
param.value->get_interface<mi::IFloat32_3>());
value->get_value(v);
std::cout << "vector ("
<< v.x << ", " << v.y << ", " << v.z << ")."<< std::endl << std::endl;
}
}
std::cout
<< "--------------------------------------------------------------------------------"
<< std::endl;
}
// Export canvases
canvas_exporter.do_export(mdl_impexp_api);
}
// Prints program usage
static void usage(const char *name)
{
std::cout
<< "usage: " << name << " [options] [<material_name1> ...]\n"
<< "-h print this text\n"
<< "--target distilling target:diffuse|ue4|transmissive_pbr|\n"
<< " specular_glossy (default: ue4)\n"
<< "--baker_resource baking device: gpu|cpu|gpu_with_cpu_fallback (default: cpu)\n"
<< "--samples baking samples (default: 4)\n"
<< "--resolution baking resolution (default: 1024)\n"
<< "--uv_range baking UV range: min_u max_u min_v max_v (default: 0 1 0 1)\n"
<< "--material_file <file> file containing fully qualified names of materials to distill\n"
<< "--do_not_save_textures if set, avoid saving baked textures to file\n"
<< "--module <module_name> distill all materials from the module, can occur multiple times\n"
<< "--no_parallel do not save texture files in parallel threads\n"
<< "--mdl_path <path> mdl search path, can occur multiple times.\n"
<< "--plugin <filename> add additional distiller plugin, can be used more than once.\n";
exit(EXIT_FAILURE);
}
void load_materials_from_file(const std::string & material_file, std::vector<std::string> & material_names)
{
std::fstream file;
file.open(material_file, std::fstream::in);
if (!file)
{
std::cout << "Invalid file: " + material_file;
return;
}
std::string fn;
while (getline(file, fn))
{
material_names.emplace_back(fn);
}
file.close();
}
void load_materials_from_modules(
, mi::neuraylib::IMdl_impexp_api * mdl_impexp_api
, const std::vector<std::string> & module_names
, std::vector<std::string> & material_names)
{
mdl_factory->create_execution_context());
for (auto module_name : module_names)
{
// Sanity check
if (module_name.find("::") != 0)
{
module_name = std::string("::") + module_name;
}
mi::Sint32 rtn(mdl_impexp_api->load_module(transaction, module_name.c_str(), context.get()));
check_success(rtn == 0 || rtn == 1);
mdl_factory->get_db_module_name(module_name.c_str()));
transaction->access<mi::neuraylib::IModule>(db_module_name->get_c_str()));
check_success(module.is_valid_interface());
mi::Size material_count = module->get_material_count();
for (mi::Size i = 0; i < material_count; i++)
{
std::string mname(module->get_material(i));
transaction->access<mi::neuraylib::IFunction_definition>(mname.c_str()));
material_names.push_back(material->get_mdl_name());
}
}
}
int MAIN_UTF8(int argc, char* argv[])
{
std::string target_model = "ue4";
mi::Uint32 baking_samples = 4;
mi::Uint32 baking_resolution = 1024;
bool uv_range_set(false);
mi::Float32 min_u = 0;
mi::Float32 max_u = 1;
mi::Float32 min_v = 0;
mi::Float32 max_v = 1;
bool parallel = true;
std::vector<std::string> material_names;
std::vector<std::string> module_names;
std::string material_file;
std::vector<std::string> additional_plugins;
bool save_baked_textures(true);
mi::examples::mdl::Configure_options configure_options;
// Collect command line arguments, if any
for (int i = 1; i < argc; ++i) {
const char *opt = argv[i];
if (opt[0] == '-') {
if (strcmp(opt, "--mdl_path") == 0) {
if (i < argc - 1)
configure_options.additional_mdl_paths.push_back(argv[++i]);
else
usage(argv[0]);
}
else if (strcmp(opt, "--plugin") == 0) {
if (i < argc - 1)
additional_plugins.push_back(argv[++i]);
else
usage(argv[0]);
}
else if (strcmp(opt, "--target") == 0) {
if (i < argc - 1)
target_model = argv[++i];
else
usage(argv[0]);
}
else if (strcmp(opt, "--baker_resource") == 0) {
if (i < argc - 1) {
std::string res = argv[++i];
if (res == "gpu")
baker_resource = mi::neuraylib::BAKE_ON_GPU;
else if (res == "gpu_with_cpu_fallback")
else if (res != "cpu")
usage(argv[0]);
}
else
usage(argv[0]);
}
else if (strcmp(opt, "--samples") == 0) {
if (i < argc - 1)
{
int val(atoi(argv[++i]));
if (val > 0)
baking_samples = val;
else
std::cout << "Invalid number of samples ignored\n";
}
else
usage(argv[0]);
}
else if (strcmp(opt, "--resolution") == 0) {
if (i < argc - 1)
{
int val(atoi(argv[++i]));
if (val > 0)
baking_resolution = val;
else
std::cout << "Invalid resolution ignored\n";
}
else
usage(argv[0]);
}
else if (strcmp(opt, "--uv_range") == 0)
{
mi::Float32 uv_range[4];
for (int idx = 0; idx < 4; idx++)
{
if (i < argc - 1)
{
int ok = sscanf(argv[++i], "%f", &val);
if (ok != 1)
{
std::cout << "Invalid UV range\n";
usage(argv[0]);
}
uv_range[idx] = val;
}
else
{
std::cout << "Invalid UV range\n";
usage(argv[0]);
}
}
min_u = uv_range[0];
max_u = uv_range[1];
min_v = uv_range[2];
max_v = uv_range[3];
uv_range_set = true;
}
else if (strcmp(opt, "--material_file") == 0) {
if (i < argc - 1)
material_file = argv[++i];
else
usage(argv[0]);
}
else if (strcmp(opt, "--do_not_save_textures") == 0) {
save_baked_textures = false;
}
else if (strcmp(opt, "--no_parallel") == 0) {
parallel = false;
}
else if (strcmp(opt, "--module") == 0) {
if (i < argc - 1)
module_names.emplace_back(argv[++i]);
else
usage(argv[0]);
}
else
usage(argv[0]);
}
else
material_names.push_back(opt);
}
if (!material_file.empty())
load_materials_from_file(material_file, material_names);
// Access the MDL SDK
mi::base::Handle<mi::neuraylib::INeuray> neuray(mi::examples::mdl::load_and_get_ineuray());
if (!neuray.is_valid_interface())
exit_failure("Failed to load the SDK.");
// Configure the MDL SDK
if (!mi::examples::mdl::configure(neuray.get(), configure_options))
exit_failure("Failed to initialize the SDK.");
// Load the distilling plugin
if (mi::examples::mdl::load_plugin(neuray.get(), "mdl_distiller" MI_BASE_DLL_FILE_EXT) != 0)
exit_failure("Failed to load the mdl_distiller plugin.");
// Load any additional distilling plugins given on the command line.
for (auto& plugin : additional_plugins) {
std::string plugin_filename(plugin);
if (mi::examples::mdl::load_plugin(neuray.get(), plugin.c_str()) != 0)
exit_failure("Failed to load the %s plugin.", plugin.c_str());
}
// Start the MDL SDK
mi::Sint32 ret = neuray->start();
if (ret != 0)
exit_failure("Failed to initialize the SDK. Result code: %d", ret);
{
neuray->get_api_component<mi::neuraylib::IMdl_impexp_api>());
// Get MDL factory
neuray->get_api_component<mi::neuraylib::IMdl_factory>());
// Create a transaction
neuray->get_api_component<mi::neuraylib::IDatabase>());
mi::base::Handle<mi::neuraylib::IScope> scope(database->get_global_scope());
mi::base::Handle<mi::neuraylib::ITransaction> transaction(scope->create_transaction());
if (!module_names.empty())
{
load_materials_from_modules(mdl_factory.get(), transaction.get(), mdl_impexp_api.get(), module_names, material_names);
}
if (material_names.empty())
{
material_names.push_back(
"::nvidia::sdk_examples::tutorials_distilling::example_distilling1");
}
for (const auto& m : material_names)
{
// split module and material name
std::string module_qualified_name, material_simple_name;
if (!mi::examples::mdl::parse_cmd_argument_material_name(
m, module_qualified_name, material_simple_name, true))
exit_failure();
// Create an execution context
mdl_factory->create_execution_context());
// Load mdl module and create a material instance
create_material_instance(
mdl_factory.get(),
transaction.get(),
mdl_impexp_api.get(),
context.get(),
module_qualified_name,
material_simple_name));
// Compile the material instance
compile_material_instance(
mdl_factory.get(),
transaction.get(),
instance.get(),
context.get(),
false));
// Acquire distilling API used for material distilling and baking
neuray->get_api_component<mi::neuraylib::IMdl_distiller_api>());
// Distill compiled material to diffuse/glossy material model
create_distilled_material(
distilling_api.get(),
compiled_material.get(),
target_model.c_str()));
// Acquire image API needed to create a canvas for baking
neuray->get_api_component<mi::neuraylib::IImage_api>());
Material out_material;
// Setup result material parameters relevant for target_model
// and collect bake paths
setup_target_material(
target_model,
transaction.get(),
distilled_material.get(),
out_material);
// Search for UV textures
mi::Size uv_texture_count = search_for_uv_textures(
transaction.get(),
distilled_material.get(),
out_material);
if (uv_texture_count > 0 && uv_range_set)
{
std::cerr << "WARNING: UV range will be ignored for UV tile textured parameters\n";
}
// Bake material inputs
bake_target_material_inputs(
baker_resource,
baking_samples,
baking_resolution,
min_u,
max_u,
min_v,
max_v,
transaction.get(),
distilled_material.get(),
distilling_api.get(),
image_api.get(),
out_material);
if (target_model == "specular_glossy")
{
// the specular glossy models f0 parameter cannot
// be directly taken from the distilling result but
// needs to be calculated
// Create f0 canvas
out_material["f0"].texture =
image_api->create_canvas("Rgb_fp", baking_resolution, baking_resolution);
// fill it
calculate_f0(transaction.get(), out_material);
}
// Process resulting material, in this case we simply
// print some information about the baked parameters
// and save the textures to disk, if any
process_target_material(
target_model,
material_simple_name,
out_material,
save_baked_textures,
parallel,
mdl_impexp_api.get());
}
transaction->commit();
}
// Shut down the MDL SDK
if (neuray->shutdown() != 0)
exit_failure("Failed to shutdown the SDK.");
// Unload the MDL SDK
neuray = nullptr;
if (!mi::examples::mdl::unload())
exit_failure("Failed to unload the SDK.");
exit_success();
}
// Convert command line arguments to UTF8 on Windows
COMMANDLINE_TO_UTF8
This interface represents RGBA colors.
Definition: icolor.h:28
This interface is the base interface of all types.
Definition: idata.h:297
This interface represents a vector of three Float32.
Definition: ivector.h:249
This interface represents mi::Float32.
Definition: inumber.h:221
Handle class template for interfaces, automatizing the lifetime control via reference counting.
Definition: handle.h:113
The basic extensible interface.
Definition: iinterface.h:103
Standard RGBA color class with floating point elements and operations.
Definition: color.h:81
Fixed-size math vector class template with generic operations.
Definition: vector.h:286
Abstract interface for a canvas represented by a rectangular array of tiles.
Definition: icanvas.h:89
virtual const ITile * get_tile(Uint32 layer=0) const =0
Returns the tile for the given layer.
This interface represents a compiled material.
Definition: icompiled_material.h:97
virtual const IExpression * lookup_sub_expression(const char *path) const =0
Looks up a sub-expression of the compiled material.
virtual const IValue * get_argument(Size index) const =0
Returns the value of an argument.
virtual const IExpression * get_temporary(Size index) const =0
Returns a temporary.
This interface is used to interact with the distributed database.
Definition: idatabase.h:289
A constant expression.
Definition: iexpression.h:96
A direct call expression.
Definition: iexpression.h:241
A parameter reference expression.
Definition: iexpression.h:217
A temporary reference expression.
Definition: iexpression.h:299
The interface to MDL expressions.
Definition: iexpression.h:50
@ EK_CALL
An indirect call expression. See mi::neuraylib::IExpression_call.
Definition: iexpression.h:57
@ EK_DIRECT_CALL
A direct call expression. See mi::neuraylib::IExpression_direct_call.
Definition: iexpression.h:61
@ EK_CONSTANT
A constant expression. See mi::neuraylib::IExpression_constant.
Definition: iexpression.h:55
@ EK_PARAMETER
A parameter reference expression. See mi::neuraylib::IExpression_parameter.
Definition: iexpression.h:59
@ EK_TEMPORARY
A temporary reference expression. See mi::neuraylib::IExpression_temporary.
Definition: iexpression.h:63
virtual Kind get_kind() const =0
Returns the kind of this expression.
This interface represents a function call.
Definition: ifunction_call.h:52
This interface represents a function definition.
Definition: ifunction_definition.h:44
Semantics
All known semantics of functions definitions.
Definition: ifunction_definition.h:54
@ DS_INTRINSIC_DF_MICROFACET_GGX_VCAVITIES_BSDF
The df::microfacet_ggx_vcavities() function.
Definition: ifunction_definition.h:295
@ DS_INTRINSIC_DF_DIFFUSE_REFLECTION_BSDF
The df::diffuse_reflection_bsdf() function.
Definition: ifunction_definition.h:259
@ DS_INTRINSIC_DF_NORMALIZED_MIX
The df::normalized_mix() function.
Definition: ifunction_definition.h:274
@ DS_INTRINSIC_DF_CUSTOM_CURVE_LAYER
The df::custom_curve_layer() function.
Definition: ifunction_definition.h:278
@ DS_INTRINSIC_DF_WEIGHTED_LAYER
The df::weighted_layer() function.
Definition: ifunction_definition.h:276
This interface provides various utilities related to canvases and buffers.
Definition: iimage_api.h:72
virtual ICanvas * create_canvas(const char *pixel_type, Uint32 width, Uint32 height, Uint32 layers=1, bool is_cubemap=false, Float32 gamma=0.0f) const =0
Creates a canvas with given pixel type, resolution, and layers.
This interface represents a pixel image file.
Definition: iimage.h:66
This interface represents a material instance.
Definition: imaterial_instance.h:34
@ CLASS_COMPILATION
Selects class compilation instead of instance compilation.
Definition: imaterial_instance.h:41
@ DEFAULT_OPTIONS
Default compilation options (e.g., instance compilation).
Definition: imaterial_instance.h:40
Provides access to various functionality related to MDL distilling.
Definition: imdl_distiller_api.h:47
virtual ICompiled_material * distill_material(const ICompiled_material *material, const char *target, const IMap *distiller_options=0, Sint32 *errors=0) const =0
Distills a material.
virtual const IBaker * create_baker(const ICompiled_material *material, const char *path, Baker_resource resource=BAKE_ON_CPU, Uint32 gpu_device_id=0) const =0
Creates a baker for texture baking.
The execution context can be used to query status information like error and warning messages concern...
Definition: imdl_execution_context.h:131
virtual Sint32 set_option(const char *name, const char *value)=0
Sets a string option.
Factory for various MDL interfaces and functions.
Definition: imdl_factory.h:53
virtual IMdl_execution_context * create_execution_context()=0
Creates an execution context.
virtual IType_factory * create_type_factory(ITransaction *transaction)=0
Returns an MDL type factory for the given transaction.
virtual const IString * get_db_module_name(const char *mdl_name)=0
Returns the DB name for the MDL name of a module (or file path for MDLE modules).
API component for MDL related import and export operations.
Definition: imdl_impexp_api.h:43
virtual Sint32 export_canvas(const char *filename, const ICanvas *canvas, const IMap *export_options=0) const =0
Exports a canvas to a file on disk.
virtual Sint32 load_module(ITransaction *transaction, const char *argument, IMdl_execution_context *context=0)=0
Loads an MDL module from disk (or a builtin module) into the database.
This interface represents an MDL module.
Definition: imodule.h:634
Textures add image processing options to images.
Definition: itexture.h:68
A transaction provides a consistent view on the database.
Definition: itransaction.h:84
virtual const base::IInterface * access(const char *name)=0
Retrieves an element from the database.
virtual base::IInterface * create(const char *type_name, Uint32 argc=0, const base::IInterface *argv[]=0)=0
Creates an object of the type type_name.
virtual Sint32 commit()=0
Commits the transaction.
@ SID_MATERIAL
The "::material" struct type.
Definition: itype.h:484
A texture value.
Definition: ivalue.h:525
The interface to MDL values.
Definition: ivalue.h:33
virtual Kind get_kind() const =0
Returns the kind of the value.
@ VK_TEXTURE
A texture value. See mi::neuraylib::IValue_texture.
Definition: ivalue.h:62
#define MI_BASE_DLL_FILE_EXT
The operating system specific default filename extension for shared libraries (DLLs)
Definition: config.h:340
virtual const IInterface * get_interface(const Uuid &interface_id) const =0
Acquires a const interface from another.
Handle<Interface> make_handle_dup(Interface *iptr)
Converts passed-in interface pointer to a handle, without taking interface over.
Definition: handle.h:439
Handle<New_interface> get_interface() const
Returns a new handle for a possibly different interface type, similar to a dynamic cast,...
Definition: handle.h:353
Interface * get() const
Access to the interface. Returns 0 for an invalid interface.
Definition: handle.h:294
unsigned int Uint32
32-bit unsigned integer.
Definition: types.h:49
Uint64 Size
Unsigned integral type that is large enough to hold the size of all types.
Definition: types.h:112
float Float32
32-bit float.
Definition: types.h:51
signed int Sint32
32-bit signed integer.
Definition: types.h:46
Float32 length(Float32 a)
Returns the Euclidean norm of the scalar a (its absolute value).
Definition: function.h:1107
bool operator<(const Bbox<T, DIM> &lhs, const Bbox<T, DIM> &rhs)
Returns true if lhs is lexicographically less than rhs.
Definition: bbox.h:607
Float32 g
Green color component.
Definition: vector.h:71
Float32 r
Red color component.
Definition: vector.h:69
Float32 b
Blue color component.
Definition: vector.h:73
Baker_resource
Identifies the resource(s) to be used by a baker.
Definition: imdl_distiller_api.h:30
@ BAKE_ON_CPU
Use only the CPU for texture baking.
Definition: imdl_distiller_api.h:33
@ BAKE_ON_GPU_WITH_CPU_FALLBACK
Prefer using the GPU for texture baking, use the CPU as fallback.
Definition: imdl_distiller_api.h:37
@ BAKE_ON_GPU
Use only the GPU for texture baking.
Definition: imdl_distiller_api.h:35
mi::Sint32 get_value(const mi::IData *data, T &value)
Simplifies reading the value of mi::IData into the corresponding classes from the base and math API.
Definition: set_get.h:341
[Previous] [Up] [Next]