MDL SDK API nvidia_logo_transpbg.gif Up
Example for Integrating MDL into a Renderer by Using the Native Backend (CPU)
[Previous] [Up] [Next]

This example demonstrates how to generate target code for the native backend (CPU) from an MDL material and use the generated BSDF functions in a CPU based renderer to account for the surface.scattering, emission, thin_walled and geometry.cutout_opacity expressions of the material. The example implements a physically based path-tracer technique for rendering a single sphere with MDL materials. The sphere is illuminated by a point light and/or an HDR environment map as seen from a perspective camera. The MDL material is loaded from a module and compiled for native backend (CPU) in order to execute different BSDF target functions during renderings. The renderer takes advantage of multi-threading by assigning different spans of the framebuffer to the available logical cores.

New Topics

  • Generating target code for BSDF functions (CPU).
  • Using generated BSDF functions (CPU).

Detailed Description

Generating target code for BSDF functions (CPU)


The whole target code generation process for this example can be divided in 3 steps:

  • Create the material instance: Here an MDL material is loaded from a module and instantiated. For further information about material instantiation, please check Example for Instantiation of MDL Definitions. In the example, this is performed by the method:
void create_material_instance(
const char* material_name,
const char* instance_name);
The execution context can be used to query status information like error and warning messages concern...
Definition: imdl_execution_context.h:131
Factory for various MDL interfaces and functions.
Definition: imdl_factory.h:53
API component for MDL related import and export operations.
Definition: imdl_impexp_api.h:43
A transaction provides a consistent view on the database.
Definition: itransaction.h:84
  • Compile the material instance: The material instance is not only required to generate target code for a given backend but also to inspect the material properties and collect the required material sub-expressions to be translated by the link unit. For example, if a material is thin walled, it may be required to translate also sub-expressions of backface. Please check Instance-compilation and class-compilation for further information about the material compilation. In the example, this is performed by the method:
void compile_material_instance(
const char* instance_name,
const char* compiled_material_name,
bool class_compilation);
  • Generate the target code: Here, depending on the compiled material, different sub-expressions are selected and translated by the link unit for the native backend (CPU). In the example, this is performed by the method:
void generate_native(
Render_context& render_context,
const char* compiled_material_name,
bool use_custom_tex_runtime,
bool use_adapt_normal,
bool enable_derivatives);
This interface can be used to obtain the MDL backends.
Definition: imdl_backend_api.h:56

Let's have a more detailed look at the last step. First, the native backend is obtained through the MDL backend API mdl_backend_api and the link unit is created:

mdl_backend_api->get_backend(mi::neuraylib::IMdl_backend_api::MB_NATIVE));
...
mi::base::Handle<mi::neuraylib::ILink_unit> link_unit(
be_native->create_link_unit(transaction, context));
Handle class template for interfaces, automatizing the lifetime control via reference counting.
Definition: handle.h:113
@ MB_NATIVE
Generate native code.
Definition: imdl_backend_api.h:64

Next, the material sub-expressions are collected and passed to the link unit along the compiled material:

std::vector<mi::neuraylib::Target_function_description> descs;
descs.push_back(mi::neuraylib::Target_function_description("surface.scattering"));
descs.push_back(mi::neuraylib::Target_function_description("surface.emission.emission"));
descs.push_back(mi::neuraylib::Target_function_description("surface.emission.intensity"));
...
bool need_backface_bsdf = is_thin_walled &&
compiled_material->get_slot_hash(mi::neuraylib::SLOT_SURFACE_SCATTERING) !=
compiled_material->get_slot_hash(mi::neuraylib::SLOT_BACKFACE_SCATTERING);
if (need_backface_bsdf)
{
backface_scattering_index = descs.size();
descs.push_back(mi::neuraylib::Target_function_description("backface.scattering"));
}
...
link_unit->add_material(
compiled_material.get(),
descs.data(),
descs.size(),
context);
@ SLOT_BACKFACE_SCATTERING
Slot "backface.scattering".
Definition: icompiled_material.h:32
@ SLOT_SURFACE_SCATTERING
Slot "surface.scattering".
Definition: icompiled_material.h:28
Description of target function.
Definition: imdl_backend.h:1754

where descs, of type mi::neuraylib::Target_function_description, serves to collect the sub-expressions required later during rendering. The special expression path "init" enables the single-init mode, where for all added sub-expressions a common init function is generated to allow reusing evaluation results among all sub-expressions. Some sub-expressions are known to be required while others are added dynamically by inspecting different elements of the compiled material.

Note that the renderer has to keep track of the indices of the sub-expressions in the array descs in order to lookup the functions later during rendering when executing any function of mi::neuraylib::ITarget_code. In the example, the struct render_context is used for that:

render_context.init_function_index = descs[0].function_index;
render_context.surface_bsdf_function_index = descs[1].function_index;
render_context.surface_edf_function_index = descs[2].function_index;
render_context.surface_emission_intensity_function_index = descs[3].function_index;
...

Finally, the target code for the native backend (CPU) is generated:

be_native->translate_link_unit(link_unit.get(), context));

In the example the struct render_context is used to take ownership of code_native in order to extend its scope to the renderer application scope, otherwise it would be freed by the end of generate_native():

render_context.target_code = code_native;

Using generated BSDF functions (CPU)


At this point the target code for the native backend is available for the renderer and it can be used to query material properties and evaluate material distribution functions. For a hit point, first, the init function has to be called, so the following calls to the generated code can reuse calculated expressions:

// shader initialization for the current hit point
ret_code = rc.target_code->execute_init(
rc.init_function_index,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr);

After that, the renderer can use target_code to obtain for example the cutout_opacity and thin_walled material properties for the current shading_state:

// evaluate material cutout opacity state
float cutout_opacity = rc.cutout.constant_opacity;
if (!rc.cutout.is_constant)
{
rc.target_code->execute(
rc.cutout_opacity_function_index,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr,
&cutout_opacity);
}
...
// evaluate thin_walled state
bool is_thin_walled = rc.thin_walled.is_thin_walled;
if (!rc.thin_walled.is_constant)
{
rc.target_code->execute(
rc.thin_walled_function_index,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr,
&is_thin_walled);
}

To evaluate the material emission contribution for the current shading_state, the renderer uses two target_code functions:

  • target_code->execute_edf_evaluate() to evaluate the material emission distribution function for the current view direction eval_data.k1 and function index edf_function_index.
  • target_code->execute() to obtain the emission intensity for the function index emission_intensity_function_index.
uint64_t edf_function_index = (is_thin_walled && ray.is_inside)
? rc.backface_edf_function_index : rc.surface_edf_function_index
mi::neuraylib::Edf_evaluate_data<mi::neuraylib::DF_HSM_NONE> eval_data;
eval_data.k1 = -ray.dir;
// evaluate material surface emission contribution
rc.target_code->execute_edf_evaluate(
edf_function_index + 1, // edf_function_index corresponds to 'sample'
// edf_function_index + 1 to 'evaluate'
&eval_data,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr);
// emission contribution is only valid for positive pdf
if (eval_data.pdf > 1.e-6f)
{
uint64_t emission_intensity_function_index = (is_thin_walled && ray.is_inside)
? rc.backface_emission_intensity_function_index
: rc.surface_emission_intensity_function_index;
mi::Float32_3 intensity(1.f);
rc.target_code->execute(
emission_intensity_function_index,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr,
&intensity);
vp_sample[VPCH_ILLUM] += static_cast<mi::Float32_3>(eval_data.edf)*intensity*ray.weight;
}
Fixed-size math vector class template with generic operations.
Definition: vector.h:286

Example Source

To compile the source code, GLFW and GLEW are required. For detailed instructions, please refer to the Getting Started section.

