Visibility culling and weighting with sphere and hemisphere

Written by Jesper Tingvall, Product Expert, Simplygon

Disclaimer: The code in this post is written using version 10.4.117.0 of Simplygon and 3ds Max 2022. If you encounter this post at a later stage, some of the API calls might have changed. However, the core concepts should still remain valid.

Introduction

This blog covers visibility sphere and hemisphere guided optimization. We'll cover visibility culling in aggregation and visibility weights in reduction.

Prerequisites

This example will use Simplygon's 3ds Max plugin, but the same concepts can be applied to all other integrations of the Simplygon API.

Aggregation with visibility culling sphere

Visibility culling can be used with the aggregation pipeline. A good use case for this is to optimize the LOD0 model by removing any surface not visible from the players point of view. In this example we have a gothic cabinet filled with goblets.

Cabinet filled with goblets

There are some goblets hiding inside it that we can see if we look at the wireframe.

Cabinet filled with goblets, wireframe

As you can see from the images, the goblets in the lower part of the cabinet will never be visible. On the top only some are partially visible. We are going to use culling to remove all invisible surfaces.

We start by adding an aggregation pipeline (Template → Advanced → Aggregation). Here we are going to set the followings settings.

  • With VisibilityCameraMode we can specify what shape our visibility geometry should be. We set this to Sphere.
  • Setting CullOccludedGeometry to true will remove all triangles not visible from our camera sphere.
  • With SphereFidelity we can specify the number of cameras to use in the camera sphere. If we see that triangles that ought to be visible in extreme angles are removed increasing this can help. Enabling ConservativeMode can also address this issue.

Aggregation pipeline

Scripted aggregation with visibility culling

It is also possible to do aggregation with visibility culling with Python scripting in 3ds Max. Here is a script snipped that creates an aggregation pipeline with same settings as above.

def aggregate_with_visibility_culling(sg: Simplygon.ISimplygon, camera_fidelity: int, scene: Simplygon.spScene):
    pipeline = sg.CreateAggregationPipeline()

    visibility_settings = pipeline.GetVisibilitySettings()

    visibility_settings.SetCullOccludedGeometry(True)

    # Sphere settings
    visibility_settings.SetVisibilityCameraMode(Simplygon.EVisibilityCameraMode_Sphere)
    visibility_settings.SetSphereFidelity(camera_fidelity)

    pipeline.RunScene(scene, Simplygon.EPipelineRunMode_RunInThisProcess)

Result

After optimization we get this output. We can see that all goblets in the lower parts are removed, since those are not visible from outside at all. On the top parts only the visible parts of the goblets are kept.

Original
Optimized

Reduction with visibility weighting hemisphere

We will now showcase how visibility can be used to guide a reduction pipeline. Our intended use case is that we want to generate LOD levels with reducer for a top down focused game. We expect that when we are looking at the assets from far then our camera is viewing them from top. So we want to keep more details visible from above.

Cabinet filled with goblets

We start by adding an reduction pipeline (Template → Advanced → Reduction). Here we are going to set the followings settings.

  • By setting UseVisibilityWeightsInReducer to true our reducer will use visibility information during reduction.
  • With VisibilityCameraMode we can specify what shape our visibility geometry should be. We set this to Hemisphere.
  • With Hemisphere we also need to specify HemisphereUpVector to match our coordinate system.
  • The coverage angle is set by HemisphereCoverageAngle. We set this to 90 degrees based on that we expect the camera to max be tilted 45 degrees in any direction.
  • CullOccludedGeometry will be set to false. We do not want to remove invisible geometry, just make it spend less triangles there.

Reduction settings

Scripted reduction with visibility weights

It is also possible to use reduction with visibility weights through scripting.

def reduce_with_visibility_culling_weights(sg: Simplygon.ISimplygon, reduction_ratio: float, coverage_angle: float, camera_fidelity: int, scene: Simplygon.spScene):
    pipeline = sg.CreateReductionPipeline()

    pipeline.GetReductionSettings().SetReductionTargetTriangleRatio(reduction_ratio)

    visibility_settings = pipeline.GetVisibilitySettings()
    visibility_settings.SetUseVisibilityWeightsInReducer(True)

    # Hemisphere settings
    visibility_settings.SetVisibilityCameraMode(Simplygon.EVisibilityCameraMode_Hemisphere)
    visibility_settings.SetHemisphereUpVector(Simplygon.EUpVector_PositiveZ) # Up vector in 3ds Max
    visibility_settings.SetHemisphereCoverageAngle(coverage_angle)
    visibility_settings.SetHemisphereFidelity(camera_fidelity)

    pipeline.RunScene(scene, Simplygon.EPipelineRunMode_RunInThisProcess)

