MaterialNodeExample.cpp

<< Click to Display Table of Contents >>

Navigation:  Simplygon 7.1 examples >

MaterialNodeExample.cpp

///////////////////////////////////////////////////////////////////////////
//
//  System:    Simplygon
//  File:      MaterialNodeExample.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 material node network.
//
//  The RunExample() function will load a scene and create a new
//  material. A material node network is created and added to the diffuse
//  channel of the new material.
//
//  The example will go through different usages of the material nodes.
//
//  Material Node usage example:
//
//  1)
//  Export GLSL/HLSL shader code from a material with a material node
//  network.
//
//  2)
//  Preview the model with the material node network.
//
//  3)
//  Reduce the geometry and create a mapping image to perform material
//  casting, baking the materials to a single material.
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
void RunExample( const std::string& readFrom );
std::string assetPath = GetAssetPath() + "MaterialNodeExampleAssets/";
int main( int argc , char* argv[] )
    {
    InitExample();
    // Set global variable. Using Orthonormal method for calculating
    // tangentspace.
    sg->SetGlobalSetting( "DefaultTBNType" , SG_TANGENTSPACEMETHOD_ORTHONORMAL );
    // Run the example code
    RunExample( assetPath + "island.obj" );
    DeinitExample();
    return 0;
    }
// Takes the existing texture coordinates level and copies it into a new texture coordinates level
// the second UV level is multiplied to get a higher frequency ( for tiling textures )
void SetupTexcoordLevels( spGeometryData newGeom )
    {
    // Get the tex_coords
    spRealArray tex_coords_0 = newGeom->GetTexCoords( 0 );
    tex_coords_0->SetAlternativeName("UVzero");
    if( !newGeom->GetTexCoords( 1 ) )
        {
        newGeom->AddTexCoords( 1 );
        spRealArray tex_coords_1 = newGeom->GetTexCoords( 1 ) ;
        // Copy texcoords in level 0 to texcoords level 1 but multiply them to change frequency
        for( uint i = 0 ; i < tex_coords_0->GetItemCount() ; ++i)
            {
            tex_coords_1->SetItem( i, tex_coords_0->GetItem( i ) * 4.0f );
            }       
        tex_coords_1->SetAlternativeName("UVone");
        }
    }
