Randomize materials and textures based on a probability / Extract path to material and texture from USD

Hi all,

I want to change the material and texture of an object by defining a probability for each frame. In 80% of the frames I want the texture/material to remain the default value. In the remaining 20% I want to set a random material/texture. My code to set random materials/textures already works, but how can I define a probability that the materials/textures will not be changed and the default value will be kept?

If I would be able to extract the materials and textures from the usd files that I use (e.g. /Isaac/Props/YCB/Axis_Aligned/002_master_chef_can.usd), I could use rep.distribution.choice and add the default material/texture with a 80% probability. But how can I extract the default values as a path of the material (e.g. /NVIDIA/Materials/Base/Architecture/Roof_Tiles.mdl)?

Code to spawn and simulate the objects:

   # Spawn distractors falling
    for i in range(number_distractors):
        distractor_usd_path = distractor_usds[i]
        distractor_prim_name = f"distractor_{i}"
        distractor_prim = prims.create_prim(
            prim_path=f"/World/distractors/{distractor_prim_name}",
            usd_path=prefix_with_isaac_asset_server(distractor_usd_path),
        )

        # Get the next spawn height for the box
        spawn_height += bb_cache.ComputeLocalBound(distractor_prim).GetRange().GetSize()[2] * 1.1

        # Wrap the distractor prim into a rigid prim to be able to simulate it
        distractor_rigid_prim = RigidPrim(
            prim_path=str(distractor_prim.GetPrimPath()),
            name=distractor_prim_name,
            position=(Gf.Vec3d(random.uniform(spawn_location[0] + CONFIG_DIST_spawn_displacement[0][0], spawn_location[0] + CONFIG_DIST_spawn_displacement[1][0]), 
                               random.uniform(spawn_location[1] + CONFIG_DIST_spawn_displacement[0][1], spawn_location[1] + CONFIG_DIST_spawn_displacement[1][1]),
                               random.uniform(spawn_location[2] + CONFIG_DIST_spawn_displacement[0][2], spawn_location[2] + CONFIG_DIST_spawn_displacement[1][2]))),
            orientation=euler_angles_to_quat([random.uniform(-math.pi, math.pi), random.uniform(-math.pi, math.pi), random.uniform(-math.pi, math.pi)]),

            scale=np.array(distractor_scales[i]),
        )

        # Make sure physics are enabled on the rigid prim
        distractor_rigid_prim.enable_rigid_body_physics()

        # Enable colission
        distractor=GeometryPrim(
            prim_path=str(distractor_prim.GetPrimPath()),
            name=f"distractor_geom_prim_{i}",
            collision=True,
        )
        distractor.set_collision_approximation("convexHull")

        # Register rigid prim with the scene
        world.scene.add(distractor)


    # Reset the world to handle the physics of the newly created rigid prims
    world.reset()

    # Simulate the world for the given number of steps
    for i in range(max_sim_steps):
        world.step(render=False)

Code to randomize the materials/textures:

# Randomization of material and texture of distractor objects
def randomize_distractor_obj():
    distractors = rep.get.prims(path_pattern="/World/distractors/")

    def rand_distractor_obj():
        with distractors:
            rep.randomizer.texture(rep.distribution.choice([prefix_with_isaac_asset_server(item) for item in textures]))
            rep.randomizer.materials(rep.distribution.choice([prefix_with_isaac_asset_server(item) for item in materials]))
            rep.randomizer.color(colors=rep.distribution.uniform((0, 0, 0), (1, 1, 1)))

        return distractors.node
    rep.randomizer.register(rand_distractor_obj)

Any help is appreciated!
Lukas

Does anyone have any idea how I can solve the problem?

I’ll consult the replicator developers to see if they have some ideas. In the interim, a thought.

Its likely to be difficult to keep mappings of the original textures and mats, and use those with a distribution, would it be easier duplicating the materials so an original copy is kept around, and swapping between the unmodified and the modified?

Hi @lukas-bergs , here’s some more info that might help you.

We don’t have a good way to split executed logic based on probabilities in the traditional replicator workflow (yet), but you can use step async to do this.

I’ve put together an example here that creates two materials, and applies the material to the cone, based on a random float each frame.

