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:
committed by
Copybara-Service
parent
1aaced190a
commit
96781fa793
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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"
|
||||
@@ -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_
|
||||
@@ -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, ¶ms);
|
||||
return UniquePtr<mjrfScene>(scene, mjrf_destroyScene);
|
||||
}
|
||||
|
||||
inline UniquePtr<mjrfLight> CreateLight(mjrfContext* ctx,
|
||||
const mjrfLightParams& params) {
|
||||
mjrfLight* light = mjrf_createLight(ctx, ¶ms);
|
||||
return UniquePtr<mjrfLight>(light, mjrf_destroyLight);
|
||||
}
|
||||
|
||||
inline UniquePtr<mjrfRenderable> CreateRenderable(
|
||||
mjrfContext* ctx, const mjrfRenderableParams& params) {
|
||||
mjrfRenderable* renderable = mjrf_createRenderable(ctx, ¶ms);
|
||||
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
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user