Initial implementation of the filament rendering backend for mujoco.

This is intended as a drop-in replacement for mjr_render to enable high
quality, physically-based rendering (PBR) using the filament render engine.

This is a work-in-progress and, as such, some features may be missing. This
is also a code-only drop; build files (including instructions on how to
generate the filament materials) will be provided at a later date.

This library is intended to be run with the experimental studio app.

PiperOrigin-RevId: 817593684
Change-Id: Ib9cd14c1881e4897c1111275bd749aa38ebb5317
This commit is contained in:
Haroon Qureshi
2025-10-10 05:00:46 -07:00
committed by Copybara-Service
parent bd68f0c6cc
commit ff7c613c79
56 changed files with 6969 additions and 19 deletions
Binary file not shown.
+54
View File
@@ -0,0 +1,54 @@
// 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,
culling: none,
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,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 : pbr_packed,
shadingModel : lit,
culling: none,
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,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,
culling : none,
flipUV : false,
parameters : [
{ type : float4, name : BaseColorFactor },
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : float3, name : UvScale },
{ 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;
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,
shadingModel : specularGlossiness,
culling : none,
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 : 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;
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,
shadingModel : specularGlossiness,
culling : none,
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,44 @@
// 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,
culling : none,
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,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,
shadingModel : specularGlossiness,
culling: none,
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,36 @@
// 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,
culling: none,
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,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,
shadingModel : specularGlossiness,
culling : none,
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,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_cube,
shadingModel : specularGlossiness,
culling : none,
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,31 @@
// 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,
culling: none,
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,29 @@
// 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,
culling: none,
parameters : [
{ type : float4, name : BaseColorFactor }
]
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
}
}
@@ -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 : unlit_ui,
parameters : [
{
type : sampler2d,
name : glyph
}
],
requires : [
uv0,
color
],
shadingModel : unlit,
culling : none,
depthCulling: false,
blending : transparent,
featureLevel : 0
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
vec2 uv = getUV0();
uv.y = 1.0 - uv.y;
vec4 tex_color = texture2D(materialParams_glyph, uv);
material.baseColor = getColor() * tex_color;
material.baseColor.rgb *= material.baseColor.a;
}
}
@@ -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.
#include "experimental/filament/filament/buffer_util.h"
#include <cstddef>
#include "third_party/filament/filament/backend/include/backend/BufferDescriptor.h"
namespace mujoco {
filament::backend::BufferDescriptor CreateBufferDescriptor(
std::size_t num_bytes, const FillBufferFn& fill) {
std::byte* bytes = new std::byte[num_bytes];
fill(bytes, num_bytes);
const auto callback = [](void* buffer, size_t size, void* user) {
auto* ptr = reinterpret_cast<std::byte*>(user);
delete[] ptr;
};
return filament::backend::BufferDescriptor(bytes, num_bytes, callback, bytes);
}
} // namespace mujoco
@@ -0,0 +1,148 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_BUFFER_UTIL_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_BUFFER_UTIL_H_
#include <cstddef>
#include <cstdint>
#include <functional>
#include <type_traits>
#include "third_party/filament/filament/backend/include/backend/BufferDescriptor.h"
#include <filament/Box.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/VertexBuffer.h>
// Functions for creating filament vertex and index buffers.
namespace mujoco {
// Simple tuple-type of a IndexBuffer+VertexBuffer.
struct FilamentBuffers {
filament::IndexBuffer* index_buffer = nullptr;
filament::VertexBuffer* vertex_buffer = nullptr;
filament::Box bounds = {{-1, -1, -1}, {1, 1, 1}};
};
// Function that fills in the given buffer with actual data.
using FillBufferFn = std::function<void(std::byte*, std::size_t)>;
// Creates and populates a BufferDescriptor (for vertex and index buffers).
filament::backend::BufferDescriptor CreateBufferDescriptor(
std::size_t num_bytes, const FillBufferFn& fill);
// Creates a filament::VertexBuffer based on the VertexType. The fill function
// will be used to populate the buffer.
template <typename VertexType>
filament::VertexBuffer* CreateVertexBuffer(filament::Engine* engine,
std::size_t num_vertices,
const FillBufferFn& fill) {
int vertex_size = 0;
if constexpr (VertexType::kHasPosition) {
vertex_size += sizeof(VertexType::position);
}
if constexpr (VertexType::kHasPosition2d) {
vertex_size += sizeof(VertexType::position);
}
if constexpr (VertexType::kHasOrientation) {
vertex_size += sizeof(VertexType::orientation);
}
if constexpr (VertexType::kHasUv) {
vertex_size += sizeof(VertexType::uv);
}
if constexpr (VertexType::kHasColor) {
vertex_size += sizeof(VertexType::color);
}
auto builder = filament::VertexBuffer::Builder();
builder.bufferCount(1);
builder.vertexCount(num_vertices);
int offset = 0;
if constexpr (VertexType::kHasPosition) {
builder.attribute(filament::VertexAttribute::POSITION, 0,
filament::VertexBuffer::AttributeType::FLOAT3, offset,
vertex_size);
offset += sizeof(VertexType::position);
}
if constexpr (VertexType::kHasPosition2d) {
builder.attribute(filament::VertexAttribute::POSITION, 0,
filament::VertexBuffer::AttributeType::FLOAT2, offset,
vertex_size);
offset += sizeof(VertexType::position);
}
if constexpr (VertexType::kHasOrientation) {
builder.attribute(filament::VertexAttribute::TANGENTS, 0,
filament::VertexBuffer::AttributeType::FLOAT4, offset,
vertex_size);
offset += sizeof(VertexType::orientation);
}
if constexpr (VertexType::kHasUv) {
builder.attribute(filament::VertexAttribute::UV0, 0,
filament::VertexBuffer::AttributeType::FLOAT2, offset,
vertex_size);
offset += sizeof(VertexType::uv);
}
if constexpr (VertexType::kHasColor) {
builder.attribute(filament::VertexAttribute::COLOR, 0,
filament::VertexBuffer::AttributeType::UBYTE4, offset,
vertex_size);
builder.normalized(filament::VertexAttribute::COLOR);
offset += sizeof(VertexType::color);
}
auto vb = builder.build(*engine);
const std::size_t buffer_size = num_vertices * vertex_size;
vb->setBufferAt(*engine, 0, CreateBufferDescriptor(buffer_size, fill));
return vb;
}
// Creates a filament::IndexBuffer. The IndexType should be either uin16_t or
// uint32_t. The fill function will be used to populate the buffer.
template <typename IndexType>
filament::IndexBuffer* CreateIndexBuffer(filament::Engine* engine,
std::size_t num_indices,
const FillBufferFn& fill) {
static_assert(std::is_same<IndexType, uint16_t>::value ||
std::is_same<IndexType, uint32_t>::value);
constexpr auto type = std::is_same<IndexType, uint16_t>::value
? filament::IndexBuffer::IndexType::USHORT
: filament::IndexBuffer::IndexType::UINT;
auto builder = filament::IndexBuffer::Builder();
builder.bufferType(type);
builder.indexCount(num_indices);
auto ib = builder.build(*engine);
const std::size_t buffer_size = num_indices * sizeof(IndexType);
ib->setBuffer(*engine, CreateBufferDescriptor(buffer_size, fill));
return ib;
}
// 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;
}
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_BUFFER_UTIL_H_
@@ -0,0 +1,662 @@
// 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 "experimental/filament/filament/builtins.h"
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <filament/Box.h>
#include <filament/Engine.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/buffer_util.h"
#include "experimental/filament/filament/vertex_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 int AppendQuadIndices(uint16_t* ptr, int idx, uint16_t a, uint16_t b,
uint16_t c, uint16_t d) {
ptr[idx++] = a;
ptr[idx++] = b;
ptr[idx++] = c;
ptr[idx++] = a;
ptr[idx++] = c;
ptr[idx++] = d;
return idx;
}
std::size_t NumVerticesPerSide(int num_quads_per_axis) {
return (num_quads_per_axis + 1) * (num_quads_per_axis + 1);
}
std::size_t NumIndicesPerSide(int num_quads_per_axis) {
return kNumIndicesPerQuad * num_quads_per_axis * num_quads_per_axis;
}
class PlaneBuilder {
public:
using VertexType = VertexNoUv;
using IndexType = uint16_t;
explicit PlaneBuilder(int num_quads_per_axis)
: num_quads_per_axis_(num_quads_per_axis),
orientation_(CalculateOrientation({0, 0, 1})) {}
std::size_t NumVertices() const {
return NumVerticesPerSide(num_quads_per_axis_);
}
std::size_t NumIndices() const {
return NumIndicesPerSide(num_quads_per_axis_);
}
void GenerateVertices(VertexType* ptr, size_t num) const {
const float delta = 2.0f / num_quads_per_axis_;
int idx = 0;
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);
ptr[idx++] = VertexType({dx - 1.0f, dy - 1.0f, 0}, orientation_);
}
}
}
void GenerateIndices(IndexType* ptr, size_t num) const {
int idx = 0;
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;
idx = AppendQuadIndices(ptr, idx, i0, i1, i2, i3);
}
}
}
filament::Box GetBounds() const { return {{-1, -1, -0.001}, {1, 1, 0.001}}; }
private:
int num_quads_per_axis_;
float4 orientation_;
};
class BoxBuilder {
public:
using VertexType = VertexNoUv;
using IndexType = uint16_t;
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_);
}
std::size_t NumVertices() const {
return NumVerticesPerSide(num_quads_per_axis_) * kNumSides;
}
std::size_t NumIndices() const {
return NumIndicesPerSide(num_quads_per_axis_) * kNumSides;
}
void GenerateVertices(VertexType* ptr, size_t num) const {
int idx = 0;
idx = GenerateVerticesForSide(ptr, idx, {0, 1, 0}, [](float2 pt) {
return float3{pt.x, 1.0f, pt.y};
});
idx = GenerateVerticesForSide(ptr, idx, {0, -1, 0}, [](float2 pt) {
return float3{pt.x, -1.0f, pt.y};
});
idx = GenerateVerticesForSide(ptr, idx, {1, 0, 0}, [](float2 pt) {
return float3{1.0f, pt.x, pt.y};
});
idx = GenerateVerticesForSide(ptr, idx, {-1, 0, 0}, [](float2 pt) {
return float3{-1.0f, pt.x, pt.y};
});
idx = GenerateVerticesForSide(ptr, idx, {0, 0, 1}, [](float2 pt) {
return float3{pt.x, pt.y, 1.0f};
});
idx = GenerateVerticesForSide(ptr, idx, {0, 0, -1}, [](float2 pt) {
return float3{pt.x, pt.y, -1.0f};
});
}
void GenerateIndices(IndexType* ptr, size_t num) const {
const int vertices_per_side = NumVerticesPerSide(num_quads_per_axis_);
int idx = 0;
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;
idx = AppendQuadIndices(ptr, idx, i0, i1, i2, i3);
}
}
}
}
filament::Box GetBounds() const { return {{-1, -1, -1}, {1, 1, 1}}; }
private:
template <typename F>
int GenerateVerticesForSide(VertexType* ptr, int idx, float3 normal,
const F& pt_gen) const {
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});
ptr[idx++] = VertexType(position, orientation);
}
}
return idx;
}
int num_quads_per_axis_;
float quad_size_;
};
class TubeBuilder {
public:
using VertexType = VertexNoUv;
using IndexType = uint16_t;
TubeBuilder(int num_stacks, int num_slices)
: num_stacks_(num_stacks), num_slices_(num_slices) {}
std::size_t NumVertices() const {
return num_slices_ * (num_stacks_ + 1);
}
std::size_t NumIndices() const {
return kNumIndicesPerQuad * num_slices_ * num_stacks_;
}
void GenerateVertices(VertexType* ptr, size_t num) const {
const float delta_angle = 2.f * M_PI / (float)num_slices_;
const float delta_stack = 2.f / static_cast<float>(num_stacks_);
int idx = 0;
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);
ptr[idx++] = VertexType({pt.x, pt.y, z}, orientation);
}
}
}
void GenerateIndices(IndexType* ptr, size_t num) const {
const int num_vertices = NumVertices();
const int num_vertices_in_spine = num_stacks_ + 1;
int idx = 0;
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;
idx = AppendQuadIndices(ptr, idx, i0, i1, i2, i3);
}
}
}
filament::Box GetBounds() const { return {{-1, -1, -1}, {1, 1, 1}}; }
private:
int num_stacks_;
int num_slices_;
};
class ConeBuilder {
public:
using VertexType = VertexNoUv;
using IndexType = uint16_t;
ConeBuilder(int num_stacks, int num_slices)
: num_stacks_(num_stacks), num_slices_(num_slices) {}
std::size_t NumVertices() const {
return (num_slices_ * kNumVerticesPerTriangle) +
((num_stacks_ - 1) * num_slices_ * kNumVerticesPerQuad);
}
std::size_t NumIndices() const {
return (num_slices_ * kNumIndicesPerTriangle) +
((num_stacks_ - 1) * num_slices_ * kNumIndicesPerQuad);
}
void GenerateVertices(VertexType* ptr, std::size_t num) const {
// pole: use triangles
const float delta_angle = 2.0 * M_PI / static_cast<float>(num_slices_);
const float delta_radius = 1.0f / static_cast<float>(num_stacks_);
int idx = 0;
for (int j = 0; j < num_slices_; ++j) {
const float angle1 = (j+0) * delta_angle;
const float angle2 = (j+1) * delta_angle;
ptr[idx++] = MakeVert(angle1, delta_radius);
ptr[idx++] = MakeVert(angle2, delta_radius);
VertexType v3;
v3.position = {0, 0, 1};
v3.orientation = CalculateOrientation(v3.position);
ptr[idx++] = v3;
}
// 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;
ptr[idx++] = MakeVert(angle1, radius2);
ptr[idx++] = MakeVert(angle2, radius2);
ptr[idx++] = MakeVert(angle2, radius1);
ptr[idx++] = MakeVert(angle1, radius1);
}
}
}
void GenerateIndices(IndexType* ptr, std::size_t num) const {
int idx = 0;
for (int j = 0; j < num_slices_ * 3; ++j) {
ptr[idx] = idx;
++idx;
}
int quad_idx = idx;
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;
idx = AppendQuadIndices(ptr, idx, i0, i1, i2, i3);
}
}
}
filament::Box GetBounds() const { return {{-1, -1, 0}, {1, 1, 1}}; }
private:
static VertexType MakeVert(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};
return VertexType(pt, CalculateOrientation(n));
}
int num_stacks_;
int num_slices_;
};
class DiskBuilder {
public:
using VertexType = VertexNoUv;
using IndexType = uint16_t;
explicit DiskBuilder(int num_slices) : num_slices_(num_slices) {
orientation_ = CalculateOrientation({0, 0, 1});
}
std::size_t NumVertices() const {
return num_slices_ + 1;
}
std::size_t NumIndices() const {
return num_slices_ * kNumVerticesPerTriangle;
}
void GenerateVertices(VertexType* ptr, std::size_t num) const {
const float delta_angle = 2.0 * M_PI / static_cast<float>(num_slices_);
int idx = 0;
ptr[idx++] = VertexType(float3{0, 0, 0}, orientation_);
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);
ptr[idx++] = VertexType(float3{x, y, 0}, orientation_);
}
}
void GenerateIndices(IndexType* ptr, std::size_t num) const {
int idx = 0;
for (int i = 0; i < num_slices_; ++i) {
const int next = i < (num_slices_ - 1) ? i + 1 : 0;
ptr[idx++] = 0;
ptr[idx++] = 1 + i;
ptr[idx++] = 1 + next;
}
}
filament::Box GetBounds() const { return {{-1, -1, -0.001}, {1, 1, 0.001}}; }
private:
int num_slices_;
float4 orientation_;
};
class SphereBuilder {
public:
using VertexType = VertexNoUv;
using IndexType = uint16_t;
static constexpr IndexType kNorthPoleIndex = 0;
static constexpr IndexType kSouthPoleIndex = 1;
SphereBuilder(int num_stacks, int num_slices)
: num_stacks_(num_stacks), num_slices_(num_slices) {}
std::size_t NumVertices() const {
return (num_stacks_ * num_slices_) + 2; // +2 for poles
}
std::size_t NumIndices() const {
const size_t num_tris_polar_cap = num_slices_;
const size_t num_quads_body = num_slices_ * (num_stacks_ - 1);
return (2 * num_tris_polar_cap * kNumIndicesPerTriangle) +
(num_quads_body * kNumIndicesPerQuad);
}
void GenerateVertices(VertexType* ptr, size_t num) const {
const float lat_angle_delta = M_PI / static_cast<float>(num_stacks_ + 1);
const float lon_angle_delta = 2.0 * M_PI / static_cast<float>(num_slices_);
// Add the north and south poles.
int idx = 0;
ptr[idx++] = MakeVert(0, 0, 1);
ptr[idx++] = MakeVert(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;
ptr[idx++] = MakeVert(x, y, z);
}
}
}
void GenerateIndices(IndexType* ptr, size_t num) const {
int idx = 0;
// The first two vertices are the poles, so the first vertex in the first
// row starts at index 2.
IndexType 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;
ptr[idx++] = kNorthPoleIndex;
ptr[idx++] = row_start + next;
ptr[idx++] = row_start + lon;
}
// Latitudinal triangle strips.
for (int lat = 0; lat < num_stacks_ - 1; lat++) {
const IndexType north_start = row_start;
const IndexType 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);
idx = AppendQuadIndices(ptr, idx, 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;
ptr[idx++] = kSouthPoleIndex;
ptr[idx++] = row_start + lon;
ptr[idx++] = row_start + adjacent;
}
}
filament::Box GetBounds() const { return {{-1, -1, -1}, {1, 1, 1}}; }
private:
static VertexType MakeVert(float x, float y, float z) {
const float3 pt{x, y, z};
return VertexType(pt, CalculateOrientation(pt));
}
int num_stacks_;
int num_slices_;
};
class DomeBuilder {
public:
using VertexType = VertexNoUv;
using IndexType = uint16_t;
static constexpr IndexType kPoleIndex = 0;
DomeBuilder(int num_stacks, int num_slices)
: num_stacks_(num_stacks), num_slices_(num_slices) {}
std::size_t NumVertices() const {
return (num_stacks_ * num_slices_) + 1; // +1 for poles
}
std::size_t NumIndices() const {
const size_t num_tris_polar_cap = num_slices_;
const size_t num_quads_body = num_slices_ * (num_stacks_ - 1);
return (num_tris_polar_cap * kNumIndicesPerTriangle) +
(num_quads_body * kNumIndicesPerQuad);
}
void GenerateVertices(VertexType* ptr, size_t num) const {
const float lat_angle_delta = 0.5 * M_PI / static_cast<float>(num_stacks_);
const float lon_angle_delta = 2.0 * M_PI / static_cast<float>(num_slices_);
// Add the pole.
int idx = 0;
ptr[idx++] = MakeVert(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;
ptr[idx++] = MakeVert(x, y, z);
}
}
}
void GenerateIndices(IndexType* ptr, size_t num) const {
int idx = 0;
// The first vertex is the poles, so the first vertex in the first row
// starts at index 1.
IndexType 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;
ptr[idx++] = kPoleIndex;
ptr[idx++] = row_start + next;
ptr[idx++] = row_start + lon;
}
// 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);
idx = AppendQuadIndices(ptr, idx, i0, i1, i2, i3);
}
row_start += num_slices_;
}
}
filament::Box GetBounds() const { return {{-1, -1, 0}, {1, 1, 1}}; }
private:
static VertexType MakeVert(float x, float y, float z) {
const float3 pt{x, y, z};
return VertexType(pt, CalculateOrientation(pt));
}
int num_stacks_;
int num_slices_;
};
template <typename T>
FilamentBuffers CreateFromBuilder(filament::Engine* engine, const T& builder) {
using VertexType = typename T::VertexType;
using IndexType = typename T::IndexType;
const int num_vertices = builder.NumVertices();
const int num_indices = builder.NumIndices();
if (num_vertices == 0 || num_indices == 0) {
return {};
}
auto vertices = [&](std::byte* buffer, std::size_t len) {
auto* ptr = reinterpret_cast<typename T::VertexType*>(buffer);
if (sizeof(*ptr) * num_vertices != len) {
mju_error("Buffer size mismatch.");
}
builder.GenerateVertices(ptr, num_vertices);
};
auto indices = [&](std::byte* buffer, std::size_t len) {
auto* ptr = reinterpret_cast<typename T::IndexType*>(buffer);
if (sizeof(*ptr) * num_indices != len) {
mju_error("Buffer size mismatch.");
}
builder.GenerateIndices(ptr, num_indices);
};
auto vb = CreateVertexBuffer<VertexType>(engine, num_vertices, vertices);
auto ib = CreateIndexBuffer<IndexType>(engine, num_indices, indices);
return {ib, vb, builder.GetBounds()};
}
FilamentBuffers CreatePlane(filament::Engine* engine, const mjModel* model) {
const int num_quads = model->vis.quality.numquads;
return CreateFromBuilder(engine, PlaneBuilder(num_quads));
}
FilamentBuffers CreateBox(filament::Engine* engine, const mjModel* model) {
const int num_quads = model->vis.quality.numquads;
return CreateFromBuilder(engine, BoxBuilder(num_quads));
}
FilamentBuffers CreateSphere(filament::Engine* engine, const mjModel* model) {
const int num_stacks = model->vis.quality.numstacks;
const int num_slices = model->vis.quality.numslices;
return CreateFromBuilder(engine, SphereBuilder(num_stacks, num_slices));
}
FilamentBuffers CreateTube(filament::Engine* engine, const mjModel* model) {
const int num_stacks = model->vis.quality.numstacks;
const int num_slices = model->vis.quality.numslices;
return CreateFromBuilder(engine, TubeBuilder(num_stacks, num_slices));
}
FilamentBuffers CreateDisk(filament::Engine* engine, const mjModel* model) {
const int num_slices = model->vis.quality.numslices;
return CreateFromBuilder(engine, DiskBuilder(num_slices));
}
FilamentBuffers CreateDome(filament::Engine* engine, const mjModel* model) {
const int num_stacks = model->vis.quality.numstacks / 2;
const int num_slices = model->vis.quality.numslices;
return CreateFromBuilder(engine, DomeBuilder(num_stacks, num_slices));
}
FilamentBuffers CreateCone(filament::Engine* engine, const mjModel* model) {
const int num_stacks = model->vis.quality.numstacks;
const int num_slices = model->vis.quality.numslices;
return CreateFromBuilder(engine, ConeBuilder(num_stacks, num_slices));
}
} // namespace mujoco
@@ -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.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_BUILTINS_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_BUILTINS_H_
#include <filament/Engine.h>
#include <mujoco/mjmodel.h>
#include "experimental/filament/filament/buffer_util.h"
// Generates buffers for built-in shapes.
namespace mujoco {
FilamentBuffers CreateBox(filament::Engine* engine, const mjModel* model);
FilamentBuffers CreatePlane(filament::Engine* engine, const mjModel* model);
FilamentBuffers CreateSphere(filament::Engine* engine, const mjModel* model);
FilamentBuffers CreateTube(filament::Engine* engine, const mjModel* model);
FilamentBuffers CreateDisk(filament::Engine* engine, const mjModel* model);
FilamentBuffers CreateDome(filament::Engine* engine, const mjModel* model);
FilamentBuffers CreateCone(filament::Engine* engine, const mjModel* model);
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_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 "experimental/filament/filament/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_EXPERIMENTAL_FILAMENT_FILAMENT_COLOR_GRADING_OPTIONS_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_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_EXPERIMENTAL_FILAMENT_FILAMENT_COLOR_GRADING_OPTIONS_H_
@@ -0,0 +1,435 @@
// 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 "experimental/filament/filament/drawable.h"
#include <cmath>
#include <cstdint>
#include <utility>
#include <filament/Material.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/Texture.h>
#include <filament/TransformManager.h>
#include <math/mat4.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <utils/Entity.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/buffer_util.h"
#include "experimental/filament/filament/geom_util.h"
#include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/object_manager.h"
namespace mujoco {
using filament::math::float2;
using filament::math::float3;
using filament::math::float4;
using filament::math::mat4;
// Planes with a 0-size dimension should be infinitely large. However, we
// don't support that directly so we use a large but finite size instead.
static constexpr float kInfinitePlaneFakeSize = 1000.0f;
// An arbitrary scale factor for arrows.
static constexpr float kArrowScale = 1.f / 6.f;
static constexpr float kArrowHeadSize = 1.75f;
// Some built-in geometries are actually composed of multiple simple shapes. A
// capsule, for example, is a open-ended tube with two dome ends. We use these
// constants to help identify which entity (by index) represents which part of
// the overall shape.
static constexpr int kCapsuleTopDome = 1;
static constexpr int kCapsuleBottomDome = 2;
static constexpr int kCylinderTopDisk = 1;
static constexpr int kCylinderBottomDisk = 2;
static constexpr int kArrow0Cone = 1;
static constexpr int kArrow0ConeDisk = 2;
static constexpr int kArrow0BottomDisk = 3;
static constexpr int kArrow1Cone = 1;
static constexpr int kArrow1BottomDisk = 2;
static constexpr int kArrow2TopCone = 1;
static constexpr int kArrow2BottomCone = 2;
static constexpr int kArrow2TopConeDisk = 3;
static constexpr int kArrow2BottomConeDisk = 4;
Drawable::Drawable(ObjectManager* object_mgr, const mjvGeom& geom)
: material_(object_mgr),
renderables_(object_mgr->GetEngine()) {
if (geom.type == mjGEOM_MESH) {
AddMesh(geom.dataid);
} else if (geom.type == mjGEOM_HFIELD) {
AddHeightField(geom.dataid);
} else if (geom.type == mjGEOM_PLANE) {
AddShape(ObjectManager::kPlane);
} else if (geom.type == mjGEOM_SPHERE) {
AddShape(ObjectManager::kSphere);
} else if (geom.type == mjGEOM_ELLIPSOID) {
AddShape(ObjectManager::kSphere);
} else if (geom.type == mjGEOM_BOX) {
AddShape(ObjectManager::kBox);
} else if (geom.type == mjGEOM_CAPSULE) {
AddShape(ObjectManager::kTube);
AddShape(ObjectManager::kDome);
AddShape(ObjectManager::kDome);
} else if (geom.type == mjGEOM_CYLINDER) {
AddShape(ObjectManager::kTube);
AddShape(ObjectManager::kDisk);
AddShape(ObjectManager::kDisk);
} else if (geom.type == mjGEOM_ARROW) {
AddShape(ObjectManager::kTube);
AddShape(ObjectManager::kCone);
AddShape(ObjectManager::kDisk);
} else if (geom.type == mjGEOM_ARROW1) {
AddShape(ObjectManager::kTube);
AddShape(ObjectManager::kCone);
AddShape(ObjectManager::kDisk);
AddShape(ObjectManager::kDisk);
} else if (geom.type == mjGEOM_ARROW2) {
AddShape(ObjectManager::kTube);
AddShape(ObjectManager::kCone);
AddShape(ObjectManager::kCone);
AddShape(ObjectManager::kDisk);
AddShape(ObjectManager::kDisk);
} else if (geom.type == mjGEOM_FLEX || geom.type == mjGEOM_SKIN) {
// Flex and skin geometries are dynamically updated every frame.
} else {
mju_warning("Unsupported geom type: %d", geom.type);
}
}
void Drawable::Update(const mjModel* model, const mjvScene* scene,
const mjvGeom& geom) {
if (geom.type == mjGEOM_FLEX || geom.type == mjGEOM_SKIN) {
// Flex geometry is updated every frame with new vertex data.
filament::Engine* engine = renderables_.GetEngine();
FilamentBuffers buffers = CreateGeomBuffers(engine, model, scene, geom);
if (renderables_.GetNumEntities() == 0) {
renderables_.Append(std::move(buffers));
} else {
renderables_.Update(0, std::move(buffers));
}
}
SetTransform(geom);
UpdateMaterial(geom);
}
void Drawable::AddMesh(int data_id) {
ObjectManager* object_mgr = material_.GetObjectManager();
const FilamentBuffers* buffers = object_mgr->GetMeshBuffer(data_id);
if (buffers == nullptr) {
mju_error("Unknown mesh %d", data_id);
}
renderables_.Append(*buffers);
}
void Drawable::AddHeightField(int hfield_id) {
ObjectManager* object_mgr = material_.GetObjectManager();
const FilamentBuffers* buffers = object_mgr->GetHeightFieldBuffer(hfield_id);
if (buffers == nullptr) {
mju_error("Unknown height field %d", hfield_id);
}
renderables_.Append(*buffers);
}
void Drawable::AddShape(ObjectManager::ShapeType shape_type) {
ObjectManager* object_mgr = material_.GetObjectManager();
const FilamentBuffers* buffers = object_mgr->GetShapeBuffer(shape_type);
if (buffers == nullptr) {
mju_error("Unknown shape %d", shape_type);
}
renderables_.Append(*buffers);
}
void Drawable::AddToScene(filament::Scene* scene) {
renderables_.AddToScene(scene);
}
void Drawable::RemoveFromScene(filament::Scene* scene) {
renderables_.RemoveFromScene(scene);
}
void Drawable::SetDrawMode(Material::DrawMode mode) {
renderables_.SetMaterialInstance(material_.GetMaterialInstance(mode));
}
void Drawable::SetTransform(const mjvGeom& geom) {
// Flex and skin geometries are in global space.
if (geom.type == mjGEOM_FLEX || geom.type == mjGEOM_SKIN) {
return;
}
const mat4 transform(ReadMat3(geom.mat), ReadFloat3(geom.pos));
float3 size = ReadFloat3(geom.size);
filament::TransformManager& tm =
renderables_.GetEngine()->getTransformManager();
for (int j = 0; j < renderables_.GetNumEntities(); ++j) {
const utils::Entity& entity = renderables_[j];
// Update object transform.
mat4 entity_transform = transform;
// Some built-in drawables are composed of multiple entities. For example,
// capsules are a combination of a open tube and two dome end caps.
if (geom.type == mjGEOM_CYLINDER) {
// Cylinders are a tube with two disks at the ends. The "bottom" disk is
// rotated so that the normals point outwards.
if (j == kCylinderTopDisk) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
} else if (j == kCylinderBottomDisk) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *= mat4::rotation(M_PI, float3{1, 0, 0});
}
} else if (geom.type == mjGEOM_CAPSULE) {
// Capsules are a tube with two domes at the ends. 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 * (size.x + size.y);
if (j == kCapsuleTopDome) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
entity_transform *= mat4::scaling(float3{1, 1, xz_size / size.z});
} else if (j == kCapsuleBottomDome) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *= mat4::rotation(M_PI, float3{1, 0, 0});
entity_transform *= mat4::scaling(float3{1, 1, xz_size / size.z});
}
} else if (geom.type == mjGEOM_ARROW) {
// An arrow is a tube with a cone at the end and a disk cap at the other
// end. Because the cone head's base is larger than the tube, an extra
// disk is added to the base of the cone. This disk is rotated such that
// its normal points outwards.
entity_transform *= mat4::scaling(float3{1, 1, kArrowScale});
entity_transform *= mat4::translation(float3{0, 0, kArrowScale});
if (j == kArrow0Cone) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
} else if (j == kArrow0ConeDisk) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
entity_transform *= mat4::rotation(M_PI, float3{1, 0, 0});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
} else if (j == kArrow0BottomDisk) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *= mat4::rotation(M_PI, float3{1, 0, 0});
}
} else if (geom.type == mjGEOM_ARROW1) {
// An arrow1 is a tube with a cone at the end and a disk cap at the other
// end.
entity_transform *= mat4::scaling(float3{1, 1, kArrowScale});
entity_transform *= mat4::translation(float3{0, 0, kArrowScale});
if (j == kArrow1Cone) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
} else if (j == kArrow1BottomDisk) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *= mat4::rotation(M_PI, float3{1, 0, 0});
}
} else if (geom.type == mjGEOM_ARROW2) {
// An arrow2 is a tube with a cone at both ends. Like the standard arrow,
// an extra disk is added to the base of each cone.
entity_transform *= mat4::scaling(float3{1, 1, kArrowScale});
entity_transform *= mat4::translation(float3{0, 0, kArrowScale});
if (j == kArrow2TopCone) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
} else if (j == kArrow2BottomCone) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *= mat4::rotation(M_PI, float3{1, 0, 0});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
} else if (j == kArrow2TopConeDisk) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
entity_transform *= mat4::rotation(M_PI, float3{1, 0, 0});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
} else if (j == kArrow2BottomConeDisk) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
}
} else if (geom.type == mjGEOM_PLANE) {
// A plane with 0-size in any dimension is considered to be an infinite
// plane, but we don't really support that so just scale it so that its
// very large.
if (size.x == 0 && size.y == 0) {
size = float3(kInfinitePlaneFakeSize, kInfinitePlaneFakeSize, size.z);
}
}
if (geom.type != mjGEOM_MESH && geom.type != mjGEOM_HFIELD) {
entity_transform *= mat4::scaling(size);
}
tm.setTransform(tm.getInstance(entity), entity_transform);
}
}
void Drawable::UpdateMaterial(const mjvGeom& geom) {
ObjectManager* object_mgr = material_.GetObjectManager();
const mjModel* model = object_mgr->GetModel();
Material::Textures textures;
if (geom.matid >= 0) {
textures.color = object_mgr->GetTexture(geom.matid, mjTEXROLE_RGB);
textures.normal = object_mgr->GetTexture(geom.matid, mjTEXROLE_NORMAL);
textures.emissive = object_mgr->GetTexture(geom.matid, mjTEXROLE_EMISSIVE);
textures.orm = object_mgr->GetTexture(geom.matid, mjTEXROLE_ORM);
textures.metallic = object_mgr->GetTexture(geom.matid, mjTEXROLE_METALLIC);
textures.roughness = object_mgr->GetTexture(geom.matid, mjTEXROLE_ROUGHNESS);
textures.occlusion = object_mgr->GetTexture(geom.matid, mjTEXROLE_OCCLUSION);
}
if (geom.matid >= 0) {
if (textures.orm) {
material_.SetNormalMaterialType(ObjectManager::kPbrPacked);
} else if (textures.metallic) {
material_.SetNormalMaterialType(ObjectManager::kPbr);
} else if (textures.roughness) {
material_.SetNormalMaterialType(ObjectManager::kPbr);
} else if (model->mat_metallic[geom.matid] >= 0) {
material_.SetNormalMaterialType(ObjectManager::kPbr);
} else if (model->mat_roughness[geom.matid] >= 0) {
material_.SetNormalMaterialType(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.
bool has_texcoords = false;
if ((geom.type == mjGEOM_MESH || geom.type == mjGEOM_SDF) &&
geom.dataid >= 0 && model->mesh_texcoordadr[geom.dataid / 2] >= 0) {
has_texcoords = true;
}
if (textures.color == nullptr) {
if (geom.rgba[3] < 1.0f) {
material_.SetNormalMaterialType(ObjectManager::kPhongColorFade);
} else {
material_.SetNormalMaterialType(ObjectManager::kPhongColor);
}
} else if (textures.color->getTarget() ==
filament::Texture::Sampler::SAMPLER_CUBEMAP) {
if (geom.rgba[3] < 1.0f) {
material_.SetNormalMaterialType(ObjectManager::kPhongCubeFade);
} else {
material_.SetNormalMaterialType(ObjectManager::kPhongCube);
}
} else if (has_texcoords) {
if (geom.rgba[3] < 1.0f) {
material_.SetNormalMaterialType(ObjectManager::kPhong2dUvFade);
} else {
material_.SetNormalMaterialType(ObjectManager::kPhong2dUv);
}
} else {
if (geom.rgba[3] < 1.0f) {
material_.SetNormalMaterialType(ObjectManager::kPhong2dFade);
} else {
material_.SetNormalMaterialType(ObjectManager::kPhong2d);
}
}
if (geom.matid >= 0) {
material_.UpdateTextures(textures);
}
Material::Params params;
params.color = ReadFloat4(geom.rgba);
params.emissive = geom.emission;
params.specular = geom.specular;
params.glossiness = geom.shininess;
if (geom.matid >= 0) {
params.metallic = model->mat_metallic[geom.matid];
params.roughness = model->mat_roughness[geom.matid];
params.tex_uniform = model->mat_texuniform[geom.matid];
params.tex_repeat = ReadFloat2(model->mat_texrepeat, geom.matid);
}
if (geom.segid >= 0) {
constexpr double phi1 = 1.61803398874989484820; // Cached Phi(1).
constexpr double coef1 = 1.0 / phi1;
const double index = static_cast<double>(geom.segid);
const double sample = std::fmod(0.5 + coef1 * index, 1.0);
uint32_t segmentation_color = 0x01000000 * sample;
const uint8_t red = (segmentation_color >> 16) & 0xff;
const uint8_t green = (segmentation_color >> 8) & 0xff;
const uint8_t blue = (segmentation_color >> 0) & 0xff;
params.segmentation_color.x = static_cast<float>(red) / 255.0f;
params.segmentation_color.y = static_cast<float>(green) / 255.0f;
params.segmentation_color.z = static_cast<float>(blue) / 255.0f;
}
// UvScale only applies to objects that don't have explicit UV coordinates
// in their vertex buffer. Instead, we set the UV coordinate to be the same
// as the vertex position.
//
// The material's `texuniform` and `texrepeat` parameters allow us to scale
// the programmatic UVs.
if (textures.color) {
if (textures.color->getTarget() == filament::Texture::Sampler::SAMPLER_2D) {
// For 2D textures, `tex_repeat` specifies how many times the texture
// image is repeated. The `tex_uniform` flag determines if the repetition
// is applied at in object space (false) or in world space (true).
params.uv_scale.x = params.tex_repeat.x;
params.uv_scale.y = params.tex_repeat.y;
if (geom.dataid >= 0) {
if (geom.size[0] > mjMINVAL) {
params.uv_scale.x /= geom.size[0];
}
if (geom.size[1] > mjMINVAL) {
params.uv_scale.y /= geom.size[1];
}
}
if (params.tex_uniform) {
if (geom.size[0] > 0) {
params.uv_scale.x *= geom.size[0];
}
if (geom.size[1] > 0) {
params.uv_scale.y *= geom.size[1];
}
}
if (geom.type == mjGEOM_PLANE) {
if (geom.size[0] == 0) {
params.uv_scale.x = kInfinitePlaneFakeSize;
}
if (geom.size[1] == 0) {
params.uv_scale.y = kInfinitePlaneFakeSize;
}
}
params.uv_scale.x = 2 * params.uv_scale.x;
params.uv_scale.y = 2 * params.uv_scale.y;
} else {
// For cube maps, if `tex_uniform` is true, then scale the texture so that
// it covers a 1x1 area of world space rather than the area of the object.
if (params.tex_uniform) {
params.uv_scale.x = 1.0f / (geom.size[0] ? geom.size[0] : 1.0f);
params.uv_scale.y = 1.0f / (geom.size[1] ? geom.size[1] : 1.0f);
params.uv_scale.z = 1.0f / (geom.size[2] ? geom.size[2] : 1.0f);
}
}
}
material_.UpdateParams(params);
}
} // namespace mujoco
@@ -0,0 +1,73 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_DRAWABLE_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_DRAWABLE_H_
#include <cstdint>
#include <filament/Engine.h>
#include <filament/Scene.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjvisualize.h>
#include "experimental/filament/filament/buffer_util.h"
#include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/renderables.h"
namespace mujoco {
// Manages the filament Entities and MaterialInstances for a single mjvGeom.
class Drawable {
public:
Drawable(ObjectManager* object_mgr, const mjvGeom& geom);
~Drawable() noexcept = default;
Drawable(const Drawable&) = delete;
Drawable& operator=(const Drawable&) = delete;
// Adds the Drawable to the given filament Scene. Note that a Drawable can
// only be assigned to a single Scene at any given time.
void AddToScene(filament::Scene* scene);
// Removes the Drawable from the given filament Scene.
void RemoveFromScene(filament::Scene* scene);
// Updates the drawable to reflect the current state (e.g. geometry,
// transform, material, etc.) of the geom.
void Update(const mjModel* model, const mjvScene* scene, const mjvGeom& geom);
// Swaps the MaterialInstance that will be used to render the Drawable (e.g.
// normal, depth, segmentation, etc.). This must be called before the filament
// beginFrame/endFrame.
void SetDrawMode(Material::DrawMode mode);
private:
void AddMesh(int data_id);
void AddHeightField(int hfield_id);
void AddShape(ObjectManager::ShapeType shape_type);
// Updates the transform of the drawable for rendering.
void SetTransform(const mjvGeom& geom);
// Updates the material parameters of the drawable for rendering.
void UpdateMaterial(const mjvGeom& geom);
Material material_;
Renderables renderables_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_DRAWABLE_H_
@@ -0,0 +1,313 @@
// 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 "experimental/filament/filament/filament_context.h"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <utility>
#include "third_party/filament/filament/backend/include/backend/DriverEnums.h"
#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 <mujoco/mjmodel.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/gui_view.h"
#include "experimental/filament/filament/imgui_editor.h"
#include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/filament/texture_util.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
FilamentContext::FilamentContext(const mjrFilamentConfig* config,
const mjModel* model, mjrContext* con)
: config_(*config), context_(con), model_(model) {
#if defined( __EMSCRIPTEN__)
filament::Engine::Backend backend = filament::Engine::Backend::OPENGL;
#else
filament::Engine::Backend backend = filament::Engine::Backend::VULKAN;
#endif
switch (config_.graphics_api) {
case mjGFX_DEFAULT:
// Use the default based on the platform above.
break;
case mjGFX_OPENGL:
backend = filament::Engine::Backend::OPENGL;
break;
case mjGFX_VULKAN:
backend = filament::Engine::Backend::VULKAN;
break;
default:
mju_error("Unsupported graphics API: %d", config_.graphics_api);
}
engine_ = filament::Engine::create(backend);
renderer_ = engine_->createRenderer();
#ifdef __EMSCRIPTEN__
swap_chain_ = engine_->createSwapChain(nullptr);
#else
if (config_.native_window) {
swap_chain_ = engine_->createSwapChain(config_.native_window);
} else {
const int width = model_->vis.global.offwidth;
const int height = model_->vis.global.offheight;
swap_chain_ = engine_->createSwapChain(width, height);
}
#endif
object_manager_ = std::make_unique<ObjectManager>(model, engine_, config);
// Set clear options.
filament::Renderer::ClearOptions opts;
opts.clear = true;
opts.discard = true;
opts.clearColor = {0.1, 0.1, 0.1, 1};
renderer_->setClearOptions(opts);
// Copy parameters from model to context.
if (model_) {
context_->shadowClip = model_->stat.extent * model_->vis.map.shadowclip;
context_->shadowScale = model_->vis.map.shadowscale;
context_->offWidth = model_->vis.global.offwidth;
context_->offHeight = model_->vis.global.offheight;
context_->offSamples = model_->vis.quality.offsamples;
context_->fogStart =
(float)(model_->stat.extent * model_->vis.map.fogstart);
context_->fogEnd = (float)(model_->stat.extent * model_->vis.map.fogend);
context_->fogRGBA[0] = model_->vis.rgba.fog[0];
context_->fogRGBA[1] = model_->vis.rgba.fog[1];
context_->fogRGBA[2] = model_->vis.rgba.fog[2];
context_->fogRGBA[3] = model_->vis.rgba.fog[3];
context_->lineWidth = model_->vis.global.linewidth;
context_->shadowSize = model_->vis.quality.shadowsize;
context_->readPixelFormat = 0x1907; // 0x1907 = GL_RGB;
context_->ntexture = model_->ntex;
for (int i = 0; i < model_->ntex; ++i) {
context_->textureType[i] = model_->tex_type[i];
}
}
scene_view_ = std::make_unique<SceneView>(engine_, object_manager_.get());
if (config_.enable_gui) {
gui_view_ = std::make_unique<GuiView>(engine_, object_manager_.get());
}
}
FilamentContext::~FilamentContext() {
DestroyRenderTargets();
gui_view_.reset();
scene_view_.reset();
object_manager_.reset();
engine_->destroy(renderer_);
engine_->destroy(swap_chain_);
filament::Engine::destroy(engine_);
}
void FilamentContext::Render(const mjrRect& viewport, const mjvScene* scene,
const mjrContext* con) {
if (con != context_) {
mju_error("Unexpected context.");
}
scene_view_->SetViewport(viewport);
scene_view_->UpdateScene(con, scene);
// Draw the GUI. We do this after processing the scene in case there are any
// label elements in the scene.
if (gui_view_) {
DrawGui(scene_view_.get());
// Prepare the filament Renderable that contains the GUI draw commands. We
// must call this function even if we do not plan on rendering the GUI to
// ensure the ImGui state is updated.
render_gui_ = gui_view_->PrepareRenderable();
}
// Render the frame if we're not rendering to a texture.s
if (!render_to_texture_) {
SceneView::DrawMode mode = scene->flags[mjRND_SEGMENT]
? SceneView::DrawMode::kSegmentation
: SceneView::DrawMode::kNormal;
filament::View* view = scene_view_->PrepareRenderView(mode);
if (renderer_->beginFrame(swap_chain_)) {
renderer_->render(view);
if (render_gui_) {
renderer_->render(gui_view_->PrepareRenderView());
render_gui_ = false;
}
renderer_->endFrame();
}
#ifdef __EMSCRIPTEN__
engine_->execute();
#endif
}
}
void FilamentContext::SetFrameBuffer(int framebuffer) {
render_to_texture_ = (framebuffer != 0);
if (!render_to_texture_) {
DestroyRenderTargets();
}
}
void FilamentContext::PrepareRenderTargets(int width, int height) {
for (int i = 0; i < kNumRenderTargetTextureTypes; ++i) {
target_textures_[i] = CreateRenderTargetTexture(
engine_, width, height, static_cast<RenderTargetTextureType>(i));
}
// Render target for color pass.
filament::RenderTarget::Builder color_target_builder;
color_target_builder.texture(filament::RenderTarget::AttachmentPoint::COLOR0,
target_textures_[kRenderTargetColor]);
color_target_builder.texture(filament::RenderTarget::AttachmentPoint::DEPTH,
target_textures_[kRenderTargetDepth]);
color_target_ = color_target_builder.build(*engine_);
// Render target for depth pass.
filament::RenderTarget::Builder depth_target_builder;
depth_target_builder.texture(filament::RenderTarget::AttachmentPoint::COLOR0,
target_textures_[kRenderTargetDepthColor]);
depth_target_builder.texture(filament::RenderTarget::AttachmentPoint::DEPTH,
target_textures_[kRenderTargetDepth]);
depth_target_ = depth_target_builder.build(*engine_);
}
void FilamentContext::DestroyRenderTargets() {
if (depth_target_) {
engine_->destroy(depth_target_);
depth_target_ = nullptr;
}
if (color_target_) {
engine_->destroy(color_target_);
color_target_ = nullptr;
}
for (int i = 0; i < kNumRenderTargetTextureTypes; ++i) {
if (target_textures_[i]) {
engine_->destroy(target_textures_[i]);
target_textures_[i] = nullptr;
}
}
}
static void ReadColorPixels(filament::Renderer* renderer,
filament::RenderTarget* target, mjrRect viewport,
unsigned char* buffer, size_t num_bytes) {
filament::backend::PixelBufferDescriptor descriptor(
buffer, num_bytes, filament::backend::PixelDataFormat::RGB,
filament::backend::PixelDataType::UBYTE);
renderer->readPixels(target, viewport.left, viewport.bottom, viewport.width,
viewport.height, std::move(descriptor));
}
static void ReadDepthPixels(filament::Renderer* renderer,
filament::RenderTarget* target, mjrRect viewport,
float* buffer, size_t num_bytes) {
filament::backend::PixelBufferDescriptor descriptor(
buffer, num_bytes, filament::backend::PixelDataFormat::R,
filament::backend::PixelDataType::FLOAT);
renderer->readPixels(target, viewport.left, viewport.bottom, viewport.width,
viewport.height, std::move(descriptor));
}
void FilamentContext::ReadPixels(mjrRect viewport, unsigned char* rgb,
float* depth) {
if (!render_to_texture_) {
mju_error("Cannot read pixels unless framebuffer is set.");
}
if (color_target_ == nullptr || depth_target_ == nullptr) {
if (viewport.left != 0) {
mju_error("Reading subpixels not supported.");
}
if (viewport.bottom != 0) {
mju_error("Reading subpixels not supported.");
}
PrepareRenderTargets(viewport.width, viewport.height);
}
if (rgb) {
filament::View* view =
scene_view_->PrepareRenderView(SceneView::DrawMode::kNormal);
if (renderer_->beginFrame(swap_chain_)) {
// We need to disable msaa in order to render to texture.
auto options = view->getMultiSampleAntiAliasingOptions();
view->setMultiSampleAntiAliasingOptions({
.enabled = false,
});
view->setRenderTarget(color_target_);
renderer_->render(view);
view->setRenderTarget(nullptr);
view->setMultiSampleAntiAliasingOptions(options);
renderer_->endFrame();
}
engine_->flushAndWait();
const size_t num_bytes = viewport.width * viewport.height * 3;
ReadColorPixels(renderer_, color_target_, viewport, rgb, num_bytes);
}
if (depth) {
filament::View* view =
scene_view_->PrepareRenderView(SceneView::DrawMode::kDepth);
if (renderer_->beginFrame(swap_chain_)) {
view->setRenderTarget(depth_target_);
renderer_->render(view);
view->setRenderTarget(nullptr);
renderer_->endFrame();
}
engine_->flushAndWait();
const size_t num_bytes = viewport.width * viewport.height * sizeof(float);
ReadDepthPixels(renderer_, depth_target_, viewport, depth, num_bytes);
}
if (rgb || depth) {
engine_->flushAndWait();
}
}
void FilamentContext::UploadMesh(const mjModel* model, int id) {
object_manager_->UploadMesh(model, id);
}
void FilamentContext::UploadTexture(const mjModel* model, int id) {
object_manager_->UploadTexture(model, id);
}
void FilamentContext::UploadHeightField(const mjModel* model, int id) {
object_manager_->UploadHeightField(model, id);
}
void FilamentContext::UploadFont(const uint8_t* pixels, int width, int height,
int id) {
object_manager_->UploadFont(pixels, width, height, id);
}
} // namespace mujoco
@@ -0,0 +1,86 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_
#include <cstdint>
#include <memory>
#include <filament/Engine.h>
#include <filament/Renderer.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjrender.h>
#include <mujoco/mjvisualize.h>
#include "experimental/filament/filament/gui_view.h"
#include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/filament/texture_util.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
// Manages the filament renderer that is exposed via the mjr functions.
class FilamentContext {
public:
FilamentContext(const mjrFilamentConfig* config, const mjModel* model,
mjrContext* con);
~FilamentContext();
void Render(const mjrRect& viewport, const mjvScene* scene,
const mjrContext* con);
void SetFrameBuffer(int framebuffer);
void ReadPixels(mjrRect viewport, unsigned char* rgb, float* depth);
void UploadMesh(const mjModel* model, int id);
void UploadTexture(const mjModel* model, int id);
void UploadHeightField(const mjModel* model, int id);
void UploadFont(const uint8_t* pixels, int width, int height, int id);
FilamentContext(const FilamentContext&) = delete;
FilamentContext& operator=(const FilamentContext&) = delete;
private:
void PrepareRenderTargets(int width, int height);
void DestroyRenderTargets();
SceneView* GetSceneView(const mjvScene* scene);
mjrFilamentConfig config_;
mjrContext* context_ = nullptr;
const mjModel* model_ = nullptr;
filament::Engine* engine_ = nullptr;
filament::SwapChain* swap_chain_ = nullptr;
filament::Renderer* renderer_ = nullptr;
filament::RenderTarget* color_target_ = nullptr;
filament::RenderTarget* depth_target_ = nullptr;
filament::Texture* target_textures_[kNumRenderTargetTextureTypes] = {
nullptr, nullptr, nullptr};
bool render_to_texture_ = false;
bool render_gui_ = false;
std::unique_ptr<ObjectManager> object_manager_;
std::unique_ptr<SceneView> scene_view_;
std::unique_ptr<GuiView> gui_view_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_
@@ -0,0 +1,171 @@
// 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 "experimental/filament/filament/geom_util.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <span>
#include <filament/Engine.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/buffer_util.h"
#include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/vertex_util.h"
namespace mujoco {
static std::span<const float> GetPositions(const mjModel* model,
const mjvScene* scene,
const mjvGeom& geom) {
if (geom.type == mjGEOM_FLEX) {
const int num = 9 * scene->flexfaceused[geom.objid];
const int addr = scene->flexfaceadr[geom.objid];
const float* ptr = scene->flexface + (9 * addr);
return {ptr, static_cast<size_t>(num)};
} else {
const int num = 3 * scene->skinvertnum[geom.objid];
const int addr = scene->skinvertadr[geom.objid];
const float* ptr = scene->skinvert + (3 * addr);
return {ptr, static_cast<size_t>(num)};
}
}
static std::span<const float> GetNormals(const mjModel* model,
const mjvScene* scene,
const mjvGeom& geom) {
if (geom.type == mjGEOM_FLEX) {
const int num = 9 * scene->flexfaceused[geom.objid];
const int addr = scene->flexfaceadr[geom.objid];
const float* ptr = scene->flexnormal + (9 * addr);
return {ptr, static_cast<size_t>(num)};
} else {
const int num = 3 * scene->skinvertnum[geom.objid];
const int addr = scene->skinvertadr[geom.objid];
const float* ptr = scene->skinnormal + (3 * addr);
return {ptr, static_cast<size_t>(num)};
}
}
static std::span<const float> GetUvs(const mjModel* model,
const mjvScene* scene,
const mjvGeom& geom) {
if (geom.type == mjGEOM_FLEX) {
if (geom.texcoord && geom.matid >= 0) {
const int num = 6 * scene->flexfaceused[geom.objid];
const int addr = scene->flexfaceadr[geom.objid];
const float* ptr = scene->flextexcoord + (6 * addr);
return {ptr, static_cast<size_t>(num)};
} else {
const float* ptr = nullptr;
return {ptr, 0};
}
} else {
if (model->skin_texcoordadr[geom.objid] >= 0) {
const int num = 3 * scene->skinvertnum[geom.objid];
const int addr = model->skin_texcoordadr[geom.objid];
const float* ptr = model->skin_texcoord + (2 * addr);
return {ptr, static_cast<size_t>(num)};
} else {
const float* ptr = nullptr;
return {ptr, 0};
}
}
}
static std::span<const int> GetIndices(const mjModel* model,
const mjvScene* scene,
const mjvGeom& geom) {
if (geom.type == mjGEOM_FLEX) {
const int* ptr = nullptr;
return {ptr, 0};
} else {
const int num = 3 * model->skin_facenum[geom.objid];
const int* ptr = model->skin_face + 3 * model->skin_faceadr[geom.objid];
return {ptr, static_cast<size_t>(num)};
}
}
template <typename T>
static void FillVertices(std::byte* buffer, std::size_t len,
std::span<const float> positions,
std::span<const float> normals,
std::span<const float> uvs) {
const int num_vertices = len / sizeof(T);
T* ptr = reinterpret_cast<T*>(buffer);
for (int i = 0; i < num_vertices; ++i) {
ptr->position = ReadFloat3(positions.data(), i);
ptr->orientation = CalculateOrientation(ReadFloat3(normals.data(), i));
if constexpr (T::kHasUv) {
ptr->uv.x = uvs[i * 2];
ptr->uv.y = uvs[i * 2 + 1];
}
++ptr;
}
}
static filament::VertexBuffer* BuildVertexBuffer(
filament::Engine* engine, std::span<const float> positions,
std::span<const float> normals, std::span<const float> uvs) {
const int num_vertices = positions.size() / 3;
if (uvs.data() != nullptr) {
using VertexType = VertexWithUv;
auto fill = [&](std::byte* buffer, std::size_t len) {
FillVertices<VertexType>(buffer, len, positions, normals, uvs);
};
return CreateVertexBuffer<VertexType>(engine, num_vertices, fill);
} else {
using VertexType = VertexNoUv;
auto fill = [&](std::byte* buffer, std::size_t len) {
FillVertices<VertexType>(buffer, len, positions, normals, uvs);
};
return CreateVertexBuffer<VertexType>(engine, num_vertices, fill);
}
}
static filament::IndexBuffer* BuildIndexBuffer(filament::Engine* engine,
std::span<const int> indices,
int num_indices) {
if (indices.data() == nullptr) {
auto fill_indices = FillSequence<uint32_t>;
return CreateIndexBuffer<uint32_t>(engine, num_indices, fill_indices);
} else {
auto fill_indices = [&](std::byte* buffer, std::size_t len) {
std::memcpy(buffer, indices.data(), len);
};
return CreateIndexBuffer<uint32_t>(engine, indices.size(), fill_indices);
}
}
FilamentBuffers CreateGeomBuffers(filament::Engine* engine,
const mjModel* model, const mjvScene* scene,
const mjvGeom& geom) {
auto positions = GetPositions(model, scene, geom);
auto normals = GetNormals(model, scene, geom);
auto uvs = GetUvs(model, scene, geom);
auto indices = GetIndices(model, scene, geom);
int num_indices = indices.size();
if (num_indices == 0 && geom.type == mjGEOM_FLEX) {
num_indices = 3 * scene->flexfaceused[geom.objid];
}
FilamentBuffers buffers;
buffers.vertex_buffer = BuildVertexBuffer(engine, positions, normals, uvs);
buffers.index_buffer = BuildIndexBuffer(engine, indices, num_indices);
return buffers;
}
} // namespace mujoco
@@ -0,0 +1,31 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_GEOM_UTIL_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_GEOM_UTIL_H_
#include <filament/Engine.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/buffer_util.h"
namespace mujoco {
// Populates the FilamentBuffers for a flex geometry.
FilamentBuffers CreateGeomBuffers(filament::Engine* engine,
const mjModel* model, const mjvScene* scene,
const mjvGeom& geom);
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_GEOM_UTIL_H_
@@ -0,0 +1,244 @@
// 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 "experimental/filament/filament/gui_view.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
#include <dear_imgui/imgui.h>
#include <filament/Engine.h>
#include <filament/RenderableManager.h>
#include <filament/TextureSampler.h>
#include <filament/Viewport.h>
#include <math/vec4.h>
#include <utils/EntityManager.h>
#include <mujoco/mjrender.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/buffer_util.h"
#include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/vertex_util.h"
namespace mujoco {
using filament::math::float4;
static constexpr auto kTriangles =
filament::RenderableManager::PrimitiveType::TRIANGLES;
GuiView::GuiView(filament::Engine* engine, ObjectManager* object_mgr)
: object_mgr_(object_mgr), engine_(engine) {
auto& em = utils::EntityManager::get();
scene_ = engine_->createScene();
camera_ = engine_->createCamera(em.create());
view_ = engine_->createView();
view_->setScene(scene_);
view_->setCamera(camera_);
view_->setPostProcessingEnabled(false);
material_ = object_mgr_->GetMaterial(ObjectManager::kUnlitUi);
// Upload the ImGui font as a texture that is sampled by the UX material.
int font_width = 0;
int font_height = 0;
int font_bpp = 0;
unsigned char* pixels = nullptr;
ImGuiIO& io = ImGui::GetIO();
io.Fonts->GetTexDataAsRGBA32(&pixels, &font_width, &font_height, &font_bpp);
object_mgr_->UploadFont(pixels, font_width, font_height, 0);
}
GuiView::~GuiView() {
ResetRenderable();
for (auto& instance : instances_) {
engine_->destroy(instance.second);
}
engine_->destroyCameraComponent(camera_->getEntity());
engine_->destroy(view_);
engine_->destroy(scene_);
}
void GuiView::ResetRenderable() {
auto& em = utils::EntityManager::get();
if (!renderable_.isNull()) {
scene_->remove(renderable_);
auto& rm = engine_->getRenderableManager();
rm.destroy(renderable_);
em.destroy(renderable_);
renderable_ = utils::Entity();
}
for (auto& buffer : buffers_) {
engine_->destroy(buffer.vertex_buffer);
engine_->destroy(buffer.index_buffer);
}
buffers_.clear();
}
bool GuiView::PrepareRenderable() {
ResetRenderable();
// Prepare the imgui draw commands. We must call this function even if we do
// not plan on rendering anything to ensure imgui state is updated.
ImGui::Render();
ImGuiIO& io = ImGui::GetIO();
const ImVec2& size = io.DisplaySize;
const ImVec2& scale = io.DisplayFramebufferScale;
ImDrawData* commands = ImGui::GetDrawData();
commands->ScaleClipRects(scale);
int num_elements = 0;
for (int n = 0; n < commands->CmdListsCount; ++n) {
const ImDrawList* cmds = commands->CmdLists[n];
if (sizeof(GuiVertex) != sizeof(cmds->VtxBuffer.Data[0])) {
mju_error("Invalid vertex buffer size.");
}
if (sizeof(uint16_t) != sizeof(cmds->IdxBuffer.Data[0])) {
mju_error("Invalid index buffer size.");
}
num_elements += cmds->CmdBuffer.size();
}
if (size.x == 0 || size.y == 0 || num_elements == 0) {
return false;
}
view_->setViewport(
filament::Viewport(0.f, 0.f, size.x * scale.x, size.y * scale.y));
camera_->setProjection(filament::Camera::Projection::ORTHO, 0.0, size.x,
size.y, 0.0, 0.0, 1.0);
filament::RenderableManager::Builder builder(num_elements);
builder.boundingBox({{-100, -100, -100}, {100, 100, 100}});
builder.culling(false);
int drawable_index = 0;
for (int n = 0; n < commands->CmdListsCount; ++n) {
const ImDrawList* cmds = commands->CmdLists[n];
auto vfill = [&](std::byte* dst, std::size_t size) {
if (size != cmds->VtxBuffer.size_in_bytes()) {
mju_error("Invalid vertex buffer size.");
}
std::memcpy(dst, cmds->VtxBuffer.Data, size);
};
auto ifill = [&](std::byte* dst, std::size_t size) {
if (size != cmds->IdxBuffer.size_in_bytes()) {
mju_error("Invalid index buffer size.");
}
std::memcpy(dst, cmds->IdxBuffer.Data, size);
};
const mujoco::FilamentBuffers& buffer = buffers_.emplace_back(
CreateIndexBuffer<uint16_t>(engine_, cmds->IdxBuffer.Size, ifill),
CreateVertexBuffer<GuiVertex>(engine_, cmds->VtxBuffer.Size, vfill));
int index_offset = 0;
for (const ImDrawCmd& command : cmds->CmdBuffer) {
const int width = size.x * scale.x;
const int height = size.y * scale.y;
int clip_left = command.ClipRect.x;
int clip_bottom = height - command.ClipRect.w;
int clip_width = command.ClipRect.z - command.ClipRect.x;
int clip_height = command.ClipRect.w - command.ClipRect.y;
// Modal dialogs try to cover the whole window, but also a little outside
// of it. This doesn't work well with filament's scissor test, so we clip
// them to the window.
if (clip_left < 0 || clip_bottom < 0) {
clip_left = 0;
clip_bottom = 0;
clip_width = width;
clip_height = height;
}
mjrRect clip_rect(clip_left, clip_bottom, clip_width, clip_height);
builder.material(drawable_index, GetMaterialInstance(clip_rect));
builder.geometry(drawable_index, kTriangles, buffer.vertex_buffer,
buffer.index_buffer, index_offset,
command.ElemCount);
builder.blendOrder(drawable_index, drawable_index);
index_offset += command.ElemCount;
++drawable_index;
}
}
auto& em = utils::EntityManager::get();
renderable_ = em.create();
builder.build(*engine_, renderable_);
scene_->addEntity(renderable_);
return true;
}
// Maps a rect into a 64-bit key to assist with lookup.
static uint64_t MakeRectKey(mjrRect rect) {
return static_cast<uint64_t>(rect.left & 0xff) << 24 |
static_cast<uint64_t>(rect.bottom & 0xff) << 16 |
static_cast<uint64_t>(rect.width & 0xff) << 8 |
static_cast<uint64_t>(rect.height & 0xff);
}
filament::MaterialInstance* GuiView::GetMaterialInstance(mjrRect rect) {
const uint64_t key = MakeRectKey(rect);
auto iter = instances_.find(key);
if (iter != instances_.end()) {
return iter->second;
}
auto instance = material_->createInstance();
instance->setScissor(rect.left, rect.bottom, rect.width, rect.height);
filament::TextureSampler sampler;
instance->setParameter("glyph", object_mgr_->GetFont(0), sampler);
instances_[key] = instance;
return instance;
}
filament::View* GuiView::PrepareRenderView() { return view_; }
static ImVec2 ClipSpaceToWindowCoordinates(float x, float y) {
const ImVec2& display_size = ImGui::GetIO().DisplaySize;
const float pos_x = display_size.x * ((x + 1) * 0.5f);
const float pos_y = display_size.y * (1.0f - ((y + 1) * 0.5f));
return ImVec2(pos_x, pos_y);
}
void DrawTextAt(const char* text, float x, float y, float z) {
if (x < -1 || y < -1 || x > 1 || y > 1 || z < -1 || z > 1) {
return;
}
const ImVec2 center_pos = ClipSpaceToWindowCoordinates(x, y);
const ImVec2 size = ImGui::CalcTextSize(text);
const ImVec2 pos = ImVec2(center_pos.x - size.x / 2, center_pos.y);
const ImVec2 shadow_pos = ImVec2(pos.x + 2, pos.y + 2);
const int flags = ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoInputs |
ImGuiWindowFlags_NoNav;
ImGui::Begin("labels", nullptr, flags);
ImGui::BeginChild("labels", ImGui::GetIO().DisplaySize, 0, flags);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
draw_list->AddText(shadow_pos, IM_COL32_BLACK, text);
draw_list->AddText(pos, IM_COL32_WHITE, text);
ImGui::EndChild();
ImGui::End();
}
} // namespace mujoco
@@ -0,0 +1,74 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_GUI_VIEW_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_GUI_VIEW_H_
#include <cstdint>
#include <unordered_map>
#include <vector>
#include <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/Scene.h>
#include <filament/Texture.h>
#include <filament/View.h>
#include <mujoco/mjrender.h>
#include "experimental/filament/filament/buffer_util.h"
#include "experimental/filament/filament/object_manager.h"
namespace mujoco {
// A filament::View that contains a filament::Scene used for rendering the GUI.
class GuiView {
public:
GuiView(filament::Engine* engine, ObjectManager* object_mgr);
~GuiView();
// Prepares the UX scene renderable using data from the current ImGui state.
// This function must be called once per frame to ensure ImGui state is
// correctly synced.
bool PrepareRenderable();
// Returns the filament::View used to render the UX scene.
filament::View* PrepareRenderView();
private:
// Returns the filament::MaterialInstance configured to draw into the given
// scissor rect.
filament::MaterialInstance* GetMaterialInstance(mjrRect rect);
// Clears the filament::Scene of the UX renderable and releases all buffers.
void ResetRenderable();
ObjectManager* object_mgr_ = nullptr;
filament::Engine* engine_ = nullptr;
filament::Scene* scene_ = nullptr;
filament::Camera* camera_ = nullptr;
filament::View* view_ = nullptr;
filament::Material* material_ = nullptr;
utils::Entity renderable_;
std::vector<FilamentBuffers> buffers_;
std::unordered_map<uint64_t, filament::MaterialInstance*> instances_;
};
// Draws text at the given screen coordinates in clip space (i.e. [-1,-1,-1] to
// [1,1,1]).
void DrawTextAt(const char* text, float x, float y, float z);
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_GUI_VIEW_H_
@@ -0,0 +1,691 @@
// 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 "experimental/filament/filament/imgui_editor.h"
#include <algorithm>
#include <cstdint>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <dear_imgui/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 "experimental/filament/filament/color_grading_options.h"
#include "experimental/filament/filament/scene_view.h"
namespace mujoco {
using filament::math::float3;
using filament::math::float4;
using filament::math::mat4;
using NameList = std::span<const char*>;
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;
};
template <typename T>
bool Ui(std::string_view label, T* value, UiOpts<T> opts = {}) {
bool changed = false;
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::InputScalar(label.data(), ImGuiDataType_U8, value);
} else if constexpr (std::is_same_v<T, uint16_t>) {
changed = ImGui::InputScalar(label.data(), ImGuiDataType_U16, value);
} else if constexpr (std::is_same_v<T, uint32_t>) {
changed = ImGui::InputScalar(label.data(), ImGuiDataType_U32, value);
} 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 {
int step = opts.step.value_or(1);
int fast_step = opts.fstep.value_or(100);
changed = ImGui::InputInt(label.data(), value, step, fast_step);
}
} 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);
} else {
float step = opts.step.value_or(0.f);
float fast_step = opts.fstep.value_or(0.f);
changed = ImGui::InputFloat(label.data(), value, step, fast_step);
}
} else if constexpr (std::is_same_v<T, float3>) {
changed = ImGui::InputFloat3(label.data(), &value->x);
} else if constexpr (std::is_same_v<T, float4>) {
changed = ImGui::InputFloat4(label.data(), &value->x);
} else {
static_assert(false, "Unsupported type");
}
return changed;
}
void DrawAmbientOcclusionGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
auto opts = view->getAmbientOcclusionOptions();
bool changed = false;
changed |= Ui("Enabled", &opts.enabled);
changed |= Ui("Radius (m)", &opts.radius);
changed |= Ui("Power Contrrast", &opts.power);
changed |= Ui("Bias (m)", &opts.bias);
changed |= Ui("Resolution Scale", &opts.resolution);
changed |= Ui("Intensity", &opts.intensity);
changed |= Ui("Bend Normals", &opts.bentNormals);
changed |= Ui("Bilateral Threshold", &opts.bilateralThreshold);
changed |= Ui("Min. Horizon Angle (Rad)", &opts.minHorizonAngleRad);
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("Cone Angle (Rad)", &opts.ssct.lightConeRad);
changed |= Ui("Shadow Distance (m)", &opts.ssct.shadowDistance);
changed |= Ui("Max Contact Distance (m)", &opts.ssct.contactDistanceMax);
changed |= Ui("Intensity", &opts.ssct.intensity);
changed |= Ui("Deeth Bias", &opts.ssct.depthBias);
changed |= Ui("Depth Slope Bias", &opts.ssct.depthSlopeBias);
changed |= Ui("Sample Count", &opts.ssct.sampleCount);
changed |= Ui("Raw Count", &opts.ssct.rayCount);
changed |= Ui("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("Forground 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);
}
}
static char filename[256];
ImGui::InputText("Filename", filename, sizeof(filename));
if (ImGui::Button("Load")) {
scene_view->SetEnvironmentLight(filename, 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);
if (changed) {
lm.setShadowOptions(li, opts);
}
ImGui::TreePop();
}
}
void DrawSceneViewGui(SceneView* scene_view) {
filament::View* view = scene_view->GetDefaultRenderView();
filament::Engine* engine = scene_view->GetEngine();
filament::LightManager& lm = engine->getLightManager();
if (ImGui::TreeNodeEx("Ambient Occlusion")) {
DrawAmbientOcclusionGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Screen Space")) {
DrawScreenSpaceGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Shadowing")) {
DrawShadowingGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Post Processing")) {
DrawPostProcessingGui(scene_view);
if (ImGui::TreeNodeEx("Anti Aliasing (FXAA)")) {
DrawFxaaGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Anti Aliasing (MSAA)")) {
DrawMsaaGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Anti Aliasing (Temporal)")) {
DrawTaaGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Bloom")) {
DrawBloomGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Color Grading")) {
DrawColorGradingGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Depth of Field")) {
DrawDepthOfFieldGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Dithering")) {
DrawDitheringGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Fog")) {
DrawFogGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Vignette")) {
DrawVignetteGui(scene_view);
ImGui::TreePop();
}
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Visibility Layers")) {
DrawVisibleLayersGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Camera")) {
DrawCameraGui(scene_view);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Lights")) {
if (ImGui::TreeNodeEx("Indirect (Image-based) Light")) {
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())) {
DrawLightGui(lm, li);
ImGui::TreePop();
}
});
ImGui::TreePop();
}
}
void DrawGui(SceneView* scene_view) {
static bool display = false;
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("View")) {
ImGui::Separator();
if (ImGui::MenuItem("Filament", "", display)) {
display = !display;
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
if (!display) {
return;
}
ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_Appearing);
ImGui::Begin("Filament");
DrawSceneViewGui(scene_view);
ImGui::End();
}
} // namespace mujoco
@@ -0,0 +1,27 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_EDITOR_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_EDITOR_H_
#include "experimental/filament/filament/scene_view.h"
namespace mujoco {
// Generates a ImGui Window for the given scene views.
void DrawGui(SceneView* scene_views);
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_EDITOR_H_
+130
View File
@@ -0,0 +1,130 @@
// 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 "experimental/filament/filament/light.h"
#include <filament/Engine.h>
#include <filament/LightManager.h>
#include <filament/Scene.h>
#include <math/vec3.h>
#include <utils/Entity.h>
#include <utils/EntityManager.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/object_manager.h"
namespace mujoco {
Light::Light(ObjectManager* object_mgr, const Params& params)
: engine_(object_mgr->GetEngine()), params_(params) {
filament::LightManager::Type type;
switch (params.type) {
case mjLIGHT_SPOT:
type = filament::LightManager::Type::FOCUSED_SPOT;
break;
case mjLIGHT_DIRECTIONAL:
// We break with the spec here slightly and use a spot light for the head
// light instead of a directional params. This is because filament only
// supports a single directional light, and we'd rather allow a scene
// light to be that directional params. It's also a bit odd for a
// directional light to move with the camera.
type = params.headlight ? filament::LightManager::Type::FOCUSED_SPOT
: 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(params.color);
builder.intensityCandela(params.intensity);
builder.castShadows(params.castshadow);
if (type == filament::LightManager::Type::FOCUSED_SPOT) {
if (params.headlight) {
builder.spotLightCone(0, M_PI / 2.0f);
} else {
builder.spotLightCone(0, params.spot_cone_angle * M_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.bulbradius;
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 {
utils::EntityManager& em = utils::EntityManager::get();
if (!entity_.isNull()) {
engine_->destroy(entity_);
em.destroy(entity_);
}
}
void Light::AddToScene(filament::Scene* scene) { scene->addEntity(entity_); }
void Light::RemoveFromScene(filament::Scene* scene) { scene->remove(entity_); }
void Light::SetTransform(filament::math::float3 position,
filament::math::float3 direction) {
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) {
filament::LightManager& lm = engine_->getLightManager();
const filament::LightManager::Instance li = lm.getInstance(entity_);
lm.setColor(li, color);
}
void Light::SetIntensity(float intensity) {
filament::LightManager& lm = engine_->getLightManager();
const filament::LightManager::Instance li = lm.getInstance(entity_);
lm.setIntensityCandela(li, intensity);
}
void Light::Enable() {
if (!enabled_) {
enabled_ = true;
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;
filament::LightManager& lm = engine_->getLightManager();
const filament::LightManager::Instance li = lm.getInstance(entity_);
lm.setLightChannel(li, 0, enabled_);
}
}
} // namespace mujoco
@@ -0,0 +1,88 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_LIGHT_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_LIGHT_H_
#include <filament/Engine.h>
#include <filament/Scene.h>
#include <math/vec3.h>
#include <utils/Entity.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/object_manager.h"
namespace mujoco {
// Manages the filament Entities for a single mjvLight.
class Light {
public:
// Configuration parameters for a light.
struct Params {
// The type of light (e.g. spot, point, directional, etc.)
mjtLightType type;
// The color of the light.
filament::math::float3 color = {0, 0, 0};
// The intensity of the light, in candela.
float intensity = 0.0f;
// Whether or not the light casts shadows.
bool castshadow = true;
// The range/distance in which the light is effective, in meters.
float range = 10.0f;
// The angle of the spot light cone, in degrees.
float spot_cone_angle = 180.f;
// The radius of the bulb used for soft shadows.
float bulbradius = 0.0f;
// Whether or not the light is a headlight.
bool headlight = false;
};
Light(ObjectManager* object_mgr, const Params& params);
~Light() noexcept;
Light(const Light&) = delete;
Light& operator=(const Light&) = delete;
// Adds the filament light Entities to the given filament Scene.
void AddToScene(filament::Scene* scene);
// Removes the filament light Entities from the given filament Scene.
void RemoveFromScene(filament::Scene* scene);
// Updates the light's position/rotation.
void SetTransform(filament::math::float3 position,
filament::math::float3 direction);
// Sets the color of the light.
void SetColor(const filament::math::float3& color);
// Sets the intensity of the light in candela.
void SetIntensity(float intensity);
// Enables/disables the light in the scene.
void Enable();
void Disable();
// Returns true if the light is a headlight.
bool IsHeadlight() const { return params_.headlight; }
private:
filament::Engine* engine_ = nullptr;
utils::Entity entity_;
bool enabled_ = true;
Params params_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_LIGHT_H_
@@ -0,0 +1,186 @@
// 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 "experimental/filament/filament/material.h"
#include <filament/Color.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/TextureSampler.h>
#include <mujoco/mjmodel.h>
#include "experimental/filament/filament/object_manager.h"
namespace mujoco {
// Various tweakable parameters for mapping mujoco values onto filament.
static constexpr float kSpecularMultiplier = 0.6f;
static constexpr float kShininessMultiplier = 0.1f;
static constexpr float kEmissiveMultiplier = 0.3f;
Material::Material(ObjectManager* object_mgr) : object_mgr_(object_mgr) {
instances_[kDepth] =
object_mgr->GetMaterial(ObjectManager::kUnlitDepth)->createInstance();
instances_[kSegmentation] =
object_mgr_->GetMaterial(ObjectManager::kUnlitSegmentation)
->createInstance();
}
Material::~Material() noexcept {
filament::Engine* engine = object_mgr_->GetEngine();
for (int i = 0; i < kNumDrawModes; ++i) {
if (instances_[i]) {
engine->destroy(instances_[i]);
}
}
}
void Material::SetNormalMaterialType(
ObjectManager::MaterialType material_type) {
filament::Material* material = object_mgr_->GetMaterial(material_type);
if (instances_[kNormal]) {
const filament::Material* current_material =
instances_[kNormal]->getMaterial();
if (current_material == material) {
return;
}
object_mgr_->GetEngine()->destroy(instances_[kNormal]);
instances_[kNormal] = nullptr;
}
if (material) {
instances_[kNormal] = material->createInstance();
UpdateMaterialInstances();
}
}
void Material::UpdateParams(const Params& params) {
params_ = params;
UpdateMaterialInstances();
}
void Material::UpdateTextures(const Textures& textures) {
textures_ = textures;
UpdateMaterialInstances();
}
void Material::UpdateMaterialInstances() {
filament::MaterialInstance* instance = instances_[DrawMode::kNormal];
if (instance == nullptr) {
return;
}
const filament::Material* material = instance->getMaterial();
if (material->hasParameter("BaseColorFactor")) {
instance->setParameter("BaseColorFactor", filament::RgbaType::sRGB,
params_.color);
}
if (material->hasParameter("EmissiveFactor")) {
instance->setParameter("EmissiveFactor",
params_.emissive * kEmissiveMultiplier);
}
if (material->hasParameter("SpecularFactor")) {
instance->setParameter("SpecularFactor",
params_.specular * kSpecularMultiplier);
}
if (material->hasParameter("GlossinessFactor")) {
instance->setParameter("GlossinessFactor",
params_.glossiness * kShininessMultiplier);
}
if (material->hasParameter("MetallicFactor")) {
instance->setParameter("MetallicFactor",
params_.metallic >= 0 ? params_.metallic : 1.0f);
}
if (material->hasParameter("RoughnessFactor")) {
instance->setParameter("RoughnessFactor",
params_.roughness >= 0 ? params_.roughness : 1.0f);
}
if (material->hasParameter("UvScale")) {
instance->setParameter("UvScale", params_.uv_scale);
}
if (instances_[DrawMode::kSegmentation]) {
instances_[DrawMode::kSegmentation]->setParameter(
"BaseColorFactor", params_.segmentation_color);
}
// 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);
if (material->hasParameter("BaseColor")) {
if (textures_.color) {
instance->setParameter("BaseColor", textures_.color, sampler);
} else {
auto* fallback = object_mgr_->GetFallbackTexture(mjTEXROLE_RGB);
instance->setParameter("BaseColor", fallback, sampler);
}
}
if (material->hasParameter("Normal")) {
if (textures_.normal) {
instance->setParameter("Normal", textures_.normal, sampler);
} else {
auto* fallback = object_mgr_->GetFallbackTexture(mjTEXROLE_NORMAL);
instance->setParameter("Normal", fallback, sampler);
}
}
if (material->hasParameter("Metallic")) {
if (textures_.metallic) {
instance->setParameter("Metallic", textures_.metallic, sampler);
} else {
auto* fallback = object_mgr_->GetFallbackTexture(mjTEXROLE_METALLIC);
instance->setParameter("Metallic", fallback, sampler);
}
}
if (material->hasParameter("Roughness")) {
if (textures_.roughness) {
instance->setParameter("Roughness", textures_.roughness, sampler);
} else {
auto* fallback = object_mgr_->GetFallbackTexture(mjTEXROLE_ROUGHNESS);
instance->setParameter("Roughness", fallback, sampler);
}
}
if (material->hasParameter("Occlusion")) {
if (textures_.occlusion) {
instance->setParameter("Occlusion", textures_.occlusion, sampler);
} else {
auto* fallback = object_mgr_->GetFallbackTexture(mjTEXROLE_OCCLUSION);
instance->setParameter("Occlusion", fallback, sampler);
}
}
if (material->hasParameter("ORM")) {
if (textures_.orm) {
instance->setParameter("ORM", textures_.orm, sampler);
} else {
auto* fallback = object_mgr_->GetFallbackTexture(mjTEXROLE_ORM);
instance->setParameter("ORM", fallback, sampler);
}
}
if (material->hasParameter("Emissive")) {
if (textures_.emissive) {
instance->setParameter("Emissive", textures_.emissive, sampler);
} else {
auto* fallback = object_mgr_->GetFallbackTexture(mjTEXROLE_EMISSIVE);
instance->setParameter("Emissive", fallback, sampler);
}
}
}
} // namespace mujoco
@@ -0,0 +1,102 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_MATERIAL_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MATERIAL_H_
#include <filament/Engine.h>
#include <filament/MaterialInstance.h>
#include <filament/Texture.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include "experimental/filament/filament/object_manager.h"
namespace mujoco {
class Material {
public:
// The different methods for rendering objects. Each mode uses a different
// material, but all materials "share" the same textures and parameters
// (unless specifically noted otherwise).
enum DrawMode {
kNormal,
kDepth,
kSegmentation,
kNumDrawModes,
};
// The textures that can be assigned to the drawable's material.
struct Textures {
const filament::Texture* color = nullptr;
const filament::Texture* normal = nullptr;
const filament::Texture* metallic = nullptr;
const filament::Texture* roughness = nullptr;
const filament::Texture* occlusion = nullptr;
const filament::Texture* orm = nullptr;
const filament::Texture* emissive = nullptr;
};
// The parameters that can be applied to the drawable's material.
struct Params {
filament::math::float4 color = {1, 1, 1, 1};
filament::math::float4 segmentation_color = {1, 1, 1, 1};
filament::math::float2 tex_repeat = {1, 1};
filament::math::float3 uv_scale = {1, 1, 1};
float specular = -1.0f;
float glossiness = -1.0f;
float metallic = -1.0f;
float roughness = -1.0f;
float emissive = -1.0f;
bool tex_uniform = false;
};
Material(ObjectManager* object_mgr);
~Material() noexcept;
Material(const Material&) = delete;
Material& operator=(const Material&) = delete;
// Assigns a material to the draw mode.
void SetNormalMaterialType(ObjectManager::MaterialType material_type);
// Updates the material parameters of the drawable for rendering.
void UpdateParams(const Params& params);
// Updates the material textures of the drawable for rendering.
void UpdateTextures(const Textures& textures);
// Returns the material instance assigned to the draw mode.
filament::MaterialInstance* GetMaterialInstance(DrawMode mode) {
return instances_[mode];
}
// Returns the ObjectManager owning the Materials which are used to create
// the MaterialInstances.
ObjectManager* GetObjectManager() { return object_mgr_; }
private:
// Updates the material instances based on the currently set parameters and
// textures.
void UpdateMaterialInstances();
ObjectManager* object_mgr_ = nullptr;
filament::MaterialInstance* instances_[kNumDrawModes] = {nullptr};
Params params_;
Textures textures_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MATERIAL_H_
@@ -0,0 +1,59 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_MATH_UTIL_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MATH_UTIL_H_
#include <math/mat3.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.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::mat3 ReadMat3(const T* arr, int index = 0) {
// clang-format off
const T* ptr = arr + (9 * index);
return filament::math::mat3(ptr[0], ptr[3], ptr[6],
ptr[1], ptr[4], ptr[7],
ptr[2], ptr[5], ptr[8]);
// clang-format on
}
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MATH_UTIL_H_
@@ -0,0 +1,477 @@
// 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 "experimental/filament/filament/model_util.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Texture.h>
#include <filament/VertexBuffer.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/buffer_util.h"
#include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/texture_util.h"
#include "experimental/filament/filament/vertex_util.h"
namespace mujoco {
using filament::math::float2;
using filament::math::float3;
using filament::math::float4;
static bool UseFaceNormal(const float3& face_normal,
const float3& mesh_normal) {
// clang-format off
return face_normal[0] * mesh_normal[0] +
face_normal[1] * mesh_normal[1] +
face_normal[2] * mesh_normal[2] < 0.8f;
// clang-format on
}
template <typename T>
static void FillConvexHullBuffer(T* ptr, std::size_t num, const mjModel* model,
int meshid) {
const int numvert = model->mesh_graph[model->mesh_graphadr[meshid]];
const int numface = model->mesh_graph[model->mesh_graphadr[meshid]+1];
const int vertadr = model->mesh_vertadr[meshid];
const float* vertices = model->mesh_vert + (3 * vertadr);
const int texcoordadr = model->mesh_texcoordadr[meshid];
const float* texcoords = model->mesh_texcoord + (2 * texcoordadr);
if (num != numface * 3) {
mju_error("Invalid vertex count.");
return;
}
for (int face = 0; face < numface; ++face) {
int j = model->mesh_graphadr[meshid] + 2 + 3*numvert + 3*numface + 3*face;
const float3 p1 = ReadFloat3(vertices, model->mesh_graph[j + 0]);
const float3 p2 = ReadFloat3(vertices, model->mesh_graph[j + 1]);
const float3 p3 = ReadFloat3(vertices, model->mesh_graph[j + 2]);
const float4 orientation = CalculateOrientation(p1, p2, p3);
ptr->position = p1;
ptr->orientation = orientation;
if constexpr (T::kHasUv) {
ptr->uv = ReadFloat2(texcoords, model->mesh_graph[j + 0]);
}
++ptr;
ptr->position = p2;
ptr->orientation = orientation;
if constexpr (T::kHasUv) {
ptr->uv = ReadFloat2(texcoords, model->mesh_graph[j + 1]);
}
++ptr;
ptr->position = p3;
ptr->orientation = orientation;
if constexpr (T::kHasUv) {
ptr->uv = ReadFloat2(texcoords, model->mesh_graph[j + 2]);
}
++ptr;
}
}
template <typename T>
static void FillMeshBuffer(T* ptr, std::size_t num, const mjModel* model,
int meshid) {
const int faceadr = model->mesh_faceadr[meshid];
const int facenum = model->mesh_facenum[meshid];
if (num != facenum * 3) {
mju_error("Invalid vertex count.");
return;
}
const int vertadr = model->mesh_vertadr[meshid];
const float* vertices = model->mesh_vert + (3 * vertadr);
const int normaladr = model->mesh_normaladr[meshid];
const float* normals = model->mesh_normal + 3 * normaladr;
const int texcoordadr = model->mesh_texcoordadr[meshid];
const float* texcoords = model->mesh_texcoord + (2 * texcoordadr);
for (int i = 0; i < facenum; ++i) {
const int face = 3 * (faceadr + i);
const float3 p1 = ReadFloat3(vertices, model->mesh_face[face + 0]);
const float3 p2 = ReadFloat3(vertices, model->mesh_face[face + 1]);
const float3 p3 = ReadFloat3(vertices, model->mesh_face[face + 2]);
const float3 face_normal = CalculateNormal(p1, p2, p3);
const float3 n1 = ReadFloat3(normals, model->mesh_facenormal[face + 0]);
const float3 n2 = ReadFloat3(normals, model->mesh_facenormal[face + 1]);
const float3 n3 = ReadFloat3(normals, model->mesh_facenormal[face + 2]);
ptr->position = p1;
if constexpr (T::kHasUv) {
ptr->orientation = CalculateOrientation(n1);
ptr->uv = ReadFloat2(texcoords, model->mesh_facetexcoord[face + 0]);
} else if (UseFaceNormal(face_normal, n1)) {
ptr->orientation = CalculateOrientation(face_normal);
} else {
ptr->orientation = CalculateOrientation(n1);
}
++ptr;
ptr->position = p2;
if constexpr (T::kHasUv) {
ptr->orientation = CalculateOrientation(n2);
ptr->uv = ReadFloat2(texcoords, model->mesh_facetexcoord[face + 1]);
} else if (UseFaceNormal(face_normal, n2)) {
ptr->orientation = CalculateOrientation(face_normal);
} else {
ptr->orientation = CalculateOrientation(n2);
}
++ptr;
ptr->position = p3;
if constexpr (T::kHasUv) {
ptr->orientation = CalculateOrientation(n3);
ptr->uv = ReadFloat2(texcoords, model->mesh_facetexcoord[face + 2]);
} else if (UseFaceNormal(face_normal, n3)) {
ptr->orientation = CalculateOrientation(face_normal);
} else {
ptr->orientation = CalculateOrientation(n3);
}
++ptr;
}
}
static void FillHeightFieldBuffer(VertexNoUv* ptr, std::size_t num,
const mjModel* model, int hfieldid) {
int count = 0;
auto append_tri = [&](float3 a, float3 b, float3 c) {
float4 orientation = CalculateOrientation(a, b, c);
ptr[count].position = a;
ptr[count].orientation = orientation;
++count;
ptr[count].position = b;
ptr[count].orientation = orientation;
++count;
ptr[count].position = c;
ptr[count].orientation = orientation;
++count;
};
auto append_quad = [&](float3 a, float3 b, float3 c, float3 d) {
append_tri(a, b, d);
append_tri(d, b, c);
};
const float* data = model->hfield_data + model->hfield_adr[hfieldid];
const int nrow = model->hfield_nrow[hfieldid];
const int ncol = model->hfield_ncol[hfieldid];
const float height = 0.5f * (nrow - 1);
const float width = 0.5f * (ncol - 1);
float sz[4];
for (int i = 0; i < 4; ++i) {
sz[i] = static_cast<float>(model->hfield_size[4 * hfieldid + i]);
}
auto get_pos = [=](int r, int c) {
const float x = sz[0] * (c / width - 1.0f);
const float y = sz[1] * (r / height - 1.0f);
const float z = sz[2] * data[(r * ncol) + c];
return float3{x, y, z};
};
// For each quad defined by 4 points in the height field, we will create 4
// triangles by introducing a vertex in the middle of the quad.
// a---b
// |\ /|
// | m |
// |/ \|
// d---c
for (int row = 0; row < nrow - 1; ++row) {
for (int col = 0; col < ncol - 1; ++col) {
const float3 a = get_pos(row, col);
const float3 b = get_pos(row, col + 1);
const float3 c = get_pos(row + 1, col + 1);
const float3 d = get_pos(row + 1, col);
const float mid_x = (a.x + b.x) * 0.5f;
const float mid_y = (a.y + d.y) * 0.5f;
// To determine the height of the middle vertex, we look at the heights
// of the opposing corners (i.e. {a, c} and {b, d}). Our goal is to avoid
// creating any odd bumps or valleys in the height field if possible.
//
// If one of the two opposing corners are of the same height, then we
// set the middle vertex such that we're effectively rendering two
// triangles, preventing an odd bump. Otherwise, we use the higher
// midpoint between two opposing corners to prevent valleys.
// 0---0 0---0 6---4
// |\ | | /| |\ /|
// | 0 | | 0 | | 7 |
// | \| |/ | |/ \|
// 2---0 0---2 0---8
float mid_z = 0;
if (a.z == c.z && b.z != d.z) {
mid_z = a.z;
} else if (a.z != c.z && b.z == d.z) {
mid_z = b.z;
} else {
const float mid_z_ac = (a.z + c.z) * 0.5f;
const float mid_z_bd = (b.z + d.z) * 0.5f;
mid_z = std::max(mid_z_ac, mid_z_bd);
}
const float3 mid = {mid_x, mid_y, mid_z};
append_tri(a, b, mid);
append_tri(b, c, mid);
append_tri(c, d, mid);
append_tri(d, a, mid);
}
}
// Build the left edge.
for (int row = 0; row < nrow - 1; ++row) {
const float3 a = get_pos(row, 0);
const float3 b = get_pos(row + 1, 0);
const float3 c = {b.x, b.y, -sz[3]};
const float3 d = {a.x, a.y, -sz[3]};
append_quad(a, b, c, d);
}
// Build the right edge.
for (int row = 0; row < nrow - 1; ++row) {
const float3 a = get_pos(row + 1, ncol-1);
const float3 b = get_pos(row, ncol-1);
const float3 c = {b.x, b.y, -sz[3]};
const float3 d = {a.x, a.y, -sz[3]};
append_quad(a, b, c, d);
}
// Build the front edge.
for (int col = 0; col < ncol - 1; ++col) {
const float3 a = get_pos(0, col);
const float3 b = get_pos(0, col + 1);
const float3 c = {b.x, b.y, -sz[3]};
const float3 d = {a.x, a.y, -sz[3]};
append_quad(a, b, c, d);
}
// Build the back edge.
for (int col = 0; col < ncol - 1; ++col) {
const float3 a = get_pos(nrow-1, col + 1);
const float3 b = get_pos(nrow-1, col);
const float3 c = {b.x, b.y, -sz[3]};
const float3 d = {a.x, a.y, -sz[3]};
append_quad(a, b, c, d);
}
// Build the base. We use the visualization quality as the size rather than
// the height field dimensions.
const float base_width = (0.5f * model->vis.quality.numquads);
const float base_height = (0.5f * model->vis.quality.numquads);
for (int row = 0; row < model->vis.quality.numquads; ++row) {
for (int col = 0; col < model->vis.quality.numquads; ++col) {
const float x0 = sz[0] * ((col + 0) / base_width - 1.0f);
const float x1 = sz[0] * ((col + 1) / base_width - 1.0f);
const float y0 = sz[1] * ((row + 0) / base_height - 1.0f);
const float y1 = sz[1] * ((row + 1) / base_height - 1.0f);
append_quad({x0, y0, -sz[3]},
{x0, y1, -sz[3]},
{x1, y1, -sz[3]},
{x1, y0, -sz[3]});
}
}
if (count != num) {
mju_error("Vertex count mismatch.");
}
}
static int CalculateHeightFieldVertexCount(const mjModel* model, int hfieldid) {
const int nrow = model->hfield_nrow[hfieldid];
const int ncol = model->hfield_ncol[hfieldid];
// For details, see the logic in FillHeightFieldBuffer for how many vertices
// we need. But, in general...
// We use 4 triangles (i.e. 12 vertices) per quad.
const int surface_count = 12 * (nrow-1) * (ncol-1);
// We use 1 quad (i.e. 6 vertices) per edge element. We double this because
// we have two edges per dimension (e.g. left/right and front/back).
const int edge_count = (12 * (nrow - 1)) + (12 * (ncol - 1));
// We use 1 quad (i.e. 6 vertices) per base element. We use the visualization
// quality as the size rather than the height field dimensions.
const int base_count =
6 * model->vis.quality.numquads * model->vis.quality.numquads;
const int total_count = surface_count + edge_count + base_count;
return total_count;
}
template <typename T, typename FillFn>
static filament::VertexBuffer* CreateVertexBuffer(filament::Engine* engine,
const mjModel* model, int id,
int vertex_count,
FillFn fill_fn) {
return CreateVertexBuffer<T>(
engine, vertex_count, [&](std::byte* buffer, std::size_t num_bytes) {
auto* ptr = reinterpret_cast<T*>(buffer);
fill_fn(ptr, num_bytes / sizeof(T), model, id);
});
}
filament::VertexBuffer* CreateVertexBuffer(filament::Engine* engine,
const mjModel* model, int id,
MeshType mesh_type) {
if (id < 0) {
mju_error("Invalid mesh index %d", id);
return nullptr;
}
int vertex_count = 0;
switch (mesh_type) {
case MeshType::kNormal:
if (id >= model->nmesh) {
mju_error("Invalid mesh index %d", id);
return nullptr;
}
vertex_count = 3 * model->mesh_facenum[id];
break;
case MeshType::kConvexHull:
if (id >= model->nmesh) {
mju_error("Invalid mesh index %d", id);
return nullptr;
}
vertex_count = 3 * model->mesh_graph[model->mesh_graphadr[id] + 1];
break;
case MeshType::kHeightField:
if (id >= model->nhfield) {
mju_error("Invalid height field index %d", id);
return nullptr;
}
vertex_count = CalculateHeightFieldVertexCount(model, id);
break;
}
if (vertex_count == 0) {
mju_error("Vertex count is zero.");
return nullptr;
}
const bool has_texcoords = mesh_type == MeshType::kHeightField
? false
: model->mesh_texcoordadr[id] >= 0;
if (has_texcoords) {
using VertexType = VertexWithUv;
switch (mesh_type) {
case MeshType::kNormal:
return CreateVertexBuffer<VertexType>(engine, model, id, vertex_count,
FillMeshBuffer<VertexType>);
break;
case MeshType::kConvexHull:
return CreateVertexBuffer<VertexType>(engine, model, id, vertex_count,
FillConvexHullBuffer<VertexType>);
break;
case MeshType::kHeightField:
mju_error("Height fields do not support UV coordinates.");
return nullptr;
}
} else {
using VertexType = VertexNoUv;
switch (mesh_type) {
case MeshType::kNormal:
return CreateVertexBuffer<VertexType>(engine, model, id, vertex_count,
FillMeshBuffer<VertexType>);
break;
case MeshType::kConvexHull:
return CreateVertexBuffer<VertexType>(engine, model, id, vertex_count,
FillConvexHullBuffer<VertexType>);
break;
case MeshType::kHeightField:
return CreateVertexBuffer<VertexType>(engine, model, id, vertex_count,
FillHeightFieldBuffer);
break;
}
}
}
filament::IndexBuffer* CreateIndexBuffer(filament::Engine* engine,
const mjModel* model, int id,
MeshType mesh_type) {
if (id < 0) {
mju_error("Invalid index %d", id);
return nullptr;
}
int index_count = 0;
switch (mesh_type) {
case MeshType::kNormal:
if (id >= model->nmesh) {
mju_error("Invalid mesh index %d", id);
return nullptr;
}
index_count = 3 * model->mesh_facenum[id];
break;
case MeshType::kConvexHull:
if (id >= model->nmesh) {
mju_error("Invalid mesh index %d", id);
return nullptr;
}
index_count = 3 * model->mesh_graph[model->mesh_graphadr[id] + 1];
break;
case MeshType::kHeightField:
if (id >= model->nhfield) {
mju_error("Invalid height field index %d", id);
return nullptr;
}
index_count = CalculateHeightFieldVertexCount(model, id);
break;
}
if (index_count == 0) {
mju_error("Index count is zero.");
return nullptr;
}
if (index_count >= std::numeric_limits<uint16_t>::max()) {
return CreateIndexBuffer<uint32_t>(engine, index_count,
FillSequence<uint32_t>);
} else {
return CreateIndexBuffer<uint16_t>(engine, index_count,
FillSequence<uint16_t>);
}
}
filament::Texture* CreateTexture(filament::Engine* engine, const mjModel* model,
int id, TextureType texture_type) {
if (id < 0 || id >= model->ntex) {
mju_error("Invalid texture index %d", id);
}
const int width = model->tex_width[id];
const int height = model->tex_height[id];
const bool is_srgb = model->tex_colorspace[id] == mjCOLORSPACE_SRGB;
const int num_channels = model->tex_nchannel[id];
const mjtByte* data = model->tex_data + model->tex_adr[id];
filament::Texture* texture =
texture_type == TextureType::kNormal2d
? Create2dTexture(engine, width, height, num_channels, data, is_srgb)
: CreateCubeTexture(engine, width, height, num_channels, data,
is_srgb);
// TODO: Revisit this to make it this work in WebGL
#ifndef __EMSCRIPTEN__
texture->generateMipmaps(*engine);
#endif
return texture;
}
} // namespace mujoco
@@ -0,0 +1,103 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_MODEL_UTIL_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MODEL_UTIL_H_
#include <string_view>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Texture.h>
#include <filament/VertexBuffer.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mujoco.h>
namespace mujoco {
// The types of meshes stored in the mjModel.
enum class MeshType {
kNormal,
kConvexHull,
kHeightField,
};
// The types of textures stored in the mjModel.
enum class TextureType {
kNormal2d,
kCube,
};
// Generates a filament VertexBuffer for a given mesh in the mjModel.
filament::VertexBuffer* CreateVertexBuffer(filament::Engine* engine,
const mjModel* model, int id,
MeshType mesh_type);
// Generates a filament IndexBuffer for a given mesh in the mjModel.
filament::IndexBuffer* CreateIndexBuffer(filament::Engine* engine,
const mjModel* model, int id,
MeshType mesh_type);
// Generates a filament Texture for a given 2D texture in the mjModel.
filament::Texture* CreateTexture(filament::Engine* engine, const mjModel* model,
int id, TextureType texture_type);
// 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_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_EXPERIMENTAL_FILAMENT_FILAMENT_MODEL_UTIL_H_
@@ -0,0 +1,400 @@
// 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 "experimental/filament/filament/object_manager.h"
#include <array>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <filament/Engine.h>
#include <filament/IndirectLight.h>
#include <filament/Material.h>
#include <filament/Skybox.h>
#include <math/mat3.h>
#include <math/scalar.h>
#include <math/vec3.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/buffer_util.h"
#include "experimental/filament/filament/builtins.h"
#include "experimental/filament/filament/model_util.h"
#include "experimental/filament/filament/texture_util.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
namespace {
// Loads binary data from a file using mjrFilamentConfig callbacks.
struct Asset {
Asset(const char* filename, const mjrFilamentConfig* config) {
const int error = config->load_asset(filename, config->load_asset_user_data,
&payload, &size);
if (error) {
mju_error("Failed to load file: %s (error: %d)", filename, error);
}
}
~Asset() {
if (payload) {
free(payload);
payload = nullptr;
}
}
uint64_t size = 0;
unsigned char* payload = nullptr;
Asset(const Asset&) = delete;
Asset& operator=(const Asset&) = delete;
};
} // namespace
ObjectManager::ObjectManager(const mjModel* model, filament::Engine* engine,
const mjrFilamentConfig* config)
: model_(model), engine_(engine), config_(config) {
shapes_[kBox] = CreateBox(engine_, model_);
shapes_[kCone] = CreateCone(engine_, model_);
shapes_[kDisk] = CreateDisk(engine_, model_);
shapes_[kDome] = CreateDome(engine_, model_);
shapes_[kTube] = CreateTube(engine_, model_);
shapes_[kPlane] = CreatePlane(engine_, model_);
shapes_[kSphere] = CreateSphere(engine_, model_);
auto LoadMaterial = [this](const char* filename) {
Asset asset(filename, config_);
filament::Material::Builder material_builder;
material_builder.package(asset.payload, asset.size);
return material_builder.build(*this->engine_);
};
materials_[kPbr] = LoadMaterial("pbr.filamat");
materials_[kPbrPacked] = LoadMaterial("pbr_packed.filamat");
materials_[kPhong2d] = LoadMaterial("phong_2d.filamat");
materials_[kPhong2dFade] = LoadMaterial("phong_2d_fade.filamat");
materials_[kPhong2dUv] = LoadMaterial("phong_2d_uv.filamat");
materials_[kPhong2dUvFade] = LoadMaterial("phong_2d_uv_fade.filamat");
materials_[kPhongColor] = LoadMaterial("phong_color.filamat");
materials_[kPhongColorFade] = LoadMaterial("phong_color_fade.filamat");
materials_[kPhongCube] = LoadMaterial("phong_cube.filamat");
materials_[kPhongCubeFade] = LoadMaterial("phong_cube_fade.filamat");
materials_[kUnlitSegmentation] = LoadMaterial("unlit_segmentation.filamat");
materials_[kUnlitDepth] = LoadMaterial("unlit_depth.filamat");
materials_[kUnlitUi] = LoadMaterial("unlit_ui.filamat");
for (int i = 0; i < model_->ntex; ++i) {
UploadTexture(model_, i);
}
for (int i = 0; i < model_->nmesh; ++i) {
UploadMesh(model_, i);
}
for (int i = 0; i < model_->nhfield; ++i) {
UploadHeightField(model_, i);
}
static uint8_t black_rgb[3] = {0, 0, 0};
fallback_black_ = Create2dTexture(engine_, 1, 1, 3, black_rgb, false);
static uint8_t white_rgb[3] = {255, 255, 255};
fallback_white_ = Create2dTexture(engine_, 1, 1, 3, white_rgb, false);
static uint8_t normal_data[3] = {128, 128, 255};
fallback_normal_ = Create2dTexture(engine_, 1, 1, 3, normal_data, false);
static uint8_t orm_data[3] = {0, 255, 0};
fallback_orm_ = Create2dTexture(engine_, 1, 1, 3, orm_data, false);
fallback_textures_[mjTEXROLE_USER] = fallback_black_;
fallback_textures_[mjTEXROLE_RGB] = fallback_black_;
fallback_textures_[mjTEXROLE_OCCLUSION] = fallback_black_;
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_;
fallback_indirect_light_ = LoadFallbackIndirectLight("ibl.ktx", 1.0f);
}
ObjectManager::~ObjectManager() {
for (auto& iter : skyboxes_) {
engine_->destroy(iter);
}
for (auto& iter : indirect_lights_) {
engine_->destroy(iter);
}
for (auto& iter : materials_) {
engine_->destroy(iter);
}
for (auto& iter : meshes_) {
engine_->destroy(iter.second.vertex_buffer);
engine_->destroy(iter.second.index_buffer);
}
for (auto& iter : shapes_) {
engine_->destroy(iter.vertex_buffer);
engine_->destroy(iter.index_buffer);
}
for (auto& iter : textures_) {
engine_->destroy(iter.second);
}
// fallback_textures_ maps to these textures.
engine_->destroy(fallback_white_);
engine_->destroy(fallback_black_);
engine_->destroy(fallback_normal_);
engine_->destroy(fallback_orm_);
for (auto& iter : fonts_) {
engine_->destroy(iter.second);
}
}
void ObjectManager::UploadMesh(const mjModel* model, int id) {
if (model != model_) {
mju_error("Model mismatch.");
}
if (id < 0 || id >= model->nmesh) {
mju_error("Invalid mesh index %d", id);
}
if (auto iter = meshes_.find(id); iter != meshes_.end()) {
engine_->destroy(iter->second.vertex_buffer);
engine_->destroy(iter->second.index_buffer);
}
if (auto iter = convex_hulls_.find(id); iter != convex_hulls_.end()) {
engine_->destroy(iter->second.vertex_buffer);
engine_->destroy(iter->second.index_buffer);
}
FilamentBuffers& buffers = meshes_[id];
buffers.vertex_buffer =
CreateVertexBuffer(engine_, model, id, MeshType::kNormal);
buffers.index_buffer =
CreateIndexBuffer(engine_, model, id, MeshType::kNormal);
if (model->mesh_graphadr[id] >= 0) {
FilamentBuffers& hull_buffers = convex_hulls_[id];
hull_buffers.vertex_buffer =
CreateVertexBuffer(engine_, model, id, MeshType::kConvexHull);
hull_buffers.index_buffer =
CreateIndexBuffer(engine_, model, id, MeshType::kConvexHull);
}
}
void ObjectManager::UploadTexture(const mjModel* model, int id) {
if (model != model_) {
mju_error("Model mismatch.");
}
if (id < 0 || id >= model->ntex) {
mju_error("Invalid texture index: %d", id);
}
if (auto iter = textures_.find(id); iter != textures_.end()) {
engine_->destroy(iter->second);
}
const int texture_type = model->tex_type[id];
if (model->tex_height[id] == 1) {
const mjtByte* bytes = model->tex_data + model->tex_adr[id];
const int num_bytes = model->tex_width[id];
textures_[id] =
CreateKtxTexture(engine_, bytes, num_bytes, spherical_harmonics_[id]);
} else if (texture_type == mjTEXTURE_2D) {
textures_[id] = CreateTexture(engine_, model, id, TextureType::kNormal2d);
} else if (texture_type == mjTEXTURE_CUBE) {
textures_[id] = CreateTexture(engine_, model, id, TextureType::kCube);
} else if (texture_type == mjTEXTURE_SKYBOX) {
textures_[id] = CreateTexture(engine_, model, id, TextureType::kCube);
} else {
mju_error("Unsupported: Texture type: %d", texture_type);
}
}
void ObjectManager::UploadHeightField(const mjModel* model, int id) {
if (model != model_) {
mju_error("Model mismatch.");
}
if (id < 0 || id >= model->nhfield) {
mju_error("Invalid height field index %d", id);
}
if (auto iter = height_fields_.find(id); iter != height_fields_.end()) {
engine_->destroy(iter->second.vertex_buffer);
engine_->destroy(iter->second.index_buffer);
}
FilamentBuffers& buffers = height_fields_[id];
buffers.vertex_buffer =
CreateVertexBuffer(engine_, model, id, MeshType::kHeightField);
buffers.index_buffer =
CreateIndexBuffer(engine_, model, id, MeshType::kHeightField);
}
void ObjectManager::UploadFont(const uint8_t* pixels, int width, int height,
int id) {
if (auto iter = fonts_.find(id); iter != fonts_.end()) {
engine_->destroy(iter->second);
}
fonts_[id] = Create2dTexture(engine_, width, height, 4, pixels, false);
}
filament::Material* ObjectManager::GetMaterial(MaterialType type) const {
if (type < 0 || type >= kNumMaterials) {
mju_error("Invalid material type: %d", type);
}
return materials_[type];
}
const FilamentBuffers* ObjectManager::GetMeshBuffer(int data_id) const {
// As defined by mjv_updateScene:
// original mesh: mesh_id * 2
// convex hull: (mesh_id * 2) + 1
const int mesh_id = data_id / 2;
if (data_id % 2 == 0) {
auto it = meshes_.find(mesh_id);
return it != meshes_.end() ? &it->second : nullptr;
} else {
auto it = convex_hulls_.find(mesh_id);
return it != convex_hulls_.end() ? &it->second : nullptr;
}
}
const FilamentBuffers* ObjectManager::GetHeightFieldBuffer(
int hfield_id) const {
auto it = height_fields_.find(hfield_id);
return it != height_fields_.end() ? &it->second : nullptr;
}
const FilamentBuffers* ObjectManager::GetShapeBuffer(ShapeType shape) const {
if (shape < 0 || shape >= kNumShapes) {
mju_error("Invalid shape type: %d", shape);
}
return &shapes_[shape];
}
const filament::Texture* ObjectManager::GetFont(int font_id) const {
auto it = fonts_.find(font_id);
return it != fonts_.end() ? it->second : nullptr;
}
const filament::Texture* ObjectManager::GetTexture(int tex_id) const {
auto it = textures_.find(tex_id);
return it != textures_.end() ? it->second : nullptr;
}
const filament::Texture* ObjectManager::GetTexture(int mat_id, int role) const {
if (mat_id < 0 || mat_id >= model_->nmat || role < 0 || role >= mjNTEXROLE) {
return nullptr;
}
const int tex_id = model_->mat_texid[mat_id * mjNTEXROLE + role];
return GetTexture(tex_id);
}
const filament::Texture* ObjectManager::GetTextureWithFallback(int mat_id,
int role) const {
if (auto texture = GetTexture(mat_id, role)) {
return texture;
}
return GetFallbackTexture(role);
}
const filament::Texture* ObjectManager::GetFallbackTexture(int role) const {
auto iter = fallback_textures_.find(role);
if (iter != fallback_textures_.end()) {
return iter->second;
}
return nullptr;
}
filament::IndirectLight* ObjectManager::GetFallbackIndirectLight() {
return fallback_indirect_light_;
}
filament::IndirectLight* ObjectManager::CreateIndirectLight(int tex_id,
float intensity) {
filament::Texture* texture = nullptr;
auto texture_iter = textures_.find(tex_id);
if (texture_iter != textures_.end()) {
texture = texture_iter->second;
}
if (texture == nullptr) {
return nullptr;
}
SphericalHarmonics* spherical_harmonics = nullptr;
auto sh_iter = spherical_harmonics_.find(tex_id);
if (sh_iter != spherical_harmonics_.end()) {
spherical_harmonics = &sh_iter->second;
}
return CreateIndirectLight(texture, spherical_harmonics, intensity);
}
filament::IndirectLight* ObjectManager::LoadFallbackIndirectLight(
std::string_view filename, float intensity) {
Asset asset(std::string(filename).c_str(), config_);
if (asset.size == 0) {
return nullptr;
}
filament::math::float3 spherical_harmonics[9];
filament::Texture* tex =
CreateKtxTexture(engine_, asset.payload, asset.size, spherical_harmonics);
// TODO: Revisit this to make it this work in WebGL
#ifndef __EMSCRIPTEN__
tex->generateMipmaps(*engine_);
#endif
return CreateIndirectLight(tex, &spherical_harmonics, intensity);
}
filament::IndirectLight* ObjectManager::CreateIndirectLight(
filament::Texture* texture, SphericalHarmonics* spherical_harmonics,
float intensity) {
filament::IndirectLight::Builder builder;
builder.reflections(texture);
if (spherical_harmonics != nullptr) {
builder.irradiance(3, *spherical_harmonics);
}
builder.intensity(intensity);
// Rotate the light to match mujoco's Z-up convention.
builder.rotation(filament::math::mat3f::rotation(
filament::math::f::PI / 2, filament::math::float3{1, 0, 0}));
filament::IndirectLight* indirect_light = builder.build(*engine_);
indirect_lights_.push_back(indirect_light);
return indirect_light;
}
filament::Skybox* ObjectManager::CreateSkybox() {
filament::Texture* skybox_texture = nullptr;
for (auto& iter : textures_) {
const int texture_type = model_->tex_type[iter.first];
if (texture_type == mjTEXTURE_SKYBOX) {
skybox_texture = iter.second;
break;
}
}
if (skybox_texture == nullptr) {
return nullptr;
}
filament::Skybox::Builder builder;
builder.environment(skybox_texture);
filament::Skybox* skybox = builder.build(*engine_);
skyboxes_.push_back(skybox);
return skybox;
}
} // namespace mujoco
@@ -0,0 +1,136 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_OBJECT_MANAGER_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_OBJECT_MANAGER_H_
#include <array>
#include <cstdint>
#include <string_view>
#include <unordered_map>
#include <vector>
#include <filament/Engine.h>
#include <filament/IndirectLight.h>
#include <filament/Skybox.h>
#include <math/vec3.h>
#include <mujoco/mjmodel.h>
#include "experimental/filament/filament/buffer_util.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
// Creates and owns various filament objects based on the data in a mjrContext.
class ObjectManager {
public:
ObjectManager(const mjModel* model, filament::Engine* engine,
const mjrFilamentConfig* config);
~ObjectManager();
enum MaterialType {
kPbr,
kPbrPacked,
kPhong2d,
kPhong2dFade,
kPhong2dUv,
kPhong2dUvFade,
kPhongColor,
kPhongColorFade,
kPhongCube,
kPhongCubeFade,
kUnlitSegmentation,
kUnlitDepth,
kUnlitUi,
kNumMaterials,
};
enum ShapeType {
kPlane,
kBox,
kSphere,
kCone,
kDisk,
kDome,
kTube,
kNumShapes,
};
using SphericalHarmonics = filament::math::float3[9];
void UploadMesh(const mjModel* model, int id);
void UploadTexture(const mjModel* model, int id);
void UploadHeightField(const mjModel* model, int id);
void UploadFont(const uint8_t* pixels, int width, int height, int id);
// Returns the filament engine used by the ObjectManager to create filament
// objects.
filament::Engine* GetEngine() const { return engine_; }
filament::Material* GetMaterial(MaterialType type) const;
// Returns the cached instance of a filament object created from the mjModel.
const FilamentBuffers* GetMeshBuffer(int data_id) const;
const FilamentBuffers* GetShapeBuffer(ShapeType shape) const;
const FilamentBuffers* GetHeightFieldBuffer(int hfield_id) const;
const filament::Texture* GetFont(int font_id) const;
const filament::Texture* GetTexture(int tex_id) const;
const filament::Texture* GetTexture(int mat_id, int role) const;
const filament::Texture* GetTextureWithFallback(int mat_id, int role) const;
const filament::Texture* GetFallbackTexture(int role) const;
filament::IndirectLight* GetFallbackIndirectLight();
// Creates and returns a new instance of a filament object. The objects are
// owned by the ObjectManager and will be deleted in the destructor.
filament::Skybox* CreateSkybox();
filament::IndirectLight* CreateIndirectLight(
filament::Texture* texture, SphericalHarmonics* spherical_harmonics,
float intensity);
filament::IndirectLight* CreateIndirectLight(int tex_id, float intensity);
filament::IndirectLight* LoadFallbackIndirectLight(std::string_view filename,
float intensity);
const mjModel* GetModel() const { return model_; }
ObjectManager(const ObjectManager&) = delete;
ObjectManager& operator=(const ObjectManager&) = delete;
private:
const mjModel* model_ = nullptr;
filament::Engine* engine_ = nullptr;
const mjrFilamentConfig* config_;
std::array<FilamentBuffers, kNumShapes> shapes_;
std::array<filament::Material*, kNumMaterials> materials_;
std::vector<filament::Skybox*> skyboxes_;
std::vector<filament::IndirectLight*> indirect_lights_;
std::unordered_map<int, FilamentBuffers> meshes_;
std::unordered_map<int, FilamentBuffers> convex_hulls_;
std::unordered_map<int, FilamentBuffers> height_fields_;
std::unordered_map<int, filament::Texture*> fonts_;
std::unordered_map<int, filament::Texture*> textures_;
std::unordered_map<int, SphericalHarmonics> spherical_harmonics_;
std::unordered_map<int, filament::Texture*> fallback_textures_;
filament::Texture* fallback_white_ = nullptr;
filament::Texture* fallback_black_ = nullptr;
filament::Texture* fallback_normal_ = nullptr;
filament::Texture* fallback_orm_ = nullptr;
filament::IndirectLight* fallback_indirect_light_ = nullptr;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_OBJECT_MANAGER_H_
@@ -0,0 +1,201 @@
// 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 "experimental/filament/filament/renderables.h"
#include <optional>
#include <filament/Engine.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <utils/EntityManager.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/buffer_util.h"
namespace mujoco {
Renderables::Renderables(filament::Engine* engine) : engine_(engine) {}
Renderables::~Renderables() {
while (!entities_.empty()) {
RemoveLast();
}
}
void Renderables::RemoveLast() {
if (entities_.empty()) {
return;
}
utils::EntityManager& em = utils::EntityManager::get();
utils::Entity entity = entities_.back();
if (assigned_scene_) {
assigned_scene_->remove(entity);
}
engine_->destroy(entity);
em.destroy(entity);
entities_.pop_back();
UpdateBuffers(owned_buffers_.size() - 1, std::nullopt);
owned_buffers_.pop_back();
}
void Renderables::Update(int index, const FilamentBuffers& buffers) {
if (index < 0 || index >= entities_.size()) {
mju_error("Invalid index %d for renderable.", index);
}
utils::Entity& entity = entities_[index];
UpdateEntity(entity, buffers);
UpdateBuffers(index, std::nullopt);
}
void Renderables::Update(int index, FilamentBuffers&& buffers) {
if (index < 0 || index >= entities_.size()) {
mju_error("Invalid index %d for renderable.", index);
}
utils::Entity& entity = entities_[index];
UpdateEntity(entity, buffers);
UpdateBuffers(index, buffers);
}
void Renderables::Append(const FilamentBuffers& buffers) {
utils::Entity entity = CreateEntity(buffers);
entities_.push_back(entity);
owned_buffers_.push_back(std::nullopt);
}
void Renderables::Append(FilamentBuffers&& buffers) {
utils::Entity entity = CreateEntity(buffers);
entities_.push_back(entity);
owned_buffers_.push_back(buffers);
}
utils::Entity Renderables::CreateEntity(const FilamentBuffers& buffers) {
if (buffers.vertex_buffer == nullptr) {
mju_error("Invalid (null) vertex buffer.");
}
if (buffers.index_buffer == nullptr) {
mju_error("Invalid (null) index buffer.");
}
utils::Entity entity = utils::EntityManager::get().create();
if (entity.isNull()) {
mju_error("Failed to create entity.");
}
filament::RenderableManager::Builder builder(1);
builder.geometry(0, filament::RenderableManager::PrimitiveType::TRIANGLES,
buffers.vertex_buffer, buffers.index_buffer);
if (material_instance_) {
builder.material(0, material_instance_);
}
builder.boundingBox(buffers.bounds)
.culling(false)
.castShadows(true)
.receiveShadows(true)
.layerMask(1, visible_ ? 1 : 0)
.screenSpaceContactShadows(true);
builder.build(*engine_, entity);
if (assigned_scene_) {
assigned_scene_->addEntity(entity);
}
return entity;
}
void Renderables::UpdateEntity(utils::Entity entity,
const FilamentBuffers& buffers) {
if (buffers.vertex_buffer == nullptr) {
mju_error("Invalid (null) vertex buffer.");
}
if (buffers.index_buffer == nullptr) {
mju_error("Invalid (null) index buffer.");
}
filament::RenderableManager& rm = engine_->getRenderableManager();
rm.setGeometryAt(rm.getInstance(entity), 0,
filament::RenderableManager::PrimitiveType::TRIANGLES,
buffers.vertex_buffer, buffers.index_buffer, 0,
buffers.index_buffer->getIndexCount());
}
void Renderables::UpdateBuffers(int index,
std::optional<FilamentBuffers> buffers) {
if (index < 0 || index >= owned_buffers_.size()) {
mju_error("Invalid index %d for renderable.", index);
}
if (owned_buffers_[index].has_value()) {
engine_->destroy(owned_buffers_[index]->vertex_buffer);
engine_->destroy(owned_buffers_[index]->index_buffer);
}
owned_buffers_[index] = buffers;
}
void Renderables::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 (utils::Entity& entity : entities_) {
scene->addEntity(entity);
}
assigned_scene_ = scene;
}
void Renderables::RemoveFromScene(filament::Scene* scene) {
if (assigned_scene_ != scene) {
mju_error("Attempting to remove renderable from wrong scene.");
}
for (utils::Entity& entity : entities_) {
scene->remove(entity);
}
assigned_scene_ = nullptr;
}
void Renderables::SetMaterialInstance(
filament::MaterialInstance* instance) {
if (instance != material_instance_) {
filament::RenderableManager& rm = engine_->getRenderableManager();
for (utils::Entity& entity : entities_) {
filament::RenderableManager::Instance ri = rm.getInstance(entity);
rm.setMaterialInstanceAt(ri, 0, instance);
}
material_instance_ = instance;
}
}
void Renderables::Hide() {
if (visible_) {
filament::RenderableManager& rm = engine_->getRenderableManager();
for (utils::Entity& entity : entities_) {
rm.setLayerMask(rm.getInstance(entity), 1, 0);
}
visible_ = false;
}
}
void Renderables::Show() {
if (!visible_) {
filament::RenderableManager& rm = engine_->getRenderableManager();
for (utils::Entity& entity : entities_) {
rm.setLayerMask(rm.getInstance(entity), 1, 1);
}
visible_ = true;
}
}
} // namespace mujoco
@@ -0,0 +1,91 @@
// 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_EXPERIMENTAL_FILAMENT_FILAMENT_RENDERABLES_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_RENDERABLES_H_
#include <optional>
#include <span>
#include <vector>
#include <filament/Engine.h>
#include <filament/Scene.h>
#include <utils/Entity.h>
#include "experimental/filament/filament/buffer_util.h"
namespace mujoco {
// Manages a collection of related filament Renderable Entities.
class Renderables {
public:
Renderables(filament::Engine* engine);
~Renderables() noexcept;
Renderables(const Renderables&) = delete;
Renderables& operator=(const Renderables&) = delete;
// Appends a new renderable entity built from the given buffers.
void Append(const FilamentBuffers& buffers);
void Append(FilamentBuffers&& buffers);
// Updates the entity at the index with new buffers.
void Update(int index, const FilamentBuffers& buffers);
void Update(int index, FilamentBuffers&& buffers);
// Removes the last entity.
void RemoveLast();
// Returns the entity at the given index.
utils::Entity operator[](int index) { return entities_[index]; }
// Returns the owned buffers at the given index.
int GetNumEntities() const { return entities_.size(); }
// Hides all managed entities.
void Hide();
// Shows all managed entities.
void Show();
// Returns true if the entities are visible.
bool IsVisible() const { return visible_; }
// Adds all managed entities to the given filament Scene.
void AddToScene(filament::Scene* scene);
// Removes all managed entities from the given filament Scene.
void RemoveFromScene(filament::Scene* scene);
// Sets the material instance for all managed entities.
void SetMaterialInstance(filament::MaterialInstance* material_instance);
// Returns the filament Engine managing the entities in this collection.
filament::Engine* GetEngine() { return engine_; }
private:
utils::Entity CreateEntity(const FilamentBuffers& buffers);
void UpdateEntity(utils::Entity entity, const FilamentBuffers& buffers);
void UpdateBuffers(int index, std::optional<FilamentBuffers> buffers);
filament::Engine* engine_ = nullptr;
filament::Scene* assigned_scene_ = nullptr;
filament::MaterialInstance* material_instance_ = nullptr;
std::vector<utils::Entity> entities_;
std::vector<std::optional<FilamentBuffers>> owned_buffers_;
bool visible_ = true;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_RENDERABLES_H_
@@ -0,0 +1,399 @@
// 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 "experimental/filament/filament/scene_view.h"
#include <array>
#include <memory>
#include <optional>
#include <string_view>
#include <utility>
#include <filament/ColorGrading.h>
#include <filament/IndirectLight.h>
#include <filament/LightManager.h>
#include <filament/Material.h>
#include <filament/Options.h>
#include <filament/RenderableManager.h>
#include <filament/Skybox.h>
#include <filament/TransformManager.h>
#include <filament/View.h>
#include <filament/Viewport.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 "experimental/filament/filament/color_grading_options.h"
#include "experimental/filament/filament/drawable.h"
#include "experimental/filament/filament/gui_view.h"
#include "experimental/filament/filament/light.h"
#include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/model_util.h"
#include "experimental/filament/filament/object_manager.h"
namespace mujoco {
using filament::math::float3;
using filament::math::float4;
using filament::math::mat4;
static constexpr int kNormalIndex =
static_cast<int>(SceneView::DrawMode::kNormal);
static constexpr int kDepthIndex =
static_cast<int>(SceneView::DrawMode::kDepth);
static constexpr int kSegmentIndex =
static_cast<int>(SceneView::DrawMode::kSegmentation);
static filament::Viewport ReadViewport(mjrRect rect) {
return filament::Viewport(rect.left, rect.bottom, rect.width, rect.height);
}
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);
}
SceneView::SceneView(filament::Engine* engine, ObjectManager* object_mgr)
: object_mgr_(object_mgr), engine_(engine) {
scene_ = engine_->createScene();
camera_ = engine_->createCamera(utils::EntityManager::get().create());
for (auto& view : views_) {
view = engine_->createView();
view->setScene(scene_);
view->setCamera(camera_);
}
const mjModel* m = object_mgr_->GetModel();
// Configure options for the normal view.
auto& cg = color_grading_options_;
cg.exposure = ReadElement(m, "filament.out.exposure", cg.exposure);
cg.contrast = ReadElement(m, "filament.out.contrast", cg.contrast);
cg.vibrance = ReadElement(m, "filament.out.vibrance", cg.vibrance);
cg.saturation = ReadElement(m, "filament.out.saturation", cg.saturation);
cg.temperature = ReadElement(m, "filament.out.temperature", cg.temperature);
cg.tint = ReadElement(m, "filament.out.tint", cg.tint);
auto tone_mapping =
ReadElement<std::string_view>(m, "filament.out.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 = views_[kNormalIndex]->getAmbientOcclusionOptions();
ao.enabled = ReadElement(m, "filament.ao.enabled", true);
ao.bentNormals = ReadElement(m, "filament.ao.bent_normals", true);
ao.ssct.enabled = ReadElement(m, "filament.ao.ssct", ao.ssct.enabled);
ao.quality = filament::QualityLevel::ULTRA;
ao.lowPassFilter = filament::QualityLevel::ULTRA;
ao.upsampling = filament::QualityLevel::ULTRA;
ao.bilateralThreshold = 0.5f;
views_[kNormalIndex]->setAmbientOcclusionOptions(ao);
auto msaa = views_[kNormalIndex]->getMultiSampleAntiAliasingOptions();
msaa.enabled = ReadElement(m, "filament.msaa.enabled", true);
views_[kNormalIndex]->setMultiSampleAntiAliasingOptions(msaa);
// Disable post processing for the depth and segmentation views to preserve
// the values.
views_[kDepthIndex]->setPostProcessingEnabled(false);
views_[kSegmentIndex]->setPostProcessingEnabled(false);
// Rotate the fog to align with mujoco's +Z up space.
auto fog = views_[kNormalIndex]->getFogEntity();
auto& tm = engine->getTransformManager();
tm.create(fog);
auto rotation_axis = ReadElement(
m, "filament.fog.rotation_axis", float3{-1, 0, 0});
tm.setTransform(tm.getInstance(fog),
mat4::rotation(filament::math::f::PI / 2, rotation_axis));
auto fog_opts = views_[kNormalIndex]->getFogOptions();
fog_opts.enabled = ReadElement(m, "filament.fog.enabled", fog_opts.enabled);
fog_opts.color = ReadElement(m, "filament.fog.color", fog_opts.color);
fog_opts.distance = ReadElement(
m, "filament.fog.distance", fog_opts.distance);
fog_opts.density = ReadElement(
m, "filament.fog.density", fog_opts.density);
fog_opts.cutOffDistance = ReadElement(
m, "filament.fog.cutOffDistance", fog_opts.cutOffDistance);
fog_opts.maximumOpacity = ReadElement(
m, "filament.fog.maximumOpacity", fog_opts.maximumOpacity);
fog_opts.height = ReadElement(m, "filament.fog.height", fog_opts.height);
fog_opts.heightFalloff = ReadElement(
m, "filament.fog.heightFalloff", fog_opts.heightFalloff);
fog_opts.inScatteringStart = ReadElement(
m, "filament.fog.inScatteringStart", fog_opts.inScatteringStart);
fog_opts.inScatteringSize = ReadElement(
m, "filament.fog.inScatteringSize", fog_opts.inScatteringSize);
views_[kNormalIndex]->setFogOptions(fog_opts);
// Create an empty/black indirect light to ensure that the skybox is oriented
// to respect mujoco's Z-up convention.
scene_->setIndirectLight(
object_mgr_->CreateIndirectLight(nullptr, nullptr, 100000));
PrepareLights();
}
SceneView::~SceneView() {
lights_.clear();
drawables_.clear();
engine_->destroyCameraComponent(camera_->getEntity());
engine_->destroy(views_[kNormalIndex]->getColorGrading());
for (auto& view : views_) {
engine_->destroy(view);
}
engine_->destroy(scene_);
}
filament::View* SceneView::PrepareRenderView(DrawMode mode) {
for (auto& iter : drawables_) {
iter->SetDrawMode(mode);
}
return views_[static_cast<int>(mode)];
}
void SceneView::SetViewport(mjrRect viewport) {
aspect_ratio_ = (float)viewport.width / (float)viewport.height;
for (auto& view : views_) {
view->setViewport(ReadViewport(viewport));
}
}
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(*engine_);
views_[kNormalIndex]->setColorGrading(color_grading);
engine_->destroy(color_grading_);
color_grading_ = color_grading;
color_grading_options_ = opts;
}
void SceneView::SetEnvironmentLight(std::string_view filename,
float intensity) {
auto* ibl = object_mgr_->LoadFallbackIndirectLight(filename, intensity);
if (ibl) {
scene_->setIndirectLight(ibl);
}
}
void SceneView::SetFallbackEnvironmentLight(float intensity) {
auto* ibl = object_mgr_->GetFallbackIndirectLight();
if (ibl) {
ibl->setIntensity(intensity);
scene_->setIndirectLight(ibl);
}
}
void SceneView::UpdateCamera(const mjvGLCamera* cameras) {
const mjvGLCamera cam = mjv_averageCamera(cameras, cameras + 1);
const filament::Camera::Projection type =
cam.orthographic ? filament::Camera::Projection::ORTHO
: filament::Camera::Projection::PERSPECTIVE;
float3 cam_pos(cam.pos[0], cam.pos[1], cam.pos[2]);
float3 cam_fwd(cam.forward[0], cam.forward[1], cam.forward[2]);
float3 cam_up(cam.up[0], cam.up[1], cam.up[2]);
float3 cam_at = cam_pos + cam_fwd;
camera_->lookAt(cam_pos, cam_at, cam_up);
float halfwidth = cam.frustum_width ? cam.frustum_width
: 0.5f * aspect_ratio_ * (cam.frustum_top - cam.frustum_bottom);
camera_->setProjection(type, cam.frustum_center - halfwidth,
cam.frustum_center + halfwidth, cam.frustum_bottom,
cam.frustum_top, cam.frustum_near, cam.frustum_far);
clip_from_world_ = camera_->getProjectionMatrix() * camera_->getViewMatrix();
}
std::optional<float3> SceneView::ClipFromWorld(const float3& pos) const{
const float4 clip_pos = clip_from_world_ * float4(pos, 1.0f);
if (clip_pos.w == 0.0f) {
return std::nullopt;
}
return clip_pos.xyz / clip_pos.w;
}
void SceneView::PrepareLights() {
const mjModel* model = object_mgr_->GetModel();
filament::Skybox* skybox = object_mgr_->CreateSkybox();
if (skybox) {
scene_->setSkybox(skybox);
}
float total_light_intensity = 0.0f;
for (int i = 0; i < model->nlight; ++i) {
total_light_intensity += model->light_intensity[i];
if (model->light_type[i] == mjLIGHT_IMAGE) {
auto* indirect_light = object_mgr_->CreateIndirectLight(
model->light_texid[i], model->light_intensity[i]);
if (indirect_light) {
scene_->setIndirectLight(indirect_light);
}
// Add an nullptr as a placeholder so that our indices still match.
lights_.emplace_back(nullptr);
} else {
Light::Params params;
params.color = ReadFloat3(model->light_diffuse);
params.type = (mjtLightType)model->light_type[i];
params.castshadow = model->light_castshadow[i];
params.bulbradius = model->light_bulbradius[i];
params.range = model->light_range[i];
params.intensity = model->light_intensity[i];
if (params.type == mjLIGHT_SPOT) {
params.spot_cone_angle = model->light_cutoff[i];
}
auto light_obj = std::make_unique<Light>(object_mgr_, params);
light_obj->AddToScene(scene_);
lights_.emplace_back(std::move(light_obj));
}
}
// Add a placeholder (black) headlight as our last light. Going forward, we'll
// assume lights_.back() is always the headlight.
{
Light::Params params;
params.color = float3(0, 0, 0);
params.headlight = true;
params.type = mjLIGHT_DIRECTIONAL;
params.castshadow = 0;
params.intensity = 0;
auto light_obj = std::make_unique<Light>(object_mgr_, params);
light_obj->AddToScene(scene_);
lights_.emplace_back(std::move(light_obj));
}
// There are no "physical" lights in the scene which means we're likely
// dealing with a "classic renderer" scene. In this case, let's add a
// default environment light and set the light intensity ourselves.
if (total_light_intensity == 0.0f) {
constexpr float kHeadlightIntensityCandela = 10'000.f;
constexpr float kTotalSceneLightIntensityCandela = 100'000.f;
constexpr float kFallbackEnvironmentLightIntensityCandela = 10'000.f;
SetFallbackEnvironmentLight(kFallbackEnvironmentLightIntensityCandela);
const float intensity = kTotalSceneLightIntensityCandela / lights_.size();
for (auto& light : lights_) {
light->SetIntensity(light->IsHeadlight() ? kHeadlightIntensityCandela
: intensity);
}
}
}
void SceneView::UpdateScene(const mjrContext* context, const mjvScene* scene) {
mjtNum hpos[3], hfwd[3];
float headpos[3], gazedir[3];
mjv_cameraInModel(hpos, hfwd, nullptr, scene);
mju_n2f(headpos, hpos, 3);
mju_n2f(gazedir, hfwd, 3);
UpdateCamera(scene->camera);
// Remove all drawables from previous render and prepare new ones.
for (auto& iter : drawables_) {
iter->RemoveFromScene(scene_);
}
drawables_.clear();
for (int i = 0; i < scene->ngeom; ++i) {
const mjvGeom* geom = scene->geoms + i;
const mjtGeom geom_type = static_cast<mjtGeom>(geom->type);
if (geom_type == mjGEOM_LABEL) {
if (geom->label[0] == 0) {
continue;
}
if (auto pos = ClipFromWorld(ReadFloat3(geom->pos))) {
DrawTextAt(geom->label, pos->x, pos->y, pos->z);
}
} else {
auto drawable = std::make_unique<Drawable>(object_mgr_, *geom);
drawable->AddToScene(scene_);
drawable->Update(object_mgr_->GetModel(), scene, *geom);
drawables_.push_back(std::move(drawable));
}
}
bool headlight_enabled = false;
for (int i = 0; i < scene->nlight; ++i) {
const mjvLight& scene_light = scene->lights[i];
if (scene_light.id < 0 && scene_light.headlight) {
// We position the headlight slightly behind the camera to avoid some
// odd clipping issues.
headlight_enabled = true;
headpos[0] -= gazedir[0] * 0.05f;
headpos[1] -= gazedir[1] * 0.05f;
headpos[2] -= gazedir[2] * 0.05f;
// The headlight is always the "back" light.
std::unique_ptr<Light>& light = lights_.back();
light->SetColor(ReadFloat3(scene_light.diffuse));
light->SetTransform(ReadFloat3(headpos), ReadFloat3(gazedir));
continue;
} else if (scene_light.id < lights_.size() - 1) {
std::unique_ptr<Light>& light = lights_[scene_light.id];
light->SetColor(ReadFloat3(scene_light.diffuse));
light->SetTransform(ReadFloat3(scene_light.pos),
ReadFloat3(scene_light.dir));
} else {
mju_error("Unexpected light id: %d", scene_light.id);
}
}
// Enable/disable the headlight based on whether or not it's in the scene.
if (headlight_enabled) {
lights_.back()->Enable();
} else {
lights_.back()->Disable();
}
}
filament::Engine* SceneView::GetEngine() const { return engine_; }
filament::View* SceneView::GetDefaultRenderView() {
return views_[kNormalIndex];
}
ColorGradingOptions SceneView::GetColorGradingOptions() const {
return color_grading_options_;
}
} // 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_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_VIEW_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_VIEW_H_
#include <array>
#include <memory>
#include <optional>
#include <string_view>
#include <vector>
#include <filament/Camera.h>
#include <filament/ColorGrading.h>
#include <filament/Engine.h>
#include <filament/IndirectLight.h>
#include <filament/Scene.h>
#include <filament/View.h>
#include <math/mat4.h>
#include <math/vec3.h>
#include <mujoco/mjrender.h>
#include <mujoco/mjvisualize.h>
#include "experimental/filament/filament/color_grading_options.h"
#include "experimental/filament/filament/drawable.h"
#include "experimental/filament/filament/light.h"
#include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/object_manager.h"
namespace mujoco {
// Creates and owns filament Scene and View classes given a mjvScene.
//
// The filament Scene is populated with the objects (e.g. lights, geoms,
// cameras, etc.) defined by the mjvScene. Multiple Views are created to allow
// different rendering modes (e.g. normal, depth, segmentation, etc.)
class SceneView {
public:
SceneView(filament::Engine* engine, ObjectManager* object_mgr);
~SceneView();
// Updates all views to render into the given viewport.
void SetViewport(mjrRect viewport);
// Updates the color grading options for the main render view.
void SetColorGradingOptions(const ColorGradingOptions& opts);
// Updates the environment light using the KTX image at the given path.
void SetEnvironmentLight(std::string_view filename, float intensity);
// Updates the environment light to the fallback light
void SetFallbackEnvironmentLight(float intensity);
// Updates the Entities in the filament Scene to match the current mjvScene
// state.
void UpdateScene(const mjrContext* context, const mjvScene* scene);
using DrawMode = Material::DrawMode;
// Prepares and returns the filament View for the given draw mode.
filament::View* PrepareRenderView(DrawMode mode);
// Accessors.
filament::Engine* GetEngine() const;
filament::View* GetDefaultRenderView();
ColorGradingOptions GetColorGradingOptions() const;
SceneView(const SceneView&) = delete;
SceneView& operator=(const SceneView&) = delete;
private:
void UpdateCamera(const mjvGLCamera* cameras);
void PrepareLights();
// Converts a point in world space to clip space, eg. in the range [-1,-1, 0]
// to [1, 1, 1]. Returns std::nullopt if the point is behind the camera.
std::optional<filament::math::float3> ClipFromWorld(
const filament::math::float3& pos) const;
ObjectManager* object_mgr_ = nullptr;
filament::Engine* engine_ = nullptr;
filament::Scene* scene_ = nullptr;
filament::Camera* camera_ = nullptr;
filament::ColorGrading* color_grading_ = nullptr;
std::vector<std::unique_ptr<Light>> lights_;
std::vector<std::unique_ptr<Drawable>> drawables_;
std::array<filament::View*, DrawMode::kNumDrawModes> views_;
filament::math::mat4 clip_from_world_;
ColorGradingOptions color_grading_options_;
DrawMode active_mode_ = DrawMode::kNumDrawModes;
float aspect_ratio_ = 1.0f;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_VIEW_H_
@@ -0,0 +1,218 @@
// 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 "experimental/filament/filament/texture_util.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 <math/vec3.h>
#include <mujoco/mujoco.h>
namespace mujoco {
static filament::Texture::Format GetTextureFormat(int num_channels) {
switch (num_channels) {
case 1:
return filament::Texture::Format::R;
case 3:
return filament::Texture::Format::RGB;
case 4:
return filament::Texture::Format::RGBA;
default:
mju_error("Unsupported number of channels: %d", num_channels);
return filament::Texture::Format::UNUSED;
}
}
static filament::Texture::InternalFormat GetTextureInternalFormat(
int num_channels, bool is_srgb) {
if (is_srgb) {
switch (num_channels) {
case 3:
return filament::Texture::InternalFormat::SRGB8;
case 4:
return filament::Texture::InternalFormat::SRGB8_A8;
default:
mju_error("Unsupported number of channels: %d", num_channels);
return filament::Texture::InternalFormat::UNUSED;
}
} else {
switch (num_channels) {
case 1:
return filament::Texture::InternalFormat::R8;
case 3:
return filament::Texture::InternalFormat::RGB8;
case 4:
return filament::Texture::InternalFormat::RGBA8;
default:
mju_error("Unsupported number of channels: %d", num_channels);
return filament::Texture::InternalFormat::UNUSED;
}
}
}
filament::Texture* Create2dTexture(filament::Engine* engine, int width,
int height, int num_channels,
const uint8_t* data, bool is_srgb) {
if (num_channels != 1 && num_channels != 3 && num_channels != 4) {
mju_error("Unsupported number of channels: %d", num_channels);
return nullptr;
}
filament::Texture::Builder builder;
builder.width(width);
builder.height(height);
builder.format(GetTextureInternalFormat(num_channels, is_srgb));
builder.sampler(filament::Texture::Sampler::SAMPLER_2D);
// TODO: Revisit this to make it this work in WebGL
#ifndef __EMSCRIPTEN__
if (!is_srgb) {
builder.usage(filament::Texture::Usage::GEN_MIPMAPPABLE |
filament::Texture::Usage::SAMPLEABLE |
filament::Texture::Usage::UPLOADABLE);
}
#endif
filament::Texture* texture = builder.build(*engine);
if (data) {
const size_t num_bytes = width * height * sizeof(uint8_t) * num_channels;
const filament::Texture::Format format = GetTextureFormat(num_channels);
texture->setImage(
*engine, 0,
filament::Texture::PixelBufferDescriptor(
data, num_bytes, format, filament::Texture::Type::UBYTE));
// TODO: Revisit this to make it this work in WebGL
#ifndef __EMSCRIPTEN__
texture->generateMipmaps(*engine);
#endif
}
return texture;
}
filament::Texture* CreateCubeTexture(filament::Engine* engine, int width,
int height, int num_channels,
const uint8_t* data, bool is_srgb) {
if (num_channels != 3) {
mju_error("Only support RGB cubemaps.");
return nullptr;
}
const int kNumFacesPerCube = 6;
int face_height = height;
if (width != height) {
if (width * kNumFacesPerCube != height) {
mju_error("Cube maps must contain 6 square images.");
}
face_height = height / kNumFacesPerCube;
}
if (width != face_height) {
mju_error("Cube map faces must be square.");
}
filament::Texture::Builder builder;
builder.width(width);
builder.height(face_height);
builder.format(GetTextureInternalFormat(num_channels, is_srgb));
builder.sampler(filament::Texture::Sampler::SAMPLER_CUBEMAP);
// TODO: Revisit this to make it this work in WebGL
#ifndef __EMSCRIPTEN__
if (!is_srgb) {
builder.usage(filament::Texture::Usage::GEN_MIPMAPPABLE |
filament::Texture::Usage::SAMPLEABLE |
filament::Texture::Usage::UPLOADABLE);
}
#endif
filament::Texture* texture = builder.build(*engine);
const int face_size = width * face_height * num_channels;
const int num_bytes = face_size * kNumFacesPerCube;
uint8_t* buffer = new uint8_t[num_bytes];
auto callback = +[](void* buffer, size_t size, void* user) {
delete [] reinterpret_cast<uint8_t*>(buffer);
};
filament::Texture::FaceOffsets offsets(face_size);
if (width == height) {
// Copy the image to all the faces.
for (int i = 0; i < kNumFacesPerCube; ++i) {
std::memcpy(buffer + (i * face_size), data, face_size);
}
} else {
// Use the cubemap as is.
std::memcpy(buffer, data, num_bytes);
}
if (data) {
filament::Texture::PixelBufferDescriptor desc(
buffer, num_bytes, filament::Texture::Format::RGB,
filament::Texture::Type::UBYTE, callback);
texture->setImage(*engine, 0, std::move(desc), offsets);
// TODO: Revisit this to make it this work in WebGL
#ifndef __EMSCRIPTEN__
texture->generateMipmaps(*engine);
#endif
}
return texture;
}
filament::Texture* CreateKtxTexture(
filament::Engine* engine, const uint8_t* data, int size,
filament::math::float3* spherical_harmonics_out) {
image::Ktx1Bundle* bundle = new image::Ktx1Bundle(data, size);
if (spherical_harmonics_out) {
bundle->getSphericalHarmonics(spherical_harmonics_out);
}
const bool is_srgb = false;
return ktxreader::Ktx1Reader::createTexture(engine, bundle, is_srgb);
}
filament::Texture* CreateRenderTargetTexture(filament::Engine* engine,
int width, int height,
RenderTargetTextureType type) {
filament::Texture::Builder builder;
builder.width(width);
builder.height(height);
switch (type) {
case kRenderTargetColor:
builder.usage(filament::Texture::Usage::COLOR_ATTACHMENT |
filament::Texture::Usage::BLIT_SRC);
builder.format(filament::Texture::InternalFormat::RGB8);
break;
case kRenderTargetDepth:
builder.usage(filament::Texture::Usage::DEPTH_ATTACHMENT |
filament::Texture::Usage::SAMPLEABLE);
builder.format(filament::Texture::InternalFormat::DEPTH32F);
break;
case kRenderTargetDepthColor:
builder.usage(filament::Texture::Usage::COLOR_ATTACHMENT |
filament::Texture::Usage::BLIT_SRC);
builder.format(filament::Texture::InternalFormat::R32F);
break;
default:
mju_error("Unknown type: %d", static_cast<int>(type));
}
return builder.build(*engine);
}
} // namespace mujoco
@@ -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.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_TEXTURE_UTIL_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_TEXTURE_UTIL_H_
#include <cstdint>
#include <filament/Engine.h>
#include <filament/Texture.h>
#include <math/vec3.h>
// Functions for creating filament textures.
namespace mujoco {
// Creates a filament Texture for the given 2D texture.
filament::Texture* Create2dTexture(filament::Engine* engine, int width,
int height, int num_channels,
const uint8_t* data, bool is_srgb);
// Creates a filament Texture for the given cube texture.
filament::Texture* CreateCubeTexture(filament::Engine* engine, int width,
int height, int num_channels,
const uint8_t* data, bool is_srgb);
// Creates a filament Texture for the given KTX payload.
filament::Texture* CreateKtxTexture(
filament::Engine* engine, const uint8_t* data, int size,
filament::math::float3* spherical_harmonics_out);
enum RenderTargetTextureType {
kRenderTargetColor,
kRenderTargetDepth,
kRenderTargetDepthColor,
kNumRenderTargetTextureTypes,
};
// Creates a filament RenderTargetTexture for the given target type.
filament::Texture* CreateRenderTargetTexture(filament::Engine* engine,
int width, int height,
RenderTargetTextureType type);
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_TEXTURE_UTIL_H_
@@ -0,0 +1,65 @@
// 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 "experimental/filament/filament/vertex_util.h"
#include <limits>
#include <math/TVecHelpers.h>
#include <math/mat3.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::quatf;
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,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_EXPERIMENTAL_FILAMENT_FILAMENT_VERTEX_UTIL_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_VERTEX_UTIL_H_
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
namespace mujoco {
// 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);
// A standard vertex with no UV coordinates.
struct VertexNoUv {
VertexNoUv() = default;
VertexNoUv(filament::math::float3 position,
filament::math::float4 orientation)
: position(position), orientation(orientation) {}
filament::math::float3 position;
filament::math::float4 orientation;
static constexpr bool kHasPosition = true;
static constexpr bool kHasPosition2d = false;
static constexpr bool kHasOrientation = true;
static constexpr bool kHasUv = false;
static constexpr bool kHasColor = false;
};
// A standard vertex with UV coordinates.
struct VertexWithUv {
VertexWithUv() = default;
VertexWithUv(filament::math::float3 position,
filament::math::float4 orientation, filament::math::float2 uv)
: position(position), orientation(orientation), uv(uv) {}
filament::math::float3 position;
filament::math::float4 orientation;
filament::math::float2 uv;
static constexpr bool kHasPosition = true;
static constexpr bool kHasPosition2d = false;
static constexpr bool kHasOrientation = true;
static constexpr bool kHasUv = true;
static constexpr bool kHasColor = false;
};
// A vertex for rendering GUI elements.
struct GuiVertex {
GuiVertex() = default;
GuiVertex(filament::math::float2 position, filament::math::float2 uv,
filament::math::ubyte4 color)
: position(position), uv(uv), color(color) {}
filament::math::float2 position;
filament::math::float2 uv;
filament::math::ubyte4 color;
static constexpr bool kHasPosition = false;
static constexpr bool kHasPosition2d = true;
static constexpr bool kHasOrientation = false;
static constexpr bool kHasUv = true;
static constexpr bool kHasColor = true;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_VERTEX_UTIL_H_
@@ -0,0 +1,149 @@
// 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 "experimental/filament/render_context_filament.h"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <ios>
#include <mujoco/mjmodel.h>
#include <mujoco/mjrender.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/filament_context.h"
#if defined(TLS_FILAMENT_CONTEXT)
static thread_local mujoco::FilamentContext* g_filament_context = nullptr;
#else
static mujoco::FilamentContext* g_filament_context = nullptr;
#endif
// Default asset loader to use when calling mjr_makeContext. This is only
// intended for basic backwards compatibility with the existing mjr_makeContext
// API. If this doesn't work as expected, you should be calling
// mjr_makeFilamentContext instead and provide your own asset loading callbacks.
// Returns 0 on success and non-zero to indicate an error.
static int DefaultLoadAsset(const char* asset_filename, void* user_data,
unsigned char** contents, uint64_t* out_size) {
std::filesystem::path full_path;
full_path.append("filament/assets/data");
full_path.append(asset_filename);
std::ifstream file(full_path, std::ios::binary);
if (!file) {
mju_error("File does not exist: %s", full_path.c_str());
return 1;
}
file.seekg(0, std::ios::end);
*out_size = static_cast<uint64_t>(file.tellg());
if (*out_size == 0) {
mju_error("File is empty: %s", full_path.c_str());
return 1;
}
file.seekg(0, std::ios::beg);
*contents = (unsigned char*)malloc(*out_size);
file.read((char*)*contents, *out_size);
file.close();
return 0;
}
static void CheckFilamentContext() {
if (g_filament_context == nullptr) {
mju_error("Missing context; did you call mjr_makeFilamentContext?");
}
}
extern "C" {
void mjr_defaultFilamentConfig(mjrFilamentConfig* config) {
memset(config, 0, sizeof(mjrFilamentConfig));
}
void mjr_makeFilamentContext(const mjModel* m, mjrContext* con,
const mjrFilamentConfig* config) {
// TODO: Support multiple contexts and multiple threads. For now, we'll just
// assume a single, global context.
if (g_filament_context != nullptr) {
mju_error("Context already exists!");
}
g_filament_context = new mujoco::FilamentContext(config, m, con);
}
void mjr_defaultContext(mjrContext* con) { memset(con, 0, sizeof(mjrContext)); }
void mjr_makeContext(const mjModel* m, mjrContext* con, int fontscale) {
mjr_freeContext(con);
mjrFilamentConfig cfg;
mjr_defaultFilamentConfig(&cfg);
cfg.load_asset = DefaultLoadAsset;
mjr_makeFilamentContext(m, con, &cfg);
}
void mjr_freeContext(mjrContext* con) {
// mjr_freeContext may be called multiple times.
if (g_filament_context) {
delete g_filament_context;
g_filament_context = nullptr;
}
mjr_defaultContext(con);
}
void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) {
CheckFilamentContext();
g_filament_context->Render(viewport, scn, con);
}
void mjr_uploadMesh(const mjModel* m, const mjrContext* con, int meshid) {
CheckFilamentContext();
g_filament_context->UploadMesh(m, meshid);
}
void mjr_uploadTexture(const mjModel* m, const mjrContext* con, int texid) {
CheckFilamentContext();
g_filament_context->UploadTexture(m, texid);
}
void mjr_uploadHField(const mjModel* m, const mjrContext* con, int hfieldid) {
CheckFilamentContext();
g_filament_context->UploadHeightField(m, hfieldid);
}
void mjr_setBuffer(int framebuffer, mjrContext* con) {
CheckFilamentContext();
g_filament_context->SetFrameBuffer(framebuffer);
}
void mjr_readPixels(unsigned char* rgb, float* depth, mjrRect viewport,
const mjrContext* con) {
CheckFilamentContext();
g_filament_context->ReadPixels(viewport, rgb, depth);
}
void mjr_uploadFont(unsigned char* pixels, int width, int height, int bpp,
int id, const mjrContext* con) {
CheckFilamentContext();
if (bpp != 4) {
mju_error("Only 4bpp fonts are supported, got %d", bpp);
}
g_filament_context->UploadFont(pixels, width, height, id);
}
} // extern "C"
@@ -0,0 +1,85 @@
// 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_EXPERIMENTAL_FILAMENT_RENDER_CONTEXT_FILAMENT_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_RENDER_CONTEXT_FILAMENT_H_
#include <cstdint>
#include <mujoco/mjmodel.h>
#include <mujoco/mjrender.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#if defined(__cplusplus)
extern "C" {
#endif
typedef enum mjtGraphicsApi_ { // backend graphics API to use
mjGFX_DEFAULT = 0, // default based on platform
mjGFX_OPENGL, // OpenGL (desktop)
mjGFX_VULKAN // Vulkan
} mjtGraphicsApi;
struct mjrFilamentConfig {
// "Loads" an asset, returning its contents in `out` and size in `out_size`.
// Returns 0 on success and non-zero to indicate an error. The caller must
// free `out`.
typedef int (*load_asset_fn)(const char* path, void* user_data,
unsigned char** out, uint64_t* out_size);
// Used to load filament assets (e.g. materials, image-based lights, etc.).
load_asset_fn load_asset;
void* load_asset_user_data;
// The native window handle into which we can render directly.
void* native_window;
// The backend graphics API to use.
int graphics_api;
// Whether or not to enable GUI rendering.
bool enable_gui;
};
void mjr_defaultFilamentConfig(mjrFilamentConfig* config);
void mjr_makeFilamentContext(const mjModel* m, mjrContext* con,
const mjrFilamentConfig* config);
void mjr_defaultContext(mjrContext* con);
void mjr_makeContext(const mjModel* m, mjrContext* con, int fontscale);
void mjr_freeContext(mjrContext* con);
void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con);
void mjr_uploadMesh(const mjModel* m, const mjrContext* con, int meshid);
void mjr_uploadTexture(const mjModel* m, const mjrContext* con, int texid);
void mjr_uploadFont(unsigned char* pixels, int width, int height, int bpp,
int id, const mjrContext* con);
void mjr_setBuffer(int framebuffer, mjrContext* con);
void mjr_readPixels(unsigned char* rgb, float* depth, mjrRect viewport,
const mjrContext* con);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_RENDER_CONTEXT_FILAMENT_H_
+82
View File
@@ -0,0 +1,82 @@
// 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 <mujoco/mujoco.h>
extern "C" {
void mjr_setAux(int index, const mjrContext* con) {
mju_error("mjr_setAux not implemented.");
}
void mjr_restoreBuffer(const mjrContext* con) {
mju_error("mjr_restoreBuffer not implemented.");
}
void mjr_rectangle(mjrRect viewport, float r, float g, float b, float a) {
mju_error("mjr_rectangle not implemented.");
}
void mjr_blitAux(int index, mjrRect src, int left, int bottom,
const mjrContext* con) {
mju_error("mjr_blitAux not implemented.");
}
void mjr_changeFont(int fontscale, mjrContext* con) {
mju_error("mjr_changeFont not implemented.");
}
void mjr_addAux(int index, int width, int height, int samples,
mjrContext* con) {
mju_error("mjr_addAux not implemented.");
}
void mjr_resizeOffscreen(int width, int height, mjrContext* con) {
mju_error("mjr_resizeOffscreen not implemented.");
}
void mjr_drawPixels(const unsigned char* rgb, const float* depth,
mjrRect viewport, const mjrContext* con) {
mju_error("mjr_drawPixels not implemented.");
}
void mjr_blitBuffer(mjrRect src, mjrRect dst, int flg_color, int flg_depth,
const mjrContext* con) {
mju_error("mjr_blitBuffer not implemented.");
}
void mjr_text(int font, const char* txt, const mjrContext* con, float x,
float y, float r, float g, float b) {
mju_error("mjr_text not implemented.");
}
void mjr_overlay(int font, int gridpos, mjrRect viewport, const char* overlay,
const char* overlay2, const mjrContext* con) {
mju_error("mjr_overlay not implemented.");
}
void mjr_label(mjrRect viewport, int font, const char* txt, float r, float g,
float b, float a, float rt, float gt, float bt,
const mjrContext* con) {
mju_error("mjr_label not implemented.");
}
void mjr_figure(mjrRect viewport, mjvFigure* fig, const mjrContext* con) {
mju_error("mjr_figure not implemented.");
}
void mjr_finish() {
mju_error("mjr_finish not implemented.");
}
int mjr_getError() {
mju_error("mjr_getError not implemented.");
return 0;
}
mjrRect mjr_maxViewport(const mjrContext* con) {
mju_error("mjr_maxViewport not implemented.");
return mjrRect{};
}
int mjr_findRect(int x, int y, int nrect, const mjrRect* rect) {
mju_error("mjr_findRect not implemented.");
return 0;
}
} // extern "C"
+4 -4
View File
@@ -29,8 +29,8 @@
#include <utility>
#include <vector>
#include "third_party/dear_imgui/imgui.h"
#include "third_party/implot/implot.h"
#include <dear_imgui/imgui.h>
#include <implot/implot.h>
#include <mujoco/mujoco.h>
#include "experimental/toolbox/helpers.h"
#include "experimental/toolbox/imgui_widgets.h"
@@ -40,9 +40,9 @@
#include "experimental/toolbox/window.h"
#if defined(USE_FILAMENT_OPENGL) || defined(USE_FILAMENT_VULKAN)
#include "third_party/mujoco/google/filament/render_context_filament.h"
#include "experimental/filament/render_context_filament.h"
#elif defined(USE_CLASSIC_OPENGL)
#include "third_party/dear_imgui/backends/imgui_impl_opengl2.h"
#include <dear_imgui/backends/imgui_impl_opengl2.h>
#else
#error No rendering mode defined.
#endif
+1 -1
View File
@@ -18,7 +18,7 @@
#include <string>
#include <unordered_map>
#include "third_party/dear_imgui/imgui.h"
#include <dear_imgui/imgui.h>
#include <mujoco/mujoco.h>
namespace mujoco::toolbox {
+2 -2
View File
@@ -21,8 +21,8 @@
#include <type_traits>
#include <unordered_map>
#include "third_party/dear_imgui/imgui.h"
#include "third_party/dear_imgui/imgui_internal.h" // For ButtonEx and PressedOnClick
#include <dear_imgui/imgui.h>
#include <dear_imgui/imgui_internal.h> // For ButtonEx and PressedOnClick
#include <mujoco/mujoco.h>
namespace mujoco::toolbox {
+1 -1
View File
@@ -15,7 +15,7 @@
#include "experimental/toolbox/interaction.h"
#include <algorithm>
#include "third_party/dear_imgui/imgui.h"
#include <dear_imgui/imgui.h>
#include "experimental/toolbox/imgui_widgets.h"
#include "experimental/toolbox/physics.h"
#include "experimental/toolbox/renderer.h"
+10 -10
View File
@@ -17,16 +17,16 @@
#include <string>
#include <string_view>
#include "third_party/SDL2/include/SDL.h"
#include "third_party/SDL2/include/SDL_error.h"
#include "third_party/SDL2/include/SDL_events.h"
#include "third_party/SDL2/include/SDL_hints.h"
#include "third_party/SDL2/include/SDL_syswm.h"
#include "third_party/SDL2/include/SDL_version.h"
#include "third_party/SDL2/include/SDL_video.h"
#include "third_party/dear_imgui/backends/imgui_impl_sdl2.h"
#include "third_party/dear_imgui/imgui.h"
#include "third_party/implot/implot.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_error.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_hints.h>
#include <SDL2/SDL_syswm.h>
#include <SDL2/SDL_version.h>
#include <SDL2/SDL_video.h>
#include <dear_imgui/backends/imgui_impl_sdl2.h>
#include <dear_imgui/imgui.h>
#include <implot/implot.h>
#include "experimental/toolbox/helpers.h"
#include <mujoco/mujoco.h>
+1 -1
View File
@@ -19,7 +19,7 @@
#include <string_view>
#include "experimental/toolbox/helpers.h"
#include "third_party/SDL2/include/SDL_video.h"
#include <SDL2/SDL_video.h>
namespace mujoco::toolbox {