///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: CustomNode.cpp
// Language: C++
//
// Copyright (c) 2019 Microsoft. All rights reserved.
//
// This is private property, and it is illegal to copy or distribute in
// any form, without written authorization by the copyright owner(s).
//
///////////////////////////////////////////////////////////////////////////
//
// #Description#
//
// This example shows how to use the custom node in the material node
// network.
//
// See MaterialNodeExample for a more general example of material nodes.
//
// The RunExample() function will load a scene and create a new
// material. A material node network that uses ICustomNode is
// created and added to the diffuse channel of the new material.
//
// ICustomNode is a node used in the material node network that does not
// have its own "material casting evaluation" or "shader code generating"
// functions. These functions will be generated and assigned to the
// custom node by the user.
//
// To use an ICustomNode the user begins by creating the object and adding
// event handlers (observers) to it. The function(s) triggered by the
// event is implemented by the user.
//
//
// The events are:
//
// -- SG_EVENT_CUSTOM_NODE_EVALUATE --
// This event is triggered when simplygon is trying to evaluate a color
// from the node. The function receives the already evaluated RGBA colors
// of all the input nodes plugged into the custom node.
//
// To "return" the evaluated color from the custom node the first RGBA
// element in the array of RGBA values should be filled with the output
// color.
//
// The array of RGBA values corresponds to the amount of input nodes (set
// by the user) in the ICustomNode. The input node order set by the user
// into the ICustomNode will correspond to the order of evaluated RGBA
// values sent to the evaluation event handler function.
//
// The block of data received should be casted to a ParameterBlockEvaluate
// object.
//
// struct ParameterBlockEvaluate
// {
// uint inputCount;
// float ** inputs;
// };
//
//
// -- SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE --
// This event is triggered when the ICustomNode is generating shader code
// (both HLSL and GLSL).
//
// The user will "return" a string with the custom node evaluation shader
// function code. The string should end with assigning the variable
// "result" to a color (float4 in HLSL, vec4 in GLSL).
//
// To use the values of the input nodes' colors, access them with
// variable names following the "rgba_custom_input_XXX" format, where XXX
// is replaced with the index of the input node.
//
// The block of data received should be casted to a
// ParameterBlockShaderCode object.
//
// struct ParameterBlockShaderCode
// {
// uint inputCount;
// char * shaderCode;
// };
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
#include <time.h>
void RunExample( const std::string& readFrom );
struct ParameterBlockEvaluate
{
uint inputCount;
float inputs[SG_NUM_SUPPORTED_CUSTOM_NODE_INPUTS][4];
};
struct ParameterBlockShaderCode
{
uint inputCount;
char shaderCode[SG_SUPPORTED_CUSTOM_NODE_SHADER_CODE_SIZE];
};
//Implements a random color node
class CustomNodeRandomColorEventHandler : public robserver
{
public:
virtual void Execute(
IObject * subject,
rid EventId,
void * EventParameterBlock,
unsigned int EventParameterBlockSize )
{
float output[4] = { 0.0f,0.0f,0.0f,0.0f };
if( EventId == SG_EVENT_CUSTOM_NODE_EVALUATE )
{
ParameterBlockEvaluate * parameters = ((ParameterBlockEvaluate *)EventParameterBlock);
output[0] = float( rand() ) / float( RAND_MAX );
output[1] = float( rand() ) / float( RAND_MAX );
output[2] = float( rand() ) / float( RAND_MAX );
output[3] = float( rand() ) / float( RAND_MAX );
parameters->inputs[0][0] = output[0];
parameters->inputs[0][1] = output[1];
parameters->inputs[0][2] = output[2];
parameters->inputs[0][3] = output[3];
}
else if( EventId == SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE )
{
ParameterBlockShaderCode * parameters = ((ParameterBlockShaderCode *)EventParameterBlock);
//Incomplete, shader code is placeholder since we can't implement a matching random shader
sprintf(
parameters->shaderCode,
"result = float4(0.5f,0.5f,0.5f,0.5f);\n"
);
}
}
} customNodeRandomColorEventHandler;
//Implements a mean color node
class CustomNodeMeanColorEventHandler : public robserver
{
public:
virtual void Execute(
IObject * subject,
rid EventId,
void * EventParameterBlock,
unsigned int EventParameterBlockSize )
{
float output[4] = { 0.0f,0.0f,0.0f,0.0f };
if( EventId == SG_EVENT_CUSTOM_NODE_EVALUATE )
{
ParameterBlockEvaluate * parameters = ((ParameterBlockEvaluate *)EventParameterBlock);
//printf ( "InputCount: %d n" , parameters.inputCount );
for( uint i = 0; i < parameters->inputCount; ++i )
{
//printf ( "InputValue %d: %f %f %f %f n", i, parameters.inputs[i][0], parameters.inputs[i][1], parameters.inputs[i][2], parameters.inputs[i][3] );
output[0] += parameters->inputs[i][0];
output[1] += parameters->inputs[i][1];
output[2] += parameters->inputs[i][2];
output[3] += parameters->inputs[i][3];
}
if( parameters->inputCount > 0 )
{
output[0] /= (float)parameters->inputCount;
output[1] /= (float)parameters->inputCount;
output[2] /= (float)parameters->inputCount;
output[3] /= (float)parameters->inputCount;
}
parameters->inputs[0][0] = output[0];
parameters->inputs[0][1] = output[1];
parameters->inputs[0][2] = output[2];
parameters->inputs[0][3] = output[3];
}
else if( EventId == SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE )
{
ParameterBlockShaderCode * parameters = ((ParameterBlockShaderCode *)EventParameterBlock);
sprintf(
parameters->shaderCode,
"result = float4(0.0f,0.0f,0.0f,0.0f);\n"
);
for( uint i = 0; i < parameters->inputCount; ++i )
{
sprintf(
parameters->shaderCode,
"%sresult += rgba_custom_input_%d / float(%d);\n",
parameters->shaderCode,
i,
parameters->inputCount
);
}
}
}
} customNodeMeanColorEventHandler;
//Implements a squared filter node
class CustomNodeSquareColorEventHandler : public robserver
{
public:
virtual void Execute(
IObject * subject,
rid EventId,
void * EventParameterBlock,
unsigned int EventParameterBlockSize )
{
float output[4] = { 0.0f,0.0f,0.0f,0.0f };
if( EventId == SG_EVENT_CUSTOM_NODE_EVALUATE )
{
ParameterBlockEvaluate * parameters = ((ParameterBlockEvaluate *)EventParameterBlock);
output[0] = parameters->inputs[0][0] * parameters->inputs[0][0];
output[1] = parameters->inputs[0][1] * parameters->inputs[0][1];
output[2] = parameters->inputs[0][2] * parameters->inputs[0][2];
output[3] = parameters->inputs[0][3] * parameters->inputs[0][3];
parameters->inputs[0][0] = output[0];
parameters->inputs[0][1] = output[1];
parameters->inputs[0][2] = output[2];
parameters->inputs[0][3] = output[3];
}
else if( EventId == SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE )
{
ParameterBlockShaderCode * parameters = ((ParameterBlockShaderCode *)EventParameterBlock);
sprintf(
parameters->shaderCode,
"result = float4(rgba_custom_input_0.r * rgba_custom_input_0.r,rgba_custom_input_0.g * rgba_custom_input_0.g,rgba_custom_input_0.b * rgba_custom_input_0.b,rgba_custom_input_0.a * rgba_custom_input_0.a);\n"
);
}
}
} customNodeSquareColorEventHandler;
int main( int argc, char* argv[] )
{
try
{
InitExample();
// Set global variable. Using Orthonormal method for calculating tangentspace.
sg->SetGlobalSetting("DefaultTBNType", SG_TANGENTSPACEMETHOD_ORTHONORMAL);
std::string assetPath = GetAssetPath();
// Run the example code
RunExample(assetPath + "object.obj");
DeinitExample();
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
return 0;
}
void RunExample( const std::string& readFrom )
{
const std::string writeTo = "output";
std::string output_geometry_filename = GetExecutablePath() + writeTo + ".obj";
std::string output_diffuse_filename = GetExecutablePath() + writeTo + "_diffuse.png";
// Load object from file
spWavefrontImporter objReader = sg->CreateWavefrontImporter();
objReader->SetImportFilePath( readFrom.c_str() );
if( !objReader->RunImport() )
throw std::exception("Failed to load input file!");
spScene scene = objReader->GetScene();
spMaterialTable materialTable = scene->GetMaterialTable();
materialTable->Clear();
spMaterial customMaterial = sg->CreateMaterial();
materialTable->AddMaterial( customMaterial );
// Create a material node network that uses ICustomNode
spShadingCustomNode custom_node_random_color = sg->CreateShadingCustomNode();
spShadingCustomNode custom_node_square_color = sg->CreateShadingCustomNode();
spShadingCustomNode custom_node_mean_color = sg->CreateShadingCustomNode();
spShadingColorNode red_node = sg->CreateShadingColorNode();
red_node->SetColor( 1.0f, 0.0f, 0.0f, 1.0f );
//Set the event handler functions for the custom nodes and their input properties
custom_node_random_color->AddObserver( &customNodeRandomColorEventHandler, SG_EVENT_CUSTOM_NODE_EVALUATE ); //Evaluate
custom_node_random_color->AddObserver( &customNodeRandomColorEventHandler, SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE ); //Generate shader
custom_node_random_color->SetInputCount( 0 );
custom_node_mean_color->AddObserver( &customNodeMeanColorEventHandler, SG_EVENT_CUSTOM_NODE_EVALUATE ); //Evaluate
custom_node_mean_color->AddObserver( &customNodeMeanColorEventHandler, SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE ); //Generate shader
custom_node_mean_color->SetInputCount( 2 );
custom_node_mean_color->SetInput( 0, custom_node_random_color );
custom_node_mean_color->SetInput( 1, red_node );
custom_node_square_color->AddObserver( &customNodeSquareColorEventHandler, SG_EVENT_CUSTOM_NODE_EVALUATE ); //Evaluate
custom_node_square_color->AddObserver( &customNodeSquareColorEventHandler, SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE ); //Generate shader
custom_node_square_color->SetInputCount( 1 );
custom_node_square_color->SetInput( 0, custom_node_mean_color );
// Assign the exit node of the material node network to the diffuse channel in the material
customMaterial->SetShadingNetwork( SG_MATERIAL_CHANNEL_DIFFUSE, custom_node_square_color );
///////////////////////////////////
// Export HLSL shader code from the material network
//
// We create a shader data object
spShaderGenerator custom_shader = sg->CreateShaderGenerator();
// We add the material to it
custom_shader->SetMaterial( customMaterial );
// We tell the shader generator object to traverse the material node tree
// and gather the information needed to create a shader from the nodes
custom_shader->GenerateShaderData();
// Get the entire shader code for HLSL
rstring hlsl_code = custom_shader->GetHLSLCode();
// Store the shader to file in the current folder
FILE * file_hlsl = fopen( "shader_hlsl.txt", "wt" );
fprintf( file_hlsl, "%s", hlsl_code.GetText() );
fclose( file_hlsl );
//
///////////////////////////////////
#ifdef _WIN32
///////////////////////////////////
// Preview the model with the material node network
//
#ifndef _HEADLESS
// Create a DirectX renderer
spDirectXRenderer renderer = sg->CreateDirectXRenderer();
// Setup the previewer with the window dimensions
renderer->CreatePreviewer( 512, 512 );
// Send the geometry and the material table into the renderer
// Make sure the material table has materials that have a material
// node network attached to it
renderer->LoadGeometryDataWithMaterialShadingNetwork( scene );
// Create a camera for the renderer
spSceneCamera sceneCamera = sg->CreateSceneCamera();
sceneCamera->SetCustomSphereCameraPath(
1,
0.0f,
0.0f,
360.0f
);
scene->GetRootNode()->AddChild( sceneCamera );
// Select the camera
spSelectionSet cameraSelectionSet = sg->CreateSelectionSet();
cameraSelectionSet->AddItem( sceneCamera->GetNodeGUID() );
rid cameraSelectionSetID = scene->GetSelectionSetTable()->AddSelectionSet( cameraSelectionSet );
// Render the model with the material node network and store the frames
// This will look slightly different from the baked material since our random node shader code is placeholder.
renderer->RenderAlongCameraPathAndStorePics( cameraSelectionSetID, "screenshot", "png" );
#endif
//
///////////////////////////////////
#endif
///////////////////////////////////
// Reduce the geometry and create a mapping image for it to be able to
// perform material casting.
//
// Cast the diffuse channel from the material node network to the new
// material.
//
// Reducer
spReductionProcessor red = sg->CreateReductionProcessor();
red->SetScene( scene );
// Set the Repair Settings.
spRepairSettings repair_settings = red->GetRepairSettings();
// Will take care of holes that are of size 0.2 or less, so small gaps etc are removed.
repair_settings->SetWeldDist( 0.2f );
repair_settings->SetTjuncDist( 0.2f );
// Set the Reduction Settings.
spReductionSettings reduction_settings = red->GetReductionSettings();
// Keep all triangles
reduction_settings->SetTriangleRatio( 1.0 );
// Set the Mapping Image Settings.
spMappingImageSettings mapping_settings = red->GetMappingImageSettings();
// Without this we cannot fetch data from the original geometry
mapping_settings->SetGenerateMappingImage( true );
// Set to generate new texture coordinates.
mapping_settings->SetGenerateTexCoords( true );
// The higher the number, the fewer texture-borders.
mapping_settings->SetParameterizerMaxStretch( 0.2f );
// Buffer space for when texture is mip-mapped, so color values dont blend over.
mapping_settings->SetGutterSpace( 4 );
// Set the new material texture dimensions
mapping_settings->SetUseAutomaticTextureSize( false );
mapping_settings->SetWidth( 512 );
mapping_settings->SetHeight( 512 );
mapping_settings->SetMultisamplingLevel( 2 );
// Run the reduction
red->RunProcessing();
// Mapping image is needed for texture casting.
spMappingImage mapping_image = red->GetMappingImage();
// Create new material table.
spMaterialTable output_materials = sg->CreateMaterialTable();
spTextureTable output_textures = sg->CreateTextureTable();
//Create new material for the table.
spMaterial output_material = sg->CreateMaterial();
output_material->SetName( "output_material" );
//Add the new material to the table
output_materials->AddMaterial( output_material );
// Cast the diffuse texture data using a color caster
spColorCaster cast = sg->CreateColorCaster();
cast->SetColorType( SG_MATERIAL_CHANNEL_DIFFUSE ); //Select the diffuse channel from the original material
cast->SetSourceMaterials( materialTable );
cast->SetMappingImage( mapping_image ); // The mapping image we got from the reduction process.
cast->SetOutputChannels( 3 ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
cast->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB.
cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree as well.
cast->SetOutputFilePath( output_diffuse_filename.c_str() ); // Where the texture map will be saved to file.
cast->RunProcessing(); // Fetch!
// Set the material properties
// Set material to point to the created texture filename.
AddSimplygonTexture( output_material, output_textures, SG_MATERIAL_CHANNEL_DIFFUSE, output_diffuse_filename.c_str() );
spWavefrontExporter objexp = sg->CreateWavefrontExporter();
scene->GetMaterialTable()->Copy( output_materials ); //What is copied here are the references to the materials, not the materials themselves.
scene->GetTextureTable()->Copy( output_textures ); //What is copied here are the references to the materials, not the materials themselves.
objexp->SetExportFilePath( output_geometry_filename.c_str() );
objexp->SetScene( scene );
objexp->RunExport();
//
///////////////////////////////////
}