Warp Renderer OpenGL Lighting Parity#1364
Conversation
|
@mhasek thanks for adding these features to the batch renderer! the visual comparisons are great and the benchmark results are helpful for understanding the performance of the different features. |
tkelestemur
left a comment
There was a problem hiding this comment.
left a few comments. thanks @mhasek
|
|
||
| bvh_ngeom = len(geom_enabled_idx) | ||
|
|
||
| headlight_on = bool(mjm.vis.headlight.active) and headlight_active |
There was a problem hiding this comment.
why do we need renderer-level argument for this? Can't we just use the mjm.vis.headlight.active for turning the headlight on and off?
There was a problem hiding this comment.
I can do either @thowell which one would you guys perfer? I could set it up where the user has absolute control over it or if it is just pulled from the model.
There was a problem hiding this comment.
let just use the mjmodel field. thanks!
There was a problem hiding this comment.
This also depends on downstream usage too. In mjlab for example, would they be able to add this option easily into their existing spec/compile from config method?
| bvh_ngeom = len(geom_enabled_idx) | ||
|
|
||
| headlight_on = bool(mjm.vis.headlight.active) and headlight_active | ||
| hl_ambient = np.asarray(mjm.vis.headlight.ambient, dtype=np.float32) |
There was a problem hiding this comment.
I would name these as headlight_ambient_mjm, headlight_diffuse_mjm etc..
93601c9 to
fa2e0c2
Compare
| False the term is dropped at compile time. Disable for performance | ||
| when no emission is present. | ||
| enable_per_light_ambient: When ambient lighting is enabled, sum each | ||
| light's XML `ambient` color into shaded pixels |
There was a problem hiding this comment.
lets remove 'XML' (could be from mjspec?)
| attenuation="1 0.1 0.05" cutoff="25" exponent="10" | ||
| ambient="0.1 0.1 0.1" diffuse="0.6 0.5 0.4" specular="0.7 0.7 0.7" | ||
| bulbradius="0.05"/> | ||
| <light name="dir" pos="0 0 5" dir="0 0 -1" directional="true" |
| attenuation = attenuation * spot_factor | ||
| if not default_attenuation: | ||
| denom = lightatten[0] + lightatten[1] * dist_to_light + lightatten[2] * dist_to_light * dist_to_light | ||
| atten = 1.0 / wp.max(denom, 1.0e-6) |
There was a problem hiding this comment.
can we use math.safe_div here?
|
|
||
| # Apply lighting and shadows | ||
| if wp.static(rc.headlight_active): | ||
| result = result + wp.cw_mul(base_color, wp.static(rc.headlight_ambient)) |
There was a problem hiding this comment.
do we need wp.static here?
There was a problem hiding this comment.
yeah I think you do since, other wise it wont compile
| view_dir, | ||
| mat_spec, | ||
| mat_shin_exp, | ||
| wp.static(rc.enable_backface_culling), |
There was a problem hiding this comment.
do we need wp.static here?
There was a problem hiding this comment.
yes we do since it accesses variables from the rc
| mat_spec, | ||
| mat_shin_exp, | ||
| wp.static(rc.enable_backface_culling), | ||
| wp.static(rc.enable_specular), |
There was a problem hiding this comment.
do we need wp.static here?
| mat_shin_exp, | ||
| wp.static(rc.enable_backface_culling), | ||
| wp.static(rc.enable_specular), | ||
| wp.static(rc.light_attenuation_is_default), |
There was a problem hiding this comment.
do we need wp.static here?
| wp.static(rc.enable_backface_culling), | ||
| wp.static(rc.enable_specular), | ||
| wp.static(rc.light_attenuation_is_default), | ||
| wp.static(rc.has_spot_lights), |
There was a problem hiding this comment.
do we need wp.static here?
There was a problem hiding this comment.
yes you do since it comes from the rc
| # Apply Headlight | ||
| if wp.static(rc.headlight_active): | ||
| cam_pos = ray_origin_world | ||
| cam_fwd = wp.vec3(-cam_mat_world[0, 2], -cam_mat_world[1, 2], -cam_mat_world[2, 2]) |
There was a problem hiding this comment.
does -cam_mat_world[:, 2] work here?
| wp.vec3(1.0, 0.0, 0.0), | ||
| 0.0, | ||
| 0.0, | ||
| wp.static(rc.headlight_diffuse), |
There was a problem hiding this comment.
is wp.static necessary here?
| 0.0, | ||
| 0.0, | ||
| wp.static(rc.headlight_diffuse), | ||
| wp.static(rc.headlight_specular), |
There was a problem hiding this comment.
is wp.static necessary here?
| view_dir, | ||
| mat_spec, | ||
| mat_shin_exp, | ||
| wp.static(rc.enable_backface_culling), |
There was a problem hiding this comment.
is wp.static necessary here?
| mat_spec, | ||
| mat_shin_exp, | ||
| wp.static(rc.enable_backface_culling), | ||
| wp.static(rc.enable_specular), |
There was a problem hiding this comment.
is wp.static necessary here?
| ) | ||
|
|
||
| @classmethod | ||
| def _fixture(cls, name: str) -> str: |
There was a problem hiding this comment.
lets remove this and provide xmls directly to test_data.fixture
| shaded pixel. When False the term is dropped at compile time. | ||
| enable_per_light_ambient: when True and `use_ambient_lighting` is also | ||
| True, sum the per-light `light_ambient` colors into each shaded pixel | ||
| even when N.L == 0 or the pixel is shadowed. When False the second |
There was a problem hiding this comment.
its the dot product between the surface normal vector and the Light vector.
|
would have expected the 'before' and 'all features off' timing results to be about the same since the features should all be statically guarded? are there new features that are not fully guarded with |
|
@mhasek left some comments throughout the code, great work! please let us know if you have any questions. thanks! |
| bvh_ngeom = len(geom_enabled_idx) | ||
|
|
||
| headlight_on = bool(mjm.vis.headlight.active) and headlight_active | ||
| hl_ambient = np.asarray(mjm.vis.headlight.ambient, dtype=np.float32) |
| attenuation="1 0.1 0.05" cutoff="25" exponent="10" | ||
| ambient="0.1 0.1 0.1" diffuse="0.6 0.5 0.4" specular="0.7 0.7 0.7" | ||
| bulbradius="0.05"/> | ||
| <light name="dir" pos="0 0 5" dir="0 0 -1" directional="true" |
| shaded pixel. When False the term is dropped at compile time. | ||
| enable_per_light_ambient: when True and `use_ambient_lighting` is also | ||
| True, sum the per-light `light_ambient` colors into each shaded pixel | ||
| even when N.L == 0 or the pixel is shadowed. When False the second |
There was a problem hiding this comment.
its the dot product between the surface normal vector and the Light vector.
| ) | ||
|
|
||
| @classmethod | ||
| def _fixture(cls, name: str) -> str: |
As would I, I attempted to guard as much as possible but the light accumulation is now in the rgb space before it was a scalar my guess is that is what is contributing to this. I can attempt to guard that too but I feel it would convolute the lighting accumulation. |
There was a problem hiding this comment.
Thanks for the changes! The rendered output definitely looks better. Also great PR description!
Major comment on the tests, I think the added tests are excessive and also unnecessary. For testing if the flags are being passed through correctly, there are tests in io.py that you can follow or add to that can make sure that setting the flags for these lighting options is getting passed through correctly. See similar tests for render_rgb, use_textures etc (test_render_context_with_textures). They are super lightweight and just check that the options are being passed through correctly.
Regarding the actual rendering tests, I would just skip them. The rendering output here is subjective, so if we all agree that the new output looks better or closer to MuJoCo, then we can ship it. In the past we have only added rendering tests for depth and segmentation since we want strict equality in output. This renderer will never be able to match a full renderer from MuJoCo so adding alot of test overhead just to measure how similar the images are is not necessary (which is why we never added these type of tests before as well). I am of the opinion that we can just remove these tests, keep testing lightweight.
|
|
||
| bvh_ngeom = len(geom_enabled_idx) | ||
|
|
||
| headlight_on = bool(mjm.vis.headlight.active) and headlight_active |
There was a problem hiding this comment.
This also depends on downstream usage too. In mjlab for example, would they be able to add this option easily into their existing spec/compile from config method?
mhasek
left a comment
There was a problem hiding this comment.
@StafaH Thanks for the review! I tried to address the comments. My only question is I remvoed the additions to the render tests but should I at least and some sanity checks tests to make sure the renderer runs with the separate configs?
| False the term is dropped at compile time. Disable for performance | ||
| when no emission is present. | ||
| enable_per_light_ambient: When ambient lighting is enabled, sum each | ||
| light's XML `ambient` color into shaded pixels |
|
|
||
| bvh_ngeom = len(geom_enabled_idx) | ||
|
|
||
| headlight_on = bool(mjm.vis.headlight.active) and headlight_active |
mhasek
left a comment
There was a problem hiding this comment.
@StafaH Thanks for the review! I tried to address the comments. My only question is I removed the additions to the render tests but should I at least and some sanity checks tests to make sure the renderer runs with the separate configs?
StafaH
left a comment
There was a problem hiding this comment.
LGTM, much cleaner. Thanks!
| if mat_id_for_spec >= 0: | ||
| if wp.static(rc.enable_specular): | ||
| mat_spec = mat_specular[worldid % mat_specular.shape[0], mat_id_for_spec] | ||
| mat_shin_exp = mat_shininess[worldid % mat_shininess.shape[0], mat_id_for_spec] * 128.0 |
There was a problem hiding this comment.
the max shininess value is 128.0, should we add a constant about for this setting and then use the constant here? something like MAX_SHININESS = 128.0 and then DEFAULT_SHININESS_EXPONENT = 0.5 * MAX_SHININESS?
There was a problem hiding this comment.
should the 128.0 here be replaced with MAX_SHININESS?
There was a problem hiding this comment.
Apologies, I missed that it is fixed now
…th opengl renderer make background color controllable in create_render_context api make light params batchable make material params batchable pr comments PR comments PR comments add render sanity tests remove lighting field prop test make max shininess a constant
7e00311 to
57af4ba
Compare
|
great work, thanks @mhasek! |
PR Description: Renderer OpenGL Lighting Parity
Summary
This PR brings the MuJoCo Warp RGB renderer much closer to MuJoCo OpenGL lighting behavior. It plumbs missing light and material fields into
types.Model, rewritescompute_lightingaround MuJoCo-style diffuse/specular/attenuation/spotlight terms, injects the MuJoCo headlight as a camera-relative light, and adds opt-out flags for the main parity features.The renderer now handles:
vis.headlightambient/diffuse/specular contribution.I realize this is on the longer side and I put this all in a single PR but if need be I can split up into subcomponents to ease the processes.
Visual Parity
Canonical fixture SSIM against
mujoco.Rendererimproves from roughly0.3-0.5before this PR to0.917-0.994after this PR.Renderer parity fixtures before/after/reference
The grid below shows each renderer parity test fixture before this PR, after this PR, and against the MuJoCo OpenGL reference.

Feature visual impact by opt-out flag
The visual comparison below shows the effect of disabling each feature independently on a representative lit scene, with shadows disabled to isolate the lighting terms.

Performance Cost
A reference scene was evaluated for 100 samples where the impact of each feature on SSIM and compute is shown compared to the baseline (origin/main)

Reference Scene