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
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()
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()
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()
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()
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()
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()
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()
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)
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()
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()
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()
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.