• Randomization

Advanced Distribution of Randomization

By: SKY ENGINE AI
scroll down ↓to find out morerandomization-advanced-distribution_6_resourcesTutorial

Randomization - Advanced Distribution

In this section you will get to know various Distribution tricks.

Agenda:

  • Passing Distributions into a Transform Provider
  • Practical control of Gaussian params
  • Modifying other existing Transform Providers
  • Custom plot-based Distributions
  • Random Gaussian use case
  • Custom Distribution trickery

Scene setup

Let's use custom scene composer to set up the scene.

    from skyrenderer.cases.utils import RandomizationAdvancedDistributionSceneComposer
    scene_composer = RandomizationAdvancedDistributionSceneComposer(antialiasing_level=64)
    scene_composer.setup_scene()
    scene_composer.visualize()
    rc = scene_composer.renderer_context
randomization-advanced-distribution_1_resourcesTutorial
2025-03-27 12:12:07,005 | skyrenderer.scene.renderer_context |  INFO: Root paths:
- root path: /home/skyengine/anaconda/lib/python3.6/site-packages/skyrenderer
- assets path: /dli/mount/assets
- config path: /home/skyengine/anaconda/lib/python3.6/site-packages/skyrenderer/config
- gpu sources path: /home/skyengine/anaconda/lib/python3.6/site-packages/skyrenderer/optix_sources/sources
- cache path: /dli/mount/cache
- ptx cache path: compiled_ptx/ptx
- ocio path: ocio_configs/aces_1.2/config.ocio

2025-03-27 12:12:07,204 | skyrenderer.basic_types.provider.unit_providers.hdr_texture_provider |  WARNING: Parameter light_adapt provided in HDR json is not supported

2025-03-27 12:12:07,205 | skyrenderer.basic_types.provider.unit_providers.hdr_texture_provider |  WARNING: Parameter color_adapt provided in HDR json is not supported

2025-03-27 12:12:07,206 | skyrenderer.scene.renderer_context |  WARNING: Light with light_id=back_LIGHT_NUL already exists in the scene and will be replace with a new one.There can only be a single light for a single node.

2025-03-27 12:12:07,207 | skyrenderer.scene.renderer_context |  WARNING: Light with light_id=front_LIGHT_NUL already exists in the scene and will be replace with a new one.There can only be a single light for a single node.

2025-03-27 12:12:07,207 | skyrenderer.scene.renderer_context |  WARNING: Light with light_id=moon_LIGHT_NUL already exists in the scene and will be replace with a new one.There can only be a single light for a single node.

2025-03-27 12:12:10,212 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.00 seconds

2025-03-27 12:12:10,514 | skyrenderer.utils.time_measurement |  INFO: Setup time: 299 ms

2025-03-27 12:12:11,540 | skyrenderer.utils.time_measurement |  INFO: Context update time: 1.02 seconds

2025-03-27 12:12:20,035 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 0 ms

2025-03-27 12:12:20,036 | skyrenderer.utils.time_measurement |  INFO: Render time: 8.50 seconds

In our initial scene we can see an alley between two office buildings. There is a single firefly floating in the
middle (that lone blue pixel, 1/3 from the bottom and 1/3 from the right edge). His name is Fred. He has the
following coordinates in meters:

    cx = -14.401  # negative x is deeper into the alley, front face of the left building is at -4.5
    cy = 6.0256  # positive y is vertically upwards, roof of the left building is at +11
    cz = 0.98872  # z is horizontal between the buildings, left facade is at +3.5, right at -1.5
    center = (cx, cy, cz)
    fred = rc.layout().get_node("firefly_GEO_NUL")
    fred_geometry = "firefly_GEO"

Passing Distributions into a Transform Provider

Fred has organized a meetup for his friends in this particular spot because there are many nice reflective windows around,
which allow everyone to show off their evening glow.

Fred's friends have arrived:

    fred.n_instances = 500