// Compute vertex colors from the model that will be used to interpolate between textures
// and other nodes
void ComputeVertexColors( spGeometryData newGeom )
    {
    float threshold_water = 0.01f; // At what height the water should end
    float threshold_grass = 0.3f; // At what height the grass should end
    float threshold_dirt = 0.5f; // At what height the dirt should end
    spRealArray coords = newGeom->GetCoords();
    spRidArray vertex_ids = newGeom->GetVertexIds();
    real max_height = -REAL_MAX;
    real min_height = REAL_MAX;
    //Find the max and min y-value of the geometry
    for( uint i = 0 ; i < coords->GetTupleCount() ; ++i )
        {
        real current_height = coords->GetItem( i * 3 + 1 );
        if( current_height > max_height )
            max_height = current_height;
        if( current_height < min_height )
            min_height = current_height;
        }
    if( !newGeom->GetColors( 0 ) )
        {
        newGeom->AddColors( 0 );
        }
    spRealArray relative_height_vertex_color = newGeom->GetColors( 0 );
    relative_height_vertex_color->SetAlternativeName("colorA");
    if( !newGeom->GetColors( 1 ) )
        {
        newGeom->AddColors( 1 );
        }
    spRealArray weight_water_vertex_color = newGeom->GetColors( 1 );
    weight_water_vertex_color->SetAlternativeName("colorB");
    if( !newGeom->GetColors( 2 ) )
        {
        newGeom->AddColors( 2 );
        }
    spRealArray weight_grass_vertex_color = newGeom->GetColors( 2 );
    weight_grass_vertex_color->SetAlternativeName("colorC");
    if( !newGeom->GetColors( 3 ) )
        {
        newGeom->AddColors( 3 );
        }
    spRealArray weight_dirt_vertex_color = newGeom->GetColors( 3 );
    weight_dirt_vertex_color->SetAlternativeName("colorD");
    for( uint corner_id = 0 ; corner_id < newGeom->GetTriangleCount() * 3 ; ++corner_id )
        {
        rid v_id = vertex_ids->GetItem( corner_id );
        // Get the y component from the coordinates
        real absolute_height = coords->GetItem( v_id * 3 + 1 );
        // Compute the relative height where the lowest model coordinate will be zero, and the highest will be one
        real relative_height = ( absolute_height - min_height ) / ( max_height - min_height );
        // Compute the blend values between textures
        // For instance, the interpolate_water_grass vertex colors will be:
        // [0 -> 1] between [zero height -> threshold_water height]
        // and then 1 for all heights above the threshold_water height
        real interpolate_water_grass = clamp( relative_height / threshold_water , 0.0f, 1.0f );
        real interpolate_grass_dirt = clamp( ( relative_height - threshold_water ) / ( threshold_grass - threshold_water ), 0.0f, 1.0f );
        real interpolate_dirt_rock = clamp( ( relative_height - threshold_grass ) / ( threshold_dirt - threshold_grass ), 0.0f, 1.0f );
        real relative_height_color[4] = {relative_height, relative_height, relative_height, 1.0};
        real blend_water_color[4] = {interpolate_water_grass, interpolate_water_grass, interpolate_water_grass, 1.0};
        real blend_grass_color[4] = {interpolate_grass_dirt, interpolate_grass_dirt, interpolate_grass_dirt, 1.0};
        real blend_dirt_color[4] = {interpolate_dirt_rock, interpolate_dirt_rock, interpolate_dirt_rock, 1.0};
        // Add the blend values to the vertex color fields
        relative_height_vertex_color->SetTuple( corner_id, relative_height_color );
        weight_water_vertex_color->SetTuple( corner_id, blend_water_color );
        weight_grass_vertex_color->SetTuple( corner_id, blend_grass_color );
        weight_dirt_vertex_color->SetTuple( corner_id, blend_dirt_color );
        }
    }
// This function will use a few nodes to create a network of nodes that will
// resemble a mountain material
spShadingNode CreateMountainNode()
    {
    // Begin by creating texture nodes, setting which UV sets they will use
    // and the paths to their respective textures
    spShadingTextureNode node_texture_icemask = sg->CreateShadingTextureNode();
    spShadingTextureNode node_texture_ice = sg->CreateShadingTextureNode();
    spShadingTextureNode node_texture_rock = sg->CreateShadingTextureNode();
    const char *level_0 = "UVzero";
    const char *level_1 = "UVone";
    node_texture_icemask->SetTexCoordSet( level_0 );
    node_texture_ice->SetTexCoordSet( level_1 );
    node_texture_rock->SetTexCoordSet( level_1 );
    node_texture_icemask->SetTextureName( (assetPath + "icemask.png").c_str() );
    node_texture_ice->SetTextureName( (assetPath + "ice.png").c_str() );
    node_texture_rock->SetTextureName( (assetPath + "rock.jpg").c_str() );
    // Create a vertex color node that will link to the vertex color: "colorA"
    spShadingVertexColorNode node_weights_relative_height = sg->CreateShadingVertexColorNode();
    node_weights_relative_height->SetVertexColorSet("colorA");
    // Blend between ice and rock using the ice mask
    spShadingInterpolateNode node_masked_ice = sg->CreateShadingInterpolateNode();
    node_masked_ice->SetInput( 0, node_texture_rock );
    node_masked_ice->SetInput( 1, node_texture_ice );
    node_masked_ice->SetInput( 2, node_texture_icemask );
    // Offset all the weights of the height slightly using a subtraction
    spShadingSubtractNode node_icyness = sg->CreateShadingSubtractNode();
    node_icyness->SetInput( 0, node_weights_relative_height );
    node_icyness->SetDefaultParameter( 1, 0.1f, 0.1f, 0.1f, 0.0f);
    // Clamp the weights to remain between zero and one after the subtraction
    spShadingClampNode node_icyness_clamp = sg->CreateShadingClampNode();
    node_icyness_clamp->SetInput( 0, node_icyness );
    node_icyness_clamp->SetDefaultParameter( 1, 0.0f, 0.0f, 0.0f, 1.0f);
    node_icyness_clamp->SetDefaultParameter( 2, 1.0f, 1.0f, 1.0f, 1.0f);
    // Blend between the rock texture and the already masked ice
    // based on the height of the mountains
    spShadingInterpolateNode node_interpolate_mountain = sg->CreateShadingInterpolateNode();
    node_interpolate_mountain->SetInput( 0, node_texture_rock );
    node_interpolate_mountain->SetInput( 1, node_masked_ice );
    node_interpolate_mountain->SetInput( 2, node_icyness_clamp );
    return spShadingNode( node_interpolate_mountain );
    }