import omni
import asyncio
from pxr import Gf, Usd, UsdGeom, UsdShade, Sdf
import omni.replicator.core as rep
import random

def change_mat_color(stage, shader_prim_path, color):
    shader_prim = stage.GetPrimAtPath(shader_prim_path)
    if not shader_prim.GetAttribute("inputs:diffuse_color_constant").IsValid():
        shader_prim.CreateAttribute("inputs:diffuse_color_constant", Sdf.ValueTypeNames.Color3f, custom=True).Set((0.0, 0.0, 0.0))

    # Set the diffuse color to the input color
    shader_prim.GetAttribute('inputs:diffuse_color_constant').Set(color)

async def run():

    # Get the stage set some details
    stage = omni.usd.get_context().get_stage()
    rep.settings.set_stage_up_axis("Y")
    rep.settings.set_stage_meters_per_unit(0.01)
    
    # These are done with a replicator graph, and only ever run ONCE on start
    distance_light = rep.create.light(rotation=(400,-23,-94), intensity=10000, temperature=6500, light_type="distant")
    camera = rep.create.camera(position=(1347,825,1440), rotation=(-20,43,0), focus_distance=200,f_stop=8)
    rep_cone = rep.create.cone(position=(0,100,0), scale=2)
    rep_floor = rep.create.cube(position=(0,0,0), scale=(10,0.1,10))
    rep_wall1 = rep.create.cube(position=(-450,250,0), scale=(1,5,10))
    rep_wall2 = rep.create.cube(position=(0,250,-450), scale=(10,5,1))
    
    # Material setup - Make 2 materials, colored red and blue
    #https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/usd/materials/create-mdl-material.html
    red_mat_path = "/World/Looks/red_mat"
    red_shader_path = "/World/Looks/red_mat/Shader"
    blue_mat_path = "/World/Looks/blue_mat"
    blue_shader_path = "/World/Looks/blue_mat/Shader"
    success, blue2_mat_path = omni.kit.commands.execute('CreateMdlMaterialPrimCommand', mtl_url='OmniPBR.mdl', mtl_name='OmniPBR', mtl_path=blue_mat_path)
    success, red2_mat_path = omni.kit.commands.execute('CreateMdlMaterialPrimCommand', mtl_url='OmniPBR.mdl', mtl_name='OmniPBR', mtl_path=red_mat_path)
    
    # My function to make changing colors easy
    change_mat_color(stage, blue_shader_path, (1.0,0.0,0.0))
    change_mat_color(stage, red_shader_path, (0.0,0.0,1.0))
    
    # Set up render products and annotators
    render_product  = rep.create.render_product(camera, (1920, 1080))
    
    # Initialize and attach writer
    writer = rep.WriterRegistry.get("BasicWriter")
    writer.initialize(output_dir="_stepasync_change_mat", rgb=True, bounding_box_2d_tight=True, semantic_segmentation=True)
    # On windows, outputs default to: C:\Users\yourname\omni.replicator_out\
    writer.attach([render_product], trigger = None)
    
    # Convert the replicator cone to a usd prim so it can be referenced by UsdShade
    cone_prim = stage.GetPrimAtPath(str(rep_cone.get_output('prims')[0]))
    
    # Material prims for the original mat (blue) and the "edited" one
    blue_mat_prim = stage.GetPrimAtPath(blue_mat_path)
    red_mat_prim = stage.GetPrimAtPath(red_mat_path)

    for frame_id in range(20):
        # Make the cone use the blue material 20% of the time
        if random.uniform(0.0, 1.0) < 0.8:
            UsdShade.MaterialBindingAPI(cone_prim).Bind(UsdShade.Material(blue_mat_prim), UsdShade.Tokens.weakerThanDescendants)
        else:
            UsdShade.MaterialBindingAPI(cone_prim).Bind(UsdShade.Material(red_mat_prim), UsdShade.Tokens.weakerThanDescendants)
        
        await rep.orchestrator.step_async(rt_subframes=20)
        
        # Trigger the writer
        writer.schedule_write()
        
    rep.orchestrator.stop()

asyncio.ensure_future(run())

This will output something like this image:

I hope this helps!