They all want to greet Fred, so they queued up.

    from skyrenderer.basic_types.provider import SimpleTransformProvider
    from skyrenderer.randomization.strategy import DrawingStrategy, UniformRandomInput, RelativeGaussianRandomInput
    def greet_fred():
        fred.modify_locus_definition(
            transform_provider=SimpleTransformProvider(
                rc,
                translation_range_x=(cx, cx + 15),
                translation_range_y=(cy, cy),
                translation_range_z=(cz - 2, cz + 2),
            ),
            strategy=DrawingStrategy(
                rc,
                inputs_strategies={
                    "translation_x": UniformRandomInput(),
                    "translation_z": RelativeGaussianRandomInput(relative_mu=0.5, relative_sigma=0.02),
                },
            ),
        )
    greet_fred()
    scene_composer.visualize()
randomization-advanced-distribution_2_resourcesTutorial
2025-03-27 12:12:21,716 | skyrenderer.utils.time_measurement |  INFO: Setup time: 832 ms

2025-03-27 12:12:24,068 | skyrenderer.utils.time_measurement |  INFO: Context update time: 2.35 seconds

2025-03-27 12:12:32,894 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 0 ms

2025-03-27 12:12:32,895 | skyrenderer.utils.time_measurement |  INFO: Render time: 8.83 seconds

For the smooth beginning of the party they want to be evenly spaced out to be able to mingle properly.
Let's define an aquarium-shaped volume within which the fireflies are allowed to move about.

    rx = 10.65  # +/- range in meters, so in this case from (-14.401 - 10.65 = -25.051) to (-14.401 + 10.65 = -3.751)
    ry = 3.64
    rz = 2.3
    uniform = UniformRandomInput()
    def disperse():
        fred.modify_locus_definition(
            transform_provider=SimpleTransformProvider(
                rc,
                translation_range_x=(cx - rx, cx + rx),
                translation_range_y=(cy - ry, cy + ry),
                translation_range_z=(cz - rz, cz + rz),
            ),
            strategy=DrawingStrategy(
                rc,
                inputs_strategies={
                    "translation_x": uniform,
                    "translation_y": uniform,
                    "translation_z": uniform,
                },
            ),
        )
    disperse()
    scene_composer.visualize()
randomization-advanced-distribution_3_resourcesTutorial
2025-03-27 12:12:34,655 | skyrenderer.utils.time_measurement |  INFO: Setup time: 913 ms

2025-03-27 12:12:37,066 | skyrenderer.utils.time_measurement |  INFO: Context update time: 2.41 seconds

2025-03-27 12:12:45,988 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 0 ms

2025-03-27 12:12:45,989 | skyrenderer.utils.time_measurement |  INFO: Render time: 8.92 seconds

Practical control of Gaussian params

Turns out there's a celebrity guest at Fred's party - Drew Engie, Fred's cousin. Everyone suddenly wants a selfie
with him, but the outpour of public interest is so overwhelming that Drew tries to hide in a corner.
Now, even Fred's friends' friends joined the party and everyone flocks to greet Drew.

    fred.n_instances = 2000
    front_of_depth_gauss = RelativeGaussianRandomInput(relative_mu=1, relative_sigma=0.1)
    mid_of_height_gauss = RelativeGaussianRandomInput(relative_mu=0.5, relative_sigma=0.2)
    left_of_width_gauss = RelativeGaussianRandomInput(relative_mu=1, relative_sigma=0.2)
    def pile():
        fred.modify_locus_definition(
            transform_provider=SimpleTransformProvider(
                rc,
                translation_range_x=(cx - rx, cx + rx),
                translation_range_y=(cy - ry, cy + ry),
                translation_range_z=(cz - rz, cz + rz),
            ),
            strategy=DrawingStrategy(
                rc,
                inputs_strategies={
                    "translation_x": front_of_depth_gauss,  # end of x range is the alley entrance
                    "translation_y": mid_of_height_gauss,  # poor Drew is trapped by the crowd from above and below
                    "translation_z": left_of_width_gauss,  # end of z range is the left facade
                },
            ),
        )
    pile()
    scene_composer.visualize()
randomization-advanced-distribution_4_resourcesTutorial
2025-03-27 12:12:46,831 | skyrenderer.basic_types.provider.iprovider |  WARNING: Provider for this config has been already initialized! There should be just one Provider created per unit source, consider fixing the scene.
Config:
{'translation_range_x': (-25.051000000000002, -3.7509999999999994), 'translation_range_y': (2.3855999999999997, 9.6656), 'translation_range_z': (-1.3112799999999998, 3.2887199999999996), 'rotation_range_x': (0.0, 0.0), 'rotation_range_y': (0.0, 0.0), 'rotation_range_z': (0.0, 0.0), 'scale_range_x': (1, 1), 'scale_range_y': (1, 1), 'scale_range_z': (1, 1), 'number_of_steps': 1000}