// This function will use a few nodes to create a network of nodes that will
// resemble a water material
spShadingNode CreateWaterNode()
    {
    // Begin by creating texture nodes, setting which UV sets they will use
    // and the paths to their respective textures
    spShadingTextureNode node_texture_water = sg->CreateShadingTextureNode();
    spShadingTextureNode node_texture_noise = sg->CreateShadingTextureNode();
    const char *level_0 = "UVzero";
    const char *level_1 = "UVone";
    node_texture_water->SetTexCoordSet( level_1 );
    node_texture_noise->SetTexCoordSet( level_1 );
    node_texture_water->SetTextureName( (assetPath + "water.png").c_str() );
    node_texture_noise->SetTextureName( (assetPath + "noise.png").c_str() );
    // Create a color node which will simply be a static color
    spShadingColorNode node_color_water = sg->CreateShadingColorNode();
    node_color_water->SetColor(0.075f, 0.368f, 0.347f, 1.0f);
    // Interpolate between the water texture and the water color
    // using the noise texture
    spShadingInterpolateNode node_interpolate_water_color = sg->CreateShadingInterpolateNode();
    node_interpolate_water_color->SetInput( 0, node_texture_water );
    node_interpolate_water_color->SetInput( 1, node_color_water );
    node_interpolate_water_color->SetInput( 2, node_texture_noise );
    return spShadingNode( node_interpolate_water_color );
    }
