<< Click to Display Table of Contents >> Navigation: Simplygon 7.1 examples > CustomNodeExample.cpp |
///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: CustomNodeExample.cpp
// Language: C++
//
// Copyright (c) 2015 Donya Labs AB. 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[] )
{
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();
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() )
exit(-1);
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
spString 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
//
// 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
///////////////////////////////////
// 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();
//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->CastMaterials(); // Fetch!
// Set the material properties
// Set the diffuse multiplier for the texture. 1 means it will not differ from original texture
output_material->SetColor(SG_MATERIAL_CHANNEL_DIFFUSE, 1.0f, 1.0f, 1.0f, 1.0f );
// Set material to point to the created texture filename.
output_material->SetTexture( 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.
objexp->SetExportFilePath( output_geometry_filename.c_str() );
objexp->SetScene(scene);
objexp->RunExport();
//
///////////////////////////////////
}