2025-03-27 12:12:50,156 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.32 seconds

2025-03-27 12:12:56,675 | skyrenderer.utils.time_measurement |  INFO: Context update time: 6.52 seconds

2025-03-27 12:13:05,901 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:13:05,903 | skyrenderer.utils.time_measurement |  INFO: Render time: 9.23 seconds

Modifying other existing Transform Providers

To rescue Drew, Fred recruits Elveera to give a keynote speech about her personal and professional success after
taking courses with Eduvera. All guests are asked to gather around Elveera.

    from skyrenderer.basic_types.provider.transform_providers.ball_transform_provider import BallTransformProvider
    def around():
        fred.modify_locus_definition(transform_provider=BallTransformProvider(rc, radius=rz, center=(cx + 5, cy, cz)))
    around()
    scene_composer.visualize()
randomization-advanced-distribution_5_resourcesTutorial
2025-03-27 12:13:10,369 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.58 seconds

2025-03-27 12:13:16,172 | skyrenderer.utils.time_measurement |  INFO: Context update time: 5.80 seconds

2025-03-27 12:13:25,377 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:13:25,379 | skyrenderer.utils.time_measurement |  INFO: Render time: 9.21 seconds

Elveera tells the crowd that while she's flattered by the keen interest expressed by the guests, it'd be better if they dispersed a bit so that everyone had a chance to see and hear her story.

    def around_away():
        fred.modify_locus_definition(
            transform_provider=BallTransformProvider(rc, radius=rz, center=(cx + 5, cy, cz)),
            strategy=DrawingStrategy(
                rc,
                inputs_strategies={
                    "radius": RelativeGaussianRandomInput(
                        # we are affecting the internal "radius" param of the BallTransformProvider,
                        # which controls how far from the center the positions are drawn
                        relative_mu=1,  # 0~1 range represents from center to ball surface
                        relative_sigma=0.1,  # we want the guests packed somewhat tightly at ~equal distance from Fiona
                    )
                },
            ),
        )
    around_away()
    scene_composer.visualize()
randomization-advanced-distribution_6_resourcesTutorial
2025-03-27 12:13:26,273 | skyrenderer.basic_types.provider.iprovider |  WARNING: Provider for this config has been already initialized! There should be just one Provider created per unit source, consider fixing the scene.
Config:
{'center': (-9.401, 6.0256, 0.98872), 'radius': 2.3, 'number_of_points': 1000000}

2025-03-27 12:13:29,873 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.60 seconds

2025-03-27 12:13:35,831 | skyrenderer.utils.time_measurement |  INFO: Context update time: 5.96 seconds

2025-03-27 12:13:44,259 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:13:44,261 | skyrenderer.utils.time_measurement |  INFO: Render time: 8.43 seconds

Custom plot-based Distributions

After the keynote, guests took a drink break and flocked to two opposite bars: cocktails on the left and
decaf-pumpkin-spice-soy-latte-with-stevia on the right. That's right, the right one serves and only that.

    def two_sides(x):
        # Create any continuous plot in the square between 0,0 and 1,1 - it will become your distribution.
        # In this example we will use: y=(2x-1)^4
        return (2 * x - 1) ** 4
        # https://www.desmos.com/calculator/v3x50bgtwb
    from skyrenderer.randomization.strategy import ProbabilityShapeFunctionRandomInput
    def sides():
        fred.modify_locus_definition(
            transform_provider=SimpleTransformProvider(
                rc,
                translation_range_x=(cx + rx - 3, cx + rx),
                translation_range_y=(cy - ry, cy + ry),
                translation_range_z=(cz - rz, cz + rz),
            ),
            strategy=DrawingStrategy(
                rc,
                inputs_strategies={
                    "translation_x": uniform,
                    "translation_y": mid_of_height_gauss,  # bars are near the middle, that's why Drew stayed nearby
                    "translation_z": ProbabilityShapeFunctionRandomInput(two_sides),  # see func definition above
                },
            ),
        )
    sides()
    scene_composer.visualize()
