# Normal repairer
The normal repairer can be used to either repair invalid normals, or recalculate new normals for an entire scene. It is usable through the NormalCalculationSettings on the ReductionProcessor, the HardEdgeAngle setting on the RemeshingProcessor, or with the standalone NormalRepairer tool.
New normals are generated for the geometry based on the angle between the triangles. It can either be set to only fix the broken normals or be set to generate new normals for the entire model.
If two triangles' angle difference is larger than the hard edge angle cutoff value then there will be a distinct hard shading border between the triangles. If the angle is below the threshold value there will be a smooth continuous normal instead.
Here we show the per fragment tangent space normals for a model before and after we compute two sets of new normals with different hard edge angles.
Here we look at only the geometry normals after a reduction with normal recalculation at different hard edge angles.
# Usage
The Normal repairer can be used through the following entrypoints:
# Simplygon API example
This example shows how to use the Reduction processor with normal calculation settings.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include <string>
#include <stdlib.h>
#include <filesystem>
#include <future>
#include "SimplygonLoader.h"
void RunReduction(Simplygon::ISimplygon* sg)
{
Simplygon::spSceneImporter sgSceneImporter = sg->CreateSceneImporter();
sgSceneImporter->SetImportFilePath( "../Assets/SimplygonMan/SimplygonMan.obj" );
if(!sgSceneImporter->RunImport())
throw std::exception("Failed to load SimplygonMan/SimplygonMan.obj.");
Simplygon::spScene sgScene = sgSceneImporter->GetScene();
// Create the reduction processor.
Simplygon::spReductionProcessor sgReductionProcessor = sg->CreateReductionProcessor();
sgReductionProcessor->SetScene( sgScene );
Simplygon::spReductionSettings sgReductionSettings = sgReductionProcessor->GetReductionSettings();
Simplygon::spNormalCalculationSettings sgNormalCalculationSettings = sgReductionProcessor->GetNormalCalculationSettings();
// Set reduction target to triangle ratio with a ratio of 50%.
sgReductionSettings->SetReductionTargets( Simplygon::EStopCondition::All, true, false, false, false );
sgReductionSettings->SetReductionTargetTriangleRatio( 0.5f );
// The angle in degrees determing the normal smoothness.
sgNormalCalculationSettings->SetHardEdgeAngle( 75 );
// Reorthogonalize the tangentspace after the reduction.
sgNormalCalculationSettings->SetReorthogonalizeTangentSpace( true );
// Repair invalid normals.
sgNormalCalculationSettings->SetRepairInvalidNormals( true );
// Don't generate new normals. However invalid normals will still be repaired.
sgNormalCalculationSettings->SetReplaceNormals( false );
// Don't generate new tangents and bitangents.
sgNormalCalculationSettings->SetReplaceTangents( false );
// Scale the vertex normal based on the triangle area.
sgNormalCalculationSettings->SetScaleByAngle( false );
sgNormalCalculationSettings->SetScaleByArea( true );
// Don't snap the normal to flat surfaces.
sgNormalCalculationSettings->SetSnapNormalsToFlatSurfaces( false );
// Start the reduction process.
sgReductionProcessor->RunProcessing();
Simplygon::spSceneExporter sgSceneExporter = sg->CreateSceneExporter();
sgSceneExporter->SetScene(sgScene);
sgSceneExporter->SetExportFilePath( "ReductionOutput.fbx" );
if(!sgSceneExporter->RunExport())
throw std::exception("Failed to save ReductionOutput.fbx.");
}
void main()
{
Simplygon::ISimplygon* sg = NULL;
Simplygon::EErrorCodes initval = Simplygon::Initialize( &sg );
if( initval != Simplygon::EErrorCodes::NoError )
{
return;
}
RunReduction(sg);
Simplygon::Deinitialize(sg);
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
using System;
using System.IO;
using System.Threading.Tasks;
public class Program
{
static void RunReduction(Simplygon.ISimplygon sg)
{
using (Simplygon.spSceneImporter sgSceneImporter = sg.CreateSceneImporter())
{
sgSceneImporter.SetImportFilePath( "../Assets/SimplygonMan/SimplygonMan.obj" );
if(!sgSceneImporter.RunImport())
throw new System.Exception("Failed to load SimplygonMan/SimplygonMan.obj.");
Simplygon.spScene sgScene = sgSceneImporter.GetScene();
// Create the reduction processor.
using (Simplygon.spReductionProcessor sgReductionProcessor = sg.CreateReductionProcessor())
{
sgReductionProcessor.SetScene( sgScene );
using (Simplygon.spReductionSettings sgReductionSettings = sgReductionProcessor.GetReductionSettings())
using (Simplygon.spNormalCalculationSettings sgNormalCalculationSettings = sgReductionProcessor.GetNormalCalculationSettings())
{
// Set reduction target to triangle ratio with a ratio of 50%.
sgReductionSettings.SetReductionTargets( Simplygon.EStopCondition.All, true, false, false, false );
sgReductionSettings.SetReductionTargetTriangleRatio( 0.5f );
// The angle in degrees determing the normal smoothness.
sgNormalCalculationSettings.SetHardEdgeAngle( 75 );
// Reorthogonalize the tangentspace after the reduction.
sgNormalCalculationSettings.SetReorthogonalizeTangentSpace( true );
// Repair invalid normals.
sgNormalCalculationSettings.SetRepairInvalidNormals( true );
// Don't generate new normals. However invalid normals will still be repaired.
sgNormalCalculationSettings.SetReplaceNormals( false );
// Don't generate new tangents and bitangents.
sgNormalCalculationSettings.SetReplaceTangents( false );
// Scale the vertex normal based on the triangle area.
sgNormalCalculationSettings.SetScaleByAngle( false );
sgNormalCalculationSettings.SetScaleByArea( true );
// Don't snap the normal to flat surfaces.
sgNormalCalculationSettings.SetSnapNormalsToFlatSurfaces( false );
}
// Start the reduction process.
sgReductionProcessor.RunProcessing();
}
using (Simplygon.spSceneExporter sgSceneExporter = sg.CreateSceneExporter())
{
sgSceneExporter.SetScene(sgScene);
sgSceneExporter.SetExportFilePath( "ReductionOutput.fbx" );
if(!sgSceneExporter.RunExport())
throw new System.Exception("Failed to save ReductionOutput.fbx.");
}
}
}
static void Main(string[] args)
{
using (var sg = Simplygon.Loader.InitSimplygon(out var errorCode, out var errorMessage))
{
if (errorCode != Simplygon.EErrorCodes.NoError)
return;
RunReduction(sg);
}
}
}
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
import math
import os
import sys
import glob
import gc
import threading
from pathlib import Path
from simplygon import simplygon_loader
from simplygon import Simplygon
def RunReduction(sg: Simplygon.ISimplygon):
sgSceneImporter = sg.CreateSceneImporter()
sgSceneImporter.SetImportFilePath( '../Assets/SimplygonMan/SimplygonMan.obj' )
if not sgSceneImporter.RunImport():
raise Exception('Failed to load SimplygonMan/SimplygonMan.obj.')
sgScene = sgSceneImporter.GetScene()
# Create the reduction processor.
sgReductionProcessor = sg.CreateReductionProcessor()
sgReductionProcessor.SetScene( sgScene )
sgReductionSettings = sgReductionProcessor.GetReductionSettings()
sgNormalCalculationSettings = sgReductionProcessor.GetNormalCalculationSettings()
# Set reduction target to triangle ratio with a ratio of 50%.
sgReductionSettings.SetReductionTargets( Simplygon.EStopCondition_All, True, False, False, False )
sgReductionSettings.SetReductionTargetTriangleRatio( 0.5 )
# The angle in degrees determing the normal smoothness.
sgNormalCalculationSettings.SetHardEdgeAngle( 75 )
# Reorthogonalize the tangentspace after the reduction.
sgNormalCalculationSettings.SetReorthogonalizeTangentSpace( True )
# Repair invalid normals.
sgNormalCalculationSettings.SetRepairInvalidNormals( True )
# Don't generate new normals. However invalid normals will still be repaired.
sgNormalCalculationSettings.SetReplaceNormals( False )
# Don't generate new tangents and bitangents.
sgNormalCalculationSettings.SetReplaceTangents( False )
# Scale the vertex normal based on the triangle area.
sgNormalCalculationSettings.SetScaleByAngle( False )
sgNormalCalculationSettings.SetScaleByArea( True )
# Don't snap the normal to flat surfaces.
sgNormalCalculationSettings.SetSnapNormalsToFlatSurfaces( False )
# Start the reduction process.
sgReductionProcessor.RunProcessing()
sgSceneExporter = sg.CreateSceneExporter()
sgSceneExporter.SetScene(sgScene)
sgSceneExporter.SetExportFilePath( 'ReductionOutput.fbx' )
if not sgSceneExporter.RunExport():
raise Exception('Failed to save ReductionOutput.fbx.')
if __name__ == '__main__':
sg = simplygon_loader.init_simplygon()
if sg is not None:
RunReduction(sg)
sg = None
gc.collect()