Source Code Location: examples/mdl_sdk/df_native/example_df_native.cpp

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/df_native/example_df_native.cpp
//
// Simple CPU renderer using compiled BSDFs with a material parameter editor GUI.
#include <iomanip>
#include <iostream>
#define _USE_MATH_DEFINES
#include <math.h>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
#include "example_shared.h"
#include "texture_support_native.h"
#include <GL/glew.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#define GL_DISPLAY_NATIVE
#include <utils/gl_display.h>
#if MI_PLATFORM_MACOSX
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
#include "utils/profiling.h"
using namespace mi::examples::profiling;
#define USE_PARALLEL_RENDERING
//#define ADD_EXTRA_TIMERS
// Global Constants
static struct
{
const float DIRAC = -1.f;
const float PI = static_cast<float>(M_PI);
const mi::Float32_3_struct tangent_u[1] = { {1.0f, 0.0f, 0.0f} };
const mi::Float32_3_struct tangent_v[1] = { { 0.0f, 1.0f, 0.0f} };
const mi::Float32_3_4 identity = mi::Float32_3_4(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f);
} Constants;
// Random Number Generator
inline unsigned tea(unsigned N, unsigned val0, unsigned val1)
{
unsigned v0 = val0;
unsigned v1 = val1;
unsigned s0 = 0;
for (unsigned n = 0; n < N; n++)
{
s0 += 0x9e3779b9;
v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + s0) ^ ((v1 >> 5) + 0xc8013ea4);
v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7e95761e);
}
return v0;
}
// Generate random uint in [0, 2^24)
inline unsigned lcg(unsigned &prev)
{
const unsigned LCG_A = 1664525u;
const unsigned LCG_C = 1013904223u;
prev = (LCG_A * prev + LCG_C);
return prev & 0x00FFFFFF;
}
// Generate random float in [0, 1)
inline float rnd(unsigned &prev)
{
const unsigned next = lcg(prev);
return ((float)next / (float)0x01000000);
}
// Window Handling
// Window context structure for window keys/mouse event callback functions.
struct Window_context
{
bool mouse_event, key_event;
// for environment
float env_intensity;
// for omni light movement
float omni_theta;
float omni_phi;
float omni_intensity;
// for camera movement
int mouse_button; // button from callback event plus one (0 = no event)
int mouse_button_action; // action from mouse button callback event
int mouse_wheel_delta;
bool moving;
double move_start_x, move_start_y;
double move_dx, move_dy;
int zoom;
// image output
bool save_sreenshot;
Window_context()
: mouse_event(false)
, key_event(false)
, env_intensity(0.0f)
, omni_theta(0.0f)
, omni_phi(0.0f)
, omni_intensity(0.0f)
, mouse_button(0)
, mouse_button_action(0)
, mouse_wheel_delta(0)
, moving(false)
, move_start_x(0.0)
, move_start_y(0.0)
, move_dx(0.0)
, move_dy(0.0)
, zoom(0)
, save_sreenshot(false)
{}
// GLFW keyboard callback
static void handle_key(GLFWwindow *window, int key, int scancode, int action, int mods)
{
// Handle key press events
if (action == GLFW_PRESS)
{
Window_context *ctx = static_cast<Window_context*>(glfwGetWindowUserPointer(window));
if (mods&;GLFW_MOD_CONTROL)
{
switch (key)
{
case GLFW_KEY_MINUS:
case GLFW_KEY_KP_SUBTRACT:
ctx->env_intensity -= 0.05f;
if (ctx->env_intensity < 0.f) ctx->env_intensity = 0.f;
ctx->key_event = true;
break;
case GLFW_KEY_KP_ADD:
case GLFW_KEY_EQUAL:
ctx->env_intensity += 0.05f;
ctx->key_event = true;
break;
}
}
else
{
switch (key)
{
// Escape closes the window
case GLFW_KEY_ESCAPE:
glfwSetWindowShouldClose(window, GLFW_TRUE);
break;
case GLFW_KEY_DOWN:
ctx->omni_theta += 0.05f*Constants.PI;
ctx->key_event = true;
break;
case GLFW_KEY_UP:
ctx->omni_theta -= 0.05f*Constants.PI;
ctx->key_event = true;
break;
case GLFW_KEY_LEFT:
ctx->omni_phi -= 0.05f*Constants.PI;
ctx->key_event = true;
break;
case GLFW_KEY_RIGHT:
ctx->omni_phi += 0.05f*Constants.PI;
ctx->key_event = true;
break;
case GLFW_KEY_MINUS:
case GLFW_KEY_KP_SUBTRACT:
ctx->omni_intensity -= 1000.f;
if (ctx->omni_intensity < 0.f) ctx->omni_intensity = 0.f;
ctx->key_event = true;
break;
case GLFW_KEY_KP_ADD:
case GLFW_KEY_EQUAL:
ctx->omni_intensity += 1000.f;
ctx->key_event = true;
break;
case GLFW_KEY_ENTER:
ctx->save_sreenshot = true;
break;
default:
break;
}
}
}
ImGui_ImplGlfw_KeyCallback(window, key, scancode, action, mods);
}
// GLFW mouse button callback
static void handle_mouse_button(GLFWwindow *window, int button, int action, int mods)
{
Window_context *ctx = static_cast<Window_context*>(glfwGetWindowUserPointer(window));
ctx->mouse_button = button + 1;
ctx->mouse_button_action = action;
ImGui_ImplGlfw_MouseButtonCallback(window, button, action, mods);
}
// GLFW mouse position callback
static void handle_mouse_pos(GLFWwindow *window, double xpos, double ypos)
{
Window_context *ctx = static_cast<Window_context*>(glfwGetWindowUserPointer(window));
if (ctx->moving)
{
ctx->move_dx += xpos - ctx->move_start_x;
ctx->move_dy += ypos - ctx->move_start_y;
ctx->move_start_x = xpos;
ctx->move_start_y = ypos;
ctx->mouse_event = true;
}
}
// GLFW scroll callback
static void handle_scroll(GLFWwindow *window, double xoffset, double yoffset)
{
Window_context *ctx = static_cast<Window_context*>(glfwGetWindowUserPointer(window));
if (yoffset > 0.0)
{
ctx->mouse_wheel_delta = 1;
ctx->mouse_event = true;
}
else if (yoffset < 0.0)
{
ctx->mouse_wheel_delta = -1;
ctx->mouse_event = true;
}
ImGui_ImplGlfw_ScrollCallback(window, xoffset, yoffset);
}
};
// Vector Helper Functions
static inline float int_as_float(uint32_t v)
{
union
{
uint32_t bit;
float value;
} temp;
temp.bit = v;
return temp.value;
}
static inline uint32_t float_as_int(float v)
{
union
{
uint32_t bit;
float value;
} temp;
temp.value = v;
return temp.bit;
}
inline void clamp(mi::Float32_3 &d, float min = 0.f, float max = 1.f)
{
for (int i = 0; i < 3; ++i)
{
if (d[i] < min)
d[i] = min;
else if (d[i] > max)
d[i] = max;
}
}
inline float length(const mi::Float32_3 &d)
{
return sqrtf(d.x * d.x + d.y * d.y + d.z * d.z);
}
inline float dot(const mi::Float32_3 &a, const mi::Float32_3 &b)
{
return (a.x * b.x + a.y * b.y + a.z * b.z);
}
inline mi::Float32_3 normalize(const mi::Float32_3 &d)
{
const float dotprod = dot(d, d);
if (dotprod > 0.f)
{
const float inv_len = 1.0f / sqrtf(dotprod);
return mi::Float32_3(d.x * inv_len, d.y * inv_len, d.z * inv_len);
}
else
{
return d;
}
}
{
return mi::Float32_3(a.x + b.x, a.y + b.y, a.z + b.z);
}
{
return mi::Float32_3(a.x - b.x, a.y - b.y, a.z - b.z);
}
{
return mi::Float32_3(a.x * b.x, a.y * b.y, a.z * b.z);
}
inline mi::Float32_3 operator*(const mi::Float32_3& d, float s)
{
return mi::Float32_3(d.x * s, d.y * s, d.z * s);
}
inline mi::Float32_3 operator/(const mi::Float32_3& d, float s)
{
const float inv_s = 1.0f / s;
return mi::Float32_3(d.x * inv_s, d.y * inv_s, d.z * inv_s);
}
// Command Line Options
// Command line options structure.
struct Options
{
// Don't open OpenGL GUI
bool no_gui;
// Number of iterations for output images
size_t iterations;
// A result output file name.
std::string outputfile;
bool output_auxiliary; // output albedo and normal auxiliary buffers.
// The resolution of the display / image.
unsigned res_x, res_y;
// Path-tracer max ray-length
int max_ray_length;
// Environment map filename and scale
std::string env_map;
float env_scale;
// Camera position and FOV
mi::Float32_3 cam_pos;
float cam_fov;
// Light position and intensity
mi::Float32_3 light_pos;
mi::Float32_3 light_intensity;
// BSDF flags
bool enable_bsdf_flags;
mi::neuraylib::Df_flags allowed_scatter_mode;
// Whether class compilation should be used for the materials.
bool use_class_compilation;
// Whether the custom texture runtime should be used.
bool use_custom_tex_runtime;
// Whether normals should be adapted.
bool use_adapt_normal;
// Whether derivative support should be enabled.
// This example does not support derivatives in combination with the custom texture runtime.
bool enable_derivatives;
// Material to use.
std::string material_name;
Options()
: no_gui(false)
, iterations(100)
, outputfile("example_df_native.png")
, output_auxiliary(false)
, res_x(700)
, res_y(520)
, max_ray_length(6)
, env_map("nvidia/sdk_examples/resources/environment.hdr")
, env_scale(1.f)
, cam_pos(0.f, 0.f, 3.f)
, cam_fov(86.f)
, light_pos(10.f, 5.f, 0.f)
, light_intensity(1.0f, 0.902f, 0.502f)
, enable_bsdf_flags(false)
, allowed_scatter_mode(mi::neuraylib::DF_FLAGS_ALLOW_REFLECT_AND_TRANSMIT)
, use_class_compilation(false)
, use_custom_tex_runtime(false)
, use_adapt_normal(false)
, enable_derivatives(false)
{}
};
// Scene Render Context
// Viewport buffers for progressive rendering
enum VP_channel
{
VPCH_ILLUM = 0,
VPCH_ALBEDO,
VPCH_NORMAL,
VPCH_NB_CHANNELS
};
struct VP_buffers
{
mi::Float32_3 *accum_buffer;
mi::Float32_3 *albedo_buffer;
mi::Float32_3 *normal_buffer;
mi::Uint32 *aux_count;
VP_buffers()
: accum_buffer(nullptr)
, albedo_buffer(nullptr)
, normal_buffer(nullptr)
, aux_count(nullptr)
{}
~VP_buffers()
{
if (accum_buffer)
delete[] accum_buffer;
if (albedo_buffer)
delete[] albedo_buffer;
if (normal_buffer)
delete[] normal_buffer;
if (aux_count)
delete[] aux_count;
}
};
// Surface intersection info
struct Isect_info
{
mi::Float32_3 pos; // surface position
mi::Float32_3 normal; // surface normal
mi::Float32_3 uvw; // uvw coordinates
mi::Float32_3 tan_u; // tangent vector in u direction
mi::Float32_3 tan_v; // tangent vector in v direction
};
// Render context
struct Render_context
{
// render options
int max_ray_length;
bool render_auxiliary;
bool use_derivatives;
mi::neuraylib::Df_flags bsdf_data_flags;
// scene data
// environment color
struct Environment
{
mi::Float32_3 color; // used when no environment map is set
float intensity;
struct Alias_map
{
unsigned int alias;
float q;
} *alias_map;
float inv_integral;
mi::Uint32_2 map_size;
const float *map_pixels;
}env;
// Perspective camera
struct Camera
{
float focal;
float aspect;
float zoom;
mi::Float32_2 inv_res;
} cam;
// Omni light
struct Omni
{
float distance;
float intensity;
} omni_light;
// sphere object
struct Sphere
{
mi::Float32_3 center;
float radius;
} sphere;
// MDL cutout opacity
struct Cutout
{
bool is_constant;
float constant_opacity;
} cutout;
// MDL thin_walled
struct Thin_walled
{
bool is_constant;
bool is_thin_walled;
} thin_walled;
// single raytracing ray
struct Ray
{
mi::Float32_3 weight;
int level;
float last_pdf;
bool is_inside;
bool is_inside_cutout;
Ray()
: weight(1.f)
, level(0)
, last_pdf(-1.f)
, is_inside(false)
, is_inside_cutout(false)
{};
//-------------------------------------------------------------------------------------------------
// Avoiding self intersections (see Ray Tracing Gems, Ch. 6)
//-------------------------------------------------------------------------------------------------
inline void offset_ray(const mi::Float32_3 &n)
{
const float origin = 1.0f / 32.0f;
const float float_scale = 1.0f / 65536.0f;
const float int_scale = 256.0f;
const mi::Sint32_3 of_i(
static_cast<int>(int_scale * n.x),
static_cast<int>(int_scale * n.y),
static_cast<int>(int_scale * n.z));
int_as_float(float_as_int(p0.x) + ((p0.x < 0.0f) ? -of_i.x : of_i.x)),
int_as_float(float_as_int(p0.y) + ((p0.y < 0.0f) ? -of_i.y : of_i.y)),
int_as_float(float_as_int(p0.z) + ((p0.z < 0.0f) ? -of_i.z : of_i.z)));
p0.x = abs(p0.x) < origin ? p0.x + float_scale * n.x : p_i.x;
p0.y = abs(p0.y) < origin ? p0.y + float_scale * n.y : p_i.y;
p0.z = abs(p0.z) < origin ? p0.z + float_scale * n.z : p_i.z;
}
};
// MDL Backend execution
Texture_handler *tex_handler;
Texture_handler_deriv *tex_handler_deriv;
mi::Size init_function_index;
mi::Size surface_bsdf_function_index;
mi::Size surface_edf_function_index;
mi::Size surface_emission_intensity_function_index;
mi::Size backface_bsdf_function_index;
mi::Size backface_edf_function_index;
mi::Size backface_emission_intensity_function_index;
mi::Size cutout_opacity_function_index;
mi::Size thin_walled_function_index;
Render_context(bool use_derivatives)
: use_derivatives(use_derivatives)
, bsdf_data_flags(mi::neuraylib::DF_FLAGS_ALLOW_REFLECT_AND_TRANSMIT)
, target_code(nullptr)
, tex_handler(nullptr)
, tex_handler_deriv(nullptr)
{
max_ray_length = 6;
render_auxiliary = false;
env.color = mi::Float32_3(0.53f, 0.81f, 0.92f);
env.intensity = 1.0f;
env.alias_map = nullptr;
omni_light.color = mi::Float32_3(1.0f, 0.902f, 0.502f);
omni_light.dir = normalize(mi::Float32_3(1.f, 1.f, 1.f));
omni_light.distance = 11.18f;
omni_light.intensity = 0.0f;
sphere.center = mi::Float32_3(0.f); // sphere in the origin
sphere.radius = 1.f;
cutout.is_constant = false;
cutout.constant_opacity = 1.f;
thin_walled.is_constant = true;
thin_walled.is_thin_walled = false;
// init constant parameters of material shader state
shading_state.animation_time = 0.f;
shading_state.text_coords = nullptr;
shading_state.tangent_u = Constants.tangent_u;
shading_state.tangent_v = Constants.tangent_v;
shading_state.text_results = nullptr;
shading_state.ro_data_segment = nullptr;
shading_state.world_to_object = &Constants.identity[0];
shading_state.object_to_world = &Constants.identity[0];
shading_state.object_id = 0;
shading_state.meters_per_scene_unit = 1.f;
shading_state_derivs.animation_time = 0.f;
shading_state_derivs.text_coords = nullptr;
shading_state_derivs.tangent_u = Constants.tangent_u;
shading_state_derivs.tangent_v = Constants.tangent_v;
shading_state_derivs.text_results = nullptr;
shading_state_derivs.ro_data_segment = nullptr;
shading_state_derivs.world_to_object = &Constants.identity[0];
shading_state_derivs.object_to_world = &Constants.identity[0];
shading_state_derivs.object_id = 0;
shading_state_derivs.meters_per_scene_unit = 1.f;
}
// Free resources owned by the render context.
void uninit()
{
if (env.alias_map)
{
free(env.alias_map);
env.alias_map = nullptr;
}
env.map = nullptr;
target_code = nullptr;
}
inline void update_light(
float phi,
float theta,
float intensity)
{
omni_light.dir.x = sinf(theta) * sinf(phi);
omni_light.dir.y = cosf(theta);
omni_light.dir.z = sinf(theta) * cosf(phi);
omni_light.intensity = intensity;
}
inline void update_camera(
float phi,
float theta,
float base_dist,
int zoom)
{
cam.dir.x = -sinf(phi) * sinf(theta);
cam.dir.y = -cosf(theta);
cam.dir.z = -cosf(phi) * sinf(theta);
cam.right.x = cosf(phi);
cam.right.y = 0.0f;
cam.right.z = -sinf(phi);
cam.up.x = -sinf(phi) * cosf(theta);
cam.up.y = sinf(theta);
cam.up.z = -cosf(phi) * cosf(theta);
const float dist = base_dist * powf(0.95f, static_cast<float>(zoom));
cam.pos.x = -cam.dir.x * dist;
cam.pos.y = -cam.dir.y * dist;
cam.pos.z = -cam.dir.z * dist;
}
// Ray to sphere intersection
inline bool isect(const Ray &ray, const Sphere &sphere, Isect_info& isect_info)
{
mi::Float32_3 oc = ray.p0 - sphere.center;
float b = 2.f * dot(oc, ray.dir);
float c = dot(oc, oc) - sphere.radius * sphere.radius;
float disc = b * b - 4.f * c;
// no intersection
if (disc <= 0.f)
return false;
disc = sqrtf(disc);
//first hit
float t = (-b - disc) * 0.5f;
if (t <= 0.f)
{
//try second hit
t = (-b + disc) * 0.5f;
//sphere behind ray?
if (t <= 0.f)
return false;
}
isect_info.pos = ray.p0 + ray.dir*t;
isect_info.normal = normalize(isect_info.pos - sphere.center);
// compute uvw coordinates
const float phi = atan2f(isect_info.normal.x, isect_info.normal.z);
const float theta = acosf(isect_info.normal.y);
isect_info.uvw.x = phi / Constants.PI + 1.f;
isect_info.uvw.y = 1.f - theta / Constants.PI;
isect_info.uvw.z = 0.f;
// compute surface derivatives
const float pi_rad = Constants.PI*sphere.radius;
const float sp = sinf(phi);
const float cp = cosf(phi);
const float st = sinf(theta);
isect_info.tan_u.x = cp * st * pi_rad;
isect_info.tan_u.y = 0.f;
isect_info.tan_u.z = -sp * st * pi_rad;
isect_info.tan_u = normalize(isect_info.tan_u);
isect_info.tan_v.x = -sp * isect_info.normal.y * pi_rad;
isect_info.tan_v.y = st * pi_rad;
isect_info.tan_v.z = -cp * isect_info.normal.y * pi_rad;
isect_info.tan_v = normalize(isect_info.tan_v);
return true;
}
// build environment importance sampling data
void build_alias_map()
{
const mi::Uint32 rx = env.map_size.x;
const mi::Uint32 ry = env.map_size.y;
env.alias_map = static_cast<Render_context::Environment::Alias_map *>(
malloc(rx * ry * sizeof(Render_context::Environment::Alias_map)));
float *importance_data = static_cast<float *>(malloc(rx * ry * sizeof(float)));
float cos_theta0 = 1.0f;
const float step_phi = 2.f * Constants.PI / static_cast<float>(rx);
const float step_theta = Constants.PI / static_cast<float>(ry);
for (unsigned int y = 0; y < ry; ++y)
{
const float theta1 = static_cast<float>(y + 1) * step_theta;
const float cos_theta1 = std::cos(theta1);
const float area = (cos_theta0 - cos_theta1) * step_phi;
cos_theta0 = cos_theta1;
for (unsigned int x = 0; x < rx; ++x)
{
const unsigned int idx = y * rx + x;
const unsigned int idx4 = idx * 4;
importance_data[idx] =
area * std::max(env.map_pixels[idx4], std::max(env.map_pixels[idx4 + 1], env.map_pixels[idx4 + 2]));
}
}
// build alias map
// create qs (normalized)
size_t size = rx * ry;
float sum = 0.0f;
for (unsigned int i = 0; i < size; ++i)
sum += importance_data[i];
for (unsigned int i = 0; i < size; ++i)
env.alias_map[i].q = (static_cast<float>(size) * importance_data[i] / sum);
// create partition table
unsigned int *partition_table = static_cast<unsigned int *>(
malloc(size * sizeof(unsigned int)));
unsigned int s = 0u, large = size;
for (unsigned int i = 0; i < size; ++i)
partition_table[(env.alias_map[i].q < 1.0f) ? (s++) : (--large)] = env.alias_map[i].alias = i;
// create alias map
for (s = 0; s < large && large < size; ++s)
{
const unsigned int j = partition_table[s], k = partition_table[large];
env.alias_map[j].alias = k;
env.alias_map[k].q += env.alias_map[j].q - 1.0f;
large = (env.alias_map[k].q < 1.0f) ? (large + 1u) : large;
}
free(partition_table);
env.inv_integral = 1.0f / sum;
free(importance_data);
}
// evaluate the environment map for a given ray direction
inline mi::Float32_3 evaluate_environment(float& pdf, const mi::Float32_3& dir)
{
// use environment map?
if (env.map.is_valid_interface())
{
const float u = atan2f(dir.z, dir.x) * (0.5f / Constants.PI) + 0.5f;
const float v = acosf(fmax(fminf(-dir.y, 1.0f), -1.0f)) / Constants.PI;
size_t x = mi::math::min(static_cast<mi::Uint32>(u * env.map_size.x), env.map_size.x - 1u);
size_t y = mi::math::min(static_cast<mi::Uint32>(v * env.map_size.y), env.map_size.y - 1u);
const float *pixel = env.map_pixels + ((y*env.map_size.x + x) * 4);
pdf = std::max(pixel[0], std::max(pixel[1], pixel[2])) * env.inv_integral;
return mi::Float32_3(pixel[0], pixel[1], pixel[2])*env.intensity;
}
else
{
pdf = 1.f;
return env.color*env.intensity;
}
}
// importance sampling the environment map
mi::Float32_3 sample_environment(mi::Float32_3& light_dir, float &light_pdf, unsigned &seed)
{
xi.x = rnd(seed);
xi.y = rnd(seed);
xi.z = rnd(seed);
// importance sample the environment using an alias map
const unsigned int size = env.map_size.x * env.map_size.y;
const unsigned int idx =
mi::math::min(static_cast<unsigned>(xi.x * static_cast<float>(size)), size - 1);
unsigned int env_idx;
float xi_y = xi.y;
if (xi_y < env.alias_map[idx].q)
{
env_idx = idx;
xi_y /= env.alias_map[idx].q;
}
else
{
env_idx = env.alias_map[idx].alias;
xi_y = (xi_y - env.alias_map[idx].q) / (1.0f - env.alias_map[idx].q);
}
const unsigned int py = env_idx / env.map_size.x;
const unsigned int px = env_idx % env.map_size.x;
// uniformly sample spherical area of pixel
const float u = static_cast<float>(px + xi_y) / static_cast<float>(env.map_size.x);
const float phi = u * 2.0f * Constants.PI - Constants.PI;
const float sin_phi = sinf(phi);
const float cos_phi = cosf(phi);
const float step_theta = Constants.PI / static_cast<float>(env.map_size.y);
const float theta0 = static_cast<float>(py)* step_theta;
const float cos_theta = cosf(theta0) * (1.0f - xi.z) + cosf(theta0 + step_theta) * xi.z;
const float theta = acosf(cos_theta);
const float sin_theta = sinf(theta);
light_dir = mi::Float32_3(cos_phi * sin_theta, -cos_theta, sin_phi * sin_theta);
// lookup filtered beauty
const float v = theta / Constants.PI;
size_t x = mi::math::min(static_cast<mi::Uint32>(u * env.map_size.x), env.map_size.x - 1u);
size_t y = mi::math::min(static_cast<mi::Uint32>(v * env.map_size.y), env.map_size.y - 1u);
const float *pix = env.map_pixels + ((y*env.map_size.x + x) * 4);
light_pdf = mi::math::max(pix[0], mi::math::max(pix[1], pix[2])) * env.inv_integral;
return mi::Float32_3(pix[0], pix[1], pix[2])*env.intensity;
}
// sample scene lights (omni + environment map)
mi::Float32_3 sample_lights(const mi::Float32_3 &pos, mi::Float32_3& light_dir, float& light_pdf, unsigned &seed)
{
float p_select_light = 1.0f;
if (omni_light.intensity > 0.f)
{
// keep it simple and use either point light or environment light, each with the same
// probability. If the environment factor is zero, we always use the point light
// Note: see also miss shader
p_select_light = env.intensity > 0.0f ? 0.5f : 1.0f;
// in general, you would select the light depending on the importance of it
// e.g. by incorporating their luminance
// randomly select one of the lights
if (rnd(seed) <= p_select_light)
{
light_pdf = Constants.DIRAC; // infinity
// compute light direction and distance
light_dir = omni_light.dir*omni_light.distance - pos;
const float inv_distance2 = 1.0f / dot(light_dir, light_dir);
light_dir *= sqrtf(inv_distance2);
return omni_light.color *
(omni_light.intensity * inv_distance2 * 0.25f / (Constants.PI * p_select_light));
}
// probability to select the environment instead
p_select_light = (1.0f - p_select_light);
}
// light from the environment map
mi::Float32_3 radiance = sample_environment(light_dir, light_pdf, seed);
// return radiance over pdf
light_pdf *= p_select_light;
return radiance / light_pdf;
}
};
// MDL Material Helper Functions
// Creates an instance of the given material.
void create_material_instance(
const char* material_name,
const char* instance_name)
{
// split module and material name
std::string module_name, material_simple_name;
if (!mi::examples::mdl::parse_cmd_argument_material_name(
material_name, module_name, material_simple_name, true))
exit_failure();
// Load the module.
mdl_impexp_api->load_module(transaction, module_name.c_str(), context);
if (!print_messages(context))
exit_failure("Loading module '%s' failed.", module_name.c_str());
// Get the database name for the module we loaded
mdl_factory->get_db_module_name(module_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_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.
// Assuming the material has defaults for all parameters.
mi::Sint32 result;
material_definition->create_function_call(0, &result));
if (result != 0)
exit_failure("Instantiating '%s' failed.", material_db_name.c_str());
transaction->store(material_instance.get(), instance_name);
}
// Compiles the given material instance in the given compilation modes and stores it in the DB.
void compile_material_instance(
const char* instance_name,
const char* compiled_material_name,
bool class_compilation)
{
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
#endif
transaction->access<mi::neuraylib::IMaterial_instance>(instance_name));
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());
material_instance->create_compiled_material(flags, context));
check_success(print_messages(context));
transaction->store(compiled_material.get(), compiled_material_name);
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
std::chrono::duration<double> et = t2 - t1;
if(class_compilation)
printf("Material Class Compilation: %f seconds.\n", et.count());
else
printf("Material Instance Compilation: %f seconds.\n", et.count());
#endif
}
// Generate and execute native CPU code for a subexpression of a given compiled material.
void generate_native(
Render_context& render_context,
const char* compiled_material_name,
bool use_custom_tex_runtime,
bool use_adapt_normal,
bool enable_derivatives,
bool enable_bsdf_flags)
{
Timing timing("generate target code");
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
#endif
transaction->access<mi::neuraylib::ICompiled_material>(compiled_material_name));
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
#endif
// has material a constant cutout opacity?
render_context.cutout.is_constant =
compiled_material->get_cutout_opacity(&render_context.cutout.constant_opacity);
// has material a constant thin_walled property?
compiled_material->lookup_sub_expression("thin_walled"));
render_context.thin_walled.is_constant = false;
render_context.thin_walled.is_thin_walled = false;
if (thin_walled->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT)
{
thin_walled->get_interface<mi::neuraylib::IExpression_constant const>());
thin_walled_const->get_value<mi::neuraylib::IValue_bool>());
render_context.thin_walled.is_constant = true;
render_context.thin_walled.is_thin_walled = thin_walled_bool->get_value();
}
// back faces could be different for thin walled materials
bool need_backface_bsdf = false;
bool need_backface_edf = false;
bool need_backface_emission_intensity = false;
if (!render_context.thin_walled.is_constant || render_context.thin_walled.is_thin_walled)
{
// first, backfaces dfs are only considered for thin_walled materials
// second, we only need to generate new code if surface and backface are different
need_backface_bsdf =
compiled_material->get_slot_hash(mi::neuraylib::SLOT_SURFACE_SCATTERING) !=
compiled_material->get_slot_hash(mi::neuraylib::SLOT_BACKFACE_SCATTERING);
need_backface_edf =
compiled_material->get_slot_hash(mi::neuraylib::SLOT_SURFACE_EMISSION_EDF_EMISSION) !=
compiled_material->get_slot_hash(mi::neuraylib::SLOT_BACKFACE_EMISSION_EDF_EMISSION);
need_backface_emission_intensity =
compiled_material->get_slot_hash(mi::neuraylib::SLOT_SURFACE_EMISSION_INTENSITY) !=
compiled_material->get_slot_hash(mi::neuraylib::SLOT_BACKFACE_EMISSION_INTENSITY);
// third, either the bsdf or the edf need to be non-default (black)
compiled_material->lookup_sub_expression("backface.scattering"));
compiled_material->lookup_sub_expression("backface.emission.emission"));
if (scattering_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT &&
emission_expr->get_kind() == mi::neuraylib::IExpression::EK_CONSTANT)
{
scattering_expr->get_interface<mi::neuraylib::IExpression_constant>());
scattering_expr_constant->get_value());
emission_expr->get_interface<mi::neuraylib::IExpression_constant>());
emission_expr_constant->get_value());
if (scattering_value->get_kind() == mi::neuraylib::IValue::VK_INVALID_DF &&
emission_value->get_kind() == mi::neuraylib::IValue::VK_INVALID_DF)
{
need_backface_bsdf = false;
need_backface_edf = false;
need_backface_emission_intensity = false;
}
}
}
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
#endif
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();
#endif
check_success(be_native->set_option("num_texture_spaces", "1") == 0);
check_success(be_native->set_option("num_texture_results", "128") == 0);
if (render_context.render_auxiliary)
check_success(be_native->set_option("enable_auxiliary", "on") == 0);
if (use_custom_tex_runtime)
check_success(be_native->set_option("use_builtin_resource_handler", "off") == 0);
if (enable_derivatives)
check_success(be_native->set_option("texture_runtime_with_derivs", "on") == 0);
if (use_adapt_normal)
check_success(be_native->set_option("use_renderer_adapt_normal", "on") == 0);
// if enabled, the generated code will use the optional "flags" field in the BSDF data structs
if (enable_bsdf_flags)
check_success(be_native->set_option("libbsdf_flags_in_bsdf_data", "on") == 0);
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t5 = std::chrono::steady_clock::now();
#endif
be_native->create_link_unit(transaction, context));
check_success(print_messages(context));
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t6 = std::chrono::steady_clock::now();
#endif
// select expressions to generate code for
std::vector<mi::neuraylib::Target_function_description> descs;
descs.push_back(mi::neuraylib::Target_function_description("surface.scattering"));
descs.push_back(mi::neuraylib::Target_function_description("surface.emission.emission"));
descs.push_back(mi::neuraylib::Target_function_description("surface.emission.intensity"));
size_t backface_scattering_index = ~0;
if (need_backface_bsdf)
{
backface_scattering_index = descs.size();
descs.push_back(mi::neuraylib::Target_function_description("backface.scattering"));
}
size_t backface_edf_index = ~0;
if (need_backface_edf)
{
backface_edf_index = descs.size();
descs.push_back(mi::neuraylib::Target_function_description("backface.emission.emission"));
}
size_t backface_emission_intensity_index = ~0;
if (need_backface_emission_intensity)
{
backface_emission_intensity_index = descs.size();
descs.push_back(mi::neuraylib::Target_function_description("backface.emission.intensity"));
}
size_t cutout_opacity_desc_index = ~0;
if (!render_context.cutout.is_constant)
{
cutout_opacity_desc_index = descs.size();
descs.push_back(mi::neuraylib::Target_function_description("geometry.cutout_opacity"));
}
size_t thin_walled_desc_index = ~0;
if (!render_context.thin_walled.is_constant)
{
thin_walled_desc_index = descs.size();
descs.push_back(mi::neuraylib::Target_function_description("thin_walled"));
}
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t7 = std::chrono::steady_clock::now();
#endif
// add the material to the link unit
link_unit->add_material(
compiled_material.get(),
descs.data(), descs.size(),
context);
check_success(print_messages(context));
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t8 = std::chrono::steady_clock::now();
#endif
// translate link unit
be_native->translate_link_unit(link_unit.get(), context));
check_success(print_messages(context));
check_success(code_native);
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t9 = std::chrono::steady_clock::now();
#endif
// update render context
render_context.target_code = code_native;
render_context.init_function_index = descs[0].function_index;
render_context.surface_bsdf_function_index = descs[1].function_index;
render_context.surface_edf_function_index = descs[2].function_index;
render_context.surface_emission_intensity_function_index = descs[3].function_index;
render_context.backface_bsdf_function_index = need_backface_bsdf
? descs[backface_scattering_index].function_index : descs[1].function_index;
render_context.backface_edf_function_index = need_backface_edf
? descs[backface_edf_index].function_index : descs[2].function_index;
render_context.backface_emission_intensity_function_index = need_backface_emission_intensity
? descs[backface_emission_intensity_index].function_index : descs[3].function_index;
render_context.cutout_opacity_function_index = !render_context.cutout.is_constant
? descs[cutout_opacity_desc_index].function_index : ~0;
render_context.thin_walled_function_index = !render_context.thin_walled.is_constant
? descs[thin_walled_desc_index].function_index : ~0;
#ifdef ADD_EXTRA_TIMERS
std::chrono::steady_clock::time_point t10 = std::chrono::steady_clock::now();
#endif
#ifdef ADD_EXTRA_TIMERS
std::chrono::duration<double> et = t10 - t1;
printf("GTC |||| Total time : %f seconds.\n", et.count());
et = t2 - t1;
printf("GTC | Compiled material DB : %f seconds.\n", et.count());
et = t3 - t2;
printf("GTC | Mateial properties inspection : %f seconds.\n", et.count());
et = t4 - t3;
printf("GTC | Native backend : %f seconds.\n", et.count());
et = t5 - t4;
printf("GTC | Material flags : %f seconds.\n", et.count());
et = t6 - t5;
printf("GTC | Create link unit : %f seconds.\n", et.count());
et = t7 - t6;
printf("GTC | Material expressions selection: %f seconds.\n", et.count());
et = t8 - t7;
printf("GTC | Add material to link unit : %f seconds.\n", et.count());
et = t9 - t8;
printf("GTC | Translate link unit : %f seconds.\n", et.count());
et = t10 - t9;
printf("GTC | RC update : %f seconds.\n", et.count());
#endif
}
// Prepare the textures for our own texture runtime.
bool prepare_textures(
std::vector<Texture>& textures,
const mi::neuraylib::ITarget_code* target_code)
{
for (mi::Size i = 1 /*skip invalid texture*/; i < target_code->get_texture_count(); ++i)
{
transaction->access<const mi::neuraylib::ITexture>(
target_code->get_texture(i)));
transaction->access<mi::neuraylib::IImage>(texture->get_image()));
mi::base::Handle<const mi::neuraylib::ICanvas> canvas(image->get_canvas(0, 0, 0));
char const* image_type = image->get_type(0, 0);
if (image->is_uvtile() || image->is_animated()) {
std::cerr << "The example does not support uvtile and/or animated textures!" << std::endl;
return false;
}
// For simplicity, the texture access functions are only implemented for float4 and gamma
// is pre-applied here (all images are converted to linear space).
// Convert to linear color space if necessary
if (texture->get_effective_gamma(0, 0) != 1.0f) {
// Copy/convert to float4 canvas and adjust gamma from "effective gamma" to 1.
image_api->convert(canvas.get(), "Color"));
gamma_canvas->set_gamma(texture->get_effective_gamma(0, 0));
image_api->adjust_gamma(gamma_canvas.get(), 1.0f);
canvas = gamma_canvas;
}
else if (strcmp(image_type, "Color") != 0 && strcmp(image_type, "Float32<4>") != 0) {
// Convert to expected format
canvas = image_api->convert(canvas.get(), "Color");
}
textures.push_back(Texture(canvas));
}
return true;
}
// Trace shadow ray
bool trace_shadow(Render_context& rc, Render_context::Ray& shadow_ray, unsigned &seed)
{
Isect_info isect_info;
// ray hits sphere?
if (rc.isect(shadow_ray, rc.sphere, isect_info))
{
shading_state.position = isect_info.pos;
shading_state.normal = shadow_ray.is_inside ? -isect_info.normal : isect_info.normal;
shading_state.geom_normal = rc.shading_state.normal;
shading_state.text_coords = &isect_info.uvw;
shading_state.tangent_u = &isect_info.tan_u;
shading_state.tangent_v = &isect_info.tan_v;
// evaluate material cutout opacity
float cutout_opacity = rc.cutout.constant_opacity;
if (!rc.cutout.is_constant)
{
mi::Sint32 ret_code = rc.target_code->execute(
rc.cutout_opacity_function_index,
shading_state,
rc.tex_handler,
/*arg_block_data=*/ nullptr,
&cutout_opacity);
assert(ret_code == 0 && "execute opacity function failed");
(void) ret_code;
}
// is the surface cut out?.
return (cutout_opacity >= rnd(seed));
}
else
{
return false;
}
}
// Recursive raytracing
bool trace_ray(mi::Float32_3 vp_sample[3], Render_context &rc, Render_context::Ray &ray, unsigned &seed)
{
if (ray.level >= rc.max_ray_length)
return false;
ray.level++;
Isect_info isect_info;
// ray hits sphere?
if (rc.isect(ray, rc.sphere, isect_info))
{
mi::neuraylib::Shading_state_material *shading_state = nullptr;
mi::neuraylib::Texture_handler_base *tex_handler = nullptr;
mi::neuraylib::tct_float4 text_results[128];
// update material shader state
if (rc.use_derivatives) {
// FIXME: compute dx, dy
isect_info.pos, // value component
{ 0.0f, 0.0f, 0.0f }, // dx component
{ 0.0f, 0.0f, 0.0f } // dy component
};
// FIXME: compute dx, dy
mi::neuraylib::tct_deriv_float3 texture_coords[1] = {
{
isect_info.uvw, // value component
{ 0.0f, 0.0f, 0.0f }, // dx component
{ 0.0f, 0.0f, 0.0f } // dy component
}
};
rc.shading_state_derivs.position = position;
rc.shading_state_derivs.normal = ray.is_inside ? -isect_info.normal : isect_info.normal;
rc.shading_state_derivs.geom_normal = rc.shading_state_derivs.normal;
rc.shading_state_derivs.text_coords = texture_coords;
rc.shading_state_derivs.tangent_u = &isect_info.tan_u;
rc.shading_state_derivs.tangent_v = &isect_info.tan_v;
rc.shading_state_derivs.text_results = text_results;
shading_state =
reinterpret_cast<mi::neuraylib::Shading_state_material *>(&rc.shading_state_derivs);
tex_handler =
reinterpret_cast<mi::neuraylib::Texture_handler_base *>(rc.tex_handler_deriv);
} else {
rc.shading_state.position = isect_info.pos;
rc.shading_state.normal = ray.is_inside ? -isect_info.normal : isect_info.normal;
rc.shading_state.geom_normal = rc.shading_state.normal;
rc.shading_state.text_coords = &isect_info.uvw;
rc.shading_state.tangent_u = &isect_info.tan_u;
rc.shading_state.tangent_v = &isect_info.tan_v;
rc.shading_state.text_results = text_results;
shading_state = &rc.shading_state;
tex_handler = rc.tex_handler;
}
// Beware: the layout of the structs *is different*
rc.use_derivatives ? rc.shading_state_derivs.normal : rc.shading_state.normal;
rc.use_derivatives ? rc.shading_state_derivs.geom_normal : rc.shading_state.geom_normal;
// return code to check if the code execution succeeded
mi::Sint32 ret_code;
// shader initialization for the current hit point
ret_code = rc.target_code->execute_init(
rc.init_function_index,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr);
assert(ret_code == 0 && "execute_bsdf_init failed");
// evaluate material cutout opacity
float cutout_opacity = rc.cutout.constant_opacity;
if (!rc.cutout.is_constant)
{
ret_code = rc.target_code->execute(
rc.cutout_opacity_function_index,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr,
&cutout_opacity);
assert(ret_code == 0 && "execute opacity function failed");
}
// is the surface cut out? Then skip the surface and send a ray through
if (cutout_opacity < rnd(seed))
{
ray.p0 = isect_info.pos;
ray.offset_ray(ray.is_inside_cutout ? isect_info.normal : -isect_info.normal);
ray.is_inside = !ray.is_inside;
ray.is_inside_cutout = !ray.is_inside_cutout;
ray.level--;
return trace_ray(vp_sample, rc, ray, seed);
}
else
{
// evaluate thin_walled state
bool is_thin_walled = rc.thin_walled.is_thin_walled;
if (!rc.thin_walled.is_constant)
{
ret_code = rc.target_code->execute(
rc.thin_walled_function_index,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr,
&is_thin_walled);
assert(ret_code == 0 && "execute thin_walled function failed");
}
// evaluate material surface emission contribution
{
uint64_t edf_function_index = (is_thin_walled && ray.is_inside) ?
rc.backface_edf_function_index : rc.surface_edf_function_index;
mi::neuraylib::Edf_evaluate_data<mi::neuraylib::DF_HSM_NONE> eval_data;
eval_data.k1 = -ray.dir;
// evaluate material surface edf
ret_code = rc.target_code->execute_edf_evaluate(
edf_function_index + 1, // edf_function_index corresponds to 'sample'
// edf_function_index+1 to 'evaluate'
&eval_data,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr);
assert(ret_code == 0 && "execute_edf_evaluate failed");
// emission contribution is only valid for positive pdf
if (eval_data.pdf > 1.e-6f)
{
uint64_t emission_intensity_function_index = (is_thin_walled && ray.is_inside)
? rc.backface_emission_intensity_function_index
: rc.surface_emission_intensity_function_index;
mi::Float32_3 intensity(1.f);
ret_code = rc.target_code->execute(
emission_intensity_function_index,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr,
&intensity);
assert(ret_code == 0 && "execute emission intensity function failed");
vp_sample[VPCH_ILLUM] += static_cast<mi::Float32_3>(eval_data.edf)*intensity*ray.weight;
}
}
uint64_t surface_bsdf_function_index = (is_thin_walled && ray.is_inside)
? rc.backface_bsdf_function_index : rc.surface_bsdf_function_index;
// get auxiliary data
if (rc.render_auxiliary && ray.level == 1)
{
mi::neuraylib::Bsdf_auxiliary_data<mi::neuraylib::DF_HSM_NONE> aux_data;
if (ray.is_inside && !is_thin_walled)
{
aux_data.ior2 = mi::Float32_3(1.0f);
}
else
{
aux_data.ior1 = mi::Float32_3(1.0f);
}
aux_data.k1 = -ray.dir;
aux_data.flags = rc.bsdf_data_flags;
ret_code = rc.target_code->execute_bsdf_auxiliary(
surface_bsdf_function_index + 3, // bsdf_function_index corresponds to 'sample'
// bsdf_function_index+3 to 'auxiliary'
&aux_data,
*shading_state,
tex_handler,
nullptr);
assert(ret_code == 0 && "execute_bsdf_auxiliary failed");
vp_sample[VPCH_ALBEDO] = aux_data.albedo_diffuse + aux_data.albedo_glossy;
vp_sample[VPCH_NORMAL] = aux_data.normal;
}
// evaluate scene lights contribution
{
mi::Float32_3 light_dir;
float light_pdf = 0.f;
mi::Float32_3 radiance_over_pdf = rc.sample_lights(isect_info.pos, light_dir, light_pdf, seed);
bool light_culled = !(
(ray.level < rc.max_ray_length) &&
(light_pdf != 0.0f) &&
((dot(normal, light_dir) > 0.f) != (ray.is_inside && !ray.is_inside_cutout)) );
// check if light is visible from inside a cutout by checking if a shadow ray can leave the object.
if (!light_culled && ray.is_inside_cutout)
{
Render_context::Ray shadow_ray = ray;
shadow_ray.p0 = isect_info.pos;
shadow_ray.offset_ray(-isect_info.normal);
shadow_ray.dir = normalize(light_dir);
light_culled = trace_shadow(rc, shadow_ray, seed);
}
if (!light_culled)
{
mi::neuraylib::Bsdf_evaluate_data<mi::neuraylib::DF_HSM_NONE> eval_data;
if (ray.is_inside && !is_thin_walled)
{
eval_data.ior2 = mi::Float32_3(1.0f);
}
else
{
eval_data.ior1 = mi::Float32_3(1.0f);
}
eval_data.k1 = -ray.dir;
eval_data.k2 = light_dir;
eval_data.bsdf_diffuse = mi::Float32_3(0.f);
eval_data.bsdf_glossy = mi::Float32_3(0.f);
eval_data.flags = rc.bsdf_data_flags;
// evaluate material surface bsdf
ret_code = rc.target_code->execute_bsdf_evaluate(
surface_bsdf_function_index + 1, // bsdf_function_index corresponds to 'sample'
// bsdf_function_index+1 to 'evaluate'
&eval_data,
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr);
assert(ret_code == 0 && "execute_bsdf_evaluate failed");
if (eval_data.pdf > 1.e-6f)
{
const float mis_weight = (light_pdf == Constants.DIRAC)
? 1.0f : light_pdf / (light_pdf + eval_data.pdf);
vp_sample[VPCH_ILLUM] += (eval_data.bsdf_diffuse + eval_data.bsdf_glossy)*(radiance_over_pdf * ray.weight)* mis_weight;
}
}
}
// sample material bsdf contribution
{
mi::neuraylib::Bsdf_sample_data sample_data; // input/output data for sample
if (ray.is_inside && !is_thin_walled)
{
sample_data.ior2 = mi::Float32_3(1.0f);
}
else
{
sample_data.ior1 = mi::Float32_3(1.0f);
}
sample_data.k1 = -ray.dir; // outgoing direction
sample_data.xi.x = rnd(seed);
sample_data.xi.y = rnd(seed);
sample_data.xi.z = rnd(seed);
sample_data.xi.w = rnd(seed);
sample_data.flags = rc.bsdf_data_flags;
ret_code = rc.target_code->execute_bsdf_sample(
surface_bsdf_function_index, // bsdf_function_index corresponds to 'sample'
&sample_data, // input/output
*shading_state,
tex_handler,
/*arg_block_data=*/ nullptr);
assert(ret_code == 0 && "execute_bsdf_sample failed");
if (sample_data.event_type != mi::neuraylib::BSDF_EVENT_ABSORB)
{
if ((sample_data.event_type & mi::neuraylib::BSDF_EVENT_SPECULAR) != 0)
ray.last_pdf = -1.0f;
else
ray.last_pdf = sample_data.pdf;
// there is a scattering event, trace either the reflection or transmission ray
ray.weight *= static_cast<mi::Float32_3>(sample_data.bsdf_over_pdf);
ray.p0 = isect_info.pos;
ray.dir = normalize(sample_data.k2);
// medium change?
if (sample_data.event_type & mi::neuraylib::BSDF_EVENT_TRANSMISSION)
{
ray.offset_ray(-mi::Float32_3(geom_normal));
ray.is_inside = !ray.is_inside;
}
else
{
ray.offset_ray(geom_normal);
}
mi::Float32_3 scat_color[3] = {mi::Float32_3(0.f)};
trace_ray(scat_color, rc, ray, seed);
vp_sample[VPCH_ILLUM] += scat_color[VPCH_ILLUM];
}
}
return true;
}
}
// ray hits environment
else
{
float pdf = 1.f;
vp_sample[VPCH_ILLUM] = rc.evaluate_environment(pdf, ray.dir)*ray.weight;
// account multi importance sampling for environment
if (ray.level > 1 && ray.last_pdf > 0.f)
{
// point light selection probability
if (rc.omni_light.intensity > 0.f)
pdf *= 0.5f;
vp_sample[VPCH_ILLUM] *= ray.last_pdf / (ray.last_pdf + pdf);
}
return false;
}
}
// Scene Rendering
void render_scene(
Render_context rc,
size_t frame_nb,
VP_buffers *vp_buffers,
unsigned char* dst,
size_t ymin,
size_t ymax,
size_t width,
size_t height,
unsigned char channels)
{
if (ymax > height)
ymax = height;
Render_context::Ray ray;
size_t pixel_offset = ymin * width * channels;
size_t vp_idx = ymin * width;
for (size_t y = ymin; y < ymax; ++y)
{
// random sequence initialization
unsigned seed = tea(16, y*width, frame_nb);
for (size_t x = 0; x < width; ++x, ++vp_idx)
{
mi::Float32_3 vp_sample[3] =
float x_rnd = rnd(seed);
float y_rnd = rnd(seed);
mi::Float32_2 screen_pos(
(x + x_rnd)*rc.cam.inv_res.x,
(y + y_rnd)*rc.cam.inv_res.y);
float r = (2.0f * screen_pos.x - 1.0f);
float u = (2.0f * screen_pos.y - 1.0f);
ray.p0 = rc.cam.pos;
ray.dir = normalize(rc.cam.dir * rc.cam.focal +
rc.cam.right * r + rc.cam.up * (rc.cam.aspect * u));
ray.weight = 1.f;
ray.is_inside = false;
ray.is_inside_cutout = false;
ray.level = 0;
ray.last_pdf = -1.f;
//trace camera ray
bool ray_hit = trace_ray(vp_sample, rc, ray, seed);
// update progressive rendering viewport buffer
if (frame_nb == 1)
{
vp_buffers->accum_buffer[vp_idx] = vp_sample[VPCH_ILLUM];
if(rc.render_auxiliary)
{
vp_buffers->aux_count[vp_idx] = 0;
if (ray_hit)
{
vp_buffers->aux_count[vp_idx]++;
vp_buffers->albedo_buffer[vp_idx] = vp_sample[VPCH_ALBEDO];
vp_buffers->normal_buffer[vp_idx] = vp_sample[VPCH_NORMAL];
}
else
{
vp_buffers->albedo_buffer[vp_idx] = mi::Float32_3(0.f);
vp_buffers->normal_buffer[vp_idx] = mi::Float32_3(0.f);
}
}
}
else
{
vp_buffers->accum_buffer[vp_idx] =
(vp_buffers->accum_buffer[vp_idx] * static_cast<float>(frame_nb - 1) + vp_sample[VPCH_ILLUM]) * (1.f / frame_nb);
vp_sample[VPCH_ILLUM] = vp_buffers->accum_buffer[vp_idx];
if (ray_hit && rc.render_auxiliary)
{
vp_buffers->aux_count[vp_idx]++;
vp_buffers->albedo_buffer[vp_idx] =
(vp_buffers->albedo_buffer[vp_idx] * static_cast<float>(vp_buffers->aux_count[vp_idx] - 1) + vp_sample[VPCH_ALBEDO]) * (1.f / vp_buffers->aux_count[vp_idx]);
vp_sample[VPCH_ALBEDO] = vp_buffers->albedo_buffer[vp_idx];
vp_buffers->normal_buffer[vp_idx] =
(vp_buffers->normal_buffer[vp_idx] * static_cast<float>(vp_buffers->aux_count[vp_idx] - 1) + vp_sample[VPCH_NORMAL]) * (1.f / vp_buffers->aux_count[vp_idx]);
vp_sample[VPCH_NORMAL] = normalize(vp_buffers->normal_buffer[vp_idx]);
}
}
if (dst)
{
// apply gamma correction
vp_sample[VPCH_ILLUM].x = powf(vp_sample[VPCH_ILLUM].x, 1.f / 2.2f);
vp_sample[VPCH_ILLUM].y = powf(vp_sample[VPCH_ILLUM].y, 1.f / 2.2f);
vp_sample[VPCH_ILLUM].z = powf(vp_sample[VPCH_ILLUM].z, 1.f / 2.2f);
// write final pixel
vp_sample[VPCH_ILLUM] *= 255.f;
clamp(vp_sample[VPCH_ILLUM], 0.f, 255.f);
dst[pixel_offset++] = static_cast<unsigned char>(vp_sample[VPCH_ILLUM].x);
dst[pixel_offset++] = static_cast<unsigned char>(vp_sample[VPCH_ILLUM].y);
dst[pixel_offset++] = static_cast<unsigned char>(vp_sample[VPCH_ILLUM].z);
dst[pixel_offset++] = 255u;
}
}
}
}
// Save current result image to disk
static void save_screenshot(
const mi::Float32_3* image_buffer,
const unsigned int width,
const unsigned int height,
const std::string &filename,
{
image_api->create_canvas("Float32<3>", width, height));
mi::base::Handle<mi::neuraylib::ITile> tile(canvas->get_tile());
memcpy(tile->get_data(), image_buffer, width*height * sizeof(mi::Float32_3));
mi::base::Handle<mi::IBoolean> option_force_default_gamma(factory->create<mi::IBoolean>());
option_force_default_gamma->set_value(true);
mi::base::Handle<mi::IMap> export_options(factory->create<mi::IMap>("Map<Interface>"));
export_options->insert("force_default_gamma", option_force_default_gamma.get());
mdl_impexp_api->export_canvas(filename.c_str(), canvas.get(), export_options.get());
}
// Main Function
// Print command line usage to console and terminate the application.
void usage(char const *prog_name)
{
std::cout
<< "Usage: " << prog_name << " [options] [<material_name>]\n"
<< "Options:\n"
<< " -h|--help print this text and exit\n"
<< " -v|--version print the MDL SDK version string and exit\n"
<< " --res <x> <y> resolution (default: 700x520)\n"
<< " --hdr <filename> environment map\n"
<< " (default: nvidia/sdk_examples/resources/environment.hdr)\n"
<< " --cc use class compilation\n"
<< " --cr use custom texture runtime\n"
<< " --allowed_scatter_mode <m> limits the allowed scatter mode to \"none\", \"reflect\", "
<< "\"transmit\" or \"reflect_and_transmit\" (default: restriction disabled)\n"
<< " --an use adapt normal function\n"
<< " -d enable use of derivatives\n"
<< " --nogui don't open interactive display\n"
<< " --spp samples per pixel (default: 100) for output image when "
"nogui\n"
<< " -o <outputfile> image file to write result to\n"
<< " (default: example_native.png)\n"
<< " -oaux output albedo and normal auxiliary buffers\n"
<< " -p|--mdl_path <path> mdl search path, can occur multiple times\n"
<< "\n"
<< "Viewport controls:\n"
<< " Mouse Camera rotation, zoom\n"
<< " Arrow keys, (+/-) Omni-light rotation, intensity\n"
<< " CTRL + (+/-) Environment intensity\n"
<< " ENTER Screenshot\n"
<< std::endl;
exit_failure();
}
int MAIN_UTF8(int argc, char *argv[])
{
// Parse command line options
Options options;
mi::examples::mdl::Configure_options configure_options;
configure_options.add_example_search_path = false;
bool print_version_and_exit = false;
for (int i = 1; i < argc; ++i)
{
char const *opt = argv[i];
if (opt[0] == '-')
{
if (strcmp(opt, "--nogui") == 0)
{
options.no_gui = true;
}
else if (strcmp(opt, "--spp") == 0 && i < argc - 1)
{
options.iterations = std::max(atoi(argv[++i]), 1);
}
else if (strcmp(opt, "-o") == 0 && i < argc - 1)
{
options.outputfile = argv[++i];
}
else if (strcmp(opt, "-oaux") == 0)
{
options.output_auxiliary = true;
}
else if (strcmp(opt, "--res") == 0 && i < argc - 2)
{
options.res_x = std::max(atoi(argv[++i]), 1);
options.res_y = std::max(atoi(argv[++i]), 1);
}
else if (strcmp(opt, "--max_path_length") == 0 && i < argc - 1)
{
options.max_ray_length = std::max(atoi(argv[++i]), 0);
}
else if (strcmp(opt, "--hdr") == 0 && i < argc - 1)
{
options.env_map = argv[++i];
}
else if (strcmp(opt, "--hdr_scale") == 0 && i < argc - 1)
{
options.env_scale = static_cast<float>(atof(argv[++i]));
}
else if (strcmp(opt, "-f") == 0 && i < argc - 1)
{
options.cam_fov = static_cast<float>(atof(argv[++i]));
}
else if (strcmp(opt, "--pos") == 0 && i < argc - 3)
{
options.cam_pos.x = static_cast<float>(atof(argv[++i]));
options.cam_pos.y = static_cast<float>(atof(argv[++i]));
options.cam_pos.z = static_cast<float>(atof(argv[++i]));
}
else if (strcmp(opt, "-l") == 0 && i < argc - 6)
{
options.light_pos.x = static_cast<float>(atof(argv[++i]));
options.light_pos.y = static_cast<float>(atof(argv[++i]));
options.light_pos.z = static_cast<float>(atof(argv[++i]));
options.light_intensity.x = static_cast<float>(atof(argv[++i]));
options.light_intensity.y = static_cast<float>(atof(argv[++i]));
options.light_intensity.z = static_cast<float>(atof(argv[++i]));
}
else if (strcmp(opt, "--cc") == 0)
{
options.use_class_compilation = true;
}
else if (strcmp(opt, "--cr") == 0)
{
options.use_custom_tex_runtime = true;
}
else if (strcmp(opt, "--allowed_scatter_mode") == 0 && i < argc - 1)
{
options.enable_bsdf_flags = true;
char const *mode = argv[++i];
if (strcmp(mode, "none") == 0) {
options.allowed_scatter_mode = mi::neuraylib::DF_FLAGS_NONE;
} else if (strcmp(mode, "reflect") == 0) {
options.allowed_scatter_mode = mi::neuraylib::DF_FLAGS_ALLOW_REFLECT;
} else if (strcmp(mode, "transmit") == 0) {
options.allowed_scatter_mode = mi::neuraylib::DF_FLAGS_ALLOW_TRANSMIT;
} else if (strcmp(mode, "reflect_and_transmit") == 0) {
options.allowed_scatter_mode =
mi::neuraylib::DF_FLAGS_ALLOW_REFLECT_AND_TRANSMIT;
} else {
std::cout << "Unknown allowed_scatter_mode: \"" << mode << "\"" << std::endl;
usage(argv[0]);
}
}
else if (strcmp(opt, "--an") == 0)
{
options.use_adapt_normal = true;
}
else if (strcmp(opt, "-d") == 0)
{
options.enable_derivatives = true;
}
else if ((strcmp(opt, "--mdl_path") == 0 || strcmp(opt, "-p") == 0) &&
i < argc - 1)
{
configure_options.additional_mdl_paths.push_back(argv[++i]);
}
else if (strcmp(opt, "-v") == 0 || strcmp(opt, "--version") == 0)
{
print_version_and_exit = true;
}
else
{
if (strcmp(opt, "-h") != 0 && strcmp(opt, "--help") != 0)
std::cout << "Unknown option: \"" << opt << "\"" << std::endl;
usage(argv[0]);
}
}
else
{
options.material_name = opt;
}
}
// Create render context
Render_context rc(options.enable_derivatives);
// Use default material, if none was provided via command line
configure_options.add_example_search_path = true;
if (options.material_name.empty())
options.material_name = "::nvidia::sdk_examples::tutorials::example_df";
// 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.");
// Handle the --version flag
if (print_version_and_exit)
{
// print library version information.
neuray->get_api_component<const mi::neuraylib::IVersion>());
fprintf(stdout, "%s\n", version->get_string());
// free the handles and unload the MDL SDK
version = nullptr;
neuray = nullptr;
if (!mi::examples::mdl::unload())
exit_failure("Failed to unload the SDK.");
exit_success();
}
// Configure the MDL SDK
if (!mi::examples::mdl::configure(neuray.get(), configure_options))
exit_failure("Failed to initialize the SDK.");
// Start the MDL SDK
mi::Sint32 ret = neuray->start();
if (ret != 0)
exit_failure("Failed to initialize the SDK. Result code: %d", ret);
// Set some render flags
rc.render_auxiliary = options.output_auxiliary;
rc.bsdf_data_flags = options.allowed_scatter_mode;
{
// 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());
neuray->get_api_component<mi::neuraylib::IFactory>());
// Acquire image API needed to prepare the textures
neuray->get_api_component<mi::neuraylib::IImage_api>());
neuray->get_api_component<mi::neuraylib::IMdl_impexp_api>());
{
neuray->get_api_component<mi::neuraylib::IMdl_factory>());
neuray->get_api_component<mi::neuraylib::IMdl_backend_api>());
mdl_factory->create_execution_context());
// Load the MDL module and create a material instance
std::string instance_name = "material instance";
create_material_instance(
mdl_factory.get(),
transaction.get(),
mdl_impexp_api.get(),
context.get(),
options.material_name.c_str(),
instance_name.c_str());
// Compile the material instance in instance compilation mode
std::string instance_compilation_name
= std::string("instance compilation of ") + instance_name;
// Compile the material instance
std::string compilation_name
= std::string("compilation of ") + instance_name;
compile_material_instance(
mdl_factory.get(),
transaction.get(),
context.get(),
instance_name.c_str(),
compilation_name.c_str(),
options.use_class_compilation);
// Generate target code for some material expression and update render context
generate_native(
rc,
transaction.get(),
mdl_backend_api.get(),
context.get(),
compilation_name.c_str(),
options.use_custom_tex_runtime,
options.use_adapt_normal,
options.enable_derivatives,
options.enable_bsdf_flags);
}
// Setup custom texture handler, if requested
std::vector<Texture> textures;
Texture_handler tex_handler = { 0, 0, 0 };
Texture_handler_deriv tex_handler_deriv = { 0, 0, 0 };
mi::neuraylib::Texture_handler_vtable tex_only_adapt_normal_vtable { };
mi::neuraylib::Texture_handler_deriv_vtable tex_only_adapt_normal_vtable_deriv { };
if (options.enable_derivatives)
{
if (options.use_custom_tex_runtime)
{
check_success(prepare_textures(
textures, transaction.get(), image_api.get(), rc.target_code.get()));
tex_handler_deriv.vtable = &tex_deriv_vtable;
tex_handler_deriv.num_textures = rc.target_code->get_texture_count() - 1;
tex_handler_deriv.textures = textures.data();
rc.tex_handler_deriv = &tex_handler_deriv;
}
else if (options.use_adapt_normal)
{
// only set the m_adapt_normal entry in the vtable of the texture handler object
tex_only_adapt_normal_vtable_deriv.m_adapt_normal = adapt_normal;
tex_handler_deriv.vtable = &tex_only_adapt_normal_vtable_deriv;
rc.tex_handler_deriv = &tex_handler_deriv;
}
}
else
{
if (options.use_custom_tex_runtime)
{
check_success(prepare_textures(
textures, transaction.get(), image_api.get(), rc.target_code.get()));
tex_handler.vtable = &tex_vtable;
tex_handler.num_textures = rc.target_code->get_texture_count() - 1;
tex_handler.textures = textures.data();
rc.tex_handler = &tex_handler;
}
else if (options.use_adapt_normal)
{
// only set the m_adapt_normal entry in the vtable of the texture handler object
tex_only_adapt_normal_vtable.m_adapt_normal = adapt_normal;
tex_handler.vtable = &tex_only_adapt_normal_vtable;
rc.tex_handler = &tex_handler;
}
}
// create window context
Window_context window_context;
// setup render data
// ------------------------------------------------------------------------
size_t window_width = 0, window_height = 0;
size_t frame_nb = 0; // frame counter
// Viewport buffers for progressive rendering
VP_buffers vp_buffers;
// Setup file name for nogl mode
std::string filename_base, filename_ext;
size_t dot_pos = options.outputfile.rfind('.');
if (dot_pos == std::string::npos)
{
filename_base = options.outputfile;
}
else
{
filename_base = options.outputfile.substr(0, dot_pos);
filename_ext = options.outputfile.substr(dot_pos);
}
#ifdef USE_PARALLEL_RENDERING
// get number of physical/virtual threads available.
#ifdef MI_PLATFORM_WINDOWS
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
const int num_threads = sysinfo.dwNumberOfProcessors;
#elif MI_PLATFORM_MACOSX
int num_threads;
size_t len = sizeof(num_threads);
sysctlbyname("hw.logicalcpu", &num_threads, &len, NULL, 0);
#else // LINUX // ARCH_64BIT
const int num_threads = sysconf(_SC_NPROCESSORS_ONLN);
#endif
std::cout << "Rendering on " << num_threads << " threads.\n";
#endif // USE_PARALLEL_RENDERING
// render options
rc.max_ray_length = options.max_ray_length;
// load/setup environment map
window_context.env_intensity = rc.env.intensity = options.env_scale;
transaction->create<mi::neuraylib::IImage>("Image"));
check_success(image->reset_file(options.env_map.c_str()) == 0);
rc.env.map = image->get_canvas(0, 0, 0);
rc.env.map_size.x = rc.env.map->get_resolution_x();
rc.env.map_size.y = rc.env.map->get_resolution_y();
// Check, whether we need to convert the image
char const *image_type = image->get_type(0, 0);
if (strcmp(image_type, "Color") != 0 && strcmp(image_type, "Float32<4>") != 0)
rc.env.map = image_api->convert(rc.env.map.get(), "Color");
rc.env.map_pixels = reinterpret_cast<const float*>(
mi::base::make_handle(rc.env.map->get_tile())->get_data());
rc.build_alias_map();
// setup omni light
rc.omni_light.intensity = std::max(std::max(options.light_intensity.x, options.light_intensity.y), options.light_intensity.y);
if (rc.omni_light.intensity > 0.f)
rc.omni_light.color = options.light_intensity / rc.omni_light.intensity;
else
rc.omni_light.color = options.light_intensity;
rc.omni_light.distance = length(options.light_pos);
rc.omni_light.dir = normalize(options.light_pos);
window_context.omni_phi = atan2f(rc.omni_light.dir.x, rc.omni_light.dir.z);
window_context.omni_theta = acosf(rc.omni_light.dir.y);
window_context.omni_intensity = rc.omni_light.intensity;
rc.update_light(window_context.omni_phi, window_context.omni_theta, window_context.omni_intensity);
// setup initial camera
float base_dist = length(options.cam_pos);
float theta, phi;
const mi::Float32_3 inv_dir = normalize(options.cam_pos);
phi = atan2f(inv_dir.x, inv_dir.z);
theta = acosf(inv_dir.y);
rc.cam.focal = 1.0f / tanf(options.cam_fov * Constants.PI / 360.f);
rc.update_camera(phi, theta, base_dist, window_context.zoom);
// render to image?
if (options.no_gui)
{
window_width = options.res_x;
window_height = options.res_y;
frame_nb = 0;
if (vp_buffers.accum_buffer)
delete[] vp_buffers.accum_buffer;
vp_buffers.accum_buffer = new mi::Float32_3[window_width*window_height];
if(options.output_auxiliary)
{
if (vp_buffers.albedo_buffer)
delete[] vp_buffers.albedo_buffer;
if (vp_buffers.normal_buffer)
delete[] vp_buffers.normal_buffer;
if (vp_buffers.aux_count)
delete[] vp_buffers.aux_count;
vp_buffers.albedo_buffer = new mi::Float32_3[window_width*window_height];
vp_buffers.normal_buffer = new mi::Float32_3[window_width*window_height];
vp_buffers.aux_count = new mi::Uint32[window_width * window_height];
}
// update camera parameters
rc.cam.inv_res.x = 1.0f / static_cast<float>(window_width);
rc.cam.inv_res.y = 1.0f / static_cast<float>(window_height);
rc.cam.aspect = static_cast<float>(window_height)
/ static_cast<float>(window_width);
{
Timing timing("rendering");
//render loop
while (frame_nb < options.iterations)
{
frame_nb++;
#ifdef USE_PARALLEL_RENDERING
// preparing render threads
std::vector<std::thread> threads;
size_t lpt = window_height / num_threads +
(window_height % num_threads != 0 ? 1 : 0); // lines per thread
// Launch render threads
for (int i = 0; i < num_threads; ++i)
threads.push_back(std::thread(render_scene, rc, frame_nb, &vp_buffers,
nullptr, lpt * i, lpt * (i + 1), window_width, window_height, 4));
// wait for threads to finish
for (int i = 0; i < num_threads; ++i)
threads[i].join();
threads.clear();
#else
render_scene(rc, frame_nb, &vp_buffers,
nullptr, 0, window_height, window_width, window_height, 4);
#endif
}
}
// save screenshot
save_screenshot(vp_buffers.accum_buffer, window_width, window_height,
filename_base + filename_ext, factory, image_api, mdl_impexp_api);
if (options.output_auxiliary)
{
save_screenshot(vp_buffers.albedo_buffer, window_width, window_height,
filename_base + "_albedo" + filename_ext, factory, image_api, mdl_impexp_api);
save_screenshot(vp_buffers.normal_buffer, window_width, window_height,
filename_base + "_normal" + filename_ext, factory, image_api, mdl_impexp_api);
}
}
else // interactive renderer
{
// create the main window
if (!glfwInit())
exit(EXIT_FAILURE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
mi::examples::mdl::GL_window::Description window_desc;
window_desc.width = options.res_x;
window_desc.height = options.res_y;
window_desc.title = "MDL Native Rendering";
window_desc.no_gui = false;
mi::examples::mdl::GL_window gl_window(window_desc);
gl_window.set_window_user_pointer(&window_context);
gl_window.set_key_callback(Window_context::handle_key);
gl_window.set_mouse_button_callback(Window_context::handle_mouse_button);
gl_window.set_cursor_pos_callback(Window_context::handle_mouse_pos);
gl_window.set_scroll_callback(Window_context::handle_scroll);
if (GLEW_OK != glewInit())
exit(EXIT_FAILURE);
glfwSwapInterval(1);
// create a display, this allows to render a buffer to screen
mi::examples::mdl::GL_display gl_display(window_desc.width, window_desc.height);
// setup GUI
// ------------------------------------------------------------------------
// init the GUI system in terms of styles and fonts
mi::examples::gui::Root* gui = window_desc.no_gui ? nullptr : gl_window.get_gui();
if (gui)
{
gui->initialize();
// add panels and other controls here
}
// render loop
while (gl_window.update())
{
// get the window size and resize the image if necessary
if (window_width != gl_window.get_width() || window_height != gl_window.get_height())
{
window_width = gl_window.get_width();
window_height = gl_window.get_height();
frame_nb = 0;
if (vp_buffers.accum_buffer)
delete[] vp_buffers.accum_buffer;
vp_buffers.accum_buffer = new mi::Float32_3[window_width*window_height];
if (options.output_auxiliary)
{
if (vp_buffers.albedo_buffer)
delete[] vp_buffers.albedo_buffer;
if (vp_buffers.normal_buffer)
delete[] vp_buffers.normal_buffer;
if (vp_buffers.aux_count)
delete[] vp_buffers.aux_count;
vp_buffers.albedo_buffer = new mi::Float32_3[window_width*window_height];
vp_buffers.normal_buffer = new mi::Float32_3[window_width*window_height];
vp_buffers.aux_count = new mi::Uint32[window_width * window_height];
}
// update camera parameters
rc.cam.inv_res.x = 1.0f / static_cast<float>(window_width);
rc.cam.inv_res.y = 1.0f / static_cast<float>(window_height);
rc.cam.aspect = static_cast<float>(window_height)
/ static_cast<float>(window_width);
}
// handle key input events
if (window_context.key_event && !ImGui::GetIO().WantCaptureMouse)
{
// update environment
rc.env.intensity = window_context.env_intensity;
// Update light
rc.update_light(window_context.omni_phi, window_context.omni_theta, window_context.omni_intensity);
}
// handle save screenshot event
if (window_context.save_sreenshot && !ImGui::GetIO().WantCaptureMouse)
{
save_screenshot(vp_buffers.accum_buffer, window_width, window_height,
filename_base + filename_ext, factory, image_api, mdl_impexp_api);
if (options.output_auxiliary)
{
save_screenshot(vp_buffers.albedo_buffer, window_width, window_height,
filename_base + "_albedo" + filename_ext, factory, image_api,
mdl_impexp_api);
save_screenshot(vp_buffers.normal_buffer, window_width, window_height,
filename_base + "_normal" + filename_ext, factory, image_api,
mdl_impexp_api);
}
}
// handle mouse input events
if (window_context.mouse_button - 1 == GLFW_MOUSE_BUTTON_LEFT)
{
// Only accept button press when not hovering GUI window
if (window_context.mouse_button_action == GLFW_PRESS &&
!ImGui::GetIO().WantCaptureMouse)
{
window_context.moving = true;
glfwGetCursorPos(gl_window.get_window(), &window_context.move_start_x, &window_context.move_start_y);
}
else
{
window_context.moving = false;
}
}
if (window_context.mouse_wheel_delta && !ImGui::GetIO().WantCaptureMouse)
{
window_context.zoom += window_context.mouse_wheel_delta;
}
if (window_context.mouse_event && !ImGui::GetIO().WantCaptureMouse)
{
// Update camera
phi -= static_cast<float>(window_context.move_dx) * 0.001f * Constants.PI;
theta -= static_cast<float>(window_context.move_dy) * 0.001f * Constants.PI;
if (theta < 0.f)
theta = 0.f;
else if (theta > Constants.PI)
theta = Constants.PI;
window_context.move_dx = window_context.move_dy = 0.0;
rc.update_camera(phi, theta, base_dist, window_context.zoom);
}
if (window_context.key_event || window_context.mouse_event)
frame_nb = 0;
// Clear all events
window_context.key_event = false;
window_context.mouse_event = false;
window_context.mouse_wheel_delta = 0;
window_context.mouse_button = 0;
window_context.save_sreenshot = false;
++frame_nb;
gl_display.resize(window_width, window_height);
// handle resize on the application side (resize rendering buffer, restart)
if (gui)
{
// begin a new frame for the GUI and update the controls
gui->new_frame(); // required even when the main GUI is not rendered
gui->update(/*transaction*/ nullptr); // update GUI elements
// process events
mi::examples::gui::Event e = gui->process_event();
while (e.is_valid())
{
/* handle custom application events here */
e = gui->process_event();
}
}
// map the buffer, update the image data and un-map afterwards
// make sure this is as fast as possible
unsigned char* dst_image_data = gl_display.map();
#ifdef USE_PARALLEL_RENDERING
// preparing render threads
std::vector<std::thread> threads;
size_t lpt = window_height / num_threads +
(window_height % num_threads != 0 ? 1 : 0); // lines per thread
// launch render threads
for (int i = 0; i < num_threads; ++i)
threads.push_back(std::thread(render_scene, rc, frame_nb, &vp_buffers,
dst_image_data, lpt*i, lpt*(i + 1), window_width, window_height, 4));
// wait for threads to finish
for (int i = 0; i < num_threads; ++i)
threads[i].join();
threads.clear();
#else
render_scene(rc, frame_nb, &vp_buffers,
dst_image_data, 0, window_height, window_width, window_height, 4);
#endif
gl_display.unmap();
// render the updated image to screen
gl_display.update_display();
// render GUI on top
if (gui)
gui->render(nullptr);
// finish the frame
gl_window.present_back_buffer();
}
glfwTerminate();
}
// free environment image
image = nullptr;
// Uninitialize the render context
rc.uninit();
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 bool.
Definition: inumber.h:122
This interface represents maps, i.e., a key-value based data structure.
Definition: imap.h:41
NxM-dimensional matrix class template of fixed dimensions.
Definition: matrix.h:367
This interface represents a compiled material.
Definition: icompiled_material.h:97
This interface is used to interact with the distributed database.
Definition: idatabase.h:289
A constant expression.
Definition: iexpression.h:96
@ EK_CONSTANT
A constant expression. See mi::neuraylib::IExpression_constant.
Definition: iexpression.h:55
This API component allows the creation, assignment, and cloning of instances of types.
Definition: ifactory.h:35
This interface represents a function definition.
Definition: ifunction_definition.h:44
This interface provides various utilities related to canvases and buffers.
Definition: iimage_api.h:72
virtual ITile * convert(const ITile *tile, const char *pixel_type) const =0
Converts a tile to a different pixel type.
virtual void adjust_gamma(ITile *tile, Float32 old_gamma, Float32 new_gamma) const =0
Sets the gamma value of a tile and adjusts the pixel data accordingly.
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
virtual IMdl_backend * get_backend(Mdl_backend_kind kind)=0
Returns an MDL backend generator.
virtual Sint32 set_option(const char *name, const char *value)=0
Sets a string option.
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).
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
Represents target code of an MDL backend.
Definition: imdl_backend.h:773
Textures add image processing options to images.
Definition: itexture.h:68
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.
virtual Sint32 store(base::IInterface *db_element, const char *name, Uint8 privacy=LOCAL_SCOPE)=0
Stores the element db_element in the database under the name name and with the privacy level privacy.
@ SID_MATERIAL
The "::material" struct type.
Definition: itype.h:484
A value of type boolean.
Definition: ivalue.h:106
@ VK_INVALID_DF
An invalid distribution function value. See mi::neuraylib::IValue_invalid_df.
Definition: ivalue.h:60
Abstract interface for accessing version information.
Definition: iversion.h:19
Handle<Interface> make_handle(Interface *iptr)
Returns a handle that holds the interface pointer passed in as argument.
Definition: handle.h:428
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
signed int Sint32
32-bit signed integer.
Definition: types.h:46
Sint32 dot(Sint32 a, Sint32 b)
Returns the inner product (a.k.a. dot or scalar product) of two integers.
Definition: function.h:1081
Float32 length(Float32 a)
Returns the Euclidean norm of the scalar a (its absolute value).
Definition: function.h:1107
Bbox<T, DIM> operator+(const Bbox<T, DIM> &bbox, T value)
Returns a bounding box that is the bbox increased by a constant value at each face,...
Definition: bbox.h:470
Bbox<T, DIM> operator/(const Bbox<T, DIM> &bbox, T divisor)
Returns a bounding box that is a version of bbox divided by divisor, i.e., bbox.max and bbox....
Definition: bbox.h:518
Bbox<T, DIM> operator-(const Bbox<T, DIM> &bbox, T value)
Returns a bounding box that is the bbox shrunk by a constant value at each face, i....
Definition: bbox.h:486
Bbox<T, DIM> operator*(const Bbox<T, DIM> &bbox, T factor)
Returns a bounding box that is a version of bbox scaled by factor, i.e., bbox.max and bbox....
Definition: bbox.h:502
Color clamp(const Color &c, const Color &low, const Color &high)
Returns the color c elementwise clamped to the range [low, high].
Definition: color.h:522
Color abs(const Color &c)
Returns a color with the elementwise absolute values of the color c.
Definition: color.h:471
Color cos(const Color &c)
Returns a color with the elementwise cosine of the color c.
Definition: color.h:558
math::Matrix<Float32, 3, 4> Float32_3_4
3 x 4 matrix of Float32.
Definition: matrix_typedefs.h:244
math::Vector<Float32, 3> Float32_3
Vector of three Float32.
Definition: vector_typedefs.h:90
virtual const char * get_texture(Size index) const =0
Returns the name of a texture resource used by the target code.
tct_traits<true>::tct_derivable_float3 tct_deriv_float3
A float3 with derivatives.
Definition: target_code_types.h:132
virtual Size get_texture_count() const =0
Returns the number of texture resources used by the target code.
#define MI_NEURAYLIB_BSDF_USE_MATERIAL_IOR
The calling code can mark the x component of an IOR field in *_data with MI_NEURAYLIB_BSDF_USE_MATERI...
Definition: target_code_types.h:758
Df_flags
Flags controlling the calculation of DF results.
Definition: target_code_types.h:761
@ DF_FLAGS_NONE
allows nothing -> black
Definition: target_code_types.h:762
@ SLOT_BACKFACE_EMISSION_INTENSITY
Slot "backface.emission.intensity".
Definition: icompiled_material.h:34
@ SLOT_SURFACE_EMISSION_INTENSITY
Slot "surface.emission.intensity".
Definition: icompiled_material.h:30
@ SLOT_BACKFACE_EMISSION_EDF_EMISSION
Slot "backface.emission.emission".
Definition: icompiled_material.h:33
@ SLOT_SURFACE_EMISSION_EDF_EMISSION
Slot "surface.emission.emission".
Definition: icompiled_material.h:29
Math API.
Common namespace for APIs of NVIDIA Advanced Rendering Center GmbH.
Definition: example_derivatives.dox:5
Generic storage class template for math vector representations storing DIM elements of type T.
Definition: vector.h:135
Input and output structure for BSDF sampling data.
Definition: target_code_types.h:773
tct_float4 xi
input: pseudo-random sample numbers in range [0, 1)
Definition: target_code_types.h:779
tct_float pdf
output: pdf (non-projected hemisphere)
Definition: target_code_types.h:780
Df_flags flags
input: flags controlling calculation of result (optional depending on backend options)
Definition: target_code_types.h:785
tct_float3 ior2
mutual input: IOR other side
Definition: target_code_types.h:775
tct_float3 k1
mutual input: outgoing direction
Definition: target_code_types.h:776
tct_float3 k2
output: incoming direction
Definition: target_code_types.h:778
Bsdf_event_type event_type
output: the type of event for the generated sample
Definition: target_code_types.h:782
tct_float3 ior1
mutual input: IOR current medium
Definition: target_code_types.h:774
tct_float3 bsdf_over_pdf
output: bsdf * dot(normal, k2) / pdf
Definition: target_code_types.h:781
The MDL material state structure inside the MDL SDK is a representation of the renderer state as defi...
Definition: target_code_types.h:210
tct_float3 normal
The result of state::normal().
Definition: target_code_types.h:217
char const * ro_data_segment
A pointer to a read-only data segment.
Definition: target_code_types.h:271
tct_int object_id
The result of state::object_id().
Definition: target_code_types.h:291
traits::tct_derivable_float3 position
The result of state::position().
Definition: target_code_types.h:225
tct_float4 const * world_to_object
A 4x4 transformation matrix in row-major order transforming from world to object coordinates.
Definition: target_code_types.h:278
tct_float3 const * tangent_v
An array containing the results of state::texture_tangent_v(i).
Definition: target_code_types.h:248
tct_float4 const * object_to_world
A 4x4 transformation matrix in row-major order transforming from object to world coordinates.
Definition: target_code_types.h:285
traits::tct_derivable_float3 const * text_coords
An array containing the results of state::texture_coordinate(i).
Definition: target_code_types.h:234
tct_float animation_time
The result of state::animation_time().
Definition: target_code_types.h:229
tct_float3 geom_normal
The result of state::geometry_normal().
Definition: target_code_types.h:221
tct_float3 const * tangent_u
An array containing the results of state::texture_tangent_u(i).
Definition: target_code_types.h:241
tct_float meters_per_scene_unit
The result of state::meters_per_scene_unit().
Definition: target_code_types.h:296
tct_float4 * text_results
The texture results lookup table.
Definition: target_code_types.h:256
The texture handler structure that is passed to the texturing functions.
Definition: target_code_types.h:712
The runtime for bitmap texture access for the generated target code can optionally be implemented in ...
Definition: target_code_types.h:344
[Previous] [Up] [Next]