randomization-advanced-distribution_7_resourcesTutorial
2025-03-27 12:13:49,271 | skyrenderer.utils.time_measurement |  INFO: Setup time: 4.13 seconds

2025-03-27 12:13:56,018 | skyrenderer.utils.time_measurement |  INFO: Context update time: 6.75 seconds

2025-03-27 12:14:05,316 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:14:05,318 | skyrenderer.utils.time_measurement |  INFO: Render time: 9.30 seconds

As the party progressed, the guests decided they did not want to force socialization, so they started creating smaller groups related to their interests.

    import math
    def four_levels(x):
        # In this example we will use: y=2sin(7pix)-1
        return 2 * math.sin(7 * math.pi * x) - 1
        # Again, focus on the 0,0 ~ 1,1 square - there are 4 distinct bumps.
        # https://www.desmos.com/calculator/osnqrxkk3w
    def strata():
        fred.modify_locus_definition(
            transform_provider=SimpleTransformProvider(
                rc,
                translation_range_x=(-10, -8),
                translation_range_y=(cy - ry, cy + ry),
                translation_range_z=(cz - rz, cz + rz),
            ),
            strategy=DrawingStrategy(
                rc,
                inputs_strategies={
                    "translation_x": uniform,
                    "translation_y": ProbabilityShapeFunctionRandomInput(four_levels),
                    "translation_z": uniform,
                },
            ),
        )

They also let go of their inhibitions and came out with their true colors. There is no judgment among fireflies.
Or is there?

    from skyrenderer.scene.scene_layout.layout_elements_definitions import MaterialDefinition
    from skyrenderer.basic_types.procedure import PBRShader
    from skyrenderer.basic_types.provider.provider_inputs import HSVColorInput
    def diversity():
        rc.instancers[fred_geometry].set_material_definition(
            material_definition=MaterialDefinition(
                shader=PBRShader(rc),
                parameter_set=PBRShader.create_parameter_provider(
                    rc,
                    specular_factor=0,
                    roughness=1,
                    base_color=HSVColorInput(
                        hue_range=(0.466, 0.006), saturation_range=(0.903, 0.903), value_range=(1, 1)
                    ),
                    ambient_gain=6,
                    casts_shadows=False,
                ),
            )
        )
    strata()
    diversity()
    scene_composer.visualize()
randomization-advanced-distribution_8_resourcesTutorial
2025-03-27 12:14:06,210 | skyrenderer.randomization.strategy.input_drawing_strategy |  WARNING: Probability shape function: four_levels() yields negative y values, they have been clamped to 0.

2025-03-27 12:14:09,873 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.66 seconds

2025-03-27 12:14:16,623 | skyrenderer.utils.time_measurement |  INFO: Context update time: 6.75 seconds

2025-03-27 12:14:25,089 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:14:25,091 | skyrenderer.utils.time_measurement |  INFO: Render time: 8.47 seconds

Random Gaussian usecase

Fireflies from the lower levels got offended by some smug looks and posh utterances from above. Their leader
Farquaad decided to invite everyone to a wild, celebratory fiesta.

    from skyrenderer.randomization.strategy import RandomGaussianRandomInput
    random_gauss = RandomGaussianRandomInput(sigma_relative_limits=(0.05, 0.2))
    def brawl():
        fred.modify_locus_definition(
            transform_provider=SimpleTransformProvider(
                rc,
                translation_range_x=(-12, -6),
                translation_range_y=(cy - ry, cy + ry),
                translation_range_z=(cz - rz, cz + rz),
            ),
            strategy=DrawingStrategy(
                rc,
                inputs_strategies={
                    # by using RandomGaussianRandomInput, mu and sigma will be randomized each frame,
                    # so the furious crowd will keep moving from one place to the next
                    "translation_x": random_gauss,
                    "translation_y": random_gauss,
                    "translation_z": random_gauss,
                },
            ),
        )
    brawl()
    scene_composer.visualize(frame=1)
    scene_composer.visualize(frame=2)
    scene_composer.visualize(frame=3)
randomization-advanced-distribution_9_resourcesTutorial
randomization-advanced-distribution_10_resourcesTutorial
randomization-advanced-distribution_11_resourcesTutorial
2025-03-27 12:14:29,587 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.59 seconds