// Creates a material node network that uses different kinds of nodes
// to create a material for an island model
// It uses multiple textures with different uv sets
// and blends nodes using vertex colors and textures
spShadingNode CreateIslandMaterialNodeNetwork()
    {
    // Create the mountain node
    spShadingNode node_mountain = CreateMountainNode();
    // Create the water node
    spShadingNode node_water = CreateWaterNode();
    // Define texture nodes, setting which UV sets they will use
    // and the paths to their respective textures
    spShadingTextureNode node_texture_grass = sg->CreateShadingTextureNode();
    spShadingTextureNode node_texture_dirt = sg->CreateShadingTextureNode();
    const char *level_0 = "UVzero";
    const char *level_1 = "UVone";
    node_texture_dirt->SetTexCoordSet( level_1 );
    node_texture_grass->SetTexCoordSet( level_1 );
    node_texture_dirt->SetTextureName( (assetPath + "dirt.jpg").c_str() );
    node_texture_grass->SetTextureName( (assetPath + "grass.jpg").c_str() );
    // Create interpolation nodes
    spShadingInterpolateNode node_interpolate_water_land = sg->CreateShadingInterpolateNode();
    spShadingInterpolateNode node_interpolate_land_0 = sg->CreateShadingInterpolateNode();
    spShadingInterpolateNode node_interpolate_land_1 = sg->CreateShadingInterpolateNode();
    spShadingVertexColorNode node_weights_water_land = sg->CreateShadingVertexColorNode();
    node_weights_water_land->SetVertexColorSet( "colorB" );
    spShadingVertexColorNode node_weights_interpolate_land_0 = sg->CreateShadingVertexColorNode();
    node_weights_interpolate_land_0->SetVertexColorSet( "colorC" );
    spShadingVertexColorNode node_weights_interpolate_land_1 = sg->CreateShadingVertexColorNode();
    node_weights_interpolate_land_1->SetVertexColorSet( "colorD" );
    node_interpolate_land_1->SetInput( 0, node_texture_dirt );
    node_interpolate_land_1->SetInput( 1, node_mountain );
    node_interpolate_land_1->SetInput( 2, node_weights_interpolate_land_1 );
    node_interpolate_land_0->SetInput( 0, node_texture_grass );
    node_interpolate_land_0->SetInput( 1, node_interpolate_land_1 );
    node_interpolate_land_0->SetInput( 2, node_weights_interpolate_land_0 );
    node_interpolate_water_land->SetInput( 0, node_water );
    node_interpolate_water_land->SetInput( 1, node_interpolate_land_0 );
    node_interpolate_water_land->SetInput( 2, node_weights_water_land );
    return spShadingNode( node_interpolate_water_land );
    }
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);
    //Get the loaded scene
    spScene scene = objReader->GetScene();
    //Find all ISceneMeshes in the scene and compute some vertex colors and texcoords for the geometries
    int selectionSetAllSceneMeshesID = scene->SelectNodes("ISceneMesh");
    spSelectionSet selectionSetAllSceneMeshes = scene->GetSelectionSetTable()->GetSelectionSet(selectionSetAllSceneMeshesID);
    for(uint i = 0 ; i < selectionSetAllSceneMeshes->GetItemCount() ; ++i)
        {
        //Get the node from the selection set and cast it to an ISceneMesh
        spSceneMesh sceneMesh = Cast<ISceneMesh>(scene->GetNodeByGUID(selectionSetAllSceneMeshes->GetItem(i)));
        //Get the goemetry from the scene mesh
        spGeometryData geom = sceneMesh->GetGeometry();
        // Compute a few sets of vertex colors based on the height of the model
        // the colors will be used to blend between textures
        ComputeVertexColors( geom );
        // Setup UV sets
        SetupTexcoordLevels( geom );
        }
    //Get the scene's material table and clear it
    spMaterialTable material_table = scene->GetMaterialTable();
    material_table->Clear();
    spMaterial island_material = sg->CreateMaterial();
    material_table->AddMaterial( island_material );
    spTextureTable texture_table = scene->GetTextureTable();
    std::string Texs[7];
    Texs[0] = assetPath + "dirt.jpg";
    Texs[1] = assetPath + "grass.jpg";
    Texs[2] = assetPath + "water.png";
    Texs[3] = assetPath + "noise.png";
    Texs[4] = assetPath + "icemask.png";
    Texs[5] = assetPath + "ice.png";
    Texs[6] = assetPath + "rock.jpg";
    for( uint i=0; i<7; ++i )
        {
        spTexture texture = sg->CreateTexture();
        texture->SetName( Texs[i].c_str() );
        texture->SetFilePath( Texs[i].c_str() );
        texture_table->AddTexture(texture);
        }
    // Create a material node network consisting of an arbitrary amount of shading nodes
    spShadingNode exit_node = CreateIslandMaterialNodeNetwork();
    // Assign the exit node of the material node network to the diffuse channel in the island material
    island_material->SetShadingNetwork( SG_MATERIAL_CHANNEL_DIFFUSE, exit_node );
    ///////////////////////////////////
    // Material Node usage example 1
    //
    // Export GLSL/HLSL shader code from a material with a material node
    // network
    //
    // Once the material has node networks assigned to it,
    // we can generate a shader for it both in GLSL and HLSL.
    //
    // We create a shader generator object
    spShaderGenerator island_shader = sg->CreateShaderGenerator();
    // We add the material to it
    island_shader->SetMaterial( island_material );
    // We tell the shader generator object to traverse the material node tree
    // and gather the information needed to create a shader from the nodes
    island_shader->GenerateShaderData();
    // Once the GenerateShaderData() has been called, the shader generator object
    // will contain the information needed to setup the renderer for the
    // GLSL or HLSL shader
    // Get all the texture paths that should be loaded and sent to the shader
    // the textures should be uploaded to the shader at the texture slot
    // corresponding to its position in the collection
    spStringArray texture_paths_collection = island_shader->GetShaderInputTexturePaths();
    // Get the UV sets that should be uploaded to the shader
    // the slot in the array corresponds to the slot in the shader
    // the values in the array corresponds to the tex coord level being used
    spStringArray UV_set_collection = island_shader->GetShaderInputUVSets();
    // Get the vertex colors that should be uploaded to the shader
    // the slot in the array corresponds to the slot in the shader
    // the values in the array corresponds to the vertex color field being used
    spStringArray vertex_color_collection = island_shader->GetShaderInputVertexColors();
    // Get the entire shader code for HLSL and GLSL
    spString hlsl_code = island_shader->GetHLSLCode();
    spString glsl_vertex_code = island_shader->GetGLSLVertexCode();
    spString glsl_fragment_code = island_shader->GetGLSLFragmentCode();
    //Store the shaders to file in the current folder
    FILE * file_hlsl = fopen( "shader_hlsl.txt", "wt" );
    fprintf( file_hlsl, "%s", hlsl_code->GetText() );
    fclose( file_hlsl );
    FILE * file_glsl_vertex = fopen( "shader_glsl_vertex.txt", "wt" );
    fprintf( file_glsl_vertex, "%s", glsl_vertex_code->GetText() );
    fclose( file_glsl_vertex );
    FILE * file_glsl_fragment = fopen( "shader_glsl_fragment.txt", "wt" );
    fprintf( file_glsl_fragment, "%s", glsl_fragment_code->GetText() );
    fclose( file_glsl_fragment );
    //
    // End material node usage example 1
    ///////////////////////////////////
