Move filament renderer out of experimental into src/render/filament.

Also:
- renames the main header file to mjrfilament.h.
- moves filament_util under a "support" folder.
PiperOrigin-RevId: 930448415
Change-Id: Iad89756aaf1ddf4c3e67e1afc3a13776f71fa20f
This commit is contained in:
Haroon Qureshi
2026-06-11 04:42:42 -07:00
committed by Copybara-Service
parent 1aaced190a
commit 96781fa793
76 changed files with 521 additions and 495 deletions
+157
View File
@@ -0,0 +1,157 @@
# Copyright 2025 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.16)
set(MUJOCO_FILAMENT_TARGET_NAME mujoco_filament)
add_library(${MUJOCO_FILAMENT_TARGET_NAME} STATIC)
target_compile_definitions(${MUJOCO_FILAMENT_TARGET_NAME} PRIVATE MJ_STATIC)
target_sources(${MUJOCO_FILAMENT_TARGET_NAME}
PUBLIC
mjrfilament.h
mjrfilament.cc
mjrfilament_cpp.h
core/builtins.cc
core/builtins.h
core/color_grading_options.cc
core/color_grading_options.h
core/filament_context.cc
core/filament_context.h
core/filament_platform_factory.cc
core/filament_platform_factory.h
core/imgui_editor.cc
core/light.cc
core/light.h
core/material_manager.cc
core/material_manager.h
core/mesh.cc
core/mesh.h
core/object_manager.cc
core/object_manager.h
core/outliner.cc
core/outliner.h
core/reflection_manager.cc
core/reflection_manager.h
core/render_target.cc
core/render_target.h
core/renderable.cc
core/renderable.h
core/scene_view.cc
core/scene_view.h
core/texture.cc
core/texture.h
support/filament_util.h
support/filament_util.cc
)
target_include_directories(${MUJOCO_FILAMENT_TARGET_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
)
include(third_party_deps/dear_imgui)
include(third_party_deps/filament)
target_link_libraries(${MUJOCO_FILAMENT_TARGET_NAME}
dear_imgui
filament
image # provided by filament
ktxreader # provided by filament
)
add_library(mujoco::filament ALIAS ${MUJOCO_FILAMENT_TARGET_NAME})
# The "mujoco::filament" library needs access to some assets (e.g. filament
# material shaders) in order to do rendering. As such, you must also include
# the assets under MUJOCO_FILAMENT_ASSETS_DIR in your project. As part of
# the filament library configuration, you will provide a function that the
# library will use to load these assets.
set(ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets")
set(OUTPUT_ASSETS_DIR "${CMAKE_CURRENT_BINARY_DIR}/assets")
file(MAKE_DIRECTORY ${OUTPUT_ASSETS_DIR})
file(COPY
"${ASSETS_DIR}/ibl.ktx"
DESTINATION ${OUTPUT_ASSETS_DIR}
)
set(MATC_EXECUTABLE matc)
set(MATERIAL_FILES
pbr.mat
pbr_transparent.mat
pbr_packed.mat
pbr_packed_transparent.mat
phong_2d_fade.mat
phong_2d.mat
phong_2d_reflect.mat
phong_2d_uv_fade.mat
phong_2d_uv.mat
phong_2d_uv_reflect.mat
phong_color_fade.mat
phong_color.mat
phong_color_reflect.mat
phong_cube_fade.mat
phong_cube.mat
phong_cube_reflect.mat
outline_composite.mat
outline_flatten.mat
outline_jumpflood.mat
unlit_decor.mat
unlit_depth.mat
unlit_segmentation.mat
unlit_ui.mat
)
foreach(MATERIAL_FILE ${MATERIAL_FILES})
get_filename_component(MATERIAL_NAME ${MATERIAL_FILE} NAME_WE)
set(INPUT_FILE "${ASSETS_DIR}/${MATERIAL_FILE}")
set(OUTPUT_FILE "${OUTPUT_ASSETS_DIR}/${MATERIAL_NAME}.filamat")
if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
set(PRECOMPILED_FILE "${MUJOCO_NATIVE_BUILD_DIR}/src/render/filament/assets/${MATERIAL_NAME}.filamat")
add_custom_command(
OUTPUT ${OUTPUT_FILE}
COMMAND ${CMAKE_COMMAND} -E copy
${PRECOMPILED_FILE}
${OUTPUT_FILE}
DEPENDS ${PRECOMPILED_FILE}
COMMENT "Copying precompiled material ${MATERIAL_NAME}.filamat"
)
else()
add_custom_command(
OUTPUT ${OUTPUT_FILE}
COMMAND ${MATC_EXECUTABLE}
--platform=all
--api=vulkan
--api=opengl
--variant-filter skinning
--optimize-size
--output ${OUTPUT_FILE}
${INPUT_FILE}
DEPENDS ${INPUT_FILE}
DEPENDS matc
COMMENT "Compiling ${MATERIAL_FILE}"
)
endif()
list(APPEND MUJOCO_FILAMENT_ASSET_FILES ${OUTPUT_FILE})
endforeach()
list(APPEND MUJOCO_FILAMENT_ASSET_FILES "${OUTPUT_ASSETS_DIR}/ibl.ktx")
add_custom_target(mujoco_filament_assets DEPENDS ${MUJOCO_FILAMENT_ASSET_FILES})
add_dependencies(${MUJOCO_FILAMENT_TARGET_NAME} mujoco_filament_assets)
set(MUJOCO_FILAMENT_ASSETS_DIR ${OUTPUT_ASSETS_DIR})
Binary file not shown.
@@ -0,0 +1,82 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This material uses the results of the Jump Flooding Algorithm to draw an
// outline around an object. The outline is drawn by checking the distance to
// the nearest edge point (as computed by the algorithm) and, if within the
// desired width, setting the color of the pixel to the desired outline color.
material {
name : outline_composite,
blending : transparent,
culling : none,
depthWrite : false,
depthCulling : false,
parameters : [
{ type : sampler2d, name : source },
{ type : float4, name : color },
{ type : float, name : width }
],
variables : [
vertex
],
domain : postprocess
}
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = uvToRenderTargetUV(postProcess.normalizedUV);
}
}
fragment {
vec2 unpack(vec4 value) {
float x = value.r + value.g / 255.0;
float y = value.b + value.a / 255.0;
// Reverse scale and bias
return (vec2(x, y) - 0.001) / 0.999;
}
void postProcess(inout PostProcessInputs postProcess) {
vec4 tex = texture(materialParams_source, variable_vertex.xy);
if (tex == vec4(0.0)) {
discard;
}
vec2 edge = unpack(tex);
// Calculate distance to the nearest edge in pixel units.
vec2 size = vec2(textureSize(materialParams_source, 0));
vec2 diff = (edge - variable_vertex.xy) * size;
float dist = length(diff);
// Exclude the interior of the selected object itself.
if (dist <= 0.5) {
discard;
}
// Only draw outline if the pixel is within 'width' of an edge.
float width = materialParams.width;
if (dist >= width) {
discard;
}
// Inner edge anti-aliasing (smooths transition from object to outline).
// Outer edge anti-aliasing (fades at the outline boundary).
float inner_alpha = smoothstep(0.0, 1.0, dist);
float outer_alpha = smoothstep(width, width - 1.0, dist);
float alpha = outer_alpha * inner_alpha;
postProcess.color = mix(vec4(0), materialParams.color, alpha * materialParams.color.a);
}
}
@@ -0,0 +1,54 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This material writes the the screen-space position of each fragment as the
// output color of that fragment. This will provide us with the initial data for
// the Jump Flood Algorithm. See "outline_jumpflood.mat" for more details.
material {
name : outline_flatten,
shadingModel : unlit,
culling : none,
depthWrite : false
}
fragment {
// Encode a vec2 value with the range [0-1] into a 32-bit (RGBA) value.
// We do this by packing each coordinate into a 16-bit value. Values are
// adjusted to ensure [0x0000] is reserved for "untouched" pixels rather
// than the pixel at the position (0, 0).
vec4 pack(vec2 value) {
// Scale and bias the value to avoid (0,0,0,0).
vec2 adjusted = value * 0.999 + 0.001;
// Pack float [0, 1] into two 8-bit channels.
float val_x = adjusted.x * 255.0;
float r_x = floor(val_x) / 255.0;
float g_x = fract(val_x);
float val_y = adjusted.y * 255.0;
float r_y = floor(val_y) / 255.0;
float g_y = fract(val_y);
return vec4(r_x, g_x, r_y, g_y);
}
void material(inout MaterialInputs material) {
prepareMaterial(material);
// Get the screen-space position of the fragment, normalized to [0-1].
vec2 uv = gl_FragCoord.xy * frameUniforms.resolution.zw;
material.baseColor = pack(uv);
}
}
@@ -0,0 +1,110 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This material implements a single pass of the Jump Flooding Algorithm (JFA).
// This algorithm is used to efficiently compute a distance transform where each
// pixel stores the UV coordinates of its closest edge point. In each pass,
// pixels look at their neighbors at a distance 'step' to see if they have a
// closer edge point.
material {
name : outline_jumpflood,
culling : none,
depthWrite : false,
depthCulling : false,
parameters : [
{ type : sampler2d, name : source },
{ type : float, name : step }
],
variables : [
vertex
],
domain : postprocess
}
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = uvToRenderTargetUV(postProcess.normalizedUV);
}
}
fragment {
// Encode a vec2 value with the range [0-1] into a 32-bit (RGBA) value.
// We do this by packing each coordinate into a 16-bit value. Values are
// adjusted to ensure [0x0000] is reserved for "untouched" pixels rather
// than the pixel at the position (0, 0).
vec4 pack(vec2 value) {
// Scale and bias the value to avoid (0,0,0,0).
vec2 adjusted = value * 0.999 + 0.001;
// Pack float [0, 1] into two 8-bit channels.
float val_x = adjusted.x * 255.0;
float r_x = floor(val_x) / 255.0;
float g_x = fract(val_x);
float val_y = adjusted.y * 255.0;
float r_y = floor(val_y) / 255.0;
float g_y = fract(val_y);
return vec4(r_x, g_x, r_y, g_y);
}
vec2 unpack(vec4 value) {
float x = value.r + value.g / 255.0;
float y = value.b + value.a / 255.0;
// Reverse scale and bias
return (vec2(x, y) - 0.001) / 0.999;
}
vec3 jump(vec3 best, vec2 offset) {
vec2 uv = variable_vertex.xy + offset;
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
return best;
}
vec4 tex = texture(materialParams_source, uv);
if (tex == vec4(0.0)) {
return best;
}
vec2 edge = unpack(tex);
vec2 diff = (edge - variable_vertex.xy);
float dist_sq = dot(diff, diff);
if (dist_sq < best.z) {
best = vec3(edge.x, edge.y, dist_sq);
}
return best;
}
void postProcess(inout PostProcessInputs postProcess) {
vec2 size = vec2(textureSize(materialParams_source, 0));
vec2 dist = materialParams.step / size;
vec3 best = vec3(-1.0, -1.0, 1e20);
best = jump(best, dist * vec2(+1, +1));
best = jump(best, dist * vec2(+1, 0));
best = jump(best, dist * vec2(+1, -1));
best = jump(best, dist * vec2( 0, +1));
best = jump(best, dist * vec2( 0, 0));
best = jump(best, dist * vec2( 0, -1));
best = jump(best, dist * vec2(-1, +1));
best = jump(best, dist * vec2(-1, 0));
best = jump(best, dist * vec2(-1, -1));
if (best.x >= 0.0) {
postProcess.color = pack(best.xy);
} else {
postProcess.color = vec4(0.0);
}
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : pbr,
shadingModel : lit,
flipUV: false,
parameters : [
{ type : sampler2d, name : BaseColor },
{ type : sampler2d, name : Normal },
{ type : sampler2d, name : Occlusion },
{ type : sampler2d, name : Roughness },
{ type : sampler2d, name : Metallic },
{ type : sampler2d, name : Emissive },
{ type : float4, name : BaseColorFactor },
{ type : float, name : MetallicFactor },
{ type : float, name : RoughnessFactor }
],
requires : [
uv0
]
}
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
material.normal = texture(materialParams_Normal, uv).xyz * 2.0 - 1.0;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.ambientOcclusion = texture(materialParams_Occlusion, uv).r;
material.roughness = materialParams.RoughnessFactor;
material.roughness *= texture(materialParams_Roughness, uv).r;
material.metallic = materialParams.MetallicFactor;
material.metallic *= texture(materialParams_Metallic, uv).r;
material.emissive = texture(materialParams_Emissive, uv);
material.emissive.a = 1.0 - material.emissive.a;
}
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : pbr_packed,
shadingModel : lit,
flipUV: false,
parameters : [
{ type : sampler2d, name : BaseColor },
{ type : sampler2d, name : Normal },
{ type : sampler2d, name : ORM },
{ type : sampler2d, name : Emissive },
{ type : float4, name : BaseColorFactor },
{ type : float, name : MetallicFactor },
{ type : float, name : RoughnessFactor }
],
requires : [
uv0
]
}
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
material.normal = texture(materialParams_Normal, uv).xyz * 2.0 - 1.0;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.ambientOcclusion = texture(materialParams_ORM, uv).r;
material.roughness = materialParams.RoughnessFactor;
material.roughness *= texture(materialParams_ORM, uv).g;
material.metallic = materialParams.MetallicFactor;
material.metallic *= texture(materialParams_ORM, uv).b;
material.emissive = texture(materialParams_Emissive, uv);
material.emissive.a = 1.0 - material.emissive.a;
}
}
@@ -0,0 +1,53 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : pbr_packed_transparent,
shadingModel : lit,
flipUV: false,
blending: transparent,
parameters : [
{ type : sampler2d, name : BaseColor },
{ type : sampler2d, name : Opacity },
{ type : sampler2d, name : Normal },
{ type : sampler2d, name : ORM },
{ type : sampler2d, name : Emissive },
{ type : float4, name : BaseColorFactor },
{ type : float, name : MetallicFactor },
{ type : float, name : RoughnessFactor }
],
requires : [
uv0
]
}
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
material.normal = texture(materialParams_Normal, uv).xyz * 2.0 - 1.0;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.baseColor.a *= texture(materialParams_Opacity, uv).r;
material.ambientOcclusion = texture(materialParams_ORM, uv).r;
material.roughness = materialParams.RoughnessFactor;
material.roughness *= texture(materialParams_ORM, uv).g;
material.metallic = materialParams.MetallicFactor;
material.metallic *= texture(materialParams_ORM, uv).b;
material.emissive = texture(materialParams_Emissive, uv);
material.emissive.a = 1.0 - material.emissive.a;
}
}
@@ -0,0 +1,56 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : pbr_transparent,
shadingModel : lit,
flipUV: false,
blending: transparent,
parameters : [
{ type : sampler2d, name : BaseColor },
{ type : sampler2d, name : Opacity },
{ type : sampler2d, name : Normal },
{ type : sampler2d, name : Occlusion },
{ type : sampler2d, name : Roughness },
{ type : sampler2d, name : Metallic },
{ type : sampler2d, name : Emissive },
{ type : float4, name : BaseColorFactor },
{ type : float, name : MetallicFactor },
{ type : float, name : RoughnessFactor }
],
requires : [
uv0
]
}
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
material.normal = texture(materialParams_Normal, uv).xyz * 2.0 - 1.0;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.baseColor.a *= texture(materialParams_Opacity, uv).r;
material.ambientOcclusion = texture(materialParams_Occlusion, uv).r;
material.roughness = materialParams.RoughnessFactor;
material.roughness *= texture(materialParams_Roughness, uv).r;
material.metallic = materialParams.MetallicFactor;
material.metallic *= texture(materialParams_Metallic, uv).r;
material.emissive = texture(materialParams_Emissive, uv);
material.emissive.a = 1.0 - material.emissive.a;
}
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_2d,
shadingModel : specularGlossiness,
flipUV : false,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : float3, name : UvScale },
{ type : float3, name : UvOffset },
{ type : sampler2d, name : BaseColor }
],
variables : [
vertex_pos
]
}
vertex {
void materialVertex(inout MaterialVertexInputs material) {
material.vertex_pos = getPosition();
}
}
fragment {
void material(inout MaterialInputs material) {
vec2 uv = variable_vertex_pos.xy * materialParams.UvScale.xy + materialParams.UvOffset.xy;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
}
}
@@ -0,0 +1,51 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_2d_fade,
shadingModel : specularGlossiness,
flipUV : false,
blending: fade,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : float3, name : UvScale },
{ type : float3, name : UvOffset },
{ type : sampler2d, name : BaseColor }
],
variables : [
vertex_pos
]
}
vertex {
void materialVertex(inout MaterialVertexInputs material) {
material.vertex_pos = getPosition();
}
}
fragment {
void material(inout MaterialInputs material) {
vec2 uv = variable_vertex_pos.xy * materialParams.UvScale.xy + materialParams.UvOffset.xy;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
}
}
@@ -0,0 +1,57 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_2d_reflect,
shadingModel : specularGlossiness,
flipUV : false,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : float, name : Reflectance },
{ type : float3, name : UvScale },
{ type : float3, name : UvOffset },
{ type : sampler2d, name : BaseColor },
{ type : sampler2d, name : Reflection }
],
variables : [
vertex_pos
]
}
vertex {
void materialVertex(inout MaterialVertexInputs material) {
material.vertex_pos = getPosition();
}
}
fragment {
void material(inout MaterialInputs material) {
vec2 uv = variable_vertex_pos.xy * materialParams.UvScale.xy + materialParams.UvOffset.xy;
vec2 screen_uv = gl_FragCoord.xy * getResolution().zw;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
// Apply reflection.
vec4 reflection = texture(materialParams_Reflection, screen_uv);
material.baseColor.rgb = mix(material.baseColor.rgb, reflection.rgb, materialParams.Reflectance);
}
}
@@ -0,0 +1,42 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_2d_uv,
shadingModel : specularGlossiness,
flipUV : false,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : sampler2d, name : BaseColor }
],
requires : [
uv0
],
}
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
}
}
@@ -0,0 +1,43 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_2d_uv_fade,
shadingModel : specularGlossiness,
flipUV : false,
blending: fade,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : sampler2d, name : BaseColor }
],
requires : [
uv0
],
}
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
}
}
@@ -0,0 +1,49 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_2d_uv_reflect,
shadingModel : specularGlossiness,
flipUV : false,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : float, name : Reflectance },
{ type : sampler2d, name : BaseColor },
{ type : sampler2d, name : Reflection }
],
requires : [
uv0
],
}
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
vec2 screen_uv = gl_FragCoord.xy * getResolution().zw;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
// Apply reflection.
vec4 reflection = texture(materialParams_Reflection, screen_uv);
material.baseColor.rgb = mix(material.baseColor.rgb, reflection.rgb, materialParams.Reflectance);
}
}
@@ -0,0 +1,34 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_color,
shadingModel : specularGlossiness,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor }
]
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
}
}
@@ -0,0 +1,35 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_color_fade,
shadingModel : specularGlossiness,
blending: fade,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor }
]
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
}
}
@@ -0,0 +1,42 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_color_reflect,
shadingModel : specularGlossiness,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : float, name : Reflectance },
{ type : sampler2d, name : Reflection }
]
}
fragment {
void material(inout MaterialInputs material) {
vec2 screen_uv = gl_FragCoord.xy * getResolution().zw;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
// Apply reflection.
vec4 reflection = texture(materialParams_Reflection, screen_uv);
material.baseColor.rgb = mix(material.baseColor.rgb, reflection.rgb, materialParams.Reflectance);
}
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_cube,
shadingModel : specularGlossiness,
flipUV : false,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : float3, name : UvScale },
{ type : samplerCubemap, name : BaseColor }
],
variables : [
vertex_pos
]
}
vertex {
void materialVertex(inout MaterialVertexInputs material) {
material.vertex_pos = getPosition();
}
}
fragment {
void material(inout MaterialInputs material) {
vec3 uv = variable_vertex_pos.xyz * materialParams.UvScale;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
}
}
@@ -0,0 +1,50 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_cube_fade,
shadingModel : specularGlossiness,
flipUV : false,
blending: fade,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : float3, name : UvScale },
{ type : samplerCubemap, name : BaseColor }
],
variables : [
vertex_pos
]
}
vertex {
void materialVertex(inout MaterialVertexInputs material) {
material.vertex_pos = getPosition();
}
}
fragment {
void material(inout MaterialInputs material) {
vec3 uv = variable_vertex_pos.xyz * materialParams.UvScale;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
}
}
@@ -0,0 +1,56 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : phong_cube_reflect,
shadingModel : specularGlossiness,
flipUV : false,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : float3, name : UvScale },
{ type : float, name : Reflectance },
{ type : samplerCubemap, name : BaseColor },
{ type : sampler2d, name : Reflection }
],
variables : [
vertex_pos
]
}
vertex {
void materialVertex(inout MaterialVertexInputs material) {
material.vertex_pos = getPosition();
}
}
fragment {
void material(inout MaterialInputs material) {
vec3 uv = variable_vertex_pos.xyz * materialParams.UvScale;
vec2 screen_uv = gl_FragCoord.xy * getResolution().zw;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
material.baseColor *= texture(materialParams_BaseColor, uv);
material.specularColor = vec3(materialParams.SpecularFactor);
material.glossiness = materialParams.GlossinessFactor;
material.emissive = vec4(materialParams.EmissiveFactor);
// Apply reflection.
vec4 reflection = texture(materialParams_Reflection, screen_uv);
material.baseColor.rgb = mix(material.baseColor.rgb, reflection.rgb, materialParams.Reflectance);
}
}
@@ -0,0 +1,28 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : unlit_decor,
shadingModel : unlit,
parameters : [
{ type : float4, name : BaseColorFactor }
]
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
}
}
@@ -0,0 +1,30 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : unlit_depth,
shadingModel : unlit,
blending : opaque,
depthWrite: true
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
float3 worldPosition = getWorldPosition();
float4 clipPosition = mulMat4x4Float3(getClipFromWorldMatrix(), worldPosition);
float3 normalizedPosition = clipPosition.xyz / clipPosition.w;
material.baseColor.rgb = vec3(normalizedPosition.z);
}
}
@@ -0,0 +1,28 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : unlit_segmentation,
shadingModel : unlit,
parameters : [
{ type : float4, name : SegmentationColor }
]
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = materialParams.SegmentationColor;
}
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
material {
name : unlit_ui,
parameters : [
{
type : sampler2d,
name : BaseColor
}
],
requires : [
uv0,
color
],
shadingModel : unlit,
culling : none,
depthCulling: false,
blending : transparent
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
vec2 uv = getUV0();
uv.y = 1.0 - uv.y;
vec4 tex_color = texture(materialParams_BaseColor, uv);
material.baseColor = getColor() * tex_color;
material.baseColor.rgb *= material.baseColor.a;
}
}
+650
View File
@@ -0,0 +1,650 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/builtins.h"
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <numbers>
#include <utility>
#include <vector>
#include <filament/Engine.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include "render/filament/core/mesh.h"
#include "render/filament/mjrfilament.h"
#include "render/filament/support/filament_util.h"
namespace mujoco {
using filament::math::float2;
using filament::math::float3;
using filament::math::float4;
static constexpr size_t kNumVerticesPerTriangle = 3;
static constexpr size_t kNumVerticesPerQuad = 4;
static constexpr size_t kNumIndicesPerTriangle = 3;
static constexpr size_t kNumIndicesPerQuad = 6;
static void AppendQuadIndices(std::vector<uint16_t>& vec, uint16_t a,
uint16_t b, uint16_t c, uint16_t d) {
vec.push_back(a);
vec.push_back(b);
vec.push_back(c);
vec.push_back(a);
vec.push_back(c);
vec.push_back(d);
}
static std::size_t NumVerticesPerSide(int num_quads_per_axis) {
return (num_quads_per_axis + 1) * (num_quads_per_axis + 1);
}
static std::size_t NumIndicesPerSide(int num_quads_per_axis) {
return kNumIndicesPerQuad * num_quads_per_axis * num_quads_per_axis;
}
class BuiltinBuilder : public mjrfMeshData {
public:
BuiltinBuilder() { mjrf_defaultMeshData(this); }
virtual ~BuiltinBuilder() = default;
template <typename T, typename... Args>
static std::unique_ptr<Mesh> Create(filament::Engine* engine,
Args&&... args) {
auto builder = new T(std::forward<Args>(args)...);
mjrfMeshData* mesh_data = builder->PrepareMeshData();
mesh_data->release = +[](void* user_data) {
delete static_cast<BuiltinBuilder*>(user_data);
};
mesh_data->user_data = builder;
return std::make_unique<Mesh>(engine, *mesh_data);
}
mjrfMeshData* PrepareMeshData() {
// Update the `mjrfMeshData` fields.
nattributes = 2;
attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION;
attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3;
attributes[0].bytes = reinterpret_cast<const void*>(positions_.data());
attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS;
attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4;
attributes[1].bytes = reinterpret_cast<const void*>(orientations_.data());
nvertices = positions_.size();
indices = indices_.data();
nindices = indices_.size();
index_type = mjINDEX_TYPE_U16;
return this;
}
protected:
void SetBounds(const float3& min, const float3& max) {
bounds_min[0] = min.x;
bounds_min[1] = min.y;
bounds_min[2] = min.z;
bounds_max[0] = max.x;
bounds_max[1] = max.y;
bounds_max[2] = max.z;
}
std::vector<float3> positions_;
std::vector<float4> orientations_;
std::vector<uint16_t> indices_;
};
class LineBuilder : public BuiltinBuilder {
public:
LineBuilder() {
primitive_type = mjMESH_PRIMITIVE_TYPE_LINES;
positions_.reserve(2);
positions_.emplace_back(0, 0, 0);
positions_.emplace_back(0, 0, 1);
orientations_.resize(positions_.size(), {0, 0, 0, 1});
indices_.reserve(2);
indices_.push_back(0);
indices_.push_back(1);
SetBounds({0, 0, 0}, {0, 0, 1});
}
};
class PlaneBuilder : public BuiltinBuilder {
public:
explicit PlaneBuilder(int num_quads_per_axis) {
const int num_vertices = NumVerticesPerSide(num_quads_per_axis);
positions_.reserve(num_vertices);
const float delta = 2.0f / num_quads_per_axis;
for (int x = 0; x <= num_quads_per_axis; ++x) {
for (int y = 0; y <= num_quads_per_axis; ++y) {
const float dx = delta * static_cast<float>(x);
const float dy = delta * static_cast<float>(y);
positions_.emplace_back(dx - 1.0f, dy - 1.0f, 0);
}
}
orientations_.resize(positions_.size(), CalculateOrientation({0, 0, 1}));
const int num_indices = NumIndicesPerSide(num_quads_per_axis);
indices_.reserve(num_indices);
for (int x = 0; x < num_quads_per_axis; ++x) {
for (int y = 0; y < num_quads_per_axis; ++y) {
const int base_idx = x * (num_quads_per_axis + 1) + y;
const int i0 = base_idx + 0;
const int i1 = base_idx + 1;
const int i2 = base_idx + num_quads_per_axis + 2;
const int i3 = base_idx + num_quads_per_axis + 1;
AppendQuadIndices(indices_, i0, i3, i2, i1);
}
}
SetBounds({-1, -1, -0.001}, {1, 1, 0.001});
}
};
class TriangleBuilder : public BuiltinBuilder {
public:
TriangleBuilder() {
positions_.reserve(3);
positions_.emplace_back(0, 0, 0);
positions_.emplace_back(1, 0, 0);
positions_.emplace_back(0, 1, 0);
orientations_.resize(positions_.size(), CalculateOrientation({0, 0, 1}));
indices_.reserve(3);
indices_.emplace_back(0);
indices_.emplace_back(1);
indices_.emplace_back(2);
SetBounds({-1, -1, -0.001}, {1, 1, 0.001});
}
};
class LineBoxBuilder : public BuiltinBuilder {
public:
explicit LineBoxBuilder() {
primitive_type = mjMESH_PRIMITIVE_TYPE_LINES;
positions_.reserve(8);
positions_.emplace_back(-1.0f, -1.0f, -1.0f);
positions_.emplace_back(1.0f, -1.0f, -1.0f);
positions_.emplace_back(-1.0f, 1.0f, -1.0f);
positions_.emplace_back(1.0f, 1.0f, -1.0f);
positions_.emplace_back(-1.0f, -1.0f, 1.0f);
positions_.emplace_back(1.0f, -1.0f, 1.0f);
positions_.emplace_back(-1.0f, 1.0f, 1.0f);
positions_.emplace_back(1.0f, 1.0f, 1.0f);
orientations_.resize(positions_.size(), {0, 0, 0, 1});
indices_.reserve(24);
indices_.push_back(0);
indices_.push_back(1);
indices_.push_back(1);
indices_.push_back(3);
indices_.push_back(3);
indices_.push_back(2);
indices_.push_back(2);
indices_.push_back(0);
// Top square (where z == 1).
indices_.push_back(4);
indices_.push_back(5);
indices_.push_back(5);
indices_.push_back(7);
indices_.push_back(7);
indices_.push_back(6);
indices_.push_back(6);
indices_.push_back(4);
// Connect edges from bottom to top.
indices_.push_back(2);
indices_.push_back(6);
indices_.push_back(3);
indices_.push_back(7);
indices_.push_back(0);
indices_.push_back(4);
indices_.push_back(1);
indices_.push_back(5);
SetBounds({-1, -1, -1}, {1, 1, 1});
}
};
class BoxBuilder : public BuiltinBuilder {
public:
static constexpr int kNumSides = 6;
explicit BoxBuilder(int num_quads_per_axis)
: num_quads_per_axis_(num_quads_per_axis) {
quad_size_ = 2.0f / static_cast<float>(num_quads_per_axis_);
const int vertices_per_side = NumVerticesPerSide(num_quads_per_axis_);
const int indices_per_side = NumIndicesPerSide(num_quads_per_axis_);
const int num_vertices = vertices_per_side * kNumSides;
const int num_indices = indices_per_side * kNumSides;
positions_.reserve(num_vertices);
orientations_.reserve(num_vertices);
indices_.reserve(num_indices);
GenerateVerticesForSide({0, 1, 0},
[](float2 pt) { return float3{pt.x, 1.0f, pt.y}; });
GenerateVerticesForSide(
{0, -1, 0}, [](float2 pt) { return float3{pt.x, -1.0f, pt.y}; });
GenerateVerticesForSide({1, 0, 0},
[](float2 pt) { return float3{1.0f, pt.x, pt.y}; });
GenerateVerticesForSide(
{-1, 0, 0}, [](float2 pt) { return float3{-1.0f, pt.x, pt.y}; });
GenerateVerticesForSide({0, 0, 1},
[](float2 pt) { return float3{pt.x, pt.y, 1.0f}; });
GenerateVerticesForSide(
{0, 0, -1}, [](float2 pt) { return float3{pt.x, pt.y, -1.0f}; });
for (int i = 0; i < kNumSides; ++i) {
for (int x = 0; x < num_quads_per_axis_; ++x) {
for (int y = 0; y < num_quads_per_axis_; ++y) {
const int base_idx =
(i * vertices_per_side) + (x * (num_quads_per_axis_ + 1)) + y;
const int i0 = base_idx + 0;
const int i1 = base_idx + 1;
const int i2 = base_idx + num_quads_per_axis_ + 2;
const int i3 = base_idx + num_quads_per_axis_ + 1;
if (i == 2 || i == 1 || i == 4) {
AppendQuadIndices(indices_, i0, i3, i2, i1);
} else {
AppendQuadIndices(indices_, i0, i1, i2, i3);
}
}
}
}
SetBounds({-1, -1, -1}, {1, 1, 1});
}
private:
template <typename F>
void GenerateVerticesForSide(float3 normal, const F& pt_gen) {
float4 orientation = CalculateOrientation(normal);
for (int x = 0; x <= num_quads_per_axis_; ++x) {
for (int y = 0; y <= num_quads_per_axis_; ++y) {
const float dx = -1.0f + (quad_size_ * static_cast<float>(x));
const float dy = -1.0f + (quad_size_ * static_cast<float>(y));
const float3 position = pt_gen({dx, dy});
positions_.push_back(position);
orientations_.push_back(orientation);
}
}
}
int num_quads_per_axis_;
float quad_size_;
};
class TubeBuilder : public BuiltinBuilder {
public:
TubeBuilder(int num_stacks, int num_slices) {
const int num_vertices = num_slices * (num_stacks + 1);
positions_.reserve(num_vertices);
orientations_.reserve(num_vertices);
const float delta_angle = 2.f * std::numbers::pi / (float)num_slices;
const float delta_stack = 2.f / static_cast<float>(num_stacks);
for (int i = 0; i < num_slices; ++i) {
const float angle = static_cast<float>(i) * delta_angle;
const float2 pt{std::cos(angle), std::sin(angle)};
const float4 orientation = CalculateOrientation({pt.x, pt.y, 0});
for (int j = 0; j <= num_stacks; ++j) {
const float z = -1.0f + (static_cast<float>(j) * delta_stack);
positions_.emplace_back(pt.x, pt.y, z);
orientations_.push_back(orientation);
}
}
const int num_indices = kNumIndicesPerQuad * num_slices * num_stacks;
indices_.reserve(num_indices);
const int num_vertices_in_spine = num_stacks + 1;
for (int i = 0; i < num_slices; ++i) {
for (int j = 0; j < num_stacks; ++j) {
const int base_idx = (i * num_vertices_in_spine) + j;
const int i0 = base_idx + 0;
const int i1 = base_idx + 1;
const int i2 = (base_idx + num_stacks + 2) % num_vertices;
const int i3 = (base_idx + num_stacks + 1) % num_vertices;
AppendQuadIndices(indices_, i0, i3, i2, i1);
}
}
SetBounds({-1, -1, -1}, {1, 1, 1});
}
};
class ConeBuilder : public BuiltinBuilder {
public:
ConeBuilder(int num_stacks, int num_slices) {
const int num_vertices =
(num_slices * kNumVerticesPerTriangle) +
((num_stacks - 1) * num_slices * kNumVerticesPerQuad);
positions_.reserve(num_vertices);
orientations_.reserve(num_vertices);
// pole: use triangles
const float delta_angle =
2.0 * std::numbers::pi / static_cast<float>(num_slices);
const float delta_radius = 1.0f / static_cast<float>(num_stacks);
for (int j = 0; j < num_slices; ++j) {
const float angle1 = (j + 0) * delta_angle;
const float angle2 = (j + 1) * delta_angle;
AppendVert(angle1, delta_radius);
AppendVert(angle2, delta_radius);
positions_.emplace_back(0, 0, 1);
orientations_.emplace_back(CalculateOrientation({0, 0, 1}));
}
// the rest: use quads
for (int i = 1; i < num_stacks; ++i) {
const float radius1 = delta_radius * (i + 0);
const float radius2 = delta_radius * (i + 1);
for (int j = 0; j < num_slices; ++j) {
const float angle1 = (j + 0) * delta_angle;
const float angle2 = (j + 1) * delta_angle;
AppendVert(angle1, radius2);
AppendVert(angle2, radius2);
AppendVert(angle2, radius1);
AppendVert(angle1, radius1);
}
}
const int num_indices =
(num_slices * kNumIndicesPerTriangle) +
((num_stacks - 1) * num_slices * kNumIndicesPerQuad);
indices_.reserve(num_indices);
for (int j = 0; j < num_slices * 3; ++j) {
indices_.push_back(j);
}
int quad_idx = num_slices * 3;
for (int i = 1; i < num_stacks; ++i) {
for (int j = 0; j < num_slices; ++j) {
const int i0 = quad_idx + 0;
const int i1 = quad_idx + 1;
const int i2 = quad_idx + 2;
const int i3 = quad_idx + 3;
quad_idx += 4;
AppendQuadIndices(indices_, i0, i1, i2, i3);
}
}
SetBounds({-1, -1, 0}, {1, 1, 1});
}
private:
void AppendVert(float theta, float radius) {
static constexpr float kNormalScale = 0.70710678118f;
const float cz = std::cos(theta);
const float sz = std::sin(theta);
const float3 pt{cz * radius, sz * radius, 1.f - radius};
const float3 n{cz * kNormalScale, sz * kNormalScale, kNormalScale};
positions_.push_back(pt);
orientations_.push_back(CalculateOrientation(n));
}
};
class DiskBuilder : public BuiltinBuilder {
public:
explicit DiskBuilder(int num_slices) {
const int num_vertices = num_slices + 1;
positions_.reserve(num_vertices);
const float delta_angle =
2.0 * std::numbers::pi / static_cast<float>(num_slices);
positions_.push_back({0, 0, 0});
for (int i = 0; i < num_slices; ++i) {
const float angle = static_cast<float>(i) * delta_angle;
const float x = std::cos(angle);
const float y = std::sin(angle);
positions_.push_back({x, y, 0});
}
orientations_.resize(positions_.size(), CalculateOrientation({0, 0, 1}));
const int num_indices = num_slices * kNumVerticesPerTriangle;
indices_.reserve(num_indices);
for (int i = 0; i < num_slices; ++i) {
const int next = i < (num_slices - 1) ? i + 1 : 0;
indices_.push_back(0);
indices_.push_back(1 + i);
indices_.push_back(1 + next);
}
SetBounds({-1, -1, -0.001}, {1, 1, 0.001});
}
};
class SphereBuilder : public BuiltinBuilder {
public:
SphereBuilder(int num_stacks, int num_slices) {
static constexpr uint16_t kNorthPoleIndex = 0;
static constexpr uint16_t kSouthPoleIndex = 1;
const int num_vertices = (num_stacks * num_slices) + 2; // +2 for poles
positions_.reserve(num_vertices);
orientations_.reserve(num_vertices);
const float lat_angle_delta =
std::numbers::pi / static_cast<float>(num_stacks + 1);
const float lon_angle_delta =
2.0 * std::numbers::pi / static_cast<float>(num_slices);
// Add the north and south poles.
AppendVert(0, 0, 1);
AppendVert(0, 0, -1);
// Vertices by latitude.
for (int lat = 0; lat < num_stacks; ++lat) {
// +1 because we handle the north pole (which would be at a lat angle of
// 0-degrees) explicitly.
const float lat_angle = static_cast<float>(lat + 1) * lat_angle_delta;
const float cos_lat_angle = std::cos(lat_angle);
const float sin_lat_angle = std::sin(lat_angle);
const float z = cos_lat_angle;
for (int lon = 0; lon < num_slices; ++lon) {
const float lon_angle = static_cast<float>(lon) * lon_angle_delta;
const float cos_lon_angle = std::cos(lon_angle);
const float sin_lon_angle = std::sin(lon_angle);
const float x = sin_lat_angle * cos_lon_angle;
const float y = sin_lat_angle * sin_lon_angle;
AppendVert(x, y, z);
}
}
const size_t num_tris_polar_cap = num_slices;
const size_t num_quads_body = num_slices * (num_stacks - 1);
const int num_indices = (2 * num_tris_polar_cap * kNumIndicesPerTriangle) +
(num_quads_body * kNumIndicesPerQuad);
indices_.reserve(num_indices);
// The first two vertices are the poles, so the first vertex in the first
// row starts at index 2.
uint16_t row_start = kSouthPoleIndex + 1;
// North polar cap.
for (int lon = 0; lon < num_slices; ++lon) {
const int next = lon < (num_slices - 1) ? lon + 1 : 0;
indices_.push_back(kNorthPoleIndex);
indices_.push_back(row_start + lon);
indices_.push_back(row_start + next);
}
// Latitudinal triangle strips.
for (int lat = 0; lat < num_stacks - 1; lat++) {
const uint16_t north_start = row_start;
const uint16_t south_start = row_start + num_slices;
for (int lon = 0; lon < num_slices; ++lon) {
// The offset to the index that is adjacent to the current index.
const int adjacent = lon < (num_slices - 1) ? lon + 1 : 0;
const int i0 = (north_start + lon);
const int i1 = (south_start + lon);
const int i2 = (south_start + adjacent);
const int i3 = (north_start + adjacent);
AppendQuadIndices(indices_, i0, i1, i2, i3);
}
row_start += num_slices;
}
// South polar cap.
for (int lon = 0; lon < num_slices; ++lon) {
const int adjacent = lon < (num_slices - 1) ? lon + 1 : 0;
indices_.push_back(kSouthPoleIndex);
indices_.push_back(row_start + adjacent);
indices_.push_back(row_start + lon);
}
SetBounds({-1, -1, -1}, {1, 1, 1});
}
private:
void AppendVert(float x, float y, float z) {
const float3 pt{x, y, z};
positions_.push_back(pt);
orientations_.push_back(CalculateOrientation(pt));
}
};
class DomeBuilder : public BuiltinBuilder {
public:
DomeBuilder(int num_stacks, int num_slices) {
static constexpr uint16_t kPoleIndex = 0;
const int num_vertices = (num_stacks * num_slices) + 1; // +1 for poles
positions_.reserve(num_vertices);
orientations_.reserve(num_vertices);
const float lat_angle_delta =
0.5 * std::numbers::pi / static_cast<float>(num_stacks);
const float lon_angle_delta =
2.0 * std::numbers::pi / static_cast<float>(num_slices);
// Add the pole.
AppendVert(0, 0, 1);
// Vertices by latitude.
for (int lat = 0; lat < num_stacks; ++lat) {
// +1 because we handle the north pole (which would be at a lat angle of
// 0-degrees) explicitly.
const float lat_angle = static_cast<float>(lat + 1) * lat_angle_delta;
const float cos_lat_angle = std::cos(lat_angle);
const float sin_lat_angle = std::sin(lat_angle);
const float z = cos_lat_angle;
for (int lon = 0; lon < num_slices; ++lon) {
const float lon_angle = static_cast<float>(lon) * lon_angle_delta;
const float cos_lon_angle = std::cos(lon_angle);
const float sin_lon_angle = std::sin(lon_angle);
const float x = sin_lat_angle * cos_lon_angle;
const float y = sin_lat_angle * sin_lon_angle;
AppendVert(x, y, z);
}
}
const size_t num_tris_polar_cap = num_slices;
const size_t num_quads_body = num_slices * (num_stacks - 1);
const int num_indices = (num_tris_polar_cap * kNumIndicesPerTriangle) +
(num_quads_body * kNumIndicesPerQuad);
indices_.reserve(num_indices);
// The first vertex is the poles, so the first vertex in the first row
// starts at index 1.
uint16_t row_start = kPoleIndex + 1;
// North polar cap.
for (int lon = 0; lon < num_slices; ++lon) {
const int next = lon < (num_slices - 1) ? lon + 1 : 0;
indices_.push_back(kPoleIndex);
indices_.push_back(row_start + lon);
indices_.push_back(row_start + next);
}
// Latitudinal quad strips. The first "stack" was handled above, so we
// only need to iterate over N-1 stacks.
for (int lat = 0; lat < num_stacks - 1; lat++) {
const int north_start = row_start;
const int south_start = row_start + num_slices;
for (int lon = 0; lon < num_slices; ++lon) {
// The offset to the index that is adjacent to the current index.
const int adjacent = lon < (num_slices - 1) ? lon + 1 : 0;
const int i0 = (north_start + lon);
const int i1 = (south_start + lon);
const int i2 = (south_start + adjacent);
const int i3 = (north_start + adjacent);
AppendQuadIndices(indices_, i0, i1, i2, i3);
}
row_start += num_slices;
}
SetBounds({-1, -1, 0}, {1, 1, 1});
}
private:
void AppendVert(float x, float y, float z) {
const float3 pt{x, y, z};
positions_.push_back(pt);
orientations_.push_back(CalculateOrientation(pt));
}
};
Builtins::Builtins(filament::Engine* engine, int nstack, int nslice,
int nquad) {
line_ = BuiltinBuilder::Create<LineBuilder>(engine);
plane_ = BuiltinBuilder::Create<PlaneBuilder>(engine, nquad);
triangle_ = BuiltinBuilder::Create<TriangleBuilder>(engine);
box_ = BuiltinBuilder::Create<BoxBuilder>(engine, nquad);
line_box_ = BuiltinBuilder::Create<LineBoxBuilder>(engine);
sphere_ = BuiltinBuilder::Create<SphereBuilder>(engine, nstack, nslice);
tube_ = BuiltinBuilder::Create<TubeBuilder>(engine, nstack, nslice);
disk_ = BuiltinBuilder::Create<DiskBuilder>(engine, nslice);
dome_ = BuiltinBuilder::Create<DomeBuilder>(engine, nstack, nslice);
cone_ = BuiltinBuilder::Create<ConeBuilder>(engine, nstack, nslice);
}
const Mesh* Builtins::Line() { return line_.get(); }
const Mesh* Builtins::LineBox() { return line_box_.get(); }
const Mesh* Builtins::Plane() { return plane_.get(); }
const Mesh* Builtins::Triangle() { return triangle_.get(); }
const Mesh* Builtins::Box() { return box_.get(); }
const Mesh* Builtins::Sphere() { return sphere_.get(); }
const Mesh* Builtins::Cone() { return cone_.get(); }
const Mesh* Builtins::Disk() { return disk_.get(); }
const Mesh* Builtins::Dome() { return dome_.get(); }
const Mesh* Builtins::Tube() { return tube_.get(); }
} // namespace mujoco
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_BUILTINS_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_BUILTINS_H_
#include <memory>
#include <filament/Engine.h>
#include "render/filament/core/mesh.h"
namespace mujoco {
// A collection of meshes that "built in" to the renderer.
class Builtins {
public:
Builtins(filament::Engine* engine, int nstack, int nslice, int nquad);
// Returns a mesh for the corresponding built-in shape.
const Mesh* Line();
const Mesh* LineBox();
const Mesh* Plane();
const Mesh* Triangle();
const Mesh* Box();
const Mesh* Sphere();
const Mesh* Cone();
const Mesh* Disk();
const Mesh* Dome();
const Mesh* Tube();
private:
std::unique_ptr<Mesh> line_;
std::unique_ptr<Mesh> line_box_;
std::unique_ptr<Mesh> plane_;
std::unique_ptr<Mesh> triangle_;
std::unique_ptr<Mesh> box_;
std::unique_ptr<Mesh> sphere_;
std::unique_ptr<Mesh> cone_;
std::unique_ptr<Mesh> disk_;
std::unique_ptr<Mesh> dome_;
std::unique_ptr<Mesh> tube_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_BUILTINS_H_
@@ -0,0 +1,48 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/color_grading_options.h"
#include <memory>
#include <filament/ToneMapper.h>
#include <mujoco/mujoco.h>
namespace mujoco {
std::unique_ptr<filament::ToneMapper> CreateToneMapper(ToneMapperType type) {
filament::ToneMapper* ptr = nullptr;
switch (type) {
case ToneMapperType::kPBRNeutral:
ptr = new filament::PBRNeutralToneMapper();
break;
case ToneMapperType::kACES:
ptr = new filament::ACESToneMapper();
break;
case ToneMapperType::kACESLegacy:
ptr = new filament::ACESLegacyToneMapper();
break;
case ToneMapperType::kFilmic:
ptr = new filament::FilmicToneMapper();
break;
case ToneMapperType::kLinear:
ptr = new filament::LinearToneMapper();
break;
default:
mju_error("Unsupported tone mapper type");
}
return std::unique_ptr<filament::ToneMapper>(ptr);
}
} // namespace mujoco
@@ -0,0 +1,81 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_COLOR_GRADING_OPTIONS_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_COLOR_GRADING_OPTIONS_H_
#include <cstdint>
#include <memory>
#include <filament/ColorGrading.h>
#include <filament/ToneMapper.h>
#include <math/vec3.h>
#include <math/vec4.h>
namespace mujoco {
// Filament has deprecated its ToneMapper enum. Instead, users can define their
// own ToneMapper objects directly. However, we don't want to expose that
// complexity to users of this library, so we define our own enum which can
// then be used to create predefined ToneMapper objects.
enum class ToneMapperType {
kPBRNeutral,
kACES,
kACESLegacy,
kFilmic,
kLinear,
};
std::unique_ptr<filament::ToneMapper> CreateToneMapper(ToneMapperType type);
// Structure used to initialize a filament::ColorGrading::Builder object.
// Unlike most other post processing options, the ColorGrading isn't defined
// as a struct, but rather as a write-only class with a Builder. However, we
// want to be able to change these values at runtime, so we define our own
// struct from which we can create new ColorGrading objects.
//
// The default values for this struct are chosen to match the default values
// for the Filament ColorGrading::Builder class.
struct ColorGradingOptions {
ToneMapperType tone_mapper = ToneMapperType::kPBRNeutral;
filament::ColorGrading::LutFormat format =
filament::ColorGrading::LutFormat::INTEGER;
uint8_t dimension = 32;
bool luminance_scaling = false;
bool gamut_mapping = false;
float exposure = 0.0f;
float night_adaptation = 0.0f;
float contrast = 1.0f;
float vibrance = 1.0f;
float saturation = 1.0f;
float temperature = 0.0f;
float tint = 0.0f;
filament::math::float3 out_red = {1.0f, 0.0f, 0.0f};
filament::math::float3 out_green = {0.0f, 1.0f, 0.0f};
filament::math::float3 out_blue = {0.0f, 0.0f, 1.0f};
filament::math::float4 shadows = {1.0f, 1.0f, 1.0f, 1.0f};
filament::math::float4 midtones = {1.0f, 1.0f, 1.0f, 1.0f};
filament::math::float4 highlights = {1.0f, 1.0f, 1.0f, 1.0f};
filament::math::float4 tonal_ranges = {0.0f, 0.333f, 0.550f, 1.0f};
filament::math::float3 slope = {1.0f};
filament::math::float3 offset = {0.0f};
filament::math::float3 power = {1.0f};
filament::math::float3 shadow_gamma = {1.0f};
filament::math::float3 mid_point = {1.0f};
filament::math::float3 highlight_scale = {1.0f};
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_COLOR_GRADING_OPTIONS_H_
@@ -0,0 +1,251 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/filament_context.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <span>
#include <unordered_map>
#include <utility>
#include <vector>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/IndirectLight.h>
#include <filament/LightManager.h>
#include <filament/Material.h>
#include <filament/RenderTarget.h>
#include <filament/RenderableManager.h>
#include <filament/Renderer.h>
#include <filament/Skybox.h>
#include <filament/Texture.h>
#include <filament/View.h>
#include <math/vec4.h>
#include <utils/FixedCapacityVector.h>
#include <utils/compiler.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/filament_platform_factory.h"
#include "render/filament/core/material_manager.h"
#include "render/filament/core/object_manager.h"
#include "render/filament/core/render_target.h"
#include "render/filament/core/scene_view.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
FilamentContext::FilamentContext(const mjrFilamentConfig* config)
: config_(*config) {
FilamentPlatformSetup setup = CreateFilamentPlatform(config_);
platform_ = std::move(setup.platform);
filament::Engine::Builder engine_builder;
engine_builder.backend(setup.backend);
engine_builder.platform(platform_.get());
engine_builder.feature("backend.disable_parallel_shader_compile",
setup.disable_parallel_shader_compile);
engine_ = engine_builder.build();
renderer_ = engine_->createRenderer();
#ifdef __EMSCRIPTEN__
window_swap_chain_ = engine_->createSwapChain(nullptr);
#else
if (config_.native_window) {
window_swap_chain_ = engine_->createSwapChain(config_.native_window);
}
#endif
object_manager_ = std::make_unique<ObjectManager>(engine_);
material_manager_ = std::make_unique<MaterialManager>(object_manager_.get());
}
FilamentContext::~FilamentContext() {
material_manager_.reset();
object_manager_.reset();
engine_->destroy(renderer_);
engine_->destroy(window_swap_chain_);
engine_->destroy(offscreen_swap_chain_);
filament::Engine::destroy(engine_);
}
mjrfFrameHandle FilamentContext::Render(
std::span<const mjrfRenderRequest> requests,
std::span<const mjrfReadPixelsRequest> read_requests) {
if (read_requests.size() > 1) {
mju_error("Only one read request is supported for now.");
}
if constexpr (UTILS_HAS_THREADING) {
// Wait until previous frame is completed before requesting a new frame.
engine_->flushAndWait();
}
ValidateSwapChains(requests);
material_manager_->BeginFrame();
std::unordered_map<mjrfScene*, std::vector<const mjrfRenderRequest*>>
scene_to_requests;
for (const mjrfRenderRequest& request : requests) {
scene_to_requests[request.scene].push_back(&request);
}
for (auto& [scene, requests] : scene_to_requests) {
SceneView::downcast(scene)->PrepareToRender(requests);
}
bool render_began = false;
mjrfRenderTarget* current_target = nullptr;
for (const mjrfRenderRequest& request : requests) {
if (request.target != current_target && render_began) {
renderer_->endFrame();
render_began = false;
}
current_target = request.target;
if (current_target == nullptr) {
if (!read_requests.empty()) {
mju_error("Cannot read pixels from the window.");
}
if (!render_began) {
render_began = renderer_->beginFrame(window_swap_chain_);
}
if (!render_began) {
break;
}
if (render_began) {
SceneView* scene_view = SceneView::downcast(request.scene);
scene_view->Render(renderer_, request);
}
} else {
if (read_requests.empty()) {
mju_error(
"Rendering to a render target without a read request is "
"pointless.");
}
const mjrfReadPixelsRequest& read_request = read_requests[0];
if (read_request.num_bytes == 0) {
mju_error("Output buffer size is zero.");
}
if (!render_began) {
render_began = renderer_->beginFrame(offscreen_swap_chain_);
}
if (!render_began) {
break;
}
if (render_began) {
SceneView* scene_view = SceneView::downcast(request.scene);
scene_view->Render(renderer_, request);
RenderTarget* render_target = RenderTarget::downcast(request.target);
render_target->ReadColorPixels(renderer_, (uint8_t*)read_request.output,
read_request.num_bytes);
}
}
}
if (render_began) {
renderer_->endFrame();
material_manager_->EndFrame();
}
if constexpr (!UTILS_HAS_THREADING) {
engine_->execute();
}
if (!read_requests.empty()) {
engine_->flushAndWait();
if (read_requests[0].read_completed) {
read_requests[0].read_completed(read_requests[0].user_data);
}
}
return ++frame_counter_;
}
void FilamentContext::WaitForFrame(mjrfFrameHandle frame_handle) {
if (frame_counter_ < frame_handle) {
engine_->flushAndWait();
}
}
void FilamentContext::SetClearColor(const filament::math::float4& color) {
filament::Renderer::ClearOptions opts;
opts.clear = true;
opts.discard = true;
opts.clearColor = color;
renderer_->setClearOptions(opts);
}
void FilamentContext::GetFrameStats(mjrfFrameHandle frame,
mjrfFrameStats* stats_out) const {
utils::FixedCapacityVector<filament::Renderer::FrameInfo> frame_info =
renderer_->getFrameInfoHistory(1);
if (!frame_info.empty()) {
const int64_t ns = frame_info[0].denoisedGpuFrameDuration;
stats_out->frame_rate = 1.0e9 / static_cast<double>(ns);
} else {
stats_out->frame_rate = 0.0;
}
}
void FilamentContext::ValidateSwapChains(
std::span<const mjrfRenderRequest> requests) {
// Determine the maximum extents of the requests.
int max_window_width = 0;
int max_window_height = 0;
int max_offscreen_width = 0;
int max_offscreen_height = 0;
for (const mjrfRenderRequest& request : requests) {
const int width = request.viewport.width + request.viewport.left;
const int height = request.viewport.height + request.viewport.bottom;
if (request.target == nullptr) {
max_window_width = std::max(max_window_width, width);
max_window_height = std::max(max_window_height, height);
} else {
max_offscreen_width = std::max(max_offscreen_width, width);
max_offscreen_height = std::max(max_offscreen_height, height);
}
}
if (max_window_width && max_window_height) {
if (max_window_width != window_width_ ||
max_window_height != window_height_) {
engine_->destroy(window_swap_chain_);
window_width_ = max_window_width;
window_height_ = max_window_height;
if (config_.native_window) {
window_swap_chain_ = engine_->createSwapChain(config_.native_window);
} else {
window_swap_chain_ =
engine_->createSwapChain(window_width_, window_height_);
}
}
}
if (max_offscreen_width && max_offscreen_height) {
if (max_offscreen_width != offscreen_width_ ||
max_offscreen_height != offscreen_height_) {
engine_->destroy(offscreen_swap_chain_);
offscreen_width_ = max_offscreen_width;
offscreen_height_ = max_offscreen_height;
offscreen_swap_chain_ =
engine_->createSwapChain(offscreen_width_, offscreen_height_);
}
}
}
} // namespace mujoco
@@ -0,0 +1,95 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_FILAMENT_CONTEXT_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_FILAMENT_CONTEXT_H_
#include <cstdint>
#include <memory>
#include <span>
#include <backend/Platform.h>
#include <filament/Engine.h>
#include <filament/Renderer.h>
#include <filament/SwapChain.h>
#include <math/vec4.h>
#include "render/filament/core/material_manager.h"
#include "render/filament/core/object_manager.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
// Manages the filament::Renderer and provides APIs for rendering scenes.
class FilamentContext : public mjrfContext {
public:
explicit FilamentContext(const mjrFilamentConfig* config);
~FilamentContext();
FilamentContext(const FilamentContext&) = delete;
FilamentContext& operator=(const FilamentContext&) = delete;
// Queues the given render requests for rendering. This function copies the
// necessary data from the requests into the renderer thread and returns
// immediately afterwards. The renderer thread will then perform the actual
// rendering on the GPU. Callers can use WaitForFrame to block until the
// rendering is complete.
mjrfFrameHandle Render(
std::span<const mjrfRenderRequest> render_requests,
std::span<const mjrfReadPixelsRequest> read_requests = {});
// Blocks until the given frame has completed rendering.
void WaitForFrame(mjrfFrameHandle frame_handle);
// Sets the clear color for the renderer.
void SetClearColor(const filament::math::float4& color);
// Returns information about the frame.
void GetFrameStats(mjrfFrameHandle frame, mjrfFrameStats* stats_out) const;
filament::Engine* GetEngine() const { return engine_; }
ObjectManager* GetObjectManager() const { return object_manager_.get(); }
MaterialManager* GetMaterialManager() const {
return material_manager_.get();
}
static FilamentContext* downcast(mjrfContext* context) {
return static_cast<FilamentContext*>(context);
}
static const FilamentContext* downcast(const mjrfContext* context) {
return static_cast<const FilamentContext*>(context);
}
private:
void ValidateSwapChains(std::span<const mjrfRenderRequest> render_requests);
mjrFilamentConfig config_;
filament::Engine* engine_ = nullptr;
filament::Renderer* renderer_ = nullptr;
filament::SwapChain* window_swap_chain_ = nullptr;
filament::SwapChain* offscreen_swap_chain_ = nullptr;
std::unique_ptr<filament::backend::Platform> platform_;
std::unique_ptr<ObjectManager> object_manager_;
std::unique_ptr<MaterialManager> material_manager_;
int window_width_ = 0;
int window_height_ = 0;
int offscreen_width_ = 0;
int offscreen_height_ = 0;
std::uint64_t frame_counter_ = 0;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_FILAMENT_CONTEXT_H_
@@ -0,0 +1,54 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/filament_platform_factory.h"
#include <memory>
#include <backend/Platform.h>
#include <filament/Engine.h>
#include <mujoco/mujoco.h>
#include "render/filament/mjrfilament.h"
namespace mujoco {
static filament::Engine::Backend ResolveBackend(int graphics_api) {
#if defined(__EMSCRIPTEN__)
filament::Engine::Backend backend = filament::Engine::Backend::OPENGL;
#else
filament::Engine::Backend backend = filament::Engine::Backend::VULKAN;
#endif
switch (graphics_api) {
case mjGRAPHICS_API_DEFAULT:
// Use the default based on the platform above.
break;
case mjGRAPHICS_API_OPENGL:
backend = filament::Engine::Backend::OPENGL;
break;
case mjGRAPHICS_API_VULKAN:
backend = filament::Engine::Backend::VULKAN;
break;
default:
mju_error("Unsupported graphics API: %d", graphics_api);
}
return backend;
}
FilamentPlatformSetup CreateFilamentPlatform(const mjrFilamentConfig& config) {
FilamentPlatformSetup setup;
setup.backend = ResolveBackend(config.graphics_api);
return setup;
}
} // namespace mujoco
@@ -0,0 +1,45 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_FILAMENT_PLATFORM_FACTORY_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_FILAMENT_PLATFORM_FACTORY_H_
#include <memory>
#include <backend/Platform.h>
#include <filament/Engine.h>
#include "render/filament/mjrfilament.h"
namespace mujoco {
struct FilamentPlatformSetup {
// The filament::Platform to use for the engine. May be null, in which case
// the engine will be created with a default platform.
std::unique_ptr<filament::backend::Platform> platform = nullptr;
// The filament::Engine::Backend to use for the engine.
filament::Engine::Backend backend;
// Whether to disable parallel shader compilation.
bool disable_parallel_shader_compile = false;
};
// Creates a filament::Platform based on the given config and defines the
// filament::Engine::Backend to use. Also returns additional information for
// setting up the filament::Engine for use with the provided Platform.
FilamentPlatformSetup CreateFilamentPlatform(const mjrFilamentConfig& config);
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_FILAMENT_PLATFORM_FACTORY_H_
+738
View File
@@ -0,0 +1,738 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm>
#include <any>
#include <cstdint>
#include <numbers>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <imgui.h>
#include <filament/ColorGrading.h>
#include <filament/IndirectLight.h>
#include <filament/LightManager.h>
#include <filament/Options.h>
#include <filament/View.h>
#include <math/mathfwd.h>
#include <math/scalar.h>
#include <math/vec3.h>
#include "render/filament/core/color_grading_options.h"
#include "render/filament/core/scene_view.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
using filament::math::float3;
using filament::math::float4;
using filament::math::mat4;
using NameList = std::span<const char*>;
namespace {
using InitValues = std::unordered_map<ImGuiID, std::any>;
// Uses static storage (not heap) for a map that is never destroyed, avoiding
// static destruction order issues. Note this is not causing a problem now but
// may do so in the future.
InitValues& GetInitValues() {
alignas(InitValues) static char buf[sizeof(InitValues)];
static InitValues* map = new (buf) InitValues();
return *map;
}
constexpr ImVec4 kModifiedColor = ImVec4(1.0f, 0.6f, 0.2f, 1.0f);
template <typename T>
bool IsModified(ImGuiID id, const T& current_value) {
auto it = GetInitValues().find(id);
if (it == GetInitValues().end()) {
GetInitValues()[id] = current_value;
return false;
}
const T& a = current_value;
const T& b = std::any_cast<const T&>(it->second);
return a != b;
}
} // namespace
NameList EnumNames(ToneMapperType v) {
static const char* names[] = {"PBR Neutral", "ACES", "ACES Legacy", "Filmic",
"Linear"};
return names;
}
NameList EnumNames(filament::QualityLevel v) {
static const char* names[] = {"LOW", "MEDIUM", "HIGH", "ULTRA"};
return names;
}
NameList EnumNames(filament::ShadowType v) {
static const char* names[] = {"PCF", "VSM", "DPCF", "PCSS"};
return names;
}
NameList EnumNames(filament::TemporalAntiAliasingOptions::BoxType v) {
static const char* names[] = {"AABB", "VARIANCE", "AABB_VARIANCE"};
return names;
}
NameList EnumNames(filament::TemporalAntiAliasingOptions::BoxClipping v) {
static const char* names[] = {"ACCURATE", "CLAMP", "NONE"};
return names;
}
NameList EnumNames(filament::TemporalAntiAliasingOptions::JitterPattern v) {
static const char* names[] = {"RGSS_X4", "UNIFORM_HELIX_X4", "HALTON_23_X8",
"HALTON_23_X16", "HALTON_23_X32"};
return names;
}
NameList EnumNames(filament::ColorGrading::LutFormat v) {
static const char* names[] = {"Integer", "Float"};
return names;
}
NameList EnumNames(filament::BloomOptions::BlendMode v) {
static const char* names[] = {"Add", "Interpolate"};
return names;
}
NameList EnumNames(filament::DepthOfFieldOptions::Filter v) {
static const char* names[] = {"NONE", "UNUSED", "MEDIAN"};
return names;
}
template <typename T>
struct UiOpts {
std::optional<T> min;
std::optional<T> max;
std::optional<T> step;
std::optional<T> fstep;
};
// This is a workaround to fix compilation on gcc <= 12 and clang <= 16
template <typename T>
struct dependent_false : std::false_type {};
template <typename T>
bool Ui(std::string_view label, T* value, UiOpts<T> opts = {}) {
bool changed = false;
bool modified = false;
ImGuiID id = ImGui::GetID(label.data());
if constexpr (std::is_enum_v<T>) {
modified = IsModified(id, static_cast<int>(*value));
} else {
modified = IsModified(id, *value);
}
if (modified) {
ImGui::PushStyleColor(ImGuiCol_Text, kModifiedColor);
}
if constexpr (std::is_enum_v<T>) {
int idx = static_cast<int>(*value);
const NameList names = EnumNames(*value);
changed = ImGui::Combo(label.data(), &idx, names.data(), names.size());
*value = static_cast<T>(idx);
} else if constexpr (std::is_same_v<T, bool>) {
changed = ImGui::Checkbox(label.data(), value);
} else if constexpr (std::is_same_v<T, uint8_t>) {
changed = ImGui::DragScalar(label.data(), ImGuiDataType_U8, value, 1.0f);
} else if constexpr (std::is_same_v<T, uint16_t>) {
changed = ImGui::DragScalar(label.data(), ImGuiDataType_U16, value, 1.0f);
} else if constexpr (std::is_same_v<T, uint32_t>) {
changed = ImGui::DragScalar(label.data(), ImGuiDataType_U32, value, 1.0f);
} else if constexpr (std::is_same_v<T, int>) {
if (opts.min.has_value() && opts.max.has_value()) {
changed = ImGui::SliderInt(label.data(), value, *opts.min, *opts.max);
} else {
float speed = opts.step.value_or(1);
changed = ImGui::DragInt(label.data(), value, speed);
}
} else if constexpr (std::is_same_v<T, float>) {
if (opts.min.has_value() && opts.max.has_value()) {
changed =
ImGui::SliderFloat(label.data(), value, *opts.min, *opts.max, "%.5f");
} else {
float speed = opts.step.value_or(0.01f);
changed =
ImGui::DragFloat(label.data(), value, speed, 0.0f, 0.0f, "%.5f");
}
} else if constexpr (std::is_same_v<T, float3>) {
changed =
ImGui::DragFloat3(label.data(), &value->x, 0.01f, 0.0f, 0.0f, "%.5f");
} else if constexpr (std::is_same_v<T, float4>) {
changed =
ImGui::DragFloat4(label.data(), &value->x, 0.01f, 0.0f, 0.0f, "%.5f");
} else {
static_assert(dependent_false<T>::value, "Unsupported type");
}
if (modified) {
ImGui::PopStyleColor();
ImGui::SetItemTooltip("RMB to revert change");
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
if constexpr (std::is_enum_v<T>) {
*value = static_cast<T>(std::any_cast<int>(GetInitValues()[id]));
} else {
*value = std::any_cast<T>(GetInitValues()[id]);
}
changed = true;
}
}
return changed;
}
void DrawAmbientOcclusionGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto opts = view->getAmbientOcclusionOptions();
constexpr float PI = std::numbers::pi_v<float>;
bool changed = false;
changed |= Ui("Enabled", &opts.enabled);
changed |= Ui("Radius (m)", &opts.radius, {.min = 0.f, .max = 5.f});
changed |= Ui("Power Contrrast", &opts.power, {.min = 0.f, .max = 5.f});
changed |= Ui("Bias (m)", &opts.bias, {.min = 0.f, .max = .001f});
changed |= Ui("Resolution Scale", &opts.resolution, {.min = .5f, .max = 1.f});
changed |= Ui("AO Intensity", &opts.intensity, {.min = 0.f, .max = 5.f});
changed |= Ui("Bend Normals", &opts.bentNormals);
changed |= Ui("Bilateral Threshold", &opts.bilateralThreshold,
{.min = 0.f, .max = 10.f});
changed |= Ui("Min. Horizon Angle (Rad)", &opts.minHorizonAngleRad,
{.min = 0.f, .max = PI / 2});
changed |= Ui("Quality", &opts.quality);
changed |= Ui("Low Pass Filter", &opts.lowPassFilter);
changed |= Ui("Upsampling", &opts.upsampling);
changed |= Ui("SSCT Enabled", &opts.ssct.enabled);
changed |= Ui("SSCT Cone Angle (Rad)", &opts.ssct.lightConeRad,
{.min = 0.0f, .max = PI / 2});
changed |= Ui("SSCT Shadow Distance (m)", &opts.ssct.shadowDistance);
changed |= Ui("SSCT Max Contact (m)", &opts.ssct.contactDistanceMax);
changed |= Ui("SSCT Intensity", &opts.ssct.intensity);
changed |= Ui("SSCT Depth Bias", &opts.ssct.depthBias);
changed |= Ui("SSCT Depth Slope Bias", &opts.ssct.depthSlopeBias);
changed |=
Ui("SSCT Sample Count", &opts.ssct.sampleCount, {.min = 1, .max = 255});
changed |= Ui("SSCT Ray Count", &opts.ssct.rayCount, {.min = 1, .max = 255});
changed |= Ui("SSCT Light Direction", &opts.ssct.lightDirection);
if (changed) {
opts.resolution = (opts.resolution < 0.75f) ? 0.f : 1.0f;
opts.power = std::max(opts.power, 0.0f);
opts.radius = std::clamp(opts.radius, 0.0f, 10.0f);
opts.bias = std::clamp(opts.bias, 0.0f, 0.001f);
opts.ssct.lightConeRad =
std::clamp(opts.ssct.lightConeRad, 0.0f, filament::math::f::PI / 2.0f);
view->setAmbientOcclusionOptions(opts);
}
}
void DrawScreenSpaceGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto reflection = view->getScreenSpaceReflectionsOptions();
bool refraction_enabled = view->isScreenSpaceRefractionEnabled();
auto guard_band = view->getGuardBandOptions();
bool changed = false;
changed |= Ui("Reflection Enabled", &reflection.enabled);
changed |= Ui("Refl. Thickness", &reflection.thickness);
changed |= Ui("Refl. Bias", &reflection.bias);
changed |= Ui("Refl. Max Distance", &reflection.maxDistance);
changed |= Ui("Refl. Stride", &reflection.stride);
if (changed) {
view->setScreenSpaceReflectionsOptions(reflection);
}
if (Ui("Refraction", &refraction_enabled)) {
view->setScreenSpaceRefractionEnabled(refraction_enabled);
}
if (Ui("Guard Band Enabled", &guard_band.enabled)) {
view->setGuardBandOptions(guard_band);
}
}
void DrawShadowingGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto vsm_opts = view->getVsmShadowOptions();
auto soft_opts = view->getSoftShadowOptions();
bool enabled = view->isShadowingEnabled();
if (Ui("Shadowing", &enabled)) {
view->setShadowingEnabled(enabled);
}
auto shadow_type = view->getShadowType();
if (Ui("Shadow Type", &shadow_type)) {
view->setShadowType(shadow_type);
}
bool changed = false;
changed |= Ui("VSM Anisotropy", &vsm_opts.anisotropy);
changed |= Ui("VSM Mipmapping", &vsm_opts.mipmapping);
changed |= Ui("VSM MSAA Samples", &vsm_opts.msaaSamples);
changed |= Ui("VSM High Precision", &vsm_opts.highPrecision);
changed |= Ui("VSM Min Variance Scale", &vsm_opts.minVarianceScale);
changed |= Ui("VSM Light Bleed Reduction", &vsm_opts.lightBleedReduction);
if (changed) {
vsm_opts.minVarianceScale = std::max(vsm_opts.minVarianceScale, 0.0f);
vsm_opts.lightBleedReduction =
std::clamp(vsm_opts.lightBleedReduction, 0.0f, 1.0f);
view->setVsmShadowOptions(vsm_opts);
}
changed = false;
changed |= Ui("Soft Penumbra Scale", &soft_opts.penumbraScale);
changed |= Ui("Soft Penumbra Ratio Scale", &soft_opts.penumbraRatioScale);
if (changed) {
soft_opts.penumbraScale = std::max(soft_opts.penumbraScale, 0.0f);
soft_opts.penumbraRatioScale = std::max(soft_opts.penumbraRatioScale, 1.0f);
view->setSoftShadowOptions(soft_opts);
}
}
void DrawPostProcessingGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
bool enabled = view->isPostProcessingEnabled();
if (Ui("Enabled", &enabled)) {
view->setPostProcessingEnabled(enabled);
}
}
void DrawFxaaGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
bool enabled = view->getAntiAliasing() == filament::AntiAliasing::FXAA;
if (Ui("Enabled", &enabled)) {
view->setAntiAliasing(enabled ? filament::AntiAliasing::FXAA
: filament::AntiAliasing::NONE);
}
}
void DrawMsaaGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto opts = view->getMultiSampleAntiAliasingOptions();
bool changed = false;
changed |= Ui("Enabled", &opts.enabled);
changed |= Ui("Samples", &opts.sampleCount);
changed |= Ui("Custom Resolve", &opts.customResolve);
if (changed) {
view->setMultiSampleAntiAliasingOptions(opts);
}
}
void DrawTaaGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto opts = view->getTemporalAntiAliasingOptions();
bool changed = false;
changed |= Ui("Enabled", &opts.enabled);
changed |= Ui("Filter Width", &opts.filterWidth);
changed |= Ui("Feedback", &opts.feedback, {.min = 0.f, .max = 1.f});
changed |= Ui("Lod Bias", &opts.lodBias);
changed |= Ui("Sharpness", &opts.sharpness);
changed |= Ui("Upscaling", &opts.upscaling);
changed |= Ui("Filter History", &opts.filterHistory);
changed |= Ui("Filter Input", &opts.filterInput);
changed |= Ui("Use YCoCg", &opts.useYCoCg);
changed |= Ui("Box Type", &opts.boxType);
changed |= Ui("Box Clipping", &opts.boxClipping);
changed |= Ui("Jitter Pattern", &opts.jitterPattern);
changed |= Ui("Variance Gamma", &opts.varianceGamma);
changed |= Ui("Prevent Flickering", &opts.preventFlickering);
if (changed) {
opts.feedback = std::clamp(opts.feedback, 0.f, 1.f);
opts.varianceGamma = std::clamp(opts.varianceGamma, 0.75f, 1.25f);
view->setTemporalAntiAliasingOptions(opts);
}
}
void DrawBloomGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto opts = view->getBloomOptions();
bool changed = false;
changed |= Ui("Enabled", &opts.enabled);
changed |= Ui("Strength", &opts.strength);
changed |= Ui("Quality", &opts.quality);
changed |= Ui("Resolution", &opts.resolution);
changed |= Ui("Levels", &opts.levels);
changed |= Ui("Blend Mode", &opts.blendMode);
changed |= Ui("Threshold", &opts.threshold);
changed |= Ui("Highlight", &opts.highlight);
changed |= Ui("Lens Flare", &opts.lensFlare);
changed |= Ui("Starburst", &opts.starburst);
changed |= Ui("Ch. Aberration", &opts.chromaticAberration);
changed |= Ui("Ghost Count", &opts.ghostCount);
changed |= Ui("Ghost Spacing", &opts.ghostSpacing);
changed |= Ui("Ghost Threshold", &opts.ghostThreshold);
changed |= Ui("Halo Radius", &opts.haloRadius);
changed |= Ui("Halo Thickness", &opts.haloThickness);
changed |= Ui("Halo Threshold", &opts.haloThreshold);
if (changed) {
opts.strength = std::clamp(opts.strength, 0.0f, 1.0f);
opts.levels = std::clamp<uint8_t>(opts.levels, 1, 11);
opts.ghostSpacing = std::clamp(opts.ghostSpacing, 0.0f, 1.0f);
opts.highlight = std::max(opts.highlight, 10.0f);
opts.haloThickness = std::clamp(opts.haloThickness, 0.0f, 1.0f);
opts.haloRadius = std::clamp(opts.haloRadius, 0.0f, 0.5f);
view->setBloomOptions(opts);
}
}
void DrawColorGradingGui(SceneView* scene_view) {
auto opts = scene_view->GetColorGradingOptions();
bool changed = false;
changed |= Ui("Tone Mapper", &opts.tone_mapper);
changed |= Ui("Lut Format", &opts.format);
changed |= Ui("Lut Dimension", &opts.dimension);
changed |= Ui("Luminance Scaling", &opts.luminance_scaling);
changed |= Ui("Gamut Mapping", &opts.gamut_mapping);
changed |= Ui("Exposure", &opts.exposure, {.step = 0.01f, .fstep = 0.1f});
changed |= Ui("Night Adaptation", &opts.night_adaptation,
{.step = 0.01f, .fstep = 0.1f});
changed |= Ui("Contrast", &opts.contrast, {.step = 0.01f, .fstep = 0.1f});
changed |= Ui("Vibrance", &opts.vibrance, {.step = 0.01f, .fstep = 0.1f});
changed |= Ui("Saturation", &opts.saturation, {.step = 0.01f, .fstep = 0.1f});
changed |=
Ui("Temperature", &opts.temperature, {.step = 0.01f, .fstep = 0.1f});
changed |= Ui("Tint", &opts.tint, {.step = 0.01f, .fstep = 0.1f});
changed |= Ui("Out Red", &opts.out_red);
changed |= Ui("Out Green", &opts.out_green);
changed |= Ui("Out Blue", &opts.out_blue);
changed |= Ui("Shadows", &opts.shadows);
changed |= Ui("Midtones", &opts.midtones);
changed |= Ui("Highlights", &opts.highlights);
changed |= Ui("Tonal Ranges", &opts.tonal_ranges);
changed |= Ui("Slope", &opts.slope);
changed |= Ui("Offset", &opts.offset);
changed |= Ui("Power", &opts.power);
changed |= Ui("Shadow Gamma", &opts.shadow_gamma);
changed |= Ui("Mid Point", &opts.mid_point);
changed |= Ui("Highlight Scale", &opts.highlight_scale);
opts.contrast = std::clamp(opts.contrast, 0.0f, 2.0f);
opts.vibrance = std::clamp(opts.vibrance, 0.0f, 2.0f);
opts.saturation = std::clamp(opts.saturation, 0.0f, 2.0f);
opts.temperature = std::clamp(opts.temperature, -1.0f, 1.0f);
opts.tint = std::clamp(opts.tint, -1.0f, 1.0f);
opts.slope.x = std::max(0.000001f, opts.slope.x);
opts.slope.y = std::max(0.000001f, opts.slope.y);
opts.slope.z = std::max(0.000001f, opts.slope.z);
opts.power.x = std::max(0.000001f, opts.power.x);
opts.power.y = std::max(0.000001f, opts.power.y);
opts.power.z = std::max(0.000001f, opts.power.z);
opts.shadow_gamma.x = std::max(0.000001f, opts.shadow_gamma.x);
opts.shadow_gamma.y = std::max(0.000001f, opts.shadow_gamma.y);
opts.shadow_gamma.z = std::max(0.000001f, opts.shadow_gamma.z);
opts.mid_point.x = std::max(0.000001f, opts.mid_point.x);
opts.mid_point.y = std::max(0.000001f, opts.mid_point.y);
opts.mid_point.z = std::max(0.000001f, opts.mid_point.z);
if (opts.highlight_scale.x == 0.0f) opts.highlight_scale.x = 0.000001f;
if (opts.highlight_scale.y == 0.0f) opts.highlight_scale.y = 0.000001f;
if (opts.highlight_scale.z == 0.0f) opts.highlight_scale.z = 0.000001f;
if (changed) {
scene_view->SetColorGradingOptions(opts);
}
}
void DrawDepthOfFieldGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto opts = view->getDepthOfFieldOptions();
bool changed = false;
changed |= Ui("Enabled", &opts.enabled);
changed |= Ui("Filter", &opts.filter);
changed |= Ui("Max. Aperture", &opts.maxApertureDiameter);
changed |= Ui("Resolution", &opts.nativeResolution);
changed |= Ui("COC Scale", &opts.cocScale);
changed |= Ui("COC Aspect Ratio", &opts.cocAspectRatio);
changed |= Ui("Max COC (Foreground)", &opts.maxForegroundCOC);
changed |= Ui("Max COC (Background)", &opts.maxBackgroundCOC);
changed |= Ui("Foreground Ring Count", &opts.foregroundRingCount);
changed |= Ui("Background Ring Count", &opts.backgroundRingCount);
changed |= Ui("Fast Gather Ring Count", &opts.fastGatherRingCount);
if (changed) {
view->setDepthOfFieldOptions(opts);
}
}
void DrawDitheringGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
bool enabled = view->getDithering() != filament::Dithering::NONE;
if (Ui("Enabled", &enabled)) {
view->setDithering(enabled ? filament::Dithering::TEMPORAL
: filament::Dithering::NONE);
}
}
void DrawFogGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto opts = view->getFogOptions();
bool changed = false;
changed |= Ui("Enabled", &opts.enabled);
changed |= Ui("Color", &opts.color);
changed |= Ui("Use IBL Color", &opts.fogColorFromIbl);
changed |= Ui("Density", &opts.density);
changed |= Ui("Distance", &opts.distance);
changed |= Ui("Cutoff", &opts.cutOffDistance);
changed |= Ui("Max. Opacity", &opts.maximumOpacity);
changed |= Ui("Height", &opts.height);
changed |= Ui("Height Falloff", &opts.heightFalloff);
changed |= Ui("InScattering Start", &opts.inScatteringStart);
changed |= Ui("InScattering Size", &opts.inScatteringSize);
if (changed) {
opts.maximumOpacity = std::clamp(opts.maximumOpacity, 0.0f, 1.0f);
view->setFogOptions(opts);
}
}
void DrawVignetteGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto opts = view->getVignetteOptions();
bool changed = false;
changed |= Ui("Enabled", &opts.enabled);
changed |= Ui("Color", &opts.color);
changed |= Ui("Midpoint", &opts.midPoint);
changed |= Ui("Roundness", &opts.roundness);
changed |= Ui("Feature", &opts.feather);
if (changed) {
opts.midPoint = std::clamp(opts.midPoint, 0.0f, 1.0f);
opts.roundness = std::clamp(opts.roundness, 0.0f, 1.0f);
opts.feather = std::clamp(opts.feather, 0.0f, 1.0f);
view->setVignetteOptions(opts);
}
}
void DrawVisibleLayersGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
uint32_t layers = view->getVisibleLayers();
bool changed = false;
ImGui::Columns(2);
changed |= ImGui::CheckboxFlags("Layer 0", &layers, 1 << 0);
changed |= ImGui::CheckboxFlags("Layer 1", &layers, 1 << 1);
changed |= ImGui::CheckboxFlags("Layer 2", &layers, 1 << 2);
changed |= ImGui::CheckboxFlags("Layer 3", &layers, 1 << 3);
ImGui::NextColumn();
changed |= ImGui::CheckboxFlags("Layer 4", &layers, 1 << 4);
changed |= ImGui::CheckboxFlags("Layer 5", &layers, 1 << 5);
changed |= ImGui::CheckboxFlags("Layer 6", &layers, 1 << 6);
changed |= ImGui::CheckboxFlags("Layer 7", &layers, 1 << 7);
ImGui::Columns(1);
if (changed) {
view->setVisibleLayers(0xff, static_cast<uint8_t>(layers));
}
}
void DrawCameraGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
filament::Camera& camera = view->getCamera();
auto position = float3(camera.getPosition());
Ui("Position", &position);
auto direction = camera.getForwardVector();
Ui("Direction", &direction);
}
void DrawIndirectLightGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto ibl = view->getScene()->getIndirectLight();
float intensity = 10000.0f;
if (ibl) {
intensity = ibl->getIntensity();
if (Ui("Intensity", &intensity, {.step = 1000.0f, .fstep = 10000.0f})) {
ibl->setIntensity(intensity);
}
}
}
void DrawLightGui(filament::LightManager& lm,
filament::LightManager::Instance li) {
auto color = lm.getColor(li);
if (ImGui::ColorEdit3("Color", &color.x)) {
lm.setColor(li, color);
}
auto position = lm.getPosition(li);
if (Ui("Position", &position)) {
lm.setPosition(li, position);
}
auto direction = lm.getDirection(li);
if (Ui("Direction", &direction)) {
lm.setDirection(li, direction);
}
float intensity = lm.getIntensity(li);
if (Ui("Intensity", &intensity, {.step = 1000.0f, .fstep = 10000.0f})) {
lm.setIntensityCandela(li, intensity);
}
float falloff = lm.getFalloff(li);
if (Ui("Falloff Radius", &falloff)) {
lm.setFalloff(li, falloff);
}
if (lm.isSpotLight(li)) {
float spotLightInnerCone = lm.getSpotLightInnerCone(li);
float spotLightOuterCone = lm.getSpotLightOuterCone(li);
if (Ui("Spot Light Inner Cone", &spotLightInnerCone)) {
lm.setSpotLightCone(li, spotLightInnerCone, spotLightOuterCone);
}
if (Ui("Spot Light Outer Cone", &spotLightOuterCone)) {
lm.setSpotLightCone(li, spotLightInnerCone, spotLightOuterCone);
}
}
ImGui::Columns(2);
for (int i = 0; i < 8; ++i) {
std::string name = "Channel " + std::to_string(i);
bool enabled = lm.getLightChannel(li, i);
if (i == 4) {
ImGui::NextColumn();
}
if (Ui(name, &enabled)) {
lm.setLightChannel(li, i, enabled);
}
}
ImGui::Columns(1);
if (ImGui::TreeNodeEx("Shadow Options")) {
bool enabled = lm.isShadowCaster(li);
if (Ui("Enabled", &enabled)) {
lm.setShadowCaster(li, enabled);
}
filament::LightManager::ShadowOptions opts = lm.getShadowOptions(li);
bool changed = false;
changed |= Ui("Stable", &opts.stable);
changed |= Ui("LiSPSM", &opts.lispsm);
changed |= Ui("SS Contact Shadows", &opts.screenSpaceContactShadows);
changed |= Ui("Map Size", &opts.mapSize);
changed |= Ui("Bulb Radius", &opts.shadowBulbRadius);
changed |= Ui("Step Count", &opts.stepCount);
changed |= Ui("# Cascades", &opts.shadowCascades);
changed |= Ui("Constant Bias", &opts.constantBias);
changed |= Ui("Normal Bias", &opts.normalBias);
changed |= Ui("EL VSM Enabled", &opts.vsm.elvsm);
changed |= Ui("VSM Blur Width", &opts.vsm.blurWidth);
if (changed) {
lm.setShadowOptions(li, opts);
}
ImGui::TreePop();
}
}
} // namespace mujoco
extern "C" {
void mjrf_DEBUG_drawImguiEditor(mjrfScene* scene) {
mujoco::SceneView* scene_view = mujoco::SceneView::downcast(scene);
filament::View* view = scene_view->GetDefaultRenderView();
filament::Engine* engine = scene_view->GetEngine();
filament::LightManager& lm = engine->getLightManager();
if (ImGui::TreeNodeEx("Ambient Occlusion")) {
mujoco::DrawAmbientOcclusionGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Screen Space")) {
mujoco::DrawScreenSpaceGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Shadowing")) {
mujoco::DrawShadowingGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Post Processing")) {
mujoco::DrawPostProcessingGui(scene_view);
if (ImGui::TreeNodeEx("Anti Aliasing (FXAA)")) {
mujoco::DrawFxaaGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Anti Aliasing (MSAA)")) {
mujoco::DrawMsaaGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Anti Aliasing (Temporal)")) {
mujoco::DrawTaaGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Bloom")) {
mujoco::DrawBloomGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Color Grading")) {
mujoco::DrawColorGradingGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Depth of Field")) {
mujoco::DrawDepthOfFieldGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Dithering")) {
mujoco::DrawDitheringGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Fog")) {
mujoco::DrawFogGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Vignette")) {
mujoco::DrawVignetteGui(scene_view);
ImGui::TreePop();
}
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Visibility Layers")) {
mujoco::DrawVisibleLayersGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Camera")) {
mujoco::DrawCameraGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Lights")) {
if (ImGui::TreeNodeEx("Indirect (Image-based) Light")) {
mujoco::DrawIndirectLightGui(scene_view);
ImGui::TreePop();
}
view->getScene()->forEach([&](utils::Entity entity) {
auto li = lm.getInstance(entity);
if (!li.isValid()) {
return;
}
const char* type = lm.isDirectional(li) ? " (D)"
: lm.isPointLight(li) ? " (P)"
: " (S)";
const std::string name = "Light " + std::to_string(entity.getId()) + type;
if (ImGui::TreeNodeEx(name.c_str())) {
mujoco::DrawLightGui(lm, li);
ImGui::TreePop();
}
});
ImGui::TreePop();
}
}
} // extern "C"
+198
View File
@@ -0,0 +1,198 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/light.h"
#include <numbers>
#include <filament/Engine.h>
#include <filament/IndirectLight.h>
#include <filament/LightManager.h>
#include <filament/Scene.h>
#include <math/mat3.h>
#include <math/vec3.h>
#include <utils/Entity.h>
#include <utils/EntityManager.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/texture.h"
#include "render/filament/mjrfilament.h"
#include "render/filament/support/filament_util.h"
namespace mujoco {
using filament::math::float3;
using filament::math::mat3f;
Light::Light(filament::Engine* engine, const mjrfLightParams& params)
: engine_(engine), params_(params) {
// Filament treats image-based lights (IBLs) as separate objects (i.e.
// filament::IndirectLight) and so we need to handle IBLs specially.
if (params.type == mjLIGHT_IMAGE) {
filament::IndirectLight::Builder builder;
if (params.texture) {
// Allow null textures for fallback lights.
const Texture* texture = Texture::downcast(params.texture);
builder.reflections(texture->GetFilamentTexture());
const Texture::SphericalHarmonics* spherical_harmonics =
texture->GetSphericalHarmonics();
if (spherical_harmonics != nullptr) {
builder.irradiance(3, *spherical_harmonics);
}
}
builder.intensity(params.intensity);
// Rotate the light to match mujoco's Z-up convention.
builder.rotation(mat3f::rotation(std::numbers::pi / 2, float3{1, 0, 0}));
ibl_ = builder.build(*engine_);
return;
}
filament::LightManager::Type type;
switch (params.type) {
case mjLIGHT_SPOT:
type = filament::LightManager::Type::FOCUSED_SPOT;
break;
case mjLIGHT_DIRECTIONAL:
type = filament::LightManager::Type::DIRECTIONAL;
break;
case mjLIGHT_POINT:
type = filament::LightManager::Type::POINT;
break;
default:
mju_error("Unsupported light type: %d", params.type);
return;
}
filament::LightManager::Builder builder(type);
builder.color(ReadFloat3(params.color));
builder.intensityCandela(params.intensity);
builder.castShadows(params.cast_shadows);
if (type == filament::LightManager::Type::FOCUSED_SPOT) {
builder.spotLightCone(0,
params.spot_cone_angle * std::numbers::pi / 180.0f);
}
if (type != filament::LightManager::Type::DIRECTIONAL) {
builder.falloff(params.range);
}
filament::LightManager::ShadowOptions opts;
opts.mapSize = 4096;
opts.shadowCascades =
type == filament::LightManager::Type::DIRECTIONAL ? 4 : 1;
opts.shadowBulbRadius = params.bulb_radius;
opts.mapSize = params.shadow_map_size;
if (params.vsm_blur_width > 0.0f) {
opts.vsm.elvsm = true;
opts.vsm.blurWidth = params.vsm_blur_width;
}
builder.shadowOptions(opts);
entity_ = utils::EntityManager::get().create();
if (entity_.isNull()) {
mju_error("Failed to create light entity.");
}
builder.build(*engine_, entity_);
}
Light::~Light() noexcept {
if (ibl_) {
engine_->destroy(ibl_);
} else {
utils::EntityManager& em = utils::EntityManager::get();
if (!entity_.isNull()) {
engine_->destroy(entity_);
em.destroy(entity_);
}
}
}
void Light::AddToScene(filament::Scene* scene) {
if (ibl_) {
scene->setIndirectLight(ibl_);
} else {
scene->addEntity(entity_);
}
}
void Light::RemoveFromScene(filament::Scene* scene) {
if (ibl_) {
scene->setIndirectLight(nullptr);
} else {
scene->remove(entity_);
}
}
void Light::SetTransform(filament::math::float3 position,
filament::math::float3 direction) {
if (!ibl_) {
filament::LightManager& lm = engine_->getLightManager();
const filament::LightManager::Instance li = lm.getInstance(entity_);
lm.setPosition(li, position);
lm.setDirection(li, direction);
}
}
void Light::SetColor(const filament::math::float3& color) {
if (!ibl_) {
params_.color[0] = color.r;
params_.color[1] = color.g;
params_.color[2] = color.b;
filament::LightManager& lm = engine_->getLightManager();
const filament::LightManager::Instance li = lm.getInstance(entity_);
lm.setColor(li, color);
}
}
void Light::SetIntensity(float intensity) {
params_.intensity = intensity;
if (ibl_) {
ibl_->setIntensity(intensity);
} else {
filament::LightManager& lm = engine_->getLightManager();
const filament::LightManager::Instance li = lm.getInstance(entity_);
lm.setIntensityCandela(li, intensity);
}
}
void Light::Enable() {
if (!enabled_) {
enabled_ = true;
if (ibl_) {
ibl_->setIntensity(params_.intensity);
} else {
filament::LightManager& lm = engine_->getLightManager();
const filament::LightManager::Instance li = lm.getInstance(entity_);
lm.setLightChannel(li, 0, enabled_);
}
}
}
void Light::Disable() {
if (enabled_) {
enabled_ = false;
if (ibl_) {
ibl_->setIntensity(0.f);
} else {
filament::LightManager& lm = engine_->getLightManager();
const filament::LightManager::Instance li = lm.getInstance(entity_);
lm.setLightChannel(li, 0, enabled_);
}
}
}
mjtLightType Light::GetType() const {
return static_cast<mjtLightType>(params_.type);
}
} // namespace mujoco
+77
View File
@@ -0,0 +1,77 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_LIGHT_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_LIGHT_H_
#include <filament/Engine.h>
#include <filament/Scene.h>
#include <math/vec3.h>
#include <utils/Entity.h>
#include <mujoco/mujoco.h>
#include "render/filament/mjrfilament.h"
namespace mujoco {
// Wrapper around both a "normal" filament Light Entity and a filament
// IndirectLight.
class Light : public mjrfLight {
public:
Light(filament::Engine* engine, const mjrfLightParams& params);
~Light() noexcept;
Light(const Light&) = delete;
Light& operator=(const Light&) = delete;
// Adds this light to the filament Scene.
void AddToScene(filament::Scene* scene);
// Removes this light from the filament Scene.
void RemoveFromScene(filament::Scene* scene);
// Updates this light's position and rotation.
void SetTransform(filament::math::float3 position,
filament::math::float3 direction);
// Sets the color of this light.
void SetColor(const filament::math::float3& color);
// Sets the intensity of this light, in candela.
void SetIntensity(float intensity);
// Returns the type of the light.
mjtLightType GetType() const;
// Enables/disables the light in the scene.
void Enable();
void Disable();
static Light* downcast(mjrfLight* light) {
return static_cast<Light*>(light);
}
static const Light* downcast(const mjrfLight* light) {
return static_cast<const Light*>(light);
}
private:
filament::Engine* engine_ = nullptr;
filament::IndirectLight* ibl_ = nullptr;
utils::Entity entity_;
mjrfLightParams params_;
bool enabled_ = true;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_LIGHT_H_
@@ -0,0 +1,319 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/material_manager.h"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <filament/Color.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/TextureSampler.h>
#include <math/vec4.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/mesh.h"
#include "render/filament/core/object_manager.h"
#include "render/filament/core/texture.h"
#include "render/filament/mjrfilament.h"
#include "render/filament/support/filament_util.h"
namespace mujoco {
template <class T>
static void Combine(uint64_t& seed, const T* v) {
seed ^= std::hash<T>()(*v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
template <typename T>
static uint64_t hash(const T& obj) {
static_assert(std::is_trivially_copyable_v<T>,
"Only trivially copyable types are hashable.");
int num_bytes = sizeof(T);
const std::byte* ptr = reinterpret_cast<const std::byte*>(&obj);
uint64_t seed = 0;
while (num_bytes >= sizeof(uint64_t)) {
Combine(seed, reinterpret_cast<const uint64_t*>(ptr));
ptr += sizeof(uint64_t);
num_bytes -= sizeof(uint64_t);
}
while (num_bytes >= sizeof(uint32_t)) {
Combine(seed, reinterpret_cast<const uint32_t*>(ptr));
ptr += sizeof(uint32_t);
num_bytes -= sizeof(uint32_t);
}
while (num_bytes >= sizeof(uint16_t)) {
Combine(seed, reinterpret_cast<const uint16_t*>(ptr));
ptr += sizeof(uint16_t);
num_bytes -= sizeof(uint16_t);
}
while (num_bytes >= sizeof(uint8_t)) {
Combine(seed, reinterpret_cast<const uint8_t*>(ptr));
ptr += sizeof(uint8_t);
num_bytes -= sizeof(uint8_t);
}
return seed;
}
MaterialManager::MaterialManager(ObjectManager* object_mgr)
: object_mgr_(object_mgr) {}
MaterialManager::~MaterialManager() {
for (auto& [key, instance] : instances_) {
object_mgr_->GetEngine()->destroy(instance);
}
}
void MaterialManager::BeginFrame() { used_keys_.clear(); }
void MaterialManager::EndFrame() {
if (instances_.size() == used_keys_.size()) {
return;
}
for (auto it = instances_.begin(); it != instances_.end();) {
if (!used_keys_.contains(it->first)) {
object_mgr_->GetEngine()->destroy(it->second);
it = instances_.erase(it);
} else {
++it;
}
}
}
static MaterialManager::MaterialKey BuildMaterialKey(
MaterialManager::MaterialType material_type, mjtGeom geom_type,
const mjrfMaterial& material) {
// Normally, hashing the struct by memory would be a problem because of
// padding and other uninitialized data. However, we do a memset(0) on the
// entire structure in mjr_defaultMaterial so this should be safe.
uint64_t key = hash(material);
Combine(key, &geom_type);
Combine(key, &material_type);
return key;
}
MaterialManager::MaterialType MaterialManager::GetMaterialType(
const mjrfMaterial& material, const Mesh* mesh) {
if (material.decor_ux) {
if (material.color_texture) {
return ObjectManager::kUnlitUi;
} else {
return ObjectManager::kUnlitDecor;
}
} else if (material.orm_texture) {
if (material.opacity_texture) {
return ObjectManager::kPbrPackedTransparent;
} else {
return ObjectManager::kPbrPacked;
}
} else if (material.metallic_texture) {
if (material.opacity_texture) {
return ObjectManager::kPbrPackedTransparent;
} else {
return ObjectManager::kPbr;
}
} else if (material.roughness_texture) {
if (material.color[3] < 1.0f) {
return ObjectManager::kPbrTransparent;
} else {
return ObjectManager::kPbr;
}
} else if (material.metallic >= 0) {
if (material.color[3] < 1.0f) {
return ObjectManager::kPbrTransparent;
} else {
return ObjectManager::kPbr;
}
} else if (material.roughness >= 0) {
if (material.color[3] < 1.0f) {
return ObjectManager::kPbrTransparent;
} else {
return ObjectManager::kPbr;
}
}
// Check to see if we're dealing with a mesh with texture coordinates.
// `data_id` is the id of the mesh in model (i.e. the geom has mesh
// geometry) and `mesh_texcoordadr` stores the address of the mesh uvs if
// it has them.
const Texture* color_texture = Texture::downcast(material.color_texture);
const bool has_texcoords =
mesh ? mesh->HasVertexAttribute(mjVERTEX_ATTRIBUTE_USAGE_UV) : false;
if (color_texture == nullptr) {
if (material.color[3] < 1.0f) {
return ObjectManager::kPhongColorFade;
} else if (material.reflectance > 0) {
return ObjectManager::kPhongColorReflect;
} else {
return ObjectManager::kPhongColor;
}
} else if (color_texture->GetSamplerType() == mjTEXTURE_CUBE) {
if (material.color[3] < 1.0f) {
return ObjectManager::kPhongCubeFade;
} else if (material.reflectance > 0) {
return ObjectManager::kPhongCubeReflect;
} else {
return ObjectManager::kPhongCube;
}
} else if (has_texcoords) {
if (material.color[3] < 1.0f) {
return ObjectManager::kPhong2dUvFade;
} else if (material.reflectance > 0) {
return ObjectManager::kPhong2dUvReflect;
} else {
return ObjectManager::kPhong2dUv;
}
} else {
if (material.color[3] < 1.0f) {
return ObjectManager::kPhong2dFade;
} else if (material.reflectance > 0) {
return ObjectManager::kPhong2dReflect;
} else {
return ObjectManager::kPhong2d;
}
}
}
MaterialManager::MaterialKey MaterialManager::PrepareMaterialInstance(
const mjrfMaterial& material, mjrDrawMode draw_mode, mjtGeom geom_type,
const Mesh* mesh) {
ObjectManager::MaterialType type;
if (material.selected) {
type = ObjectManager::kOutlineFlatten;
} else if (draw_mode == mjDRAW_MODE_DEPTH) {
type = ObjectManager::kUnlitDepth;
} else if (draw_mode == mjDRAW_MODE_SEGMENTATION_BY_ID) {
type = ObjectManager::kUnlitSegmentation;
} else if (draw_mode == mjDRAW_MODE_SEGMENTATION_BY_COLOR) {
type = ObjectManager::kUnlitSegmentation;
} else {
type = GetMaterialType(material, mesh);
}
const MaterialKey key = BuildMaterialKey(type, geom_type, material);
auto it = instances_.find(key);
if (it == instances_.end()) {
filament::MaterialInstance* instance =
object_mgr_->GetMaterial(type)->createInstance();
UpdateMaterialInstance(instance, material);
if (geom_type == mjGEOM_PLANE || geom_type == mjGEOM_TRIANGLE) {
instance->setCullingMode(filament::MaterialInstance::CullingMode::NONE);
}
instances_[key] = instance;
}
used_keys_.insert(key);
return key;
}
filament::MaterialInstance* MaterialManager::GetInstance(MaterialKey key) {
auto it = instances_.find(key);
if (it == instances_.end()) {
return nullptr;
}
return it->second;
}
void MaterialManager::UpdateMaterialInstance(
filament::MaterialInstance* instance, const mjrfMaterial& material) {
if (material.scissor[2] != 0 && material.scissor[3] != 0) {
instance->setScissor(material.scissor[0], material.scissor[1],
material.scissor[2], material.scissor[3]);
}
const filament::Material* fmaterial = instance->getMaterial();
if (fmaterial->hasParameter("BaseColorFactor")) {
instance->setParameter("BaseColorFactor", filament::RgbaType::sRGB,
ReadFloat4(material.color));
}
if (fmaterial->hasParameter("SegmentationColor")) {
const uint8_t r = material.segmentation_id >> 0;
const uint8_t g = material.segmentation_id >> 8;
const uint8_t b = material.segmentation_id >> 16;
const filament::math::float4 color{static_cast<float>(r) / 255.0f,
static_cast<float>(g) / 255.0f,
static_cast<float>(b) / 255.0f, 1.0f};
instance->setParameter("SegmentationColor", filament::RgbaType::LINEAR,
color);
}
if (fmaterial->hasParameter("EmissiveFactor")) {
instance->setParameter("EmissiveFactor", material.emissive);
}
if (fmaterial->hasParameter("SpecularFactor")) {
instance->setParameter("SpecularFactor", material.specular);
}
if (fmaterial->hasParameter("GlossinessFactor")) {
instance->setParameter("GlossinessFactor", material.glossiness);
}
if (fmaterial->hasParameter("MetallicFactor")) {
instance->setParameter("MetallicFactor",
material.metallic >= 0 ? material.metallic : 1.0f);
}
if (fmaterial->hasParameter("RoughnessFactor")) {
instance->setParameter("RoughnessFactor",
material.roughness >= 0 ? material.roughness : 1.0f);
}
if (fmaterial->hasParameter("UvScale")) {
instance->setParameter("UvScale", ReadFloat3(material.uv_scale));
}
if (fmaterial->hasParameter("UvOffset")) {
instance->setParameter("UvOffset", ReadFloat3(material.uv_offset));
}
if (fmaterial->hasParameter("Reflectance")) {
instance->setParameter("Reflectance", material.reflectance);
}
// All textures use the same default sampler.
filament::TextureSampler sampler;
sampler.setWrapModeR(filament::TextureSampler::WrapMode::REPEAT);
sampler.setWrapModeS(filament::TextureSampler::WrapMode::REPEAT);
sampler.setWrapModeT(filament::TextureSampler::WrapMode::REPEAT);
sampler.setMagFilter(filament::TextureSampler::MagFilter::LINEAR);
sampler.setMinFilter(
filament::TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR);
auto TrySetTexture = [&](const char* name, const mjrfTexture* texture,
mjtTextureRole role) {
if (fmaterial->hasParameter(name)) {
if (texture != nullptr) {
instance->setParameter(
name, Texture::downcast(texture)->GetFilamentTexture(), sampler);
} else {
instance->setParameter(name, object_mgr_->GetFallbackTexture(role),
sampler);
}
}
};
TrySetTexture("BaseColor", material.color_texture, mjTEXROLE_RGB);
TrySetTexture("Opacity", material.opacity_texture, mjTEXROLE_OPACITY);
TrySetTexture("Normal", material.normal_texture, mjTEXROLE_NORMAL);
TrySetTexture("Metallic", material.metallic_texture, mjTEXROLE_METALLIC);
TrySetTexture("Roughness", material.roughness_texture, mjTEXROLE_ROUGHNESS);
TrySetTexture("Occlusion", material.occlusion_texture, mjTEXROLE_OCCLUSION);
TrySetTexture("ORM", material.orm_texture, mjTEXROLE_ORM);
TrySetTexture("Emissive", material.emissive_texture, mjTEXROLE_EMISSIVE);
TrySetTexture("Reflection", material.reflection_texture, mjTEXROLE_USER);
}
ObjectManager* MaterialManager::GetObjectManager() const { return object_mgr_; }
filament::Engine* MaterialManager::GetEngine() const {
return object_mgr_->GetEngine();
}
} // namespace mujoco
@@ -0,0 +1,89 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_MATERIAL_MANAGER_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_MATERIAL_MANAGER_H_
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
#include <filament/Engine.h>
#include <filament/MaterialInstance.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/mesh.h"
#include "render/filament/core/object_manager.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
// Manages the filament MaterialInstances used by Renderables.
//
// Centralizing the MaterialInstance management allows multiple Renderables to
// share the same MaterialInstance, which can reduce GPU overhead.
// MaterialInstance uniqueness is determined by hashing the parameters (mainly
// the mjrfMaterial) used to create the instance.
//
// Any unused MaterialInstances are destroyed at the end of each frame.
class MaterialManager {
public:
using MaterialType = ObjectManager::MaterialType;
explicit MaterialManager(ObjectManager* object_mgr);
~MaterialManager();
MaterialManager(const MaterialManager&) = delete;
MaterialManager& operator=(const MaterialManager&) = delete;
// A key used to uniquely identify a MaterialInstance.
using MaterialKey = uint64_t;
// Marks the beginning of a new frame, allowing us to track which
// MaterialInstances are used during the frame so they can be removed at the
// end of the frame.
void BeginFrame();
// Removes any unused MaterialInstances.
void EndFrame();
// Returns a MaterialType that best matches the given material data and mesh.
MaterialType GetMaterialType(const mjrfMaterial& material, const Mesh* mesh);
// Prepares a MaterialInstance based on the given parameters if one does not
// already exist. Returns the key associated with the MaterialInstance.
MaterialKey PrepareMaterialInstance(const mjrfMaterial& material,
mjrDrawMode draw_mode, mjtGeom geom_type,
const Mesh* mesh);
// Returns the MaterialInstance associated with the given key.
filament::MaterialInstance* GetInstance(MaterialKey key);
ObjectManager* GetObjectManager() const;
filament::Engine* GetEngine() const;
private:
// Updates the material instance using the given parameters and texture
// data. In some cases where a material needs a texture, but a specific
// texture is not provided, a default texture from the ObjectManager will be
// used instead.
void UpdateMaterialInstance(filament::MaterialInstance* instance,
const mjrfMaterial& material);
ObjectManager* object_mgr_;
std::unordered_map<MaterialKey, filament::MaterialInstance*> instances_;
std::unordered_set<MaterialKey> used_keys_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_MATERIAL_MANAGER_H_
+360
View File
@@ -0,0 +1,360 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/mesh.h"
#include <algorithm>
#include <cfloat>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <mutex>
#include <utility>
#include <backend/BufferDescriptor.h>
#include <filament/Box.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/VertexBuffer.h>
#include <math/TVecHelpers.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mujoco.h>
#include "render/filament/mjrfilament.h"
#include "render/filament/support/filament_util.h"
namespace mujoco {
using filament::math::float3;
using filament::math::float4;
static filament::VertexAttribute GetUsage(const mjrVertexAttribute& attrib) {
switch (attrib.usage) {
case mjVERTEX_ATTRIBUTE_USAGE_POSITION:
return filament::VertexAttribute::POSITION;
case mjVERTEX_ATTRIBUTE_USAGE_NORMAL:
return filament::VertexAttribute::TANGENTS;
case mjVERTEX_ATTRIBUTE_USAGE_TANGENTS:
return filament::VertexAttribute::TANGENTS;
case mjVERTEX_ATTRIBUTE_USAGE_UV:
return filament::VertexAttribute::UV0;
case mjVERTEX_ATTRIBUTE_USAGE_COLOR:
return filament::VertexAttribute::COLOR;
default:
mju_error("Unsupported vertex attribute usage: %d", attrib.usage);
return filament::VertexAttribute::POSITION;
}
}
static filament::VertexBuffer::AttributeType GetType(
const mjrVertexAttribute& attrib) {
switch (attrib.type) {
case mjVERTEX_ATTRIBUTE_TYPE_FLOAT2:
return filament::VertexBuffer::AttributeType::FLOAT2;
case mjVERTEX_ATTRIBUTE_TYPE_FLOAT3:
return filament::VertexBuffer::AttributeType::FLOAT3;
case mjVERTEX_ATTRIBUTE_TYPE_FLOAT4:
return filament::VertexBuffer::AttributeType::FLOAT4;
case mjVERTEX_ATTRIBUTE_TYPE_UBYTE4:
return filament::VertexBuffer::AttributeType::UBYTE4;
default:
mju_error("Unsupported vertex attribute type: %d", attrib.type);
return filament::VertexBuffer::AttributeType::FLOAT3;
}
}
int VertexAttributeTypeSize(const mjrVertexAttribute& attrib) {
switch (attrib.type) {
case mjVERTEX_ATTRIBUTE_TYPE_FLOAT2:
return sizeof(float) * 2;
case mjVERTEX_ATTRIBUTE_TYPE_FLOAT3:
return sizeof(float) * 3;
case mjVERTEX_ATTRIBUTE_TYPE_FLOAT4:
return sizeof(float) * 4;
case mjVERTEX_ATTRIBUTE_TYPE_UBYTE4:
return sizeof(uint8_t) * 4;
default:
mju_error("Unsupported vertex attribute type: %d", attrib.type);
return 0;
}
}
// Fills an index buffer with a basic incrementing sequence.
template <typename T>
int FillSequence(std::byte* buffer, std::size_t num_bytes) {
const T num = num_bytes / sizeof(T);
T* ptr = reinterpret_cast<T*>(buffer);
for (T i = 0; i < num; ++i) {
ptr[i] = i;
}
return num;
}
Mesh::Mesh(filament::Engine* engine, const mjrfMeshData& data)
: engine_(engine), shared_state_(std::make_shared<SharedState>()) {
type_ = data.primitive_type == mjMESH_PRIMITIVE_TYPE_TRIANGLES
? filament::RenderableManager::PrimitiveType::TRIANGLES
: filament::RenderableManager::PrimitiveType::LINES;
// If the user has provided a release callback, then we need to ensure we
// call is when filament is done with the mesh data.
if (data.release) {
shared_state_->callbacks.push_back([=]() { data.release(data.user_data); });
}
BuildVertexBuffer(data);
BuildIndexBuffer(data);
UpdateBounds(data);
}
Mesh::~Mesh() {
ReleaseResources();
if (index_buffer_) {
engine_->destroy(index_buffer_);
}
if (vertex_buffer_) {
engine_->destroy(vertex_buffer_);
}
}
void Mesh::BuildVertexBuffer(const mjrfMeshData& data) {
if (data.nvertices == 0) {
mju_error("mjrfMeshData has no vertices.");
}
// The filament BufferDescriptor callback for releasing the memory.
// We pass a heap-allocated shared_ptr to the shared state as the user data.
auto callback = +[](void* buffer, size_t size, void* user) {
auto* state_ptr = static_cast<std::shared_ptr<SharedState>*>(user);
auto state = *state_ptr;
delete state_ptr;
std::lock_guard<std::mutex> lock(state->mutex);
if (!state->called) {
for (const auto& cb : state->callbacks) {
cb();
}
state->callbacks.clear();
state->called = true;
}
};
// Pointers to specific attributes in the mesh data, used for additional
// validation and processing.
const mjrVertexAttribute* positions = nullptr;
const mjrVertexAttribute* normals = nullptr;
const mjrVertexAttribute* tangents = nullptr;
for (int i = 0; i < data.nattributes; ++i) {
if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_POSITION) {
positions = &data.attributes[i];
} else if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) {
normals = &data.attributes[i];
} else if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_TANGENTS) {
tangents = &data.attributes[i];
}
}
if (!positions) {
mju_error("mjrfMeshData has no positions.");
}
if (data.attributes[0].usage != mjVERTEX_ATTRIBUTE_USAGE_POSITION) {
mju_error("Positions must be the first attribute.");
}
if (normals && tangents) {
mju_error("mjrfMeshData has both normals and tangents.");
}
if (normals && data.interleaved) {
// We need to build orientations from normals and so we require each
// attribute to be in a separate buffer.
mju_error("Cannot support normals with interleaved vertex attributes.");
}
// Build the vertex buffer.
filament::VertexBuffer::Builder vb_builder;
vb_builder.vertexCount(data.nvertices);
if (data.interleaved) {
// For an interleaved vertex buffer, we will create a single buffer which
// contains the data in the order specified by the attributes array,
// starting from the first attribute's payload.
vb_builder.bufferCount(1);
int total_vertex_size = 0;
for (int i = 0; i < data.nattributes; ++i) {
total_vertex_size += VertexAttributeTypeSize(data.attributes[i]);
}
const void* bytes = data.attributes[0].bytes;
const size_t nbytes = data.nvertices * total_vertex_size;
// We assume the buffer is tightly packed with no padding between
// attributes. As such, the stride is equal to the total vertex size and
// each offset is the sum of the sizes of the preceding attributes.
int offset = 0;
for (int i = 0; i < data.nattributes; ++i) {
const mjrVertexAttribute& attrib = data.attributes[i];
const filament::VertexAttribute usage = GetUsage(attrib);
filament::VertexBuffer::AttributeType type = GetType(attrib);
vb_builder.attribute(usage, 0, type, offset, total_vertex_size);
if (usage == filament::VertexAttribute::COLOR) {
vb_builder.normalized(usage);
}
offset += VertexAttributeTypeSize(attrib);
attributes_[i] = usage;
}
vertex_buffer_ = vb_builder.build(*engine_);
auto* user_data = new std::shared_ptr<SharedState>(shared_state_);
vertex_buffer_->setBufferAt(*engine_, 0,
{bytes, nbytes, callback, user_data});
} else {
// For a non-interleaved vertex buffer, we assign a separate buffer to each
// attribute.
vb_builder.bufferCount(data.nattributes);
for (int i = 0; i < data.nattributes; ++i) {
const mjrVertexAttribute& attrib = data.attributes[i];
const filament::VertexAttribute usage = GetUsage(attrib);
filament::VertexBuffer::AttributeType type = GetType(attrib);
if (attrib.usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) {
// We will replace normals with orientations.
type = filament::VertexBuffer::AttributeType::FLOAT4;
}
vb_builder.attribute(usage, i, type);
if (usage == filament::VertexAttribute::COLOR) {
vb_builder.normalized(usage);
}
attributes_[i] = usage;
}
num_attributes_ = data.nattributes;
vertex_buffer_ = vb_builder.build(*engine_);
// Assign the individual data buffers.
for (int i = 0; i < data.nattributes; ++i) {
const mjrVertexAttribute& attrib = data.attributes[i];
const void* bytes = attrib.bytes;
size_t nbytes = data.nvertices * VertexAttributeTypeSize(attrib);
if (attrib.usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) {
// Replace normals with orientations.
nbytes = data.nvertices * sizeof(float4);
bytes = BuildOrientationsFromNormals(data.nvertices, attrib);
}
auto* user_data = new std::shared_ptr<SharedState>(shared_state_);
vertex_buffer_->setBufferAt(*engine_, i,
{bytes, nbytes, callback, user_data});
}
}
}
void Mesh::BuildIndexBuffer(const mjrfMeshData& data) {
if (data.nindices == 0) {
return;
}
const int element_size =
data.index_type == mjINDEX_TYPE_U16 ? sizeof(uint16_t) : sizeof(uint32_t);
const int num_bytes = data.nindices * element_size;
// If indices == 0 and nindices > 0, then the user is specifying that the
// vertices are provided "in order", i.e. the indices are 0, 1, 2, 3, ...
// In this case, we need to create the sequence of indices explicitly.
const void* indices = data.indices;
if (indices == nullptr) {
std::byte* sequence = new std::byte[num_bytes];
shared_state_->callbacks.push_back([=]() { delete[] sequence; });
if (data.index_type == mjINDEX_TYPE_U16) {
FillSequence<uint16_t>(sequence, num_bytes);
} else {
FillSequence<uint32_t>(sequence, num_bytes);
}
indices = sequence;
}
filament::IndexBuffer::Builder ib_builder;
ib_builder.indexCount(data.nindices);
ib_builder.bufferType(data.index_type == mjINDEX_TYPE_U16
? filament::IndexBuffer::IndexType::USHORT
: filament::IndexBuffer::IndexType::UINT);
index_buffer_ = ib_builder.build(*engine_);
// We don't worry about setting a release callback here because the release
// callback for the vertex buffer will call release_callbacks_.
filament::backend::BufferDescriptor desc(indices, num_bytes);
index_buffer_->setBuffer(*engine_, std::move(desc));
}
float4* Mesh::BuildOrientationsFromNormals(int nvertices,
const mjrVertexAttribute& normals) {
float4* orientations = new float4[nvertices];
shared_state_->callbacks.push_back([=]() { delete[] orientations; });
const float* normals_ptr = reinterpret_cast<const float*>(normals.bytes);
for (int i = 0; i < nvertices; ++i) {
orientations[i] = CalculateOrientation(ReadFloat3(normals_ptr, i));
}
return orientations;
}
void Mesh::UpdateBounds(const mjrfMeshData& data) {
float3 bounds_min = ReadFloat3(data.bounds_min);
float3 bounds_max = ReadFloat3(data.bounds_max);
if (bounds_min != bounds_max) {
bounds_.emplace().set(bounds_min, bounds_max);
} else if (data.compute_bounds) {
bounds_min = float3(FLT_MAX, FLT_MAX, FLT_MAX);
bounds_max = float3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
if (data.attributes[0].usage != mjVERTEX_ATTRIBUTE_USAGE_POSITION) {
mju_error("mjrfMeshData has no positions.");
}
const float* positions =
reinterpret_cast<const float*>(data.attributes[0].bytes);
for (int i = 0; i < data.nvertices; ++i) {
const float3 position = ReadFloat3(positions, i);
bounds_min = min(bounds_min, position);
bounds_max = max(bounds_max, position);
}
bounds_.emplace().set(bounds_min, bounds_max);
}
}
void Mesh::ReleaseResources() {
std::lock_guard<std::mutex> lock(shared_state_->mutex);
if (!shared_state_->called) {
for (const auto& callback : shared_state_->callbacks) {
callback();
}
shared_state_->callbacks.clear();
shared_state_->called = true;
}
}
filament::IndexBuffer* Mesh::GetFilamentIndexBuffer() const {
return index_buffer_;
}
filament::VertexBuffer* Mesh::GetFilamentVertexBuffer() const {
return vertex_buffer_;
}
filament::RenderableManager::PrimitiveType Mesh::GetPrimitiveType() const {
return type_;
}
bool Mesh::HasVertexAttribute(mjrVertexAttributeUsage attrib) const {
auto fattrib = GetUsage(mjrVertexAttribute{.usage = attrib});
auto it = std::find(attributes_.begin(), attributes_.end(), fattrib);
return it != attributes_.end();
}
bool Mesh::HasBounds() const { return bounds_.has_value(); }
filament::Box Mesh::GetBounds() const { return bounds_.value(); }
} // namespace mujoco
+96
View File
@@ -0,0 +1,96 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_MESH_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_MESH_H_
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <vector>
#include <filament/Box.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/RenderableManager.h>
#include <filament/VertexBuffer.h>
#include <math/vec4.h>
#include "render/filament/mjrfilament.h"
namespace mujoco {
// Owns a filament Vertex and Index buffer representing a geometry mesh.
class Mesh : public mjrfMesh {
public:
// Creates a Mesh from the given MeshData.
Mesh(filament::Engine* engine, const mjrfMeshData& data);
~Mesh();
Mesh(const Mesh&) = delete;
Mesh& operator=(const Mesh&) = delete;
// Returns the filament IndexBuffer for the mesh.
filament::IndexBuffer* GetFilamentIndexBuffer() const;
// Returns the filament VertexBuffer for the mesh.
filament::VertexBuffer* GetFilamentVertexBuffer() const;
// Returns the primitive type of the mesh.
filament::RenderableManager::PrimitiveType GetPrimitiveType() const;
// Returns true if the mesh has the given attribute.
bool HasVertexAttribute(mjrVertexAttributeUsage attrib) const;
// Returns whether the mesh has bounds.
bool HasBounds() const;
// Returns the bounds of the mesh.
filament::Box GetBounds() const;
static Mesh* downcast(mjrfMesh* mesh) { return static_cast<Mesh*>(mesh); }
static const Mesh* downcast(const mjrfMesh* mesh) {
return static_cast<const Mesh*>(mesh);
}
private:
void BuildVertexBuffer(const mjrfMeshData& data);
void BuildIndexBuffer(const mjrfMeshData& data);
void UpdateBounds(const mjrfMeshData& data);
filament::math::float4* BuildOrientationsFromNormals(
int nvertices, const mjrVertexAttribute& normals);
void ReleaseResources();
filament::Engine* engine_ = nullptr;
filament::IndexBuffer* index_buffer_ = nullptr;
filament::VertexBuffer* vertex_buffer_ = nullptr;
filament::RenderableManager::PrimitiveType type_ =
filament::RenderableManager::PrimitiveType::TRIANGLES;
std::optional<filament::Box> bounds_;
struct SharedState {
std::vector<std::function<void()>> callbacks;
std::mutex mutex;
bool called = false;
};
std::shared_ptr<SharedState> shared_state_;
std::array<filament::VertexAttribute, mjMAX_VERTEX_ATTRIBUTES> attributes_;
int num_attributes_ = 0;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_MESH_H_
+192
View File
@@ -0,0 +1,192 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/object_manager.h"
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <filament/Engine.h>
#include <filament/IndirectLight.h>
#include <filament/Material.h>
#include <filament/Skybox.h>
#include <filament/Texture.h>
#include <math/vec2.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/builtins.h"
#include "user/user_resource.h"
namespace mujoco {
std::string ResolveFilamentAssetPath(const std::string& filename) {
std::string path = "filament:" + filename;
return path;
}
static filament::Material* LoadMaterial(filament::Engine* engine,
std::string_view filename) {
const std::string path = ResolveFilamentAssetPath(std::string(filename));
mjResource* resource =
mju_openResource("", path.c_str(), nullptr, nullptr, 0);
void* payload = nullptr;
int size = mju_readResource(resource, const_cast<const void**>(&payload));
filament::Material::Builder material_builder;
material_builder.package(payload, size);
filament::Material* material = material_builder.build(*engine);
mju_closeResource(resource);
return material;
};
ObjectManager::ObjectManager(filament::Engine* engine) : engine_(engine) {
materials_[kPbr] = LoadMaterial(engine, "pbr.filamat");
materials_[kPbrPacked] = LoadMaterial(engine, "pbr_packed.filamat");
materials_[kPbrTransparent] = LoadMaterial(engine, "pbr_transparent.filamat");
materials_[kPbrPackedTransparent] =
LoadMaterial(engine, "pbr_packed_transparent.filamat");
materials_[kPhong2d] = LoadMaterial(engine, "phong_2d.filamat");
materials_[kPhong2dFade] = LoadMaterial(engine, "phong_2d_fade.filamat");
materials_[kPhong2dReflect] =
LoadMaterial(engine, "phong_2d_reflect.filamat");
materials_[kPhong2dUv] = LoadMaterial(engine, "phong_2d_uv.filamat");
materials_[kPhong2dUvFade] = LoadMaterial(engine, "phong_2d_uv_fade.filamat");
materials_[kPhong2dUvReflect] =
LoadMaterial(engine, "phong_2d_uv_reflect.filamat");
materials_[kPhongColor] = LoadMaterial(engine, "phong_color.filamat");
materials_[kPhongColorFade] =
LoadMaterial(engine, "phong_color_fade.filamat");
materials_[kPhongColorReflect] =
LoadMaterial(engine, "phong_color_reflect.filamat");
materials_[kPhongCube] = LoadMaterial(engine, "phong_cube.filamat");
materials_[kPhongCubeFade] = LoadMaterial(engine, "phong_cube_fade.filamat");
materials_[kPhongCubeReflect] =
LoadMaterial(engine, "phong_cube_reflect.filamat");
materials_[kUnlitSegmentation] =
LoadMaterial(engine, "unlit_segmentation.filamat");
materials_[kUnlitDecor] = LoadMaterial(engine, "unlit_decor.filamat");
materials_[kUnlitDepth] = LoadMaterial(engine, "unlit_depth.filamat");
materials_[kUnlitUi] = LoadMaterial(engine, "unlit_ui.filamat");
materials_[kOutlineComposite] =
LoadMaterial(engine, "outline_composite.filamat");
materials_[kOutlineFlatten] = LoadMaterial(engine, "outline_flatten.filamat");
materials_[kOutlineJumpFlood] =
LoadMaterial(engine, "outline_jumpflood.filamat");
static uint8_t black_rgb[3] = {0, 0, 0};
static uint8_t white_rgb[3] = {255, 255, 255};
static uint8_t normal_data[3] = {128, 128, 255};
static uint8_t orm_data[3] = {0, 255, 0};
auto CreateFallbackTexture = [this](uint8_t color[3]) {
filament::Texture::Builder builder;
builder.width(1);
builder.height(1);
builder.format(filament::Texture::InternalFormat::RGB8);
builder.sampler(filament::Texture::Sampler::SAMPLER_2D);
filament::Texture* texture = builder.build(*engine_);
const filament::Texture::Type type = filament::Texture::Type::UBYTE;
const filament::Texture::Format format = filament::Texture::Format::RGB;
texture->setImage(*engine_, 0, {color, 3, format, type});
return texture;
};
fallback_black_ = CreateFallbackTexture(black_rgb);
fallback_white_ = CreateFallbackTexture(white_rgb);
fallback_normal_ = CreateFallbackTexture(normal_data);
fallback_orm_ = CreateFallbackTexture(orm_data);
fallback_textures_[mjTEXROLE_USER] = fallback_black_;
fallback_textures_[mjTEXROLE_RGB] = fallback_white_;
fallback_textures_[mjTEXROLE_OPACITY] = fallback_white_;
fallback_textures_[mjTEXROLE_OCCLUSION] = fallback_white_;
fallback_textures_[mjTEXROLE_ROUGHNESS] = fallback_white_;
fallback_textures_[mjTEXROLE_METALLIC] = fallback_black_;
fallback_textures_[mjTEXROLE_NORMAL] = fallback_normal_;
fallback_textures_[mjTEXROLE_EMISSIVE] = fallback_black_;
fallback_textures_[mjTEXROLE_ORM] = fallback_orm_;
CreateQuadBuffers();
}
ObjectManager::~ObjectManager() {
engine_->destroy(quad_vb_);
engine_->destroy(quad_ib_);
engine_->destroy(fallback_black_);
engine_->destroy(fallback_white_);
engine_->destroy(fallback_normal_);
engine_->destroy(fallback_orm_);
for (auto& iter : materials_) {
engine_->destroy(iter);
}
}
filament::Material* ObjectManager::GetMaterial(MaterialType type) const {
if (type < 0 || type >= kNumMaterials) {
mju_error("Invalid material type: %d", type);
}
return materials_[type];
}
Builtins* ObjectManager::GetBuiltins(int nstack, int nslice, int nquad) {
// Assumes nstack, nslice, and nquad are non-negative and less than 2^20.
std::uint64_t key = (static_cast<uint64_t>(nstack) << 20) |
(static_cast<uint64_t>(nslice) << 40) |
static_cast<uint64_t>(nquad);
auto iter = builtins_.find(key);
if (iter == builtins_.end()) {
auto builtins = std::make_unique<Builtins>(engine_, nstack, nslice, nquad);
Builtins* ptr = builtins.get();
builtins_[key] = std::move(builtins);
return ptr;
}
return iter->second.get();
}
const filament::Texture* ObjectManager::GetFallbackTexture(
mjtTextureRole role) const {
if (role < 0 || role >= mjNTEXROLE) {
mju_error("Invalid texture role: %d", role);
}
return fallback_textures_[role];
}
void ObjectManager::CreateQuadBuffers() {
// Define a single triangle that completely covers the viewport. (A single
// large triangle is more efficient than a two-triangle quad because it
// avoids the diagonal edge where pixels might be rasterized twice.)
static const filament::math::float2 kVertices[3] = {
{-1.0f, -1.0f}, {3.0f, -1.0f}, {-1.0f, 3.0f}};
static const uint16_t kIndices[3] = {0, 1, 2};
filament::VertexBuffer::Builder vb_builder;
vb_builder.bufferCount(1);
vb_builder.vertexCount(3);
vb_builder.attribute(filament::VertexAttribute::POSITION, 0,
filament::VertexBuffer::AttributeType::FLOAT2, 0,
sizeof(filament::math::float2));
quad_vb_ = vb_builder.build(*engine_);
quad_vb_->setBufferAt(*engine_, 0, {kVertices, sizeof(kVertices)});
filament::IndexBuffer::Builder ib_builder;
ib_builder.indexCount(3);
ib_builder.bufferType(filament::IndexBuffer::IndexType::USHORT);
quad_ib_ = ib_builder.build(*engine_);
quad_ib_->setBuffer(*engine_, {kIndices, sizeof(kIndices)});
}
} // namespace mujoco
+107
View File
@@ -0,0 +1,107 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_OBJECT_MANAGER_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_OBJECT_MANAGER_H_
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string_view>
#include <unordered_map>
#include <filament/Engine.h>
#include <filament/IndirectLight.h>
#include <filament/Skybox.h>
#include <filament/Texture.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/builtins.h"
namespace mujoco {
// Creates and owns various filament objects based on the data in a mjrContext.
class ObjectManager {
public:
ObjectManager(filament::Engine* engine);
~ObjectManager();
// The different filament::Materials that are loaded and managed by the
// ObjectManager.
enum MaterialType {
kPbr,
kPbrTransparent,
kPbrPacked,
kPbrPackedTransparent,
kPhong2d,
kPhong2dFade,
kPhong2dReflect,
kPhong2dUv,
kPhong2dUvFade,
kPhong2dUvReflect,
kPhongColor,
kPhongColorFade,
kPhongColorReflect,
kPhongCube,
kPhongCubeFade,
kPhongCubeReflect,
kUnlitSegmentation,
kUnlitDecor,
kUnlitDepth,
kUnlitUi,
kOutlineComposite,
kOutlineFlatten,
kOutlineJumpFlood,
kNumMaterials,
};
// Returns the Material of the given type.
filament::Material* GetMaterial(MaterialType type) const;
// Returns the fallback Texture with the given role.
const filament::Texture* GetFallbackTexture(mjtTextureRole role) const;
// Returns the buffers for creating a full-screen quad.
filament::VertexBuffer* GetQuadVertexBuffer() const { return quad_vb_; }
filament::IndexBuffer* GetQuadIndexBuffer() const { return quad_ib_; }
// Returns the built-in mesh collection with the given dimensions. For
// performance reasons, you should consider always using the same dimensions
// in order to reuse the same meshes.
Builtins* GetBuiltins(int nstack, int nslice, int nquad);
// Returns the filament Engine that owns the assets.
filament::Engine* GetEngine() const { return engine_; }
ObjectManager(const ObjectManager&) = delete;
ObjectManager& operator=(const ObjectManager&) = delete;
private:
void CreateQuadBuffers();
filament::Engine* engine_ = nullptr;
std::array<filament::Material*, kNumMaterials> materials_;
std::array<filament::Texture*, mjNTEXROLE> fallback_textures_;
std::unordered_map<std::uint64_t, std::unique_ptr<Builtins>> builtins_;
filament::Texture* fallback_white_ = nullptr;
filament::Texture* fallback_black_ = nullptr;
filament::Texture* fallback_normal_ = nullptr;
filament::Texture* fallback_orm_ = nullptr;
filament::VertexBuffer* quad_vb_ = nullptr;
filament::IndexBuffer* quad_ib_ = nullptr;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_OBJECT_MANAGER_H_
+243
View File
@@ -0,0 +1,243 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/outliner.h"
#include <cstdint>
#include <memory>
#include <filament/Camera.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderTarget.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/TextureSampler.h>
#include <filament/View.h>
#include <filament/Viewport.h>
#include <math/vec4.h>
#include <utils/EntityManager.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/object_manager.h"
#include "render/filament/core/render_target.h"
#include "render/filament/core/texture.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
Outliner::Outliner(ObjectManager* object_mgr, uint8_t layer_mask,
filament::math::float4 color, float thickness)
: object_mgr_(object_mgr),
engine_(object_mgr->GetEngine()),
layer_mask_(layer_mask),
color_(color),
thickness_(thickness) {}
Outliner::~Outliner() { Reset(); }
void Outliner::Prepare(int width, int height) {
if (width == width_ && height == height_) {
return;
}
Reset();
width_ = width;
height_ = height;
if (width_ <= 0 || height_ <= 0) {
return;
}
auto& em = utils::EntityManager::get();
// Sets up a pass with a full-screen quad that renders with a given material.
auto setup_fullscreen = [&](int pass, ObjectManager::MaterialType type) {
filament::Material* material = object_mgr_->GetMaterial(type);
filament::VertexBuffer* quad_vb = object_mgr_->GetQuadVertexBuffer();
filament::IndexBuffer* quad_ib = object_mgr_->GetQuadIndexBuffer();
const filament::RenderableManager::PrimitiveType primitive_type =
filament::RenderableManager::PrimitiveType::TRIANGLES;
material_instances_[pass] = material->createInstance();
quads_[pass] = em.create();
filament::RenderableManager::Builder(1)
.geometry(0, primitive_type, quad_vb, quad_ib)
.material(0, material_instances_[pass])
.culling(false)
.receiveShadows(false)
.castShadows(false)
.build(*engine_, quads_[pass]);
scenes_[pass] = engine_->createScene();
scenes_[pass]->addEntity(quads_[pass]);
views_[pass]->setScene(scenes_[pass]);
};
// Sets up a pass to use the given render target as the source and/or output.
auto bind = [&](int pass, int src, int out) {
// Set the `source` texture as the color texture from a render target.
if (src >= 0) {
const filament::TextureSampler sampler(
filament::TextureSampler::MinFilter::NEAREST,
filament::TextureSampler::MagFilter::NEAREST);
material_instances_[pass]->setParameter(
"source", targets_[src]->GetColorTexture()->GetFilamentTexture(),
sampler);
}
// Set the output of the view to the given render target.
if (out >= 0) {
views_[pass]->setRenderTarget(targets_[out]->GetFilamentRenderTarget());
}
};
// Set up two render targets. We will alternate between the two targets to
// allow for chaining passes together.
for (int i = 0; i < 2; ++i) {
mjrfRenderTargetConfig config;
mjrf_defaultRenderTargetConfig(&config);
config.color_format = mjPIXEL_FORMAT_RGBA8;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
config.width = width;
config.height = height;
targets_[i] = std::make_unique<RenderTarget>(engine_, config);
targets_[i]->Prepare(width, height);
}
// Setup orthographic camera for full-screen quad rendering.
camera_ = engine_->createCamera(em.create());
camera_->setProjection(filament::Camera::Projection::ORTHO, -1.0, 1.0, -1.0,
1.0, -1.0, 1.0);
camera_->lookAt({0, 0, 1}, {0, 0, 0}, {0, 1, 0});
// Setup all the views.
for (auto& view : views_) {
view = engine_->createView();
view->setCamera(camera_);
view->setViewport({0, 0, (uint32_t)width, (uint32_t)height});
view->setPostProcessingEnabled(false);
view->setShadowingEnabled(false);
view->setMultiSampleAntiAliasingOptions({.enabled = false});
}
// In the first pass, we will render a given scene, but only render the
// objects marked as outlines. We assume that the objects have already been
// assigned the kOutlineFlatten material.
views_[kPassFlatten]->setVisibleLayers(0xff, layer_mask_);
// All subsequent passes are full-screen post-processing passes.
setup_fullscreen(kPassJumpFlood1, ObjectManager::kOutlineJumpFlood);
setup_fullscreen(kPassJumpFlood2, ObjectManager::kOutlineJumpFlood);
setup_fullscreen(kPassJumpFlood3, ObjectManager::kOutlineJumpFlood);
setup_fullscreen(kPassJumpFlood4, ObjectManager::kOutlineJumpFlood);
setup_fullscreen(kPassJumpFlood5, ObjectManager::kOutlineJumpFlood);
setup_fullscreen(kPassDrawOutline, ObjectManager::kOutlineComposite);
// Chain the passes together such that the output of a pass is the input to
// the next pass. The first pass has no input (we are just rendering the
// selected objects) and the last pass has no output (we are just rendering
// the outline to the externally provided target).
bind(kPassFlatten, -1, 0);
bind(kPassJumpFlood1, 0, 1);
bind(kPassJumpFlood2, 1, 0);
bind(kPassJumpFlood3, 0, 1);
bind(kPassJumpFlood4, 1, 0);
bind(kPassJumpFlood5, 0, 1);
bind(kPassDrawOutline, 1, -1);
// Bind the parameters for each pass. For the jump flood passes, the step
// parameter determines how far to propagate the outline in each pass.
material_instances_[kPassJumpFlood1]->setParameter("step", 16.0f);
material_instances_[kPassJumpFlood2]->setParameter("step", 8.0f);
material_instances_[kPassJumpFlood3]->setParameter("step", 4.0f);
material_instances_[kPassJumpFlood4]->setParameter("step", 2.0f);
material_instances_[kPassJumpFlood5]->setParameter("step", 1.0f);
// The final pass renders the actual outline onto a render target.
material_instances_[kPassDrawOutline]->setParameter("color", color_);
material_instances_[kPassDrawOutline]->setParameter("width", thickness_);
// Commit all the material instances to the engine.
for (auto& material_instance : material_instances_) {
if (material_instance) {
material_instance->commit(*engine_);
}
}
}
void Outliner::Reset() {
auto& em = utils::EntityManager::get();
for (auto& scene : scenes_) {
if (scene) {
engine_->destroy(scene);
scene = nullptr;
}
}
for (auto& quad : quads_) {
if (quad) {
engine_->destroy(quad);
em.destroy(quad);
quad = utils::Entity();
}
}
for (auto& material_instance : material_instances_) {
if (material_instance) {
engine_->destroy(material_instance);
material_instance = nullptr;
}
}
for (auto& view : views_) {
if (view) {
engine_->destroy(view);
view = nullptr;
}
}
if (camera_) {
utils::Entity entity = camera_->getEntity();
engine_->destroyCameraComponent(entity);
em.destroy(entity);
camera_ = nullptr;
}
for (auto& target : targets_) {
target.reset();
}
width_ = 0;
height_ = 0;
}
void Outliner::Render(filament::Renderer* renderer, filament::View* view,
filament::RenderTarget* render_target) {
filament::Viewport viewport = view->getViewport();
Prepare(viewport.width, viewport.height);
for (auto& view : views_) {
view->setViewport(viewport);
}
// Re-render the view's scene to create the flattened selection mask.
auto prev_clear_opts = renderer->getClearOptions();
renderer->setClearOptions({.clearColor = {0, 0, 0, 0}, .clear = true});
views_[kPassFlatten]->setScene(view->getScene());
views_[kPassFlatten]->setCamera(&view->getCamera());
renderer->render(views_[kPassFlatten]);
renderer->setClearOptions(prev_clear_opts);
// Run the jump flood steps to expand the selection mask
for (int i = 0; i < kNumJumpFloodPasses; ++i) {
renderer->render(views_[kPassJumpFlood1 + i]);
}
// Render the final pass as an outline onto the provided render target.
views_[kPassDrawOutline]->setRenderTarget(render_target);
renderer->render(views_[kPassDrawOutline]);
views_[kPassDrawOutline]->setRenderTarget(nullptr);
}
} // namespace mujoco
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_OUTLINER_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_OUTLINER_H_
#include <cstdint>
#include <memory>
#include <filament/Camera.h>
#include <filament/Renderer.h>
#include <filament/View.h>
#include <math/vec4.h>
#include <utils/Entity.h>
#include "render/filament/core/object_manager.h"
#include "render/filament/core/render_target.h"
namespace mujoco {
// Renders an outline of selected objects.
//
// This class uses the "jump flood" algorithm to create an outline of selected
// objects.
class Outliner {
public:
Outliner(ObjectManager* object_mgr, uint8_t layer_mask,
filament::math::float4 color, float thickness);
~Outliner();
Outliner(const Outliner&) = delete;
Outliner& operator=(const Outliner&) = delete;
// Renders the outline of the selected objects in the given view. The outline
// will be rendered on top of the provided render target. This assumes that
// any objects to be outlined have been assigned an `OutlineFlatten` material
// instance.
void Render(filament::Renderer* renderer, filament::View* view,
filament::RenderTarget* render_target);
private:
void Prepare(int width, int height);
void Reset();
enum Pass {
kPassFlatten,
kPassJumpFlood1,
kPassJumpFlood2,
kPassJumpFlood3,
kPassJumpFlood4,
kPassJumpFlood5,
kPassDrawOutline,
kNumPasses,
kNumJumpFloodPasses = kPassJumpFlood5 - kPassJumpFlood1 + 1,
};
ObjectManager* object_mgr_ = nullptr;
filament::Engine* engine_ = nullptr;
uint8_t layer_mask_ = 0xff;
filament::math::float4 color_ = {1.0f, 1.0f, 1.0f, 1.0f};
float thickness_ = 2.5f;
int width_ = 0;
int height_ = 0;
filament::Camera* camera_ = nullptr;
std::unique_ptr<RenderTarget> targets_[2];
filament::View* views_[kNumPasses] = {};
filament::Scene* scenes_[kNumPasses] = {};
utils::Entity quads_[kNumPasses] = {};
filament::MaterialInstance* material_instances_[kNumPasses] = {};
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_OUTLINER_H_
@@ -0,0 +1,57 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/reflection_manager.h"
#include <memory>
#include <filament/Engine.h>
#include "render/filament/core/render_target.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
ReflectionManager::ReflectionManager(filament::Engine* engine)
: engine_(engine) {}
ReflectionManager::~ReflectionManager() {}
void ReflectionManager::ClearRenderables() { renderables_.clear(); }
mjrfTexture* ReflectionManager::Register(mjrfRenderable* renderable, int width,
int height) {
if (targets_.size() == renderables_.size()) {
mjrfRenderTargetConfig config;
mjrf_defaultRenderTargetConfig(&config);
config.color_format = mjPIXEL_FORMAT_RGBA8;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
targets_.push_back(std::make_unique<RenderTarget>(engine_, config));
}
RenderTarget* target = targets_[renderables_.size()].get();
target->Prepare(width, height);
renderables_.push_back(renderable);
return target->GetColorTexture();
}
int ReflectionManager::GetNumRenderables() const { return renderables_.size(); }
mjrfRenderable* ReflectionManager::GetRenderable(int index) const {
return renderables_[index];
}
const RenderTarget* ReflectionManager::GetRenderTarget(int index) const {
return targets_[index].get();
}
} // namespace mujoco
@@ -0,0 +1,61 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_REFLECTION_MANAGER_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_REFLECTION_MANAGER_H_
#include <memory>
#include <vector>
#include <filament/Engine.h>
#include "render/filament/core/render_target.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
// Manages the allocation of RenderTargets for reflective surfaces.
class ReflectionManager {
public:
ReflectionManager(filament::Engine* engine);
~ReflectionManager();
ReflectionManager(const ReflectionManager&) = delete;
ReflectionManager& operator=(const ReflectionManager&) = delete;
// Registers a Renderable as being reflective. Internally, this function will
// create a RenderTarget of the given size and return the texture that the
// Renderable can use as its reflection.
mjrfTexture* Register(mjrfRenderable* renderable, int width, int height);
// Clears all previously registered renderables. This should be called at the
// beginning of a frame.
void ClearRenderables();
// Returns the number of registered renderables
int GetNumRenderables() const;
// Returns the renderable at the given index.
mjrfRenderable* GetRenderable(int index) const;
// Returns the RenderTarget at the given index.
const RenderTarget* GetRenderTarget(int index) const;
private:
filament::Engine* engine_;
std::vector<mjrfRenderable*> renderables_;
std::vector<std::unique_ptr<RenderTarget>> targets_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_REFLECTION_MANAGER_H_
+135
View File
@@ -0,0 +1,135 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/render_target.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
#include <backend/DriverEnums.h>
#include <backend/PixelBufferDescriptor.h>
#include <filament/Engine.h>
#include <filament/RenderTarget.h>
#include <filament/Renderer.h>
#include <filament/Texture.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/texture.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
RenderTarget::RenderTarget(filament::Engine* engine,
const mjrfRenderTargetConfig& config)
: engine_(engine), config_(config) {
if (config_.width > 0 && config_.height > 0) {
Prepare(config_.width, config_.height);
}
}
RenderTarget::~RenderTarget() noexcept { Destroy(); }
void RenderTarget::Prepare(int width, int height) {
if (width == width_ && height == height_) {
return;
}
Destroy();
width_ = width;
height_ = height;
if (width_ <= 0 || height_ <= 0) {
width_ = 0;
height_ = 0;
return;
}
mjrfTextureConfig color_config;
mjrf_defaultTextureConfig(&color_config);
Texture::InternalFlags color_flags;
color_config.width = width;
color_config.height = height;
color_config.sampler_type = mjTEXTURE_2D;
color_config.format = config_.color_format;
color_config.color_space = mjCOLORSPACE_LINEAR;
color_flags.color_attachment = true;
color_texture_ =
std::make_unique<Texture>(engine_, color_config, color_flags);
mjrfTextureConfig depth_config;
mjrf_defaultTextureConfig(&depth_config);
Texture::InternalFlags depth_flags;
depth_config.width = width;
depth_config.height = height;
depth_config.sampler_type = mjTEXTURE_2D;
depth_config.format = config_.depth_format;
depth_config.color_space = mjCOLORSPACE_LINEAR;
depth_flags.depth_attachment = true;
depth_texture_ =
std::make_unique<Texture>(engine_, depth_config, depth_flags);
filament::RenderTarget::Builder builder;
builder.texture(filament::RenderTarget::AttachmentPoint::COLOR,
color_texture_->GetFilamentTexture());
builder.texture(filament::RenderTarget::AttachmentPoint::DEPTH,
depth_texture_->GetFilamentTexture());
render_target_ = builder.build(*engine_);
}
void RenderTarget::ReadColorPixels(filament::Renderer* renderer, uint8_t* bytes,
size_t num_bytes) {
filament::backend::PixelDataFormat format;
filament::backend::PixelDataType type;
size_t expected_num_bytes = 0;
switch (config_.color_format) {
case mjPIXEL_FORMAT_RGB8:
format = filament::backend::PixelDataFormat::RGB;
type = filament::backend::PixelDataType::UBYTE;
expected_num_bytes = width_ * height_ * 3;
break;
case mjPIXEL_FORMAT_R32F:
format = filament::backend::PixelDataFormat::R;
type = filament::backend::PixelDataType::FLOAT;
expected_num_bytes = width_ * height_ * sizeof(float);
break;
default:
mju_error("Unsupported pixel format: %d", config_.color_format);
return;
}
if (num_bytes != expected_num_bytes) {
mju_error("Invalid number of bytes.");
return;
}
filament::backend::PixelBufferDescriptor desc(bytes, num_bytes, format, type);
renderer->readPixels(render_target_, 0, 0, width_, height_, std::move(desc));
}
void RenderTarget::Destroy() {
if (render_target_) {
engine_->destroy(render_target_);
render_target_ = nullptr;
}
color_texture_.reset();
depth_texture_.reset();
}
Texture* RenderTarget::GetColorTexture() const { return color_texture_.get(); }
Texture* RenderTarget::GetDepthTexture() const { return depth_texture_.get(); }
filament::RenderTarget* RenderTarget::GetFilamentRenderTarget() const {
return render_target_;
}
} // namespace mujoco
+78
View File
@@ -0,0 +1,78 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_RENDER_TARGET_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_RENDER_TARGET_H_
#include <cstddef>
#include <cstdint>
#include <memory>
#include <filament/Engine.h>
#include <filament/Texture.h>
#include "render/filament/core/texture.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
// Manages a filament RenderTarget and the textures which are bound to it.
class RenderTarget : public mjrfRenderTarget {
public:
// Defines the types of textures to create for the color and depth
// attachments.
RenderTarget(filament::Engine* engine, const mjrfRenderTargetConfig& config);
~RenderTarget() noexcept;
RenderTarget(const RenderTarget&) = delete;
RenderTarget& operator=(const RenderTarget&) = delete;
// Creates the textures and render target if the width of height differ from
// the last time the render target was prepared.
void Prepare(int width, int height);
// Reads the pixels from the render target texture.
void ReadColorPixels(filament::Renderer* renderer, uint8_t* bytes,
size_t num_bytes);
// Returns the color texture.
Texture* GetColorTexture() const;
// Returns the depth texture.
Texture* GetDepthTexture() const;
// Returns the underlying filament render target.
filament::RenderTarget* GetFilamentRenderTarget() const;
static RenderTarget* downcast(mjrfRenderTarget* render_target) {
return static_cast<RenderTarget*>(render_target);
}
static const RenderTarget* downcast(const mjrfRenderTarget* render_target) {
return static_cast<const RenderTarget*>(render_target);
}
private:
void Destroy();
filament::Engine* engine_ = nullptr;
mjrfRenderTargetConfig config_;
filament::RenderTarget* render_target_ = nullptr;
std::unique_ptr<Texture> color_texture_ = nullptr;
std::unique_ptr<Texture> depth_texture_ = nullptr;
int width_ = 0;
int height_ = 0;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_RENDER_TARGET_H_
+668
View File
@@ -0,0 +1,668 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/renderable.h"
#include <cmath>
#include <cstdint>
#include <functional>
#include <numbers>
#include <span>
#include <filament/Engine.h>
#include <filament/Material.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/TransformManager.h>
#include <math/TMatHelpers.h>
#include <math/TVecHelpers.h>
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <utils/EntityManager.h>
#include <mujoco/mujoco.h>
#include "engine/engine_vis_visualize.h"
#include "render/filament/core/builtins.h"
#include "render/filament/core/material_manager.h"
#include "render/filament/core/mesh.h"
#include "render/filament/core/reflection_manager.h"
#include "render/filament/mjrfilament.h"
#include "render/filament/support/filament_util.h"
namespace mujoco {
using filament::math::float2;
using filament::math::float3;
using filament::math::float4;
using filament::math::mat3f;
using filament::math::mat4f;
// An arbitrary scale factor for arrows.
static constexpr float kArrowScale = 1.f / 6.f;
static constexpr float kArrowHeadSize = 1.75f;
Renderable::Renderable(filament::Engine* engine,
const mjrfRenderableParams& params,
MaterialManager* material_mgr)
: material_mgr_(material_mgr), params_(params) {
mjrf_defaultMaterial(&material_);
}
Renderable::~Renderable() noexcept {
filament::Engine* engine = GetEngine();
utils::EntityManager& em = utils::EntityManager::get();
for (Part& part : parts_) {
if (assigned_scene_) {
assigned_scene_->remove(part.entity);
}
engine->destroy(part.entity);
em.destroy(part.entity);
}
}
void Renderable::SetMesh(const Mesh* mesh, int elem_offset, int elem_count) {
if (mesh == nullptr) {
mju_error("Cannot set mesh to nullptr.");
}
// We use MESH, even though it could be any mesh-like geom type, e.g.
// heightfields, flex, skin, sdf, etc.
geom_type_ = mjGEOM_MESH;
filament::VertexBuffer* vertex_buffer = mesh->GetFilamentVertexBuffer();
if (vertex_buffer == nullptr) {
mju_error("Invalid (null) vertex buffer.");
}
filament::IndexBuffer* index_buffer = mesh->GetFilamentIndexBuffer();
if (index_buffer == nullptr) {
mju_error("Invalid (null) index buffer.");
}
if (elem_count == 0) {
elem_count = index_buffer->getIndexCount() - elem_offset;
}
if (parts_.empty()) {
Part& part = parts_.emplace_back();
part.mesh = mesh;
part.elem_offset = elem_offset;
part.elem_count = elem_count;
InitPartEntity(part);
} else if (parts_.size() == 1) {
Part& part = parts_[0];
part.mesh = mesh;
part.elem_offset = elem_offset;
part.elem_count = elem_count;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
rm.setGeometryAt(rm.getInstance(part.entity), 0,
part.mesh->GetPrimitiveType(), vertex_buffer, index_buffer,
part.elem_offset, part.elem_count);
} else {
mju_error("Cannot set mesh for renderable with multiple parts.");
}
}
void Renderable::InitPartEntity(Part& part) {
part.entity = utils::EntityManager::get().create();
if (part.entity.isNull()) {
mju_error("Failed to create entity.");
}
filament::VertexBuffer* vertex_buffer = part.mesh->GetFilamentVertexBuffer();
filament::IndexBuffer* index_buffer = part.mesh->GetFilamentIndexBuffer();
filament::RenderableManager::Builder builder(1);
builder.geometry(0, part.mesh->GetPrimitiveType(), vertex_buffer,
index_buffer, part.elem_offset, part.elem_count);
if (part.mesh->HasBounds()) {
builder.boundingBox(part.mesh->GetBounds());
} else {
builder.culling(false);
}
builder.castShadows(params_.cast_shadows);
builder.receiveShadows(params_.receive_shadows);
builder.blendOrder(0, params_.blend_order);
builder.screenSpaceContactShadows(true);
builder.build(*GetEngine(), part.entity);
if (assigned_scene_) {
assigned_scene_->addEntity(part.entity);
}
}
void Renderable::SetTransform(const float3& position, const mat3f& rotation) {
trs_.translation = position;
trs_.rotation = rotation;
UpdateTransform();
}
void Renderable::SetSize(const float3& size) {
trs_.size = size;
UpdateTransform();
}
void Renderable::UpdateTransform() {
if (parts_.empty()) {
transform_ = trs_.ToTransform();
return;
}
filament::TransformManager& tm = GetEngine()->getTransformManager();
if (geom_type_ == mjGEOM_PLANE && (trs_.size[0] <= 0 || trs_.size[1] <= 0)) {
infinite_plane_ = true;
const mat4f transform =
filament::math::mat4f(trs_.rotation, trs_.translation);
for (Part& part : parts_) {
tm.setTransform(tm.getInstance(part.entity), transform);
}
} else if (get_transform_fn_) {
for (int i = 0; i < parts_.size(); ++i) {
const mat4f transform = get_transform_fn_(i, trs_);
tm.setTransform(tm.getInstance(parts_[i].entity), transform);
}
} else {
const mat4f transform = trs_.ToTransform();
for (Part& part : parts_) {
tm.setTransform(tm.getInstance(part.entity), transform);
}
}
transform_ = tm.getTransform(tm.getInstance(parts_[0].entity));
}
const mat4f& Renderable::GetTransform() const { return transform_; }
void Renderable::AppendMesh(const Mesh* mesh) {
Part& part = parts_.emplace_back();
part.mesh = Mesh::downcast(mesh);
part.elem_offset = 0;
part.elem_count = part.mesh->GetFilamentIndexBuffer()->getIndexCount();
InitPartEntity(part);
}
void Renderable::AddToScene(filament::Scene* scene) {
if (assigned_scene_) {
if (assigned_scene_ != scene) {
mju_error("Cannot add renderable to multiple scenes.");
}
// Entities are already added to the scene.
return;
}
for (Part& part : parts_) {
scene->addEntity(part.entity);
}
assigned_scene_ = scene;
}
void Renderable::RemoveFromScene(filament::Scene* scene) {
if (assigned_scene_ != scene) {
mju_error("Attempting to remove renderable from wrong scene.");
}
for (Part& part : parts_) {
scene->remove(part.entity);
}
assigned_scene_ = nullptr;
}
void Renderable::UpdateMaterial(const mjrfMaterial& material) {
uint8_t layer_mask_ = kLayerMask_Object;
if (material.decor_ux) {
layer_mask_ = kLayerMask_Decor;
}
if (material.selected) {
layer_mask_ |= kLayerMask_Outline;
}
SetLayerMask(layer_mask_);
material_ = material;
}
const mjrfMaterial& Renderable::GetMaterial() const { return material_; }
void Renderable::Prepare(std::span<const mjrfRenderRequest*> requests,
ReflectionManager* reflection_mgr) {
// We assume BindMaterialInstance will be called with the same requests in
// the same order. As such, we'll just store the draw state in a deque rather
// than trying to perform any kind of matching with the requests.
draw_queue_.clear();
curr_state_ = DrawState();
for (const mjrfRenderRequest* request : requests) {
DrawState draw_state;
mjrfMaterial material = material_;
material.selected = false;
draw_state.wireframe = (request->draw_mode == mjDRAW_MODE_WIREFRAME);
if (material.decor_ux) {
draw_state.cast_shadows = false;
draw_state.receive_shadows = false;
}
if (geom_type_ == mjGEOM_PLANE) {
const float3 camera_pos = ReadFloat3(request->camera.pos);
const float3 position = transform_[3].xyz;
const float3 forward = transform_[2].xyz;
const bool is_behind_camera = dot(camera_pos - position, forward) < 0;
if (is_behind_camera) {
material.color[3] *= 0.3;
draw_state.receive_shadows = false;
}
}
if (request->draw_mode == mjDRAW_MODE_SEGMENTATION_BY_COLOR) {
constexpr double phi1 = 1.61803398874989484820; // Cached Phi(1).
constexpr double coef1 = 1.0 / phi1;
const double index = static_cast<double>(material.segmentation_id);
const double sample = std::fmod(0.5 + coef1 * index, 1.0);
material.segmentation_id = 0x01000000 * sample;
} else if (request->draw_mode != mjDRAW_MODE_SEGMENTATION_BY_ID) {
// Clear out the segmentation color in order to take advantage of shared
// materials.
material.segmentation_id = 0;
}
if (request->draw_mode == mjDRAW_MODE_ISLANDS &&
material.sleep_state != mjS_STATIC) {
float hue = 1.0f;
float saturation = 0.0f;
float value = 0.7f;
if (material.island_id >= 0) {
const double h = static_cast<double>(material.island_id) + 1.0;
hue = mju_Halton(h, 7);
saturation = 0.5 + 0.5 * mju_Halton(h, 3);
value = 0.6 + 0.4 * mju_Halton(h, 5);
}
if (material.sleep_state == mjS_ASLEEP) {
value *= 0.6;
saturation *= 0.7;
}
hsv2rgb(material.color, hue, saturation, value);
material.color[3] = 1;
// Remove textures so they don't interfere with the island color.
material.color_texture = nullptr;
material.metallic_texture = nullptr;
material.roughness_texture = nullptr;
material.occlusion_texture = nullptr;
material.orm_texture = nullptr;
}
if (request->draw_mode == mjDRAW_MODE_DEFAULT_NO_TEXTURES) {
material.color_texture = nullptr;
material.metallic_texture = nullptr;
material.roughness_texture = nullptr;
material.occlusion_texture = nullptr;
material.orm_texture = nullptr;
}
const bool reflective = request->draw_mode == mjDRAW_MODE_DEFAULT &&
request->enable_reflections &&
material.reflectance > 0.0;
if (reflective) {
material.reflection_texture = reflection_mgr->Register(
this, request->viewport.width, request->viewport.height);
}
if (material.selected) {
material.emissive += 0.3f; // vis->global.glow
}
const Mesh* mesh = !parts_.empty() ? parts_[0].mesh : nullptr;
draw_state.material_key = material_mgr_->PrepareMaterialInstance(
material, static_cast<mjrDrawMode>(request->draw_mode), geom_type_,
mesh);
draw_queue_.push_back(draw_state);
}
}
void Renderable::BindMaterialInstance(const mjrfRenderRequest& request) {
if (draw_queue_.empty()) {
mju_error("No material instances to bind.");
}
if (geom_type_ == mjGEOM_PLANE && infinite_plane_) {
// Emulate an infinite plane by recentering a large quad in world space
// relative to the camera. We use the shared mjMAXPLANEGRID value as the
// size of the quad to ensure the texture scaling matches.
static constexpr float kInfiniteScale = 0.5f * mjMAXPLANEGRID;
const mat4f scaling =
mat4f::scaling(float3{kInfiniteScale, kInfiniteScale, 1.0f});
const float3 camera_pos = ReadFloat3(request.camera.pos);
const float3 plane_origin = transform_[3].xyz;
const mat3f plane_rotation = transform_.upperLeft();
const float3 vec = camera_pos - plane_origin;
const float3 plane_x = normalize(plane_rotation[0]);
const float3 plane_y = normalize(plane_rotation[1]);
// Project camera position onto the plane's local XY axes.
float dx = dot(vec, plane_x);
float dy = dot(vec, plane_y);
// Quantize based on uv_scale.
const float tile_size[] = {kInfiniteScale / material_.uv_scale[0],
kInfiniteScale / material_.uv_scale[1]};
dx = tile_size[0] * mju_round(dx / tile_size[0]);
dy = tile_size[1] * mju_round(dy / tile_size[1]);
// Calculate the new center quad as a displacement from the plane origin.
const float3 displacement = dx * plane_x + dy * plane_y;
const float3 center = plane_origin + displacement;
const mat4f transform = mat4f(plane_rotation, center) * scaling;
filament::TransformManager& tm = GetEngine()->getTransformManager();
for (Part& part : parts_) {
tm.setTransform(tm.getInstance(part.entity), transform);
}
}
const DrawState& state = draw_queue_.front();
SetCastShadows(state.cast_shadows);
SetReceiveShadows(state.receive_shadows);
SetWireframe(state.wireframe);
SetMaterialInstance(state.material_key);
curr_state_ = state;
draw_queue_.pop_front();
}
MaterialManager::MaterialKey Renderable::SetMaterialInstance(
MaterialManager::MaterialKey key) {
MaterialManager::MaterialKey prev = curr_state_.material_key;
if (key != curr_state_.material_key) {
filament::MaterialInstance* instance = material_mgr_->GetInstance(key);
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (Part& part : parts_) {
filament::RenderableManager::Instance ri = rm.getInstance(part.entity);
rm.setMaterialInstanceAt(ri, 0, instance);
}
}
return prev;
}
std::uint8_t Renderable::SetLayerMask(std::uint8_t mask) {
std::uint8_t prev = layer_mask_;
if (mask != layer_mask_) {
layer_mask_ = mask;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (Part& part : parts_) {
rm.setLayerMask(rm.getInstance(part.entity), 0xff, layer_mask_);
}
}
return prev;
}
std::uint16_t Renderable::SetBlendOrder(std::uint16_t blend_order) {
std::uint16_t prev = params_.blend_order;
if (blend_order != params_.blend_order) {
params_.blend_order = blend_order;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (Part& part : parts_) {
rm.setBlendOrderAt(rm.getInstance(part.entity), 0, params_.blend_order);
}
}
return prev;
}
void Renderable::SetCastShadows(bool cast_shadows) {
if (params_.cast_shadows != cast_shadows) {
params_.cast_shadows = cast_shadows;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (Part& part : parts_) {
rm.setCastShadows(rm.getInstance(part.entity), params_.cast_shadows);
}
}
}
void Renderable::SetReceiveShadows(bool receive_shadows) {
if (params_.receive_shadows != receive_shadows) {
params_.receive_shadows = receive_shadows;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (Part& part : parts_) {
rm.setReceiveShadows(rm.getInstance(part.entity),
params_.receive_shadows);
}
}
}
void Renderable::SetWireframe(bool wireframe) {
static constexpr auto kWireframeType =
filament::RenderableManager::PrimitiveType::LINES;
if (wireframe != curr_state_.wireframe) {
curr_state_.wireframe = wireframe;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (Part& part : parts_) {
filament::VertexBuffer* vertex_buffer =
part.mesh->GetFilamentVertexBuffer();
filament::IndexBuffer* index_buffer = part.mesh->GetFilamentIndexBuffer();
rm.setGeometryAt(
rm.getInstance(part.entity), 0,
wireframe ? kWireframeType : part.mesh->GetPrimitiveType(),
vertex_buffer, index_buffer, part.elem_offset, part.elem_count);
}
}
}
void Renderable::SetGeomMesh(mjtGeom type, int nstack, int nslice, int nquad) {
Builtins* builtins =
material_mgr_->GetObjectManager()->GetBuiltins(nstack, nslice, nquad);
geom_type_ = type;
switch (type) {
case mjGEOM_PLANE:
AppendMesh(builtins->Plane());
break;
case mjGEOM_SPHERE:
AppendMesh(builtins->Sphere());
break;
case mjGEOM_ELLIPSOID:
AppendMesh(builtins->Sphere());
break;
case mjGEOM_BOX:
AppendMesh(builtins->Box());
break;
case mjGEOM_CAPSULE:
// Capsules are a tube with two domes at the ends.
AppendMesh(builtins->Tube());
AppendMesh(builtins->Dome());
AppendMesh(builtins->Dome());
get_transform_fn_ = [](int index, const Trs& trs) {
// We apply an inverse scale to the domes to counteract the capsule's
// overall scale so that the domes remain spherical in shape.
const float xz_size = 0.5f * (trs.size.x + trs.size.y);
if (index == 0) {
return trs.ToTransform();
} else if (index == 1) {
// Move the first dome to the top of the capsule.
mat4f top = mat4f(trs.rotation, trs.translation);
top *= mat4f::translation(float3{0, 0, trs.size.z});
top *= mat4f::scaling(float3{trs.size.x, trs.size.y, xz_size});
return top;
} else if (index == 2) {
// Move the second dome to the bottom of the capsule and rotate it 180
// degrees so that it's facing the right way.
mat4f bottom = mat4f(trs.rotation, trs.translation);
bottom *= mat4f::translation(float3{0, 0, -trs.size.z});
bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0});
bottom *= mat4f::scaling(float3{trs.size.x, trs.size.y, xz_size});
return bottom;
} else {
mju_error("Invalid index for capsule geom: %d (expected [0,2])",
index);
return trs.ToTransform();
}
};
break;
case mjGEOM_CYLINDER:
// Cylinders are a tube with two disks at the ends.
AppendMesh(builtins->Tube());
AppendMesh(builtins->Disk());
AppendMesh(builtins->Disk());
get_transform_fn_ = [](int index, const Trs& trs) {
if (index == 0) {
return trs.ToTransform();
} else if (index == 1) {
// Move the first disk to the top of the cylinder.
mat4f top = mat4f(trs.rotation, trs.translation);
top *= mat4f::translation(float3{0, 0, trs.size.z});
top *= mat4f::scaling(trs.size);
return top;
} else if (index == 2) {
// Move the second disk to the bottom of the cylinder. Rotate the disk
// 180 degrees so that the normals point outwards.
mat4f bottom = mat4f(trs.rotation, trs.translation);
bottom *= mat4f::translation(float3{0, 0, -trs.size.z});
bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0});
bottom *= mat4f::scaling(trs.size);
return bottom;
} else {
mju_error("Invalid index for cylinder geom: %d (expected [0,2])",
index);
return trs.ToTransform();
}
};
break;
case mjGEOM_ARROW:
AppendMesh(builtins->Tube());
AppendMesh(builtins->Cone());
AppendMesh(builtins->Disk());
AppendMesh(builtins->Disk());
get_transform_fn_ = [](int index, const Trs& trs) {
mat4f base = mat4f(trs.rotation, trs.translation);
base *= mat4f::scaling(float3{1, 1, kArrowScale});
base *= mat4f::translation(float3{0, 0, trs.size.z});
if (index == 0) {
return base * mat4f::scaling(trs.size);
} else if (index == 1) {
mat4f top = base;
top *= mat4f::translation(float3{0, 0, trs.size.z});
top *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
return top * mat4f::scaling(trs.size);
} else if (index == 2) {
mat4f top_disk = base;
top_disk *= mat4f::translation(float3{0, 0, trs.size.z});
top_disk *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0});
top_disk *=
mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
return top_disk * mat4f::scaling(trs.size);
} else if (index == 3) {
mat4f bottom = base;
bottom *= mat4f::translation(float3{0, 0, -trs.size.z});
bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0});
return bottom * mat4f::scaling(trs.size);
} else {
mju_error("Invalid index for arrow geom: %d (expected [0,3])", index);
return trs.ToTransform();
}
};
break;
case mjGEOM_ARROW1:
AppendMesh(builtins->Tube());
AppendMesh(builtins->Cone());
AppendMesh(builtins->Disk());
get_transform_fn_ = [](int index, const Trs& trs) {
mat4f base = mat4f(trs.rotation, trs.translation);
base *= mat4f::scaling(float3{1, 1, kArrowScale});
base *= mat4f::translation(float3{0, 0, trs.size.z});
if (index == 0) {
return base * mat4f::scaling(trs.size);
} else if (index == 1) {
mat4f top = base;
top *= mat4f::translation(float3{0, 0, trs.size.z});
return top * mat4f::scaling(trs.size);
} else if (index == 2) {
mat4f bottom = base;
bottom *= mat4f::translation(float3{0, 0, -trs.size.z});
bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0});
return bottom * mat4f::scaling(trs.size);
} else {
mju_error("Invalid index for arrow1 geom: %d (expected [0,2])",
index);
return trs.ToTransform();
}
};
break;
case mjGEOM_ARROW2:
AppendMesh(builtins->Tube());
AppendMesh(builtins->Cone());
AppendMesh(builtins->Cone());
AppendMesh(builtins->Disk());
AppendMesh(builtins->Disk());
get_transform_fn_ = [](int index, const Trs& trs) {
mat4f base = mat4f(trs.rotation, trs.translation);
base *= mat4f::scaling(float3{1, 1, kArrowScale});
base *= mat4f::translation(float3{0, 0, trs.size.z});
if (index == 0) {
return base * mat4f::scaling(trs.size);
} else if (index == 1) {
mat4f top = base;
top *= mat4f::translation(float3{0, 0, trs.size.z});
top *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
return top * mat4f::scaling(trs.size);
} else if (index == 2) {
mat4f bottom = base;
bottom *= mat4f::translation(float3{0, 0, -trs.size.z});
bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0});
bottom *=
mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
return bottom * mat4f::scaling(trs.size);
} else if (index == 3) {
mat4f top_disk = base;
top_disk *= mat4f::translation(float3{0, 0, trs.size.z});
top_disk *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0});
top_disk *=
mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
return top_disk * mat4f::scaling(trs.size);
} else if (index == 4) {
mat4f bottom_disk = base;
bottom_disk *= mat4f::translation(float3{0, 0, -trs.size.z});
return bottom_disk * mat4f::scaling(trs.size);
} else {
mju_error("Invalid index for arrow2 geom: %d (expected [0,4])",
index);
return trs.ToTransform();
}
};
break;
case mjGEOM_LINE:
AppendMesh(builtins->Line());
break;
case mjGEOM_LINEBOX:
AppendMesh(builtins->LineBox());
break;
case mjGEOM_TRIANGLE:
AppendMesh(builtins->Triangle());
break;
default:
mju_error("Unsupported geom type: %d", type);
break;
}
}
filament::Engine* Renderable::GetEngine() { return material_mgr_->GetEngine(); }
} // namespace mujoco
+189
View File
@@ -0,0 +1,189 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_RENDERABLE_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_RENDERABLE_H_
#include <cstdint>
#include <deque>
#include <functional>
#include <span>
#include <vector>
#include <filament/Engine.h>
#include <filament/Scene.h>
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/vec3.h>
#include <utils/Entity.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/material_manager.h"
#include "render/filament/core/mesh.h"
#include "render/filament/core/reflection_manager.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
enum LayerMask : uint8_t {
kLayerMask_Object = 0x01 << 1,
kLayerMask_Decor = 0x01 << 2,
kLayerMask_Outline = 0x01 << 3,
kLayerMask_All = 0xff,
kLayerMask_None = 0x00,
};
// A Renderable is effectively two things: a mesh and a material.
//
// The mesh describes the surface geometry of the object and the material
// describes how that surface interacts with light (i.e. the color of each point
// on the surface).
class Renderable : public mjrfRenderable {
public:
Renderable(filament::Engine* engine, const mjrfRenderableParams& params,
MaterialManager* material_mgr);
~Renderable() noexcept;
Renderable(const Renderable&) = delete;
Renderable& operator=(const Renderable&) = delete;
// Sets the mesh of this renderable. The elem_offset and elem_count parameters
// can be used to specify a submesh within the mesh. If elem_count is 0,
// assumes the entire mesh should be appended.
void SetMesh(const Mesh* mesh, int elem_offset = 0, int elem_count = 0);
// Sets the mesh of this renderable to a built-in mesh based on the geom type.
void SetGeomMesh(mjtGeom type, int nstack, int nslice, int nquad);
// Sets the position and rotation of this renderable.
void SetTransform(const filament::math::float3& position,
const filament::math::mat3f& rotation);
// Sets the size of this renderable. Note: this is effectively the same as a
// scale for most renderables. However, for e.g. capsules, the spherical ends
// are not scaled and remain fixed in size.
void SetSize(const filament::math::float3& size);
// Returns the current transform of this renderable.
const filament::math::mat4f& GetTransform() const;
// Sets the layer mask for this renderable. Layer masks can be used to
// show/hide groups of renderables in scenes. Returns the previous layer mask.
std::uint8_t SetLayerMask(std::uint8_t mask);
// Sets the blend order for this renderable. This determines the order in
// which transparent renderables are blended together. Returns the previous
// blend order.
std::uint16_t SetBlendOrder(std::uint16_t blend_order);
// Disables this renderable from casting shadows.
void SetCastShadows(bool cast_shadows);
// Disables this renderable from receiving shadows.
void SetReceiveShadows(bool receive_shadows);
// If true, forces this renderable to use wireframe rendering.
void SetWireframe(bool wireframe);
// Adds this renderable to the filament Scene.
void AddToScene(filament::Scene* scene);
// Removes this renderable from the filament Scene.
void RemoveFromScene(filament::Scene* scene);
// Updates the parameters and textures of the material for this renderable.
void UpdateMaterial(const mjrfMaterial& material);
// Returns this renderable's current material.
const mjrfMaterial& GetMaterial() const;
// Prepares the material instances for the given render requests.
void Prepare(std::span<const mjrfRenderRequest*> requests,
ReflectionManager* reflection_mgr);
// Binds the material instance for the given render request.
void BindMaterialInstance(const mjrfRenderRequest& request);
MaterialManager::MaterialKey SetMaterialInstance(
MaterialManager::MaterialKey key);
static Renderable* downcast(mjrfRenderable* renderable) {
return static_cast<Renderable*>(renderable);
}
static const Renderable* downcast(const mjrfRenderable* renderable) {
return static_cast<const Renderable*>(renderable);
}
private:
// In most cases, a Renderable will be composed of a single filament Entity.
// However, for some built-in geom types (e.g. capsules) we compose the
// renderable out of multiple Entities.
struct Part {
utils::Entity entity;
const Mesh* mesh = nullptr;
int elem_offset = 0;
int elem_count = 0;
};
// A tuple of translation, rotation, and size.
struct Trs {
filament::math::float3 translation{0.0f, 0.0f, 0.0f};
filament::math::mat3f rotation;
filament::math::float3 size{1.0f, 1.0f, 1.0f};
filament::math::mat4f ToTransform() const {
return filament::math::mat4f(rotation, translation) *
filament::math::mat4f::scaling(size);
}
};
// For each render request in a batch, we may need to render the object
// differently. However, we cannot change the material instance during the
// actual frame rendering (i.e. between beginFrame/endFrame). Instead, we can
// switch out the material instance entirely for each request. We keep track
// of which instance, as well as other render state, to use with each render
// request.
struct DrawState {
MaterialManager::MaterialKey material_key;
bool cast_shadows = true;
bool receive_shadows = true;
bool wireframe = false;
};
// When composing a multi-part renderable, each Entity will have its own
// transform offset based on the transform of the Renderable itself.
using GetTransformFn = std::function<filament::math::mat4f(int, const Trs&)>;
void AppendMesh(const Mesh* mesh);
void InitPartEntity(Part& part);
void UpdateTransform();
filament::Engine* GetEngine();
MaterialManager* material_mgr_;
mjrfRenderableParams params_;
mjtGeom geom_type_ = mjGEOM_NONE;
mjrfMaterial material_;
std::deque<DrawState> draw_queue_;
DrawState curr_state_;
filament::Scene* assigned_scene_ = nullptr;
std::vector<Part> parts_;
filament::math::mat4f transform_;
GetTransformFn get_transform_fn_;
Trs trs_;
uint8_t layer_mask_ = 0x00;
bool infinite_plane_ = false;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_RENDERABLE_H_
+456
View File
@@ -0,0 +1,456 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/scene_view.h"
#include <cstdint>
#include <memory>
#include <span>
#include <string_view>
#include <vector>
#include <filament/ColorGrading.h>
#include <filament/LightManager.h>
#include <filament/Material.h>
#include <filament/Options.h>
#include <filament/RenderTarget.h>
#include <filament/RenderableManager.h>
#include <filament/Renderer.h>
#include <filament/Skybox.h>
#include <filament/TransformManager.h>
#include <filament/View.h>
#include <filament/Viewport.h>
#include <math/TVecHelpers.h>
#include <math/mat4.h>
#include <math/mathfwd.h>
#include <math/scalar.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <utils/EntityManager.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/color_grading_options.h"
#include "render/filament/core/light.h"
#include "render/filament/core/material_manager.h"
#include "render/filament/core/object_manager.h"
#include "render/filament/core/outliner.h"
#include "render/filament/core/reflection_manager.h"
#include "render/filament/core/render_target.h"
#include "render/filament/core/renderable.h"
#include "render/filament/core/texture.h"
#include "render/filament/mjrfilament.h"
#include "render/filament/support/filament_util.h"
namespace mujoco {
using filament::math::float3;
using filament::math::float4;
using filament::math::mat4;
static filament::ColorGrading::Builder ToBuilder(
const ColorGradingOptions& opts) {
return filament::ColorGrading::Builder()
.format(opts.format)
.dimensions(opts.dimension)
.luminanceScaling(opts.luminance_scaling)
.gamutMapping(opts.gamut_mapping)
.exposure(opts.exposure)
.nightAdaptation(opts.night_adaptation)
.contrast(opts.contrast)
.vibrance(opts.vibrance)
.saturation(opts.saturation)
.whiteBalance(opts.temperature, opts.tint)
.channelMixer(opts.out_red, opts.out_green, opts.out_blue)
.shadowsMidtonesHighlights(opts.shadows, opts.midtones, opts.highlights,
opts.tonal_ranges)
.slopeOffsetPower(opts.slope, opts.offset, opts.power)
.curves(opts.shadow_gamma, opts.mid_point, opts.highlight_scale);
}
static void SetupCamera(const mjrCamera& cam,
const filament::Viewport& viewport,
filament::Camera* camera) {
const filament::Camera::Projection type =
cam.orthographic ? filament::Camera::Projection::ORTHO
: filament::Camera::Projection::PERSPECTIVE;
const float3 cam_pos(cam.pos[0], cam.pos[1], cam.pos[2]);
const float3 cam_fwd(cam.forward[0], cam.forward[1], cam.forward[2]);
const float3 cam_up(cam.up[0], cam.up[1], cam.up[2]);
const float3 cam_at = cam_pos + cam_fwd;
const float aspect_ratio = (float)viewport.width / (float)viewport.height;
const float halfwidth =
cam.frustum_width
? cam.frustum_width
: 0.5f * aspect_ratio * (cam.frustum_top - cam.frustum_bottom);
camera->lookAt(cam_pos, cam_at, cam_up);
camera->setProjection(type, cam.frustum_center - halfwidth,
cam.frustum_center + halfwidth, cam.frustum_bottom,
cam.frustum_top, cam.frustum_near, cam.frustum_far);
}
// Sets up the `reflection_camera`'s projection matrix so that it is a
// reflection of the `src_camera` across the plane defined by the
// `surface_xform`. The generated projection is an oblique projection so that
// the texture can be applied directly to the plane with screenspace uvs.
static void SetupReflectionCamera(const mat4& surface_xform,
const filament::Camera* src_camera,
filament::Camera* reflection_camera,
float near = 0.01f, float far = 100.0f) {
const mat4 src_model_matrix = src_camera->getModelMatrix();
const mat4 src_view_matrix = src_camera->getViewMatrix();
const mat4 src_projection = src_camera->getProjectionMatrix();
reflection_camera->setModelMatrix(ToReflectionMatrix(surface_xform) *
src_model_matrix);
const float3 normal = surface_xform[2].xyz;
const float3 view_pos = (src_view_matrix * surface_xform[3]).xyz;
const float3 view_normal = (src_view_matrix * float4(normal, 0.0f)).xyz;
const float3 plane_normal_camera = view_normal;
const float plane_dist_camera = -dot(plane_normal_camera, view_pos);
const float4 oblique_plane(plane_normal_camera, plane_dist_camera);
const mat4 oblique =
CalculateObliqueProjection(src_projection, oblique_plane);
reflection_camera->setCustomProjection(oblique, near, far);
}
SceneView::SceneView(ObjectManager* object_mgr, MaterialManager* material_mgr,
const mjrfSceneParams& params)
: object_mgr_(object_mgr), material_mgr_(material_mgr) {
filament::Engine* engine = object_mgr_->GetEngine();
reflection_mgr_ = std::make_unique<ReflectionManager>(engine);
scene_ = engine->createScene();
camera_ = engine->createCamera(utils::EntityManager::get().create());
reflect_camera_ = engine->createCamera(utils::EntityManager::get().create());
main_view_ = engine->createView();
main_view_->setScene(scene_);
main_view_->setCamera(camera_);
main_view_->setVisibleLayers(0xff, kLayerMask_All);
depth_segment_view_ = engine->createView();
depth_segment_view_->setScene(scene_);
depth_segment_view_->setCamera(camera_);
depth_segment_view_->setVisibleLayers(0xff, kLayerMask_Object);
depth_segment_view_->setPostProcessingEnabled(false);
reflect_view_ = engine->createView();
reflect_view_->setScene(scene_);
reflect_view_->setCamera(reflect_camera_);
reflect_view_->setShadowingEnabled(false);
reflect_view_->setPostProcessingEnabled(false);
reflect_view_->setFrontFaceWindingInverted(true);
reflect_view_->setVisibleLayers(0xff, kLayerMask_Object);
reflect_view_->setMultiSampleAntiAliasingOptions({.enabled = false});
// Rotate the fog to align with mujoco's +Z up space.
auto fog = main_view_->getFogEntity();
auto& tm = engine->getTransformManager();
tm.create(fog);
tm.setTransform(tm.getInstance(fog),
mat4::rotation(filament::math::f::PI / 2, float3{-1, 0, 0}));
}
SceneView::~SceneView() {
filament::Engine* engine = object_mgr_->GetEngine();
outliner_.reset();
if (skybox_) {
scene_->setSkybox(nullptr);
engine->destroy(skybox_);
}
for (auto& light : lights_) {
light->RemoveFromScene(scene_);
}
for (auto& renderable : renderables_) {
renderable->RemoveFromScene(scene_);
}
lights_.clear();
renderables_.clear();
engine->destroyCameraComponent(reflect_camera_->getEntity());
engine->destroy(reflect_view_);
engine->destroyCameraComponent(camera_->getEntity());
if (color_grading_) {
engine->destroy(color_grading_);
}
engine->destroy(scene_);
engine->destroy(depth_segment_view_);
engine->destroy(main_view_);
}
void SceneView::AddToScene(Light* light) {
if (lights_.insert(light).second) {
light->AddToScene(scene_);
}
}
void SceneView::RemoveFromScene(Light* light) {
if (lights_.erase(light)) {
light->RemoveFromScene(scene_);
}
}
void SceneView::AddToScene(Renderable* renderable) {
if (renderables_.insert(renderable).second) {
renderable->AddToScene(scene_);
}
}
void SceneView::RemoveFromScene(Renderable* renderable) {
if (renderables_.erase(renderable)) {
renderable->RemoveFromScene(scene_);
}
}
void SceneView::SetSkybox(const Texture* skybox_texture) {
if (skybox_) {
scene_->setSkybox(nullptr);
GetEngine()->destroy(skybox_);
skybox_ = nullptr;
}
if (skybox_texture) {
filament::Skybox::Builder builder;
builder.environment(skybox_texture->GetFilamentTexture());
skybox_ = builder.build(*GetEngine());
scene_->setSkybox(skybox_);
}
}
void SceneView::PrepareToRender(std::span<const mjrfRenderRequest*> requests) {
for (const mjrfRenderRequest* request : requests) {
if (request->scene != this) {
mju_error("Invalid scene for SceneView::PrepareToRender.");
}
}
bool has_selected = false;
reflection_mgr_->ClearRenderables();
for (Renderable* renderable : renderables_) {
renderable->Prepare(requests, reflection_mgr_.get());
if (renderable->GetMaterial().selected) {
has_selected = true;
}
}
if (has_selected) {
mjrfMaterial material;
mjrf_defaultMaterial(&material);
material.selected = true;
outline_material_key_ = material_mgr_->PrepareMaterialInstance(
material, mjDRAW_MODE_DEFAULT, mjGEOM_NONE, nullptr);
}
}
void SceneView::Render(filament::Renderer* renderer,
const mjrfRenderRequest& request) {
if (request.scene != this) {
mju_error("Invalid scene for SceneView::Render.");
}
filament::Viewport viewport(request.viewport.left, request.viewport.bottom,
request.viewport.width, request.viewport.height);
filament::View* view = main_view_;
if (request.draw_mode == mjDRAW_MODE_DEPTH ||
request.draw_mode == mjDRAW_MODE_SEGMENTATION_BY_ID ||
request.draw_mode == mjDRAW_MODE_SEGMENTATION_BY_COLOR) {
view = depth_segment_view_;
}
view->setViewport(viewport);
view->setShadowingEnabled(request.enable_shadows);
view->setPostProcessingEnabled(request.enable_post_processing);
filament::MultiSampleAntiAliasingOptions options =
view->getMultiSampleAntiAliasingOptions();
RenderTarget* render_target = RenderTarget::downcast(request.target);
if (render_target) {
// We need to disable msaa in order to render to texture.
view->setMultiSampleAntiAliasingOptions({.enabled = false});
}
SetupCamera(request.camera, viewport, camera_);
std::vector<Renderable*> selected_renderables;
for (auto& iter : renderables_) {
iter->BindMaterialInstance(request);
if (iter->GetMaterial().selected &&
request.draw_mode == mjDRAW_MODE_DEFAULT) {
selected_renderables.push_back(iter);
}
}
for (int i = 0; i < reflection_mgr_->GetNumRenderables(); ++i) {
Renderable* renderable =
Renderable::downcast(reflection_mgr_->GetRenderable(i));
const mat4 transform(renderable->GetTransform());
SetupReflectionCamera(transform, camera_, reflect_camera_);
// Hide reflective surface from its own reflection pass.
std::uint8_t prev_layer_mask = renderable->SetLayerMask(kLayerMask_None);
// Render the reflection to its render target.
viewport.left = 0;
viewport.bottom = 0;
reflect_view_->setViewport(viewport);
const RenderTarget* target = reflection_mgr_->GetRenderTarget(i);
reflect_view_->setRenderTarget(target->GetFilamentRenderTarget());
renderer->render(reflect_view_);
reflect_view_->setRenderTarget(nullptr);
// Unhide the reflective surface.
renderable->SetLayerMask(prev_layer_mask);
}
filament::RenderTarget* filament_render_target =
render_target ? render_target->GetFilamentRenderTarget() : nullptr;
view->setRenderTarget(filament_render_target);
renderer->render(view);
view->setRenderTarget(nullptr);
if (!selected_renderables.empty()) {
if (!outliner_) {
outliner_ =
std::make_unique<Outliner>(object_mgr_, kLayerMask_Outline,
float4{0.9f, 0.9f, 0.2f, 1.0f}, 3.5f);
}
for (Renderable* renderable : selected_renderables) {
renderable->SetMaterialInstance(outline_material_key_);
}
outliner_->Render(renderer, view, filament_render_target);
}
if (request.target) {
view->setMultiSampleAntiAliasingOptions(options);
}
}
void SceneView::SetColorGradingOptions(const ColorGradingOptions& opts) {
auto tone_mapper = CreateToneMapper(opts.tone_mapper);
auto color_grading = ToBuilder(color_grading_options_)
.toneMapper(tone_mapper.get())
.build(*GetEngine());
main_view_->setColorGrading(color_grading);
if (color_grading_) {
GetEngine()->destroy(color_grading_);
}
color_grading_ = color_grading;
color_grading_options_ = opts;
}
filament::View* SceneView::GetDefaultRenderView() { return main_view_; }
ColorGradingOptions SceneView::GetColorGradingOptions() const {
return color_grading_options_;
}
void SceneView::Configure(const mjModel* model) {
auto cg = color_grading_options_;
cg.exposure = ReadElement(model, "filament.cg.exposure", cg.exposure);
cg.contrast = ReadElement(model, "filament.cg.contrast", cg.contrast);
cg.vibrance = ReadElement(model, "filament.cg.vibrance", cg.vibrance);
cg.saturation = ReadElement(model, "filament.cg.saturation", cg.saturation);
cg.temperature =
ReadElement(model, "filament.cg.temperature", cg.temperature);
cg.tint = ReadElement(model, "filament.cg.tint", cg.tint);
cg.gamut_mapping =
ReadElement(model, "filament.cg.gamut_mapping", cg.gamut_mapping);
cg.luminance_scaling =
ReadElement(model, "filament.cg.luminance_scaling", cg.luminance_scaling);
cg.slope = ReadElement(model, "filament.cg.slope", cg.slope);
cg.offset = ReadElement(model, "filament.cg.offset", cg.offset);
cg.power = ReadElement(model, "filament.cg.power", cg.power);
cg.shadow_gamma =
ReadElement(model, "filament.cg.shadow_gamma", cg.shadow_gamma);
cg.mid_point = ReadElement(model, "filament.cg.mid_point", cg.mid_point);
cg.highlight_scale =
ReadElement(model, "filament.cg.highlight_scale", cg.highlight_scale);
cg.shadows = ReadElement(model, "filament.cg.shadows", cg.shadows);
cg.midtones = ReadElement(model, "filament.cg.midtones", cg.midtones);
cg.highlights = ReadElement(model, "filament.cg.highlights", cg.highlights);
cg.tonal_ranges =
ReadElement(model, "filament.cg.tonal_ranges", cg.tonal_ranges);
auto tone_mapping =
ReadElement<std::string_view>(model, "filament.cg.tone_mapping");
if (tone_mapping == "aces") {
cg.tone_mapper = ToneMapperType::kACES;
} else if (tone_mapping == "aces_legacy") {
cg.tone_mapper = ToneMapperType::kACESLegacy;
} else if (tone_mapping == "filmic") {
cg.tone_mapper = ToneMapperType::kFilmic;
} else if (tone_mapping == "linear") {
cg.tone_mapper = ToneMapperType::kLinear;
} else if (tone_mapping == "pbr_neutral") {
cg.tone_mapper = ToneMapperType::kPBRNeutral;
}
SetColorGradingOptions(cg);
auto ao = main_view_->getAmbientOcclusionOptions();
ao.enabled = ReadElement(model, "filament.ao.enabled", true);
ao.bentNormals = ReadElement(model, "filament.ao.bent_normals", false);
ao.ssct.enabled = ReadElement(model, "filament.ao.ssct", ao.ssct.enabled);
ao.quality =
ReadElement(model, "filament.ao.quality", filament::QualityLevel::ULTRA);
ao.lowPassFilter = ReadElement(model, "filament.ao.low_pass_filter",
filament::QualityLevel::ULTRA);
ao.upsampling = ReadElement(model, "filament.ao.upsampling",
filament::QualityLevel::ULTRA);
ao.bilateralThreshold =
ReadElement(model, "filament.ao.bilateral_threshold", 0.5f);
main_view_->setAmbientOcclusionOptions(ao);
auto msaa = main_view_->getMultiSampleAntiAliasingOptions();
msaa.enabled = ReadElement(model, "filament.msaa.enabled", true);
main_view_->setMultiSampleAntiAliasingOptions(msaa);
auto shadow_type = main_view_->getShadowType();
shadow_type = ReadElement(model, "filament.shadows.type", shadow_type);
main_view_->setShadowType(shadow_type);
auto fog_opts = main_view_->getFogOptions();
fog_opts.enabled =
ReadElement(model, "filament.fog.enabled", fog_opts.enabled);
fog_opts.color = ReadElement(model, "filament.fog.color", fog_opts.color);
fog_opts.distance =
ReadElement(model, "filament.fog.distance", fog_opts.distance);
fog_opts.density =
ReadElement(model, "filament.fog.density", fog_opts.density);
fog_opts.cutOffDistance = ReadElement(model, "filament.fog.cutOffDistance",
fog_opts.cutOffDistance);
fog_opts.maximumOpacity = ReadElement(model, "filament.fog.maximumOpacity",
fog_opts.maximumOpacity);
fog_opts.height = ReadElement(model, "filament.fog.height", fog_opts.height);
fog_opts.heightFalloff =
ReadElement(model, "filament.fog.heightFalloff", fog_opts.heightFalloff);
fog_opts.inScatteringStart = ReadElement(
model, "filament.fog.inScatteringStart", fog_opts.inScatteringStart);
fog_opts.inScatteringSize = ReadElement(
model, "filament.fog.inScatteringSize", fog_opts.inScatteringSize);
main_view_->setFogOptions(fog_opts);
auto bloom = main_view_->getBloomOptions();
bloom.enabled = ReadElement(model, "filament.bloom.enabled", bloom.enabled);
bloom.strength =
ReadElement(model, "filament.bloom.strength", bloom.strength);
bloom.dirtStrength =
ReadElement(model, "filament.bloom.dirt_strength", bloom.dirtStrength);
bloom.quality = ReadElement(model, "filament.bloom.quality", bloom.quality);
bloom.resolution =
ReadElement(model, "filament.bloom.resolution", bloom.resolution);
bloom.levels = ReadElement(model, "filament.bloom.levels", bloom.levels);
main_view_->setBloomOptions(bloom);
}
} // namespace mujoco
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_SCENE_VIEW_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_SCENE_VIEW_H_
#include <memory>
#include <span>
#include <unordered_set>
#include <filament/Camera.h>
#include <filament/ColorGrading.h>
#include <filament/Engine.h>
#include <filament/Scene.h>
#include <filament/View.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/color_grading_options.h"
#include "render/filament/core/light.h"
#include "render/filament/core/material_manager.h"
#include "render/filament/core/object_manager.h"
#include "render/filament/core/outliner.h"
#include "render/filament/core/reflection_manager.h"
#include "render/filament/core/renderable.h"
#include "render/filament/core/texture.h"
#include "render/filament/mjrfilament.h"
namespace mujoco {
// Creates and owns the filament Scene and View (and Camera) classes.
//
// The filament Scene is populated with the objects (e.g. lights, renderables,
// skybox, etc.). It manages multiple views to support a variety of draw modes
// (e.g. normal, depth, segmentation, etc.) as well as reflective surfaces.
class SceneView : public mjrfScene {
public:
SceneView(ObjectManager* object_mgr, MaterialManager* material_mgr,
const mjrfSceneParams& params);
~SceneView();
SceneView(const SceneView&) = delete;
SceneView& operator=(const SceneView&) = delete;
// Adds/removes entities from the scene.
void AddToScene(Light* light);
void RemoveFromScene(Light* light);
void AddToScene(Renderable* renderable);
void RemoveFromScene(Renderable* renderable);
void SetSkybox(const Texture* skybox_texture);
// Performs necessary preparations in order to render the given requests.
// Assumes that the Render() function will be called the same number of times
// and in the same order with the given requests.
void PrepareToRender(std::span<const mjrfRenderRequest*> requests);
// Fulfills the given render request using the renderer.
void Render(filament::Renderer* renderer, const mjrfRenderRequest& request);
// Returns the filament Engine managing the scene.
filament::Engine* GetEngine() const { return object_mgr_->GetEngine(); }
// Returns the underlying filament View that is used for normal rendering.
// Callers can update rendering settings (e.g. post processing) directly.
filament::View* GetDefaultRenderView();
// Helpers for managing the color grading options for the default render view.
ColorGradingOptions GetColorGradingOptions() const;
void SetColorGradingOptions(const ColorGradingOptions& opts);
// Reads filament-specific settings from the mjModel and configures the
// scene view accordingly.
void Configure(const mjModel* model);
static SceneView* downcast(mjrfScene* scene) {
return static_cast<SceneView*>(scene);
}
static const SceneView* downcast(const mjrfScene* scene) {
return static_cast<const SceneView*>(scene);
}
private:
ObjectManager* object_mgr_ = nullptr;
MaterialManager* material_mgr_ = nullptr;
filament::Scene* scene_ = nullptr;
filament::Camera* camera_ = nullptr;
filament::ColorGrading* color_grading_ = nullptr;
ColorGradingOptions color_grading_options_;
filament::View* main_view_;
filament::View* depth_segment_view_;
// Scene objects.
std::unordered_set<Light*> lights_;
std::unordered_set<Renderable*> renderables_;
filament::Skybox* skybox_ = nullptr;
// Custom view and camera for reflective surfaces.
filament::View* reflect_view_ = nullptr;
filament::Camera* reflect_camera_ = nullptr;
MaterialManager::MaterialKey outline_material_key_;
std::unique_ptr<Outliner> outliner_;
std::unique_ptr<ReflectionManager> reflection_mgr_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_SCENE_VIEW_H_
+251
View File
@@ -0,0 +1,251 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/core/texture.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <utility>
#include <filament/Engine.h>
#include <filament/Texture.h>
#include <image/Ktx1Bundle.h>
#include <ktxreader/Ktx1Reader.h>
#include <mujoco/mujoco.h>
#include "render/filament/mjrfilament.h"
namespace mujoco {
static constexpr int kNumFacesPerCube = 6;
static bool IsCompressed(const mjrfTextureConfig& config) {
return config.format == mjPIXEL_FORMAT_KTX;
}
static bool IsCubeMap(const mjrfTextureConfig& config) {
return config.sampler_type == mjTEXTURE_CUBE ||
config.sampler_type == mjTEXTURE_SKYBOX;
}
static int GetFaceHeight(const mjrfTextureConfig& config) {
int face_height = config.height;
if (config.width != config.height) {
if (config.width * kNumFacesPerCube != config.height) {
mju_error("Cube maps must contain 6 square images.");
}
face_height = config.height / kNumFacesPerCube;
}
if (config.width != face_height) {
mju_error("Cube map faces must be square.");
}
return face_height;
}
static int GetNumChannels(const mjrfTextureConfig& config) {
switch (config.format) {
case mjPIXEL_FORMAT_R8:
return 1;
case mjPIXEL_FORMAT_RGB8:
return 3;
case mjPIXEL_FORMAT_RGBA8:
return 4;
default:
mju_error("Unsupported format: %d", (int)config.format);
return 0;
}
}
static filament::Texture::Format GetTextureFormat(
const mjrfTextureConfig& config) {
switch (config.format) {
case mjPIXEL_FORMAT_R8:
return filament::Texture::Format::R;
case mjPIXEL_FORMAT_RGB8:
return filament::Texture::Format::RGB;
case mjPIXEL_FORMAT_RGBA8:
return filament::Texture::Format::RGBA;
default:
mju_error("Unsupported format: %d", (int)config.format);
return filament::Texture::Format::UNUSED;
}
}
static filament::Texture::InternalFormat GetTextureInternalFormat(
const mjrfTextureConfig& config) {
if (config.color_space == mjCOLORSPACE_SRGB) {
switch (config.format) {
case mjPIXEL_FORMAT_RGB8:
return filament::Texture::InternalFormat::SRGB8;
case mjPIXEL_FORMAT_RGBA8:
return filament::Texture::InternalFormat::SRGB8_A8;
default:
mju_error("Unsupported format: %d", (int)config.format);
return filament::Texture::InternalFormat::UNUSED;
}
} else {
switch (config.format) {
case mjPIXEL_FORMAT_R8:
return filament::Texture::InternalFormat::R8;
case mjPIXEL_FORMAT_RGB8:
return filament::Texture::InternalFormat::RGB8;
case mjPIXEL_FORMAT_RGBA8:
return filament::Texture::InternalFormat::RGBA8;
case mjPIXEL_FORMAT_R32F:
return filament::Texture::InternalFormat::R32F;
case mjPIXEL_FORMAT_DEPTH32F:
return filament::Texture::InternalFormat::DEPTH32F;
default:
mju_error("Unsupported format: %d", (int)config.format);
return filament::Texture::InternalFormat::UNUSED;
}
}
}
Texture::Texture(filament::Engine* engine, const mjrfTextureConfig& config,
InternalFlags flags)
: engine_(engine), config_(config) {
if (IsCompressed(config_)) {
// We defer creation of compressed textures until Upload() is called. In
// the meantime, we don't really know anything about the texture (e.g.
// width, height, etc.).
return;
}
filament::Texture::Builder builder;
builder.width(config_.width);
builder.height(config_.height);
builder.format(GetTextureInternalFormat(config_));
if (IsCubeMap(config_)) {
if (config_.format != mjPIXEL_FORMAT_RGB8) {
mju_error("Only support RGB cubemaps.");
return;
}
builder.height(GetFaceHeight(config_));
builder.sampler(filament::Texture::Sampler::SAMPLER_CUBEMAP);
} else {
builder.sampler(filament::Texture::Sampler::SAMPLER_2D);
}
filament::Texture::Usage usage = filament::Texture::Usage::DEFAULT;
if (flags.color_attachment) {
usage |= filament::Texture::Usage::COLOR_ATTACHMENT;
usage |= filament::Texture::Usage::BLIT_SRC;
} else if (flags.depth_attachment) {
usage |= filament::Texture::Usage::DEPTH_ATTACHMENT;
usage |= filament::Texture::Usage::BLIT_SRC;
} else if (config_.color_space != mjCOLORSPACE_SRGB) {
usage |= filament::Texture::Usage::GEN_MIPMAPPABLE;
}
builder.usage(usage);
texture_ = builder.build(*engine_);
}
Texture::~Texture() {
ReleaseData();
if (texture_) {
engine_->destroy(texture_);
}
}
void Texture::Upload(const mjrfTextureData& data) {
user_data_ = data.user_data;
release_callback_ = data.release;
if (data.bytes == nullptr || data.nbytes == 0) {
ReleaseData();
return;
}
if (config_.format == mjPIXEL_FORMAT_KTX) {
image::Ktx1Bundle* bundle = new image::Ktx1Bundle(
reinterpret_cast<const uint8_t*>(data.bytes), data.nbytes);
has_spherical_harmonics_ = true;
bundle->getSphericalHarmonics(spherical_harmonics_);
const bool is_srgb = false;
texture_ = ktxreader::Ktx1Reader::createTexture(engine_, bundle, is_srgb);
config_.width = texture_->getWidth();
config_.height = texture_->getHeight();
ReleaseData();
return;
}
const int num_channels = GetNumChannels(config_);
const filament::Texture::Type type = filament::Texture::Type::UBYTE;
const filament::Texture::Format format = GetTextureFormat(config_);
if (!IsCubeMap(config_)) {
if (config_.width * config_.height * num_channels != data.nbytes) {
mju_error("Texture size does not match data size.");
}
auto callback = +[](void* buffer, size_t size, void* user) {
reinterpret_cast<Texture*>(user)->ReleaseData();
};
filament::Texture::PixelBufferDescriptor desc(data.bytes, data.nbytes,
format, type, callback, this);
texture_->setImage(*engine_, 0, std::move(desc));
} else {
const int face_size = config_.width * GetFaceHeight(config_) * num_channels;
const int num_bytes = face_size * kNumFacesPerCube;
if (config_.width == config_.height) {
uint8_t* copy = new uint8_t[num_bytes];
auto release_callback = +[](void* buffer, size_t size, void* user) {
delete[] reinterpret_cast<uint8_t*>(buffer);
};
for (int i = 0; i < kNumFacesPerCube; ++i) {
std::memcpy(copy + (i * face_size), data.bytes, face_size);
}
filament::Texture::PixelBufferDescriptor desc(copy, num_bytes, format,
type, release_callback);
texture_->setImage(*engine_, /*level=*/0, /*xoffset=*/0, /*yoffset=*/0,
/*zoffset=*/0, config_.width, GetFaceHeight(config_),
/*depth=*/6, std::move(desc));
ReleaseData();
} else {
if (num_bytes != data.nbytes) {
mju_error("Texture size does not match data size.");
}
auto callback = +[](void* buffer, size_t size, void* user) {
reinterpret_cast<Texture*>(user)->ReleaseData();
};
filament::Texture::PixelBufferDescriptor desc(
data.bytes, data.nbytes, format, type, callback, this);
texture_->setImage(*engine_, /*level=*/0, /*xoffset=*/0, /*yoffset=*/0,
/*zoffset=*/0, config_.width, GetFaceHeight(config_),
/*depth=*/6, std::move(desc));
}
}
if (config_.color_space != mjCOLORSPACE_SRGB) {
texture_->generateMipmaps(*engine_);
}
}
void Texture::ReleaseData() {
if (release_callback_) {
release_callback_(user_data_);
release_callback_ = nullptr;
user_data_ = nullptr;
}
}
mjrSamplerType Texture::GetSamplerType() const {
return static_cast<mjrSamplerType>(config_.sampler_type);
}
} // namespace mujoco
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_CORE_TEXTURE_H_
#define MUJOCO_SRC_RENDER_FILAMENT_CORE_TEXTURE_H_
#include <filament/Engine.h>
#include <filament/Texture.h>
#include <math/vec3.h>
#include "render/filament/mjrfilament.h"
// Functions for creating filament textures.
namespace mujoco {
// Wrapper around a filament::Texture.
class Texture : public mjrfTexture {
public:
// Flags for internal use.
struct InternalFlags {
InternalFlags() : color_attachment(false), depth_attachment(false) {}
bool color_attachment;
bool depth_attachment;
};
// Creates a texture with the given data.
Texture(filament::Engine* engine, const mjrfTextureConfig& config,
InternalFlags flags = InternalFlags());
~Texture();
Texture(const Texture&) = delete;
Texture& operator=(const Texture&) = delete;
// Uploads the given data to the texture.
void Upload(const mjrfTextureData& data);
// Returns the width of the texture.
int GetWidth() const { return config_.width; }
// Returns the height of the texture.
int GetHeight() const { return config_.height; }
// Returns the target of the texture.
mjrSamplerType GetSamplerType() const;
// Returns the underlying filament texture.
filament::Texture* GetFilamentTexture() const { return texture_; }
// Returns any spherical harmonics data associated with the texture.
using SphericalHarmonics = filament::math::float3[9];
const SphericalHarmonics* GetSphericalHarmonics() const {
return has_spherical_harmonics_ ? &spherical_harmonics_ : nullptr;
}
static Texture* downcast(mjrfTexture* texture) {
return static_cast<Texture*>(texture);
}
static const Texture* downcast(const mjrfTexture* texture) {
return static_cast<const Texture*>(texture);
}
private:
void ReleaseData();
filament::Engine* engine_ = nullptr;
filament::Texture* texture_ = nullptr;
mjrfTextureConfig config_;
SphericalHarmonics spherical_harmonics_;
bool has_spherical_harmonics_ = false;
void* user_data_ = nullptr;
void (*release_callback_)(void* user_data) = nullptr;
};
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_CORE_TEXTURE_H_
+320
View File
@@ -0,0 +1,320 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/mjrfilament.h"
#include <array>
#include <cstdint>
#include <cstring>
#include <math/mat3.h>
#include <math/vec3.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mujoco.h>
#include "render/filament/core/filament_context.h"
#include "render/filament/core/light.h"
#include "render/filament/core/mesh.h"
#include "render/filament/core/render_target.h"
#include "render/filament/core/renderable.h"
#include "render/filament/core/scene_view.h"
#include "render/filament/core/texture.h"
template <int N>
static void setf(float (&arr)[N], const std::array<float, N>& values) {
for (int i = 0; i < N; ++i) {
arr[i] = values[i];
}
}
extern "C" {
void mjrf_defaultFilamentConfig(mjrFilamentConfig* config) {
memset(config, 0, sizeof(mjrFilamentConfig));
}
void mjrf_defaultTextureData(mjrfTextureData* data) {
memset(data, 0, sizeof(mjrfTextureData));
}
void mjrf_defaultTextureConfig(mjrfTextureConfig* config) {
memset(config, 0, sizeof(mjrfTextureConfig));
}
void mjrf_defaultMeshData(mjrfMeshData* data) {
memset(data, 0, sizeof(mjrfMeshData));
}
void mjrf_defaultSceneParams(mjrfSceneParams* params) {
memset(params, 0, sizeof(mjrfSceneParams));
}
void mjrf_defaultLightParams(mjrfLightParams* params) {
memset(params, 0, sizeof(mjrfLightParams));
params->type = mjLIGHT_POINT;
params->texture = nullptr;
params->color[0] = 0;
params->color[1] = 0;
params->color[2] = 0;
params->intensity = 0.0f;
params->cast_shadows = true;
params->range = 10.0f;
params->spot_cone_angle = 180.f;
params->bulb_radius = 0.0f;
params->shadow_map_size = 2048;
params->vsm_blur_width = 0.0f;
}
void mjrf_defaultMaterial(mjrfMaterial* material) {
memset(material, 0, sizeof(mjrfMaterial));
setf(material->color, {1.f, 1.f, 1.f, 1.f});
setf(material->uv_scale, {1, 1, 1});
material->sleep_state = mjS_STATIC;
material->island_id = -1;
material->emissive = -1.0f;
material->specular = -1.0f;
material->glossiness = -1.0f;
material->metallic = -1.0f;
material->roughness = -1.0f;
}
void mjrf_defaultRenderableParams(mjrfRenderableParams* params) {
memset(params, 0, sizeof(mjrfRenderableParams));
params->cast_shadows = true;
params->receive_shadows = true;
params->blend_order = 0;
}
void mjrf_defaultRenderTargetConfig(mjrfRenderTargetConfig* config) {
memset(config, 0, sizeof(mjrfRenderTargetConfig));
config->color_format = mjPIXEL_FORMAT_RGBA8;
config->depth_format = mjPIXEL_FORMAT_DEPTH32F;
}
void mjrf_defaultRenderRequest(mjrfRenderRequest* request) {
memset(request, 0, sizeof(mjrfRenderRequest));
request->enable_post_processing = true;
request->enable_reflections = true;
request->enable_shadows = true;
}
void mjrf_defaultReadPixelsRequest(mjrfReadPixelsRequest* request) {
memset(request, 0, sizeof(mjrfReadPixelsRequest));
}
void mjrf_defaultFrameStats(mjrfFrameStats* stats) {
memset(stats, 0, sizeof(mjrfFrameStats));
}
mjrfContext* mjrf_createContext(const mjrFilamentConfig* config) {
return new mujoco::FilamentContext(config);
}
void mjrf_destroyContext(mjrfContext* ctx) {
delete mujoco::FilamentContext::downcast(ctx);
}
mjrfTexture* mjrf_createTexture(mjrfContext* ctx,
const mjrfTextureConfig* config) {
return new mujoco::Texture(
mujoco::FilamentContext::downcast(ctx)->GetEngine(), *config);
}
void mjrf_destroyTexture(mjrfTexture* texture) {
delete mujoco::Texture::downcast(texture);
}
mjrfMesh* mjrf_createMesh(mjrfContext* ctx, const mjrfMeshData* data) {
return new mujoco::Mesh(mujoco::FilamentContext::downcast(ctx)->GetEngine(),
*data);
}
void mjrf_destroyMesh(mjrfMesh* mesh) { delete mujoco::Mesh::downcast(mesh); }
mjrfScene* mjrf_createScene(mjrfContext* ctx, const mjrfSceneParams* params) {
return new mujoco::SceneView(
mujoco::FilamentContext::downcast(ctx)->GetObjectManager(),
mujoco::FilamentContext::downcast(ctx)->GetMaterialManager(), *params);
}
void mjrf_destroyScene(mjrfScene* scene) {
delete mujoco::SceneView::downcast(scene);
}
mjrfLight* mjrf_createLight(mjrfContext* ctx, const mjrfLightParams* params) {
return new mujoco::Light(mujoco::FilamentContext::downcast(ctx)->GetEngine(),
*params);
}
void mjrf_destroyLight(mjrfLight* light) {
delete mujoco::Light::downcast(light);
}
mjrfRenderable* mjrf_createRenderable(mjrfContext* ctx,
const mjrfRenderableParams* params) {
return new mujoco::Renderable(
mujoco::FilamentContext::downcast(ctx)->GetEngine(), *params,
mujoco::FilamentContext::downcast(ctx)->GetMaterialManager());
}
void mjrf_destroyRenderable(mjrfRenderable* renderable) {
delete mujoco::Renderable::downcast(renderable);
}
mjrfRenderTarget* mjrf_createRenderTarget(
mjrfContext* ctx, const mjrfRenderTargetConfig* config) {
return new mujoco::RenderTarget(
mujoco::FilamentContext::downcast(ctx)->GetEngine(), *config);
}
void mjrf_destroyRenderTarget(mjrfRenderTarget* render_target) {
delete mujoco::RenderTarget::downcast(render_target);
}
void mjrf_setTextureData(mjrfTexture* texture, const mjrfTextureData* data) {
mujoco::Texture::downcast(texture)->Upload(*data);
}
int mjrf_getTextureWidth(const mjrfTexture* texture) {
return mujoco::Texture::downcast(texture)->GetWidth();
}
int mjrf_getTextureHeight(const mjrfTexture* texture) {
return mujoco::Texture::downcast(texture)->GetHeight();
}
int mjrf_getSamplerType(const mjrfTexture* texture) {
return mujoco::Texture::downcast(texture)->GetSamplerType();
}
void mjrf_setLightEnabled(mjrfLight* light, mjtBool enabled) {
if (enabled) {
mujoco::Light::downcast(light)->Enable();
} else {
mujoco::Light::downcast(light)->Disable();
}
}
void mjrf_setLightIntensity(mjrfLight* light, float intensity) {
mujoco::Light::downcast(light)->SetIntensity(intensity);
}
void mjrf_setLightColor(mjrfLight* light, const float color[3]) {
mujoco::Light::downcast(light)->SetColor({color[0], color[1], color[2]});
}
void mjrf_setLightTransform(mjrfLight* light, const float position[3],
const float direction[3]) {
mujoco::Light::downcast(light)->SetTransform(
{position[0], position[1], position[2]},
{direction[0], direction[1], direction[2]});
}
int mjrf_getLightType(const mjrfLight* light) {
return mujoco::Light::downcast(light)->GetType();
}
void mjrf_setRenderableMesh(mjrfRenderable* renderable, const mjrfMesh* mesh,
int elem_offset, int elem_count) {
mujoco::Renderable::downcast(renderable)
->SetMesh(mujoco::Mesh::downcast(mesh), elem_offset, elem_count);
}
void mjrf_setRenderableGeomMesh(mjrfRenderable* renderable, mjtGeom type,
int nstack, int nslice, int nquad) {
mujoco::Renderable::downcast(renderable)
->SetGeomMesh(type, nstack, nslice, nquad);
}
void mjrf_setRenderableMaterial(mjrfRenderable* renderable,
const mjrfMaterial* material) {
mujoco::Renderable::downcast(renderable)->UpdateMaterial(*material);
}
void mjrf_getRenderableMaterial(mjrfRenderable* renderable,
mjrfMaterial* material) {
*material = mujoco::Renderable::downcast(renderable)->GetMaterial();
}
void mjrf_setRenderableTransform(mjrfRenderable* renderable,
const float position[3],
const float rotation[9]) {
const filament::math::float3 fposition{position[0], position[1], position[2]};
const filament::math::mat3f frotation{rotation[0], rotation[3], rotation[6],
rotation[1], rotation[4], rotation[7],
rotation[2], rotation[5], rotation[8]};
mujoco::Renderable::downcast(renderable)->SetTransform(fposition, frotation);
}
void mjrf_setRenderableSize(mjrfRenderable* renderable, const float size[3]) {
const filament::math::float3 fsize{size[0], size[1], size[2]};
mujoco::Renderable::downcast(renderable)->SetSize(fsize);
}
void mjrf_addLightToScene(mjrfScene* scene, mjrfLight* light) {
mujoco::SceneView::downcast(scene)->AddToScene(
mujoco::Light::downcast(light));
}
void mjrf_removeLightFromScene(mjrfScene* scene, mjrfLight* light) {
mujoco::SceneView::downcast(scene)->RemoveFromScene(
mujoco::Light::downcast(light));
}
void mjrf_addRenderableToScene(mjrfScene* scene, mjrfRenderable* renderable) {
mujoco::SceneView::downcast(scene)->AddToScene(
mujoco::Renderable::downcast(renderable));
}
void mjrf_removeRenderableFromScene(mjrfScene* scene,
mjrfRenderable* renderable) {
mujoco::SceneView::downcast(scene)->RemoveFromScene(
mujoco::Renderable::downcast(renderable));
}
void mjrf_setSceneSkybox(mjrfScene* scene, const mjrfTexture* texture) {
mujoco::SceneView::downcast(scene)->SetSkybox(
mujoco::Texture::downcast(texture));
}
void mjrf_configureSceneFromModel(mjrfScene* scene, const mjModel* model) {
mujoco::SceneView::downcast(scene)->Configure(model);
}
void mjrf_resizeRenderTarget(mjrfRenderTarget* render_target, int width,
int height) {
mujoco::RenderTarget::downcast(render_target)->Prepare(width, height);
}
mjrfFrameHandle mjrf_render(mjrfContext* ctx, const mjrfRenderRequest* req,
int nreq, const mjrfReadPixelsRequest* read_req,
int nread_req) {
return mujoco::FilamentContext::downcast(ctx)->Render(
{req, static_cast<size_t>(nreq)},
{read_req, static_cast<size_t>(nread_req)});
}
void mjrf_waitForFrame(mjrfContext* ctx, mjrfFrameHandle frame) {
mujoco::FilamentContext::downcast(ctx)->WaitForFrame(frame);
}
void mjrf_setClearColor(mjrfContext* ctx, const float color[3]) {
mujoco::FilamentContext::downcast(ctx)->SetClearColor(
{color[0], color[1], color[2], 1.0f});
}
void mjrf_getFrameStats(mjrfContext* ctx, mjrfFrameHandle frame,
mjrfFrameStats* stats_out) {
mujoco::FilamentContext::downcast(ctx)->GetFrameStats(frame, stats_out);
}
} // extern "C"
+481
View File
@@ -0,0 +1,481 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_MJRFILAMENT_H_
#define MUJOCO_SRC_RENDER_FILAMENT_MJRFILAMENT_H_
#include <cstdint>
#include <mujoco/mjmodel.h>
#include <mujoco/mjrender.h>
#include <mujoco/mujoco.h>
#if defined(__cplusplus)
extern "C" {
#endif
// IMPORTANT: This API should still be considered experimental and is likely change frequently.
// This library provides a C API for the filament rendering library (github.com/google/filament)
// that is designed to work with the MuJoCo library for visualizing simulations.
//
// The filament renderer is a real-time physically based rendering (PBR) engine developed by Google.
// It is designed to be as small as possible and as efficient as possible, while still providing
// high-quality results. It works across all major platforms (Linux, Windows, macOS, Android, iOS,
// Web) and supports OpenGL, Vulkan, and Metal.
//
// For the purposes of this API, we assume the reader has a basic understanding of rendering
// concepts (e.g. textures, vertices, cameras, framebuffers, etc.). We will also highlight some of
// the key differences between this renderer and the legacy/classic MuJoCo (mjr) renderer.
//
// ## API Overview
//
// There are seven key components: Context, Texture, Mesh, Scene, Light, Renderable, and
// RenderTarget. We'll describe these in detail further below.
//
// Each object is created using a `create` function and destroyed using a `destroy` function, e.g.
// `mjrf_createTexture` and `mjrf_destroyTexture`. The `create` functions accept a pointer to a
// configuration struct (e.g. `mjrTextureConfig`) which describes the parameters for the object to
// be created. Each of these structs has a corresponding `default` function (e.g.
// `mjrf_defaultTextureConfig`) which can be used to initialize the struct to default values. Default
// values are assumed to be 0/NULL unless otherwise specified.
//
// For now, we'll just define opaque handles for each of our components.
struct mjrfContext {};
struct mjrfTexture {};
struct mjrfMesh {};
struct mjrfScene {};
struct mjrfLight {};
struct mjrfRenderable {};
struct mjrfRenderTarget {};
// ## Rendering Context (mjrfContext)
//
// The Context is the main entry point for the library. It manages all the core filament objects
// that are responsible for the rendering of an image.
//
// All other objects (e.g. Textures, Meshes, Scenes, etc.) need a Context in order to be created.
// Otherwise, the main function to use with the Context is `mjrf_render()` which does the actual
// rendering.
//
// Filament uses a separate thread for doing the actual rendering. However, despite that, this API
// is not thread-safe; calls are expected to be made from a single thread. Also, due to the
// asynchronous nature of filament, some APIs provide handles or callbacks to signal when an
// operation is complete. (Note: for WASM builds, filament does not use a separate thread.)
//
// There are two key differences between the mjrfContext and the classic mjrContext. Firstly, the
// filament context will manage the underlying graphics context itself. This means users do not need
// to initialize EGL or similar libraries beforehand. Secondly, the filament context is independent
// of a MuJoCo model. That means you can use a single mjrfContext to render images for multiple
// models.
// Callback function type for rendering operations.
typedef void (*mjrfCallback)(void* user_data);
typedef enum mjrGraphicsApi_ { // underlying graphics API to use for rendering
mjGRAPHICS_API_DEFAULT = 0, // default (platform-dependent)
mjGRAPHICS_API_OPENGL, // desktop, mobile (GLES), web (WebGL)
mjGRAPHICS_API_VULKAN, // vulkan
} mjrGraphicsApi;
struct mjrFilamentConfig { // parameters for creating filament context (mjrfContext)
int graphics_api; // mjrGraphicsApi; rendering graphics API
mjtBool force_software_rendering; // force backend to use software rendering
void* native_window; // platform-dependent window handle (or nullptr for windowless)
};
// Initializes the mjrFilamentConfig to default values.
void mjrf_defaultFilamentConfig(mjrFilamentConfig* config);
// Creates a filament rendering context.
mjrfContext* mjrf_createContext(const mjrFilamentConfig* config);
// Destroys the filament rendering context.
void mjrf_destroyContext(mjrfContext* ctx);
typedef enum mjrDrawMode_ { // how to draw objects in the scene
mjDRAW_MODE_DEFAULT, // default colors and lighting
mjDRAW_MODE_DEFAULT_NO_TEXTURES, // default, but without textures
mjDRAW_MODE_WIREFRAME, // wireframe rendering
mjDRAW_MODE_DEPTH, // grayscale depth map
mjDRAW_MODE_ISLANDS, // color objects based on island and sleep state
mjDRAW_MODE_SEGMENTATION_BY_ID, // color objects based on segmentation id
mjDRAW_MODE_SEGMENTATION_BY_COLOR, // generate visually distinct colors using segmentation id
} mjrDrawMode;
struct mjrfRenderRequest { // a single rendering operation
mjrfScene* scene; // scene to render
mjrCamera camera; // camera (viewpoint) from which to render scene
mjrRect viewport; // viewport (rect area) into which to render
mjrfRenderTarget* target; // target used for rendering (or nullptr for window rendering)
int draw_mode; // mjrDrawMode; method to use for drawing objects
mjtBool enable_post_processing; // enable post processing, enabled by default
mjtBool enable_reflections; // enable reflections, enabled by default
mjtBool enable_shadows; // enable shadows, enabled by default
};
// Initializes the mjrfRenderRequest to default values.
void mjrf_defaultRenderRequest(mjrfRenderRequest* request);
struct mjrfReadPixelsRequest { // a single read operation
mjrfRenderTarget* target; // render target from which to read the image pixels
void* output; // buffer into which the pixels will be stored
mjtSize num_bytes; // size of output buffer
mjrfCallback read_completed; // callback when read is complete; can use to free output
void* user_data; // user data for read_completed_callback
};
// Initializes the mjrfReadPixelsRequest to default values.
void mjrf_defaultReadPixelsRequest(mjrfReadPixelsRequest* request);
// Unique handle assigned to each render request; used to block until request is completed or query
// the status of the request.
typedef uint64_t mjrfFrameHandle;
// Submits the given requests for rendering. Because rendering happens asynchronously, callers have
// to submit both the render and read requests in the same call. Multiple requests and reads can be
// submitted in a single call. These requests will be processed in order, so some care must be
// taken. Firstly, requests should be grouped by target. Next, the combined area of the viewports
// for all requests for a given target must be contained within the dimensions of the target itself.
//
// Callbacks will be invoked from within this function, though there is no guarantee on when exactly
// that will be done.
mjrfFrameHandle mjrf_render(mjrfContext* ctx, const mjrfRenderRequest* req, int nreq,
const mjrfReadPixelsRequest* read_req, int nread_req);
// Waits for all rendering operations to complete for the given frame handle,
// triggering any callbacks as needed.
void mjrf_waitForFrame(mjrfContext* ctx, mjrfFrameHandle frame);
// Sets the clear color for the renderer.
void mjrf_setClearColor(mjrfContext* ctx, const float color[3]);
struct mjrfFrameStats { // stats for a single frame of rendering
double frame_rate; // frame rate, in frames per second
};
// Initializes the mjrFrameStats to default values.
void mjrf_defaultFrameStats(mjrfFrameStats* stats);
// Returns the stats for the given frame but updating the given `stats_out`.
void mjrf_getFrameStats(mjrfContext* ctx, mjrfFrameHandle frame, mjrfFrameStats* stats_out);
// ## Textures (mjrfTexture)
//
// A texture is a 2D or 3D (cubemap) image that adds visual detail to a rendered model, such as
// color or bumpiness, without increasing geometric complexity.
//
// For textures intended to be used for image-based lights (see `mjrfLight` below), you should use
// filament's `cmgen` tool to generate a KTX image from your source image. This tool will calculate
// additional data (i.e. the spherical harmonics) and encode that information into the KTX file.
struct mjrfTextureConfig { // parameters for creating a texture (mjrfTexture)
int width; // texture width; or number of bytes for compressed data (e.g. KTX)
int height; // texture height; or 0 for compressed data (e.g. KTX)
int format; // mjrPixelFormat; (e.g. RGB8, RGBA8, KTX, etc.)
int color_space; // mjrColorSpace; (e.g. LINEAR, sRGB, etc.)
int sampler_type; // mjrSamplerType; texture sampler (e.g. 2D, cube, etc.)
};
// Initializes the mjrfTextureConfig to default values.
void mjrf_defaultTextureConfig(mjrfTextureConfig* config);
// Creates a filament texture. Note that the texture will not be created on the GPU until
// `mjrf_setTextureData()` is called.
mjrfTexture* mjrf_createTexture(mjrfContext* ctx, const mjrfTextureConfig* config);
// Destroys the texture.
void mjrf_destroyTexture(mjrfTexture* texture);
struct mjrfTextureData { // binary data for a texture (mjrfTexture)
const void* bytes; // pointer to image data, or nullptr for empty texture
mjtSize nbytes; // number of bytes in the image data
mjrfCallback release; // callback when data has finished uploading
void* user_data; // user data for release callback
};
// Initializes the mjrfTextureData to default values.
void mjrf_defaultTextureData(mjrfTextureData* data);
// Uploads the given texture data to the texture.
void mjrf_setTextureData(mjrfTexture* texture, const mjrfTextureData* data);
// Returns the width of the texture.
int mjrf_getTextureWidth(const mjrfTexture* texture);
// Returns the height of the texture.
int mjrf_getTextureHeight(const mjrfTexture* texture);
// Returns the sampler type (mjrSamplerType) used by the texture.
int mjrf_getSamplerType(const mjrfTexture* texture);
// ## Meshes (mjrfMesh)
//
// A mesh describes the surface geometry of an object to be rendered. It is defined as a collection
// of vertices (i.e. a VertexBuffer), a set of indices (i.e. an IndexBuffer) that describes the
// order in which the vertices should be processed, and a primitive type that defined how the
// vertices are to be interpreted (e.g. triangles, lines, etc.) when rendering the surface.
//
// Filament does not directly support normals. Instead, it encodes the normal, tangent, and
// bitangent into a 4-component quaternion describing the "orientation" of the vertex. Ideally, you
// should preprocess your assets to generate this data offline, but we will compute it on the fly if
// needed (at a performance cost).
//
// Vertex data may or may not be interleaved. Interleaved data assumes that the attributes are
// packed in the order specified in the attributes array, with no padding in-between. Additionally,
// the `data` pointer for each attribute is assumed to point to the first element of that type. For
// non-interleaved data, each attribute is assumed to be stored in a separate array.
//
// Additionally, the bounds of the mesh should be computed in order to allow the filament renderer
// to perform frustum-based culling. Alternatively, the bounds can be computed at runtime (though
// there is a small performance cost). If no bounds are provided (or calculated), then frustum
// culling will not be performed.
// Maximum number of vertex attributes in a mesh.
enum { mjMAX_VERTEX_ATTRIBUTES = 16 };
struct mjrfMeshData { // binary data for a mesh (mjrfMesh)
mjtSize nvertices; // number of vertices; all vertex attributes share this size
int nattributes; // number of attributes defined
mjrVertexAttribute attributes[mjMAX_VERTEX_ATTRIBUTES]; // per-vertex attribute information
mjtBool interleaved; // true if vertex attributes are interleaved
mjtSize nindices; // number of indices
const void* indices; // indices data array
int index_type; // mjrIndexType; (e.g. UINT16 or UINT32)
int primitive_type; // mjrMeshPrimitiveType; (e.g. TRIANGLES, etc.)
mjtBool compute_bounds; // if true, compute bounds from vertex positions
float bounds_min[3]; // min/max bounds; assume unset if bounds_min == bounds_max
float bounds_max[3];
mjrfCallback release; // callback when data has finished uploading
void* user_data; // user data for release callback
};
// Initializes the mjrfMeshData to default values.
void mjrf_defaultMeshData(mjrfMeshData* data);
// Creates a mesh with the given data.
mjrfMesh* mjrf_createMesh(mjrfContext* ctx, const mjrfMeshData* data);
// Destroys the mesh.
void mjrf_destroyMesh(mjrfMesh* mesh);
// ## Scenes (mjrfScene)
//
// A scene is a collection of entities (Lights and Renderables) that describes what is to be
// rendered.
struct mjrfSceneParams { // parameters for creating a scene (mjrfScene)
};
// Initializes the mjrfSceneParams to default values.
void mjrf_defaultSceneParams(mjrfSceneParams* params);
// Creates a scene with the given parameters.
mjrfScene* mjrf_createScene(mjrfContext* ctx, const mjrfSceneParams* params);
// Destroys the scene.
void mjrf_destroyScene(mjrfScene* scene);
// Adds a light to the scene.
void mjrf_addLightToScene(mjrfScene* scene, mjrfLight* light);
// Removes the light from the scene.
void mjrf_removeLightFromScene(mjrfScene* scene, mjrfLight* light);
// Adds a renderable to the scene.
void mjrf_addRenderableToScene(mjrfScene* scene, mjrfRenderable* renderable);
// Removes the renderable from the scene.
void mjrf_removeRenderableFromScene(mjrfScene* scene, mjrfRenderable* renderable);
// Sets the skybox (cube texture) for the scene.
void mjrf_setSceneSkybox(mjrfScene* scene, const mjrfTexture* texture);
// Configures the scene based on the parameters in an mjModel.
void mjrf_configureSceneFromModel(mjrfScene* scene, const mjModel* model);
// ## Lights (mjrfLight)
//
// A light is a source of illumination in the scene. (Without lights, a scene will be completely
// black.) There are several different types of lights such as directional, spot, point, and image
// lights.
//
// The primary light in a scene is the image light (also sometimes known as the environment light).
// This is a light that "surrounds" the entire scene and is defined as a 3D texture. Each "pixel" of
// the cubemap is interpreted as the color of projected into the scene from a particular direction.
//
// Directional lights are the next most common type of light and is usually used to simulate the
// sun; a uniformly colored light that is emitted in a single direction.
//
// Filament only supports a single image and directional light. You can define as many point or spot
// lights as you want. Each light source (except image based lights) may or may not cast shadows.
// Each shadow-casting light incurs a performance cost.
struct mjrfLightParams { // parameters for creating a light (mjrfLight)
int type; // mjrLightType; type of light (e.g. spot, point, image, etc.)
const mjrfTexture* texture; // texture; only for image lights
float color[3]; // RGB color
float intensity; // light intensity, in candela
mjtBool cast_shadows; // if true, cast shadows
float range; // effective range of light, in meters
float spot_cone_angle; // spot light cone angle, in degrees
int shadow_map_size; // size of shadow map texture, 0 to use default size
float bulb_radius; // bulb radius, used for soft shadows
float vsm_blur_width; // variance shadow map blur width
};
// Initializes the mjrfLightParams to default values.
void mjrf_defaultLightParams(mjrfLightParams* params);
// Creates a light for the filament renderer.
mjrfLight* mjrf_createLight(mjrfContext* ctx, const mjrfLightParams* params);
// Destroys the light.
void mjrf_destroyLight(mjrfLight* light);
// Enables or disables the light.
void mjrf_setLightEnabled(mjrfLight* light, mjtBool enabled);
// Sets the intensity of the light, in candela.
void mjrf_setLightIntensity(mjrfLight* light, float intensity);
// Sets the RGB color of the light.
void mjrf_setLightColor(mjrfLight* light, const float color[3]);
// Sets the position and direction of the light.
void mjrf_setLightTransform(mjrfLight* light, const float position[3], const float direction[3]);
// Returns the type of the light (mjrLightType).
int mjrf_getLightType(const mjrfLight* light);
// ## Renderables (mjrfRenderable)
//
// A renderable is a single drawable object in the scene. It is defined as a combination of a mesh
// (i.e. surface geometry) and a material (i.e. surface appearance and properties).
//
// In terms of materials, there are three lighting models currently supported:
//
// 1. Metallic-roughness (PBR): this is the preferred model for rendering models based standard
// metallic-roughness workflows.
// 2. Specular-glossiness (non-PBR): this is a legacy model designed to be compatible with classic
// mjr renderer, though it is not 100% identical.
// 3. Unlit: this model ignores lighting and used for rendering UX or decorative elements like
// contact forces and labels.
//
// Which lighting model is used is determined by the mjrfMaterial properties.
struct mjrfMaterial { // material properties for a renderable (mjrfMaterial)
float color[4]; // object color; defaults to white
int32_t segmentation_id; // ID for segmentation rendering; maps to RGB8 color (i.e. 24 bits)
int32_t island_id; // ID to which the renderable belongs
int sleep_state; // mjtSleepState; sleep state of the renderable
float uv_scale[3]; // scale applied to UV coordinates; defaults to (1,1,1)
float uv_offset[3]; // offset applied to UV coordinates; defaults to (0,0,0)
float scissor[4]; // if non-zero, applies scissor testing when rendering
float metallic; // metallic factory [0, 1]; disabled if < 0
float roughness; // roughness factor [0, 1]; disabled if < 0
float specular; // specular factor [0, 1]; disabled if < 0
float glossiness; // glossiness factor [0, 1]; disabled if < 0
float emissive; // emissive/glow factor [0, 1]; disabled if < 0
float reflectance; // blend factor for reflective surfaces [0, 1]; applies only to planes
mjtBool decor_ux; // for ux elements, does not apply any lighting
mjtBool selected; // for "selected" ux elements, adds additional styling
const mjrfTexture* color_texture; // color/albedo texture (RGB8)
const mjrfTexture* opacity_texture; // opacity texture (A8)
const mjrfTexture* normal_texture; // normal map texture (RGB8)
const mjrfTexture* metallic_texture; // metallic map texture (R8)
const mjrfTexture* roughness_texture; // roughness map texture (R8)
const mjrfTexture* occlusion_texture; // ambient occlusion texture (R8)
const mjrfTexture* orm_texture; // occlusion/roughness/metallic texture (RGB8)
const mjrfTexture* emissive_texture; // emissive texture (RGB8)
const mjrfTexture* reflection_texture; // reflection texture, for internal use only
};
// Initializes the mjrfMaterial to default values.
void mjrf_defaultMaterial(mjrfMaterial* material);
struct mjrfRenderableParams { // parameters for creating a renderable (mjrfRenderable)
mjtBool cast_shadows; // if true, casts shadows
mjtBool receive_shadows; // if true, receives shadows
uint16_t blend_order; // controls draw order for transparent objects [0, 8]
};
// Initializes the mjrfRenderableParams to default values.
void mjrf_defaultRenderableParams(mjrfRenderableParams* params);
// Creates a renderable with the given parameters.
mjrfRenderable* mjrf_createRenderable(mjrfContext* ctx, const mjrfRenderableParams* params);
// Destroys the renderable.
void mjrf_destroyRenderable(mjrfRenderable* renderable);
// Sets the mesh of the renderable.
void mjrf_setRenderableMesh(mjrfRenderable* renderable, const mjrfMesh* mesh, int elem_offset,
int elem_count);
// Sets the mesh of the renderable to a built-in mesh based on the geom type. Note: using the same
// parameters (nstack, nslice, nquad) will have better performance as the internal mesh data can be
// shared across renderables.
void mjrf_setRenderableGeomMesh(mjrfRenderable* renderable, mjtGeom type, int nstack, int nslice,
int nquad);
// Sets the material properties and textures of the renderable.
void mjrf_setRenderableMaterial(mjrfRenderable* renderable, const mjrfMaterial* material);
// Copies the material properties of the renderable into the given mjrfMaterial.
void mjrf_getRenderableMaterial(mjrfRenderable* renderable, mjrfMaterial* material);
// Sets the transform position and rotation of the renderable.
void mjrf_setRenderableTransform(mjrfRenderable* renderable, const float position[3],
const float rotation[9]);
// Sets the size of the renderable. Note that, for most renderables, this is equivalent to setting
// the scale. However, for some geom-based renderables, the size scale is not applied uniformly
// (e.g. the spherical ends of a capsule are scaled such that they always remain spherical).
void mjrf_setRenderableSize(mjrfRenderable* renderable, const float size[3]);
// ## Render Targets (mjrfRenderTarget)
//
// A render target is a memory buffer that holds the results of a rendering operation. (This is an
// alternative to rendering directly to the screen.) See mjrf_render for more details.
struct mjrfRenderTargetConfig { // parameters for creating a render target (mjrfRenderTarget)
int width; // texture width
int height; // texture height
int color_format; // mjrPixelFormat; pixel format for color buffer
int depth_format; // mjrPixelFormat; pixel format for depth buffer
};
// Initializes the RenderTargetConfig to default values.
void mjrf_defaultRenderTargetConfig(mjrfRenderTargetConfig* config);
// Creates a render target for the filament renderer.
mjrfRenderTarget* mjrf_createRenderTarget(mjrfContext* ctx, const mjrfRenderTargetConfig* config);
// Destroys the render target.
void mjrf_destroyRenderTarget(mjrfRenderTarget* render_target);
// Resizes the render target to the given width and height.
void mjrf_resizeRenderTarget(mjrfRenderTarget* render_target, int width, int height);
// ## Debug-only functions.
// Draws an ImGui editor for the given scene, exposing filament-specific settings.
void mjrf_DEBUG_drawImguiEditor(mjrfScene* scene);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // MUJOCO_SRC_RENDER_FILAMENT_MJRFILAMENT_H_
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_MJRFILAMENT_CPP_H_
#define MUJOCO_SRC_RENDER_FILAMENT_MJRFILAMENT_CPP_H_
#include <memory>
#include <string>
#include "render/filament/mjrfilament.h"
namespace mujoco {
// A unique pointer to a mujoco object.
template <typename T>
using UniquePtr = std::unique_ptr<T, void (*)(T*)>;
inline UniquePtr<mjrfContext> CreateContext(const mjrFilamentConfig& config) {
mjrfContext* context = mjrf_createContext(&config);
return UniquePtr<mjrfContext>(context, mjrf_destroyContext);
}
inline UniquePtr<mjrfTexture> CreateTexture(mjrfContext* ctx,
const mjrfTextureConfig& config) {
mjrfTexture* texture = mjrf_createTexture(ctx, &config);
return UniquePtr<mjrfTexture>(texture, mjrf_destroyTexture);
}
inline UniquePtr<mjrfMesh> CreateMesh(mjrfContext* ctx,
const mjrfMeshData& data) {
mjrfMesh* mesh = mjrf_createMesh(ctx, &data);
return UniquePtr<mjrfMesh>(mesh, mjrf_destroyMesh);
}
inline UniquePtr<mjrfScene> CreateScene(mjrfContext* ctx,
const mjrfSceneParams& params) {
mjrfScene* scene = mjrf_createScene(ctx, &params);
return UniquePtr<mjrfScene>(scene, mjrf_destroyScene);
}
inline UniquePtr<mjrfLight> CreateLight(mjrfContext* ctx,
const mjrfLightParams& params) {
mjrfLight* light = mjrf_createLight(ctx, &params);
return UniquePtr<mjrfLight>(light, mjrf_destroyLight);
}
inline UniquePtr<mjrfRenderable> CreateRenderable(
mjrfContext* ctx, const mjrfRenderableParams& params) {
mjrfRenderable* renderable = mjrf_createRenderable(ctx, &params);
return UniquePtr<mjrfRenderable>(renderable, mjrf_destroyRenderable);
}
inline UniquePtr<mjrfRenderTarget> CreateRenderTarget(
mjrfContext* ctx, const mjrfRenderTargetConfig& config) {
mjrfRenderTarget* render_target = mjrf_createRenderTarget(ctx, &config);
return UniquePtr<mjrfRenderTarget>(render_target, mjrf_destroyRenderTarget);
}
std::string ResolveFilamentAssetPath(const std::string& filename);
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_MJRFILAMENT_CPP_H_
@@ -0,0 +1,121 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/support/filament_util.h"
#include <limits>
#include <math/TVecHelpers.h>
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/quat.h>
#include <math/vec3.h>
#include <math/vec4.h>
namespace mujoco {
using filament::math::float3;
using filament::math::float4;
using filament::math::mat3f;
using filament::math::mat4;
using filament::math::quatf;
mat4 ToReflectionMatrix(const mat4& xform) {
const float3 normal = xform[2].xyz;
const float dist = dot(xform[3].xyz, normal);
// clang-format off
return mat4(
1.0f - 2.0f * normal.x * normal.x,
0.0f - 2.0f * normal.y * normal.x,
0.0f - 2.0f * normal.z * normal.x,
0.0f,
0.0f - 2.0f * normal.x * normal.y,
1.0f - 2.0f * normal.y * normal.y,
0.0f - 2.0f * normal.z * normal.y,
0.0f,
0.0f - 2.0f * normal.x * normal.z,
0.0f - 2.0f * normal.y * normal.z,
1.0f - 2.0f * normal.z * normal.z,
0.0f,
2.0f * dist * normal.x,
2.0f * dist * normal.y,
2.0f * dist * normal.z,
1.0f
);
// clang-format on
}
mat4 CalculateObliqueProjection(const mat4& projection, const float4& plane) {
mat4 res = projection;
auto sgn = [](float x) {
return (x > 0.0f) ? 1.0f : x < 0.0f ? -1.0f : 0.0f;
};
// The plane should be oriented such that the side to be kept is positive.
// The camera is at (0,0,0) in camera space. The value of the plane equation
// at the camera origin is plane.w. The reflected scene is on the opposite
// side of the plane from the camera. If plane.w is positive, the camera is
// on the positive side, so the reflected scene is on the negative side.
// We need to flip the plane in this case.
float4 clip_plane = plane;
if (plane.w > 0) {
clip_plane = -plane;
}
float4 q;
q.x = (sgn(clip_plane.x) + res[2][0]) / res[0][0];
q.y = (sgn(clip_plane.y) + res[2][1]) / res[1][1];
q.z = -1.0f;
q.w = (1.0f + res[2][2]) / res[3][2];
const float4 c = clip_plane * (2.0f / dot(clip_plane, q));
res[0][2] = c.x;
res[1][2] = c.y;
res[2][2] = c.z;
res[3][2] = c.w;
return res;
}
float4 CalculateOrientation(const float3& normal) {
float3 tangent;
float3 bitangent;
if (normal.y < -1.0f + std::numeric_limits<float>::epsilon()) {
// Handle the singularity.
tangent = float3{-1.0f, 0.0f, 0.0f};
bitangent = float3{0.0f, 0.0f, -1.0f};
} else {
const float a = 1.0f / (1.0f + normal.y);
const float b = -normal.z * normal.x * a;
tangent = float3(b, -normal.z, 1.0f - normal.z * normal.z * a);
bitangent = float3(1.0f - normal.x * normal.x * a, -normal.x, b);
}
quatf orientation = mat3f::packTangentFrame({tangent, bitangent, normal});
return float4(orientation.xyz, orientation.w);
}
float3 CalculateNormal(const filament::math::float3& p1,
const filament::math::float3& p2,
const filament::math::float3& p3) {
const float3 v12 = p2 - p1;
const float3 v13 = p3 - p1;
return normalize(cross(v12, v13));
}
float4 CalculateOrientation(const filament::math::float3& p1,
const filament::math::float3& p2,
const filament::math::float3& p3) {
return CalculateOrientation(CalculateNormal(p1, p2, p3));
}
} // namespace mujoco
+132
View File
@@ -0,0 +1,132 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_RENDER_FILAMENT_SUPPORT_FILAMENT_UTIL_H_
#define MUJOCO_SRC_RENDER_FILAMENT_SUPPORT_FILAMENT_UTIL_H_
#include <string_view>
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mujoco.h>
namespace mujoco {
// Reads a float2 from an array buffer in the model/scene.
template <typename T>
inline filament::math::float2 ReadFloat2(const T* arr, int index = 0) {
const T* ptr = arr + (2 * index);
return filament::math::float2(ptr[0], ptr[1]);
}
// Reads a float3 from an array buffer in the model/scene.
template <typename T>
inline filament::math::float3 ReadFloat3(const T* arr, int index = 0) {
const T* ptr = arr + (3 * index);
return filament::math::float3(ptr[0], ptr[1], ptr[2]);
}
// Reads a float4 from an array buffer in the model/scene.
template <typename T>
inline filament::math::float4 ReadFloat4(const T* arr, int index = 0) {
const T* ptr = arr + (4 * index);
return filament::math::float4(ptr[0], ptr[1], ptr[2], ptr[3]);
}
// Reads a mat3 from an array buffer in the model/scene.
template <typename T>
inline filament::math::mat3f ReadMat3(const T* arr, int index = 0) {
// clang-format off
const T* ptr = arr + (9 * index);
return filament::math::mat3f(ptr[0], ptr[1], ptr[2],
ptr[3], ptr[4], ptr[5],
ptr[6], ptr[7], ptr[8]);
// clang-format on
}
// Calculates a reflection matrix for a plane defined by its transform.
filament::math::mat4 ToReflectionMatrix(const filament::math::mat4& xform);
// Modifies a projection matrix so its near plane coincides with an arbitrary
// plane defined in camera space.
filament::math::mat4 CalculateObliqueProjection(
const filament::math::mat4& projection,
const filament::math::float4& plane);
// Calculates the normal of a triangle given its three vertices.
filament::math::float3 CalculateNormal(const filament::math::float3& p1,
const filament::math::float3& p2,
const filament::math::float3& p3);
// Calculates the orientation of a vertex given just its normal.
filament::math::float4 CalculateOrientation(
const filament::math::float3& normal);
// Calculates the orientation of a triangle given its three vertices.
filament::math::float4 CalculateOrientation(const filament::math::float3& p1,
const filament::math::float3& p2,
const filament::math::float3& p3);
// Reads a value with the given name from the mjModel's data sections. The
// default_value is returned if the named element is not found.
template <typename T>
T ReadElement(const mjModel* model, const char* name, T default_value = T()) {
constexpr bool is_string =
std::is_same_v<T, const char*> || std::is_same_v<T, std::string_view>;
const int type = is_string ? mjOBJ_TEXT : mjOBJ_NUMERIC;
const int id = mj_name2id(model, type, name);
if (id < 0) {
return default_value;
}
if constexpr (std::is_same_v<T, const char*>) {
const char* ptr = model->text_data + model->text_adr[id];
return ptr;
} else if constexpr (std::is_same_v<T, std::string_view>) {
const char* ptr = model->text_data + model->text_adr[id];
// Do not include the null terminator in the string view.
return std::string_view(ptr, model->text_size[id] - 1);
} else if constexpr (std::is_arithmetic_v<T>) {
const mjtNum* ptr = model->numeric_data + model->numeric_adr[id];
return static_cast<T>(*ptr);
} else if constexpr (std::is_enum_v<T>) {
const mjtNum* ptr = model->numeric_data + model->numeric_adr[id];
return static_cast<T>(static_cast<int>(*ptr));
} else if constexpr (std::is_same_v<T, filament::math::float2>) {
const mjtNum* ptr = model->numeric_data + model->numeric_adr[id];
if (model->numeric_size[id] != 2) mju_error("Invalid numeric size.");
return T{ptr[0], ptr[1]};
} else if constexpr (std::is_same_v<T, filament::math::float3>) {
const mjtNum* ptr = model->numeric_data + model->numeric_adr[id];
if (model->numeric_size[id] != 3) mju_error("Invalid numeric size.");
return T{ptr[0], ptr[1], ptr[2]};
} else if constexpr (std::is_same_v<T, filament::math::float4>) {
const mjtNum* ptr = model->numeric_data + model->numeric_adr[id];
if (model->numeric_size[id] != 4) mju_error("Invalid numeric size.");
return T{ptr[0], ptr[1], ptr[2], ptr[3]};
} else if constexpr (std::is_same_v<T, bool>) {
const mjtNum* ptr = model->numeric_data + model->numeric_adr[id];
return static_cast<T>(*ptr != 0);
}
return default_value;
}
} // namespace mujoco
#endif // MUJOCO_SRC_RENDER_FILAMENT_SUPPORT_FILAMENT_UTIL_H_