2025-03-27 12:14:36,305 | skyrenderer.utils.time_measurement |  INFO: Context update time: 6.72 seconds

2025-03-27 12:14:44,752 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:14:44,754 | skyrenderer.utils.time_measurement |  INFO: Render time: 8.45 seconds

2025-03-27 12:14:49,217 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.60 seconds

2025-03-27 12:14:55,934 | skyrenderer.utils.time_measurement |  INFO: Context update time: 6.72 seconds

2025-03-27 12:15:05,123 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:15:05,125 | skyrenderer.utils.time_measurement |  INFO: Render time: 9.19 seconds

2025-03-27 12:15:09,616 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.64 seconds

2025-03-27 12:15:16,422 | skyrenderer.utils.time_measurement |  INFO: Context update time: 6.80 seconds

2025-03-27 12:15:24,869 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:15:24,871 | skyrenderer.utils.time_measurement |  INFO: Render time: 8.45 seconds

Custom Distribution trickery

Situation quickly spiraled out of control.

    from skyrenderer.randomization.strategy import CustomRandomInput
    import random
    last_x_seed = -1
    def spiral_generator(seed, number_of_states, input_name, y_func, z_func):
        # note the use of input_name
        fuzz = 0.002
        global last_x_seed
        if input_name == "translation_x":
            last_x_seed = seed
        else:
            seed = last_x_seed
        max_seed = 2 ** 32 - 1
        frac = seed / max_seed
        frac += fuzz * (random.random() - 0.5)
        if input_name == "translation_x":
            ret_val = round(frac * number_of_states)
        elif input_name == "translation_y":
            ret_val = round(y_func(frac) * number_of_states)
        elif input_name == "translation_z":
            ret_val = round(z_func(frac) * number_of_states)
        if ret_val < 0:
            ret_val = 0
        elif ret_val >= number_of_states:
            ret_val = number_of_states - 1
        return ret_val
    def normal_spiral(seed, number_of_states, input_name, *args):
        freq = 10
        # https://www.desmos.com/calculator/tdzig3kpme

        def normal_y(x):
            return math.sin(freq * math.pi * x) / 2 + 0.5

        def normal_z(x):
            return math.cos(freq * math.pi * x) / 2 + 0.5

        return spiral_generator(seed, number_of_states, input_name, normal_y, normal_z)
    def spiral(spiral_func):
        fred.modify_locus_definition(
            transform_provider=SimpleTransformProvider(
                rc,
                translation_range_x=(cx - rx, cx + rx),
                translation_range_y=(cy - ry, cy + ry),
                translation_range_z=(cz - rz, cz + rz),
            ),
            strategy=DrawingStrategy(
                rc,
                inputs_strategies={
                    "translation_x": CustomRandomInput(spiral_func),
                    "translation_y": CustomRandomInput(spiral_func),
                    "translation_z": CustomRandomInput(spiral_func),
                },
            ),
        )
    spiral(normal_spiral)
    scene_composer.visualize()
randomization-advanced-distribution_12_resourcesTutorial
2025-03-27 12:15:25,775 | skyrenderer.basic_types.provider.iprovider |  WARNING: Provider for this config has been already initialized! There should be just one Provider created per unit source, consider fixing the scene.
Config:
{'translation_range_x': (-25.051000000000002, -3.7509999999999994), 'translation_range_y': (2.3855999999999997, 9.6656), 'translation_range_z': (-1.3112799999999998, 3.2887199999999996), 'rotation_range_x': (0.0, 0.0), 'rotation_range_y': (0.0, 0.0), 'rotation_range_z': (0.0, 0.0), 'scale_range_x': (1, 1), 'scale_range_y': (1, 1), 'scale_range_z': (1, 1), 'number_of_steps': 1000}

2025-03-27 12:15:29,480 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.70 seconds

2025-03-27 12:15:36,207 | skyrenderer.utils.time_measurement |  INFO: Context update time: 6.73 seconds

