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-01-30 13:13:50,693 | skyrenderer.scene.renderer_context | INFO: Root paths:
- root path: /dli/skyenvironment/skyrenderer/skyrenderer
- assets path: /dli/mount/assets
- config path: /dli/skyenvironment/skyrenderer/skyrenderer/config
- gpu sources path: /dli/skyenvironment/skyrenderer/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-01-30 13:13:51,061 | skyrenderer.basic_types.provider.unit_providers.hdr_texture_provider | WARNING: Parameter light_adapt provided in HDR json is not supported
2025-01-30 13:13:51,062 | skyrenderer.basic_types.provider.unit_providers.hdr_texture_provider | WARNING: Parameter color_adapt provided in HDR json is not supported
2025-01-30 13:13:51,063 | 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-01-30 13:13:51,064 | 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-01-30 13:13:51,064 | 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-01-30 13:13:51,146 | skyrenderer.utils.time_measurement | INFO: Setup time: 80 ms
2025-01-30 13:13:51,214 | skyrenderer.utils.time_measurement | INFO: Setup time: 65 ms
2025-01-30 13:13:52,249 | skyrenderer.utils.time_measurement | INFO: Context update time: 1.04 seconds
2025-01-30 13:15:07,343 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 0 ms
2025-01-30 13:15:07,344 | skyrenderer.utils.time_measurement | INFO: Render time: 75.09 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 yellow 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 allows 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-01-30 13:15:09,065 | skyrenderer.utils.time_measurement | INFO: Setup time: 935 ms
2025-01-30 13:15:11,485 | skyrenderer.utils.time_measurement | INFO: Context update time: 2.42 seconds
2025-01-30 13:16:30,825 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 0 ms
2025-01-30 13:16:30,825 | skyrenderer.utils.time_measurement | INFO: Render time: 79.34 seconds
For the courteous beginning of the party they want to be evenly spaced out to be able to mingle properly. Time for
really spacing out will come later. 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-01-30 13:16:32,523 | skyrenderer.utils.time_measurement | INFO: Setup time: 926 ms
2025-01-30 13:16:34,881 | skyrenderer.utils.time_measurement | INFO: Context update time: 2.36 seconds
2025-01-30 13:17:53,475 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 0 ms
2025-01-30 13:17:53,476 | skyrenderer.utils.time_measurement | INFO: Render time: 78.59 seconds
Practical control of Gaussian params
Turns out one firefly is Elon's cousin - Felon. Everyone suddenly wants a selfie with him, but Felon is an
introvert, just like Elon, so he tries to hide in the corner. Now Fred's friends' hyped friends joined the party
and everyone piles up on Felon.
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 Felon 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-01-30 13:17:54,249 | 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-01-30 13:17:57,657 | skyrenderer.utils.time_measurement | INFO: Setup time: 3.41 seconds
2025-01-30 13:18:04,485 | skyrenderer.utils.time_measurement | INFO: Context update time: 6.83 seconds
2025-01-30 13:19:23,504 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:19:23,505 | skyrenderer.utils.time_measurement | INFO: Render time: 79.02 seconds
Modifying other existing Transform Providers
To rescue Felon, Fred recruits Fiona to give a keynote speech about success and work-life balance. All guests are
asked to gather around.
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-01-30 13:19:27,734 | skyrenderer.utils.time_measurement | INFO: Setup time: 3.45 seconds
2025-01-30 13:19:33,647 | skyrenderer.utils.time_measurement | INFO: Context update time: 5.91 seconds
2025-01-30 13:20:52,321 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:20:52,322 | skyrenderer.utils.time_measurement | INFO: Render time: 78.67 seconds
Fiona tells the crowd that while it's nice to feel like a supernova, it'd be better for them to back up some, so
everyone can have a chance to see and hear her.
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-01-30 13:20:53,085 | 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-01-30 13:20:56,673 | skyrenderer.utils.time_measurement | INFO: Setup time: 3.59 seconds
2025-01-30 13:21:02,920 | skyrenderer.utils.time_measurement | INFO: Context update time: 6.25 seconds
2025-01-30 13:22:22,431 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:22:22,431 | skyrenderer.utils.time_measurement | INFO: Render time: 79.51 seconds
Custom plot-based Distributions
After the keynote, fireflies took a drink break and flocked to two opposite bars: cocktails on the left and
pumpkin spice soy latte decaf with stevia on the right. That's right, the right one serves precisely 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 Felon stayed nearby
"translation_z": ProbabilityShapeFunctionRandomInput(two_sides), # see func definition above
},
),
)
sides()
scene_composer.visualize()
2025-01-30 13:22:27,360 | skyrenderer.utils.time_measurement | INFO: Setup time: 4.14 seconds
2025-01-30 13:22:33,993 | skyrenderer.utils.time_measurement | INFO: Context update time: 6.63 seconds
2025-01-30 13:23:52,844 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:23:52,845 | skyrenderer.utils.time_measurement | INFO: Render time: 78.85 seconds
As they got buzzed, the guests decided they no longer need to be politically correct, so they went on to join
their respective groups of self-perceived social status.
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-01-30 13:23:53,612 | skyrenderer.randomization.strategy.input_drawing_strategy | WARNING: Probability shape function: four_levels() yields negative y values, they have been clamped to 0.
2025-01-30 13:23:57,293 | skyrenderer.utils.time_measurement | INFO: Setup time: 3.68 seconds
2025-01-30 13:24:04,198 | skyrenderer.utils.time_measurement | INFO: Context update time: 6.90 seconds
2025-01-30 13:25:23,324 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:25:23,324 | skyrenderer.utils.time_measurement | INFO: Render time: 79.13 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 into a drunken brawl.
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-01-30 13:25:27,878 | skyrenderer.utils.time_measurement | INFO: Setup time: 3.75 seconds
2025-01-30 13:25:34,733 | skyrenderer.utils.time_measurement | INFO: Context update time: 6.85 seconds
2025-01-30 13:26:54,244 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:26:54,245 | skyrenderer.utils.time_measurement | INFO: Render time: 79.51 seconds
2025-01-30 13:26:58,536 | skyrenderer.utils.time_measurement | INFO: Setup time: 3.55 seconds
2025-01-30 13:27:05,335 | skyrenderer.utils.time_measurement | INFO: Context update time: 6.80 seconds
2025-01-30 13:28:24,875 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:28:24,877 | skyrenderer.utils.time_measurement | INFO: Render time: 79.54 seconds
2025-01-30 13:28:29,273 | skyrenderer.utils.time_measurement | INFO: Setup time: 3.67 seconds
2025-01-30 13:28:36,073 | skyrenderer.utils.time_measurement | INFO: Context update time: 6.80 seconds
2025-01-30 13:29:55,233 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:29:55,234 | skyrenderer.utils.time_measurement | INFO: Render time: 79.16 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-01-30 13:29:56,008 | 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-01-30 13:29:59,595 | skyrenderer.utils.time_measurement | INFO: Setup time: 3.59 seconds
2025-01-30 13:30:06,544 | skyrenderer.utils.time_measurement | INFO: Context update time: 6.95 seconds
2025-01-30 13:31:26,531 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:31:26,532 | skyrenderer.utils.time_measurement | INFO: Render time: 79.99 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-01-30 13:31:27,302 | 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-01-30 13:31:30,968 | skyrenderer.utils.time_measurement | INFO: Setup time: 3.67 seconds
2025-01-30 13:31:37,941 | skyrenderer.utils.time_measurement | INFO: Context update time: 6.97 seconds
2025-01-30 13:32:57,264 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:32:57,265 | skyrenderer.utils.time_measurement | INFO: Render time: 79.32 seconds
Epilogue
Finally, Fred, Fiona, Felon 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-01-30 13:32:58,047 | skyrenderer.randomization.strategy.input_drawing_strategy | WARNING: Probability shape function: waves() yields negative y values, they have been clamped to 0.
2025-01-30 13:33:01,183 | skyrenderer.utils.time_measurement | INFO: Setup time: 3.14 seconds
2025-01-30 13:33:07,468 | skyrenderer.utils.time_measurement | INFO: Context update time: 6.28 seconds
2025-01-30 13:34:26,829 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 1 ms
2025-01-30 13:34:26,830 | skyrenderer.utils.time_measurement | INFO: Render time: 79.36 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.