Show / Hide Table of Contents
    ///////////////////////////////////////////////////////////////////////////
    //
    //  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();
    
        //
        ///////////////////////////////////
        }
    
    Back to top Terms of Use | Privacy and cookies | Trademarks | Copyright © 2019 Microsoft