2025-03-27 12:15:45,225 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:15:45,227 | skyrenderer.utils.time_measurement |  INFO: Render time: 9.02 seconds

    def bonkers_spiral(seed, number_of_states, input_name, *args):
        freq = 10
        phase = 0.02
        # https://www.desmos.com/calculator/0rizu1fihh

        def bonkers_y(x):
            return (math.sin(freq * math.pi * (x - phase)) / 2) / (2 - math.sin((freq + 1) * math.pi * x)) + 0.5

        def bonkers_z(x):
            return (math.cos(freq * math.pi * (x - phase)) / 2) / (
                    2 - math.cos((freq / 2 - 0.4) * math.pi * (x - phase - 0.1))
            ) + 0.5

        return spiral_generator(seed, number_of_states, input_name, bonkers_y, bonkers_z)
    spiral(bonkers_spiral)
    scene_composer.visualize()
randomization-advanced-distribution_13_resourcesTutorial
2025-03-27 12:15:46,133 | skyrenderer.basic_types.provider.iprovider |  WARNING: Provider for this config has been already initialized! There should be just one Provider created per unit source, consider fixing the scene.
Config:
{'translation_range_x': (-25.051000000000002, -3.7509999999999994), 'translation_range_y': (2.3855999999999997, 9.6656), 'translation_range_z': (-1.3112799999999998, 3.2887199999999996), 'rotation_range_x': (0.0, 0.0), 'rotation_range_y': (0.0, 0.0), 'rotation_range_z': (0.0, 0.0), 'scale_range_x': (1, 1), 'scale_range_y': (1, 1), 'scale_range_z': (1, 1), 'number_of_steps': 1000}

2025-03-27 12:15:49,710 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.58 seconds

2025-03-27 12:15:56,375 | skyrenderer.utils.time_measurement |  INFO: Context update time: 6.66 seconds

2025-03-27 12:16:05,548 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:16:05,550 | skyrenderer.utils.time_measurement |  INFO: Render time: 9.17 seconds

Epilogue

Finally, Fred, Elveera, Drew and Farquaad managed to convince everyone to chill. Fireflies put some festive lights
on and flew away, singing merry tunes.

    from skyrenderer.basic_types.provider.transform_providers.disc_transform_provider import DiscTransformProvider
    def waves(x):
        # https://www.desmos.com/calculator/ilfdueawyl
        return (2 * math.sin(12 * math.pi * x) * math.sin(8 * x)) / (39 * (x + 0.01)) + 0.65 - 1.5 * x
    def milky_way():
        fred.modify_locus_definition(
            transform_provider=DiscTransformProvider(
                rc,
                center=(cx + 2, cy - 2, cz - rz - 1),
                radius=3 * rz,
                normal_vector=(1, 1, -0.2),
                number_of_points=1000000,
            ),
            strategy=DrawingStrategy(
                rc,
                inputs_strategies={
                    "radius": RelativeGaussianRandomInput(relative_mu=0.7, relative_sigma=0.1),
                    "phi": ProbabilityShapeFunctionRandomInput(waves),
                },
            ),
        )
        rc.instancers[fred_geometry].set_material_definition(
            material_definition=MaterialDefinition(
                shader=PBRShader(rc),
                parameter_set=PBRShader.create_parameter_provider(
                    rc,
                    specular_factor=0,
                    roughness=1,
                    base_color=HSVColorInput(hue_range=(0, 1), saturation_range=(1, 1), value_range=(1, 1)),
                    ambient_gain=6,
                    casts_shadows=False,
                ),
            )
        )
    milky_way()
    scene_composer.visualize()
randomization-advanced-distribution_14_resourcesTutorial
2025-03-27 12:16:06,467 | skyrenderer.randomization.strategy.input_drawing_strategy |  WARNING: Probability shape function: waves() yields negative y values, they have been clamped to 0.

2025-03-27 12:16:09,830 | skyrenderer.utils.time_measurement |  INFO: Setup time: 3.36 seconds

2025-03-27 12:16:16,048 | skyrenderer.utils.time_measurement |  INFO: Context update time: 6.22 seconds

2025-03-27 12:16:24,469 | skyrenderer.utils.time_measurement |  INFO: Key points calculation time: 1 ms

2025-03-27 12:16:24,471 | skyrenderer.utils.time_measurement |  INFO: Render time: 8.42 seconds

Summary

In this section you have learnt:

  • There are many ways to use Distributions, aside from the default Uniform.
  • Built-in Gaussians with sensible parameters will cover many common cases.
  • Understanding Custom callbacks enables handling unusual scenarios with arbitrary math.