///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: MaterialNodeExample.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 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[] )
{
try
{
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();
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
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->SetTextureLevelName( level_0 );
node_texture_ice->SetTextureLevelName( level_1 );
node_texture_rock->SetTextureLevelName( 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->SetTextureLevelName( level_1 );
node_texture_noise->SetTextureLevelName( 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->SetTextureLevelName( level_1 );
node_texture_grass->SetTextureLevelName( level_1 );
node_texture_dirt->SetTextureName( (assetPath + "stonedirt.jpg").c_str() );
node_texture_grass->SetTextureName( (assetPath + "greengrass.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() )
throw std::exception("Failed to load input file!");
//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 + "stonedirt.jpg";
Texs[1] = assetPath + "greengrass.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
std::cout << "Shader Input Texture Paths:" << std::endl;
for (uint index = 0; index < island_shader->GetShaderInputTexturePathsCount(); index++)
{
rstring str = island_shader->GetShaderInputTexturePath(index);
std::cout << "\t" << str.GetText() << std::endl;
}
// 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
std::cout << "Shader Input UV Sets:" << std::endl;
for (uint index = 0; index < island_shader->GetShaderInputUVSetsCount(); index++)
{
rstring str = island_shader->GetShaderInputUVSet(index);
std::cout << "\t" << str.GetText() << std::endl;
}
// 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
std::cout << "Shader Input Vertex Color Sets:" << std::endl;
for (uint index = 0; index < island_shader->GetShaderInputVertexColorsCount(); index++)
{
rstring str = island_shader->GetShaderInputVertexColor(index);
std::cout << "\t" << str.GetText() << std::endl;
}
// Get the entire shader code for HLSL and GLSL
rstring hlsl_code = island_shader->GetHLSLCode();
rstring glsl_vertex_code = island_shader->GetGLSLVertexCode();
rstring 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.
//
#ifndef _HEADLESS
// 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" );
#endif
//
// 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();
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( 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->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();
//Copy the output material table to the scene's materials
material_table->Clear();
material_table->Copy( output_materials );
texture_table->Clear();
texture_table->Copy( output_textures );
objexp->SetExportFilePath( output_geometry_filename.c_str() );
objexp->SetScene( scene );
objexp->RunExport();
//
// End material node usage example 3
///////////////////////////////////
}