#ifdef _WIN32
    ///////////////////////////////////
    // Material Node usage example 2
    //
    // Preview the model with the material node network
    //
    // This example will render the model using DirectX
    // A shader will be generated like above (but automatically)
    // And the renderer will automatically upload the textures, uv sets and
    // vertex colors channels to a shader at the proper slots.
    //
    // Then we will render the model with the shader and store the frames
    // to file.
    //
    // Create a DirectX renderer
    spDirectXRenderer renderer = sg->CreateDirectXRenderer();
    // Setup the previewer with the window dimensions
    renderer->CreatePreviewer( 2048, 2048 );
    //Load the scene into the renderer
    renderer->LoadGeometryDataWithMaterialShadingNetwork( scene );
    // Create a camera for the renderer
    spSceneCamera sceneCamera = sg->CreateSceneCamera();
    //Generate a few camera views above the scene
    sceneCamera->SetCustomSphereCameraPath(
        1, //Fidelity
        0.0f, //pitch
        0.0f, //yaw
        100.0f //coverage
        );
    scene->GetRootNode()->AddChild(sceneCamera);
    //Create a selection set containing the camera views
    spSelectionSet cameraSelectionSet = sg->CreateSelectionSet();
    cameraSelectionSet->AddItem(sceneCamera->GetNodeGUID());
    //Add the selection set to the scene's selection set table
    rid cameraSelectionSetID = scene->GetSelectionSetTable()->AddSelectionSet(cameraSelectionSet);
    // Render the model with the material node network and store the frames
    renderer->RenderAlongCameraPathAndStorePics( cameraSelectionSetID, "screenshot", "png" );
    //
    // End material node usage example 2
    ///////////////////////////////////
#endif
   
    ///////////////////////////////////
    // Material Node usage example 3
    //
    // 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.
    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();
    // Set the reduction ratios to 0.9, meaning that we keep 90% of the triangles
    reduction_settings->SetTriangleRatio(0.9f);
    // 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.5f );
    // 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( 2048 );
    mapping_settings->SetHeight( 2048 );
    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( scene->GetMaterialTable() );
    cast->SetSourceTextures( scene->GetTextureTable() );
    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();
    //Copy the output material table to the scene's materials
    material_table->Clear();
    material_table->Copy(output_materials);
    objexp->SetExportFilePath( output_geometry_filename.c_str() );
    objexp->SetScene(scene);
    objexp->RunExport();
    //
    // End material node usage example 3
    ///////////////////////////////////
    }