Result

Here is our 50% reduced asset after optimization.

Original
50% reduction with visibility culling

Let us compare to a standard 50% reduction. We can see that the top surfaces of our asset has more triangles, while the inside area and bottom that we are not seeing from above has less. This allows us to spend our triangle budget more wise we we know that the assets will only be seen from certain directions.

50% reduction
50% reduction with visibility weights

Complete script

# Copyright (c) Microsoft Corporation. 
# Licensed under the MIT license. 
 
from simplygon10 import simplygon_loader
from simplygon10 import Simplygon
from pymxs import runtime as rt

export_path = 'C:/Temp/ExportedScene.sb'
processed_path = 'C:/Temp/ProcessedScene.sb'


def aggregate_with_visibility_culling(sg: Simplygon.ISimplygon, camera_fidelity: int, scene: Simplygon.spScene):
    pipeline = sg.CreateAggregationPipeline()

    visibility_settings = pipeline.GetVisibilitySettings()

    visibility_settings.SetCullOccludedGeometry(True)

    # Sphere settings
    visibility_settings.SetVisibilityCameraMode(Simplygon.EVisibilityCameraMode_Sphere)
    visibility_settings.SetSphereFidelity(camera_fidelity)

    pipeline.RunScene(scene, Simplygon.EPipelineRunMode_RunInThisProcess)
    

def reduce_with_visibility_culling_weights(sg: Simplygon.ISimplygon, reduction_ratio: float, coverage_angle: float, camera_fidelity: int, scene: Simplygon.spScene):
    pipeline = sg.CreateReductionPipeline()

    pipeline.GetReductionSettings().SetReductionTargetTriangleRatio(reduction_ratio)

    visibility_settings = pipeline.GetVisibilitySettings()
    visibility_settings.SetUseVisibilityWeightsInReducer(True)

    # Hemisphere settings
    visibility_settings.SetVisibilityCameraMode(Simplygon.EVisibilityCameraMode_Hemisphere)
    visibility_settings.SetHemisphereUpVector(Simplygon.EUpVector_PositiveZ) # Up vector in 3ds Max
    visibility_settings.SetHemisphereCoverageAngle(coverage_angle)
    visibility_settings.SetHemisphereFidelity(camera_fidelity)

    pipeline.RunScene(scene, Simplygon.EPipelineRunMode_RunInThisProcess)
    

def export_selection_from_max(sg: Simplygon.ISimplygon, temp_path: str) -> Simplygon.spScene:
    if not rt.sgsdk_ExportToFile(temp_path, False):
        return None

    scene_importer = sg.CreateSceneImporter()
    scene_importer.SetImportFilePath(temp_path)

    if scene_importer.Run() == Simplygon.EErrorCodes_NoError:
        return scene_importer.GetScene()
    return None


def import_to_max(sg: Simplygon.ISimplygon, scene: Simplygon.spScene, temp_path: str):
    scene_exporter = sg.CreateSceneExporter()
    scene_exporter.SetExportFilePath(temp_path)
    scene_exporter.SetScene(scene)
    scene_exporter.Run()
    rt.sgsdk_ImportFromFile(processed_path, True, True, True)


def main():
    sg = simplygon_loader.init_simplygon()

    sg.SetGlobalDefaultTangentCalculatorTypeSetting(Simplygon.ETangentSpaceMethod_Autodesk3dsMax)
    scene = export_selection_from_max(sg, export_path)
	
    # aggregate_with_visibility_culling(sg, 6, scene)
    reduce_with_visibility_culling_weights(sg, 0.5, 90, 6, scene)

    import_to_max(sg, scene, processed_path)
    del sg

if __name__== "__main__":
    main()
⇐ Back to all posts

Request 30-days free evaluation license

*
*
*
*
Industry
*

Request 30-days free evaluation license

*
*
*
*
Industry
*