Skip to content

markaren/threepp

Repository files navigation

threepp

Cross-platform C++20 port of the popular JavaScript 3D library three.js r129.

Current state of the project

The core library is mature and feature-complete, with advanced rendering capabilities including real-time path tracing on the WebGPU backend. It is usable for a wide variety of rendering applications, from interactive 3D apps to robotics and scientific visualisation.

The high-level API is mostly in line with three.js r129 with changes from newer revisions in some areas. The OpenGL backend is a mechanical port of the WebGL renderer. The WebGPU renderer is mostly at feature parity with the GL backend, but is not a direct port of the three.js WebGPU renderer.

You get a scene graph, materials, lighting, cameras, render loop, controls, loaders, all composable, in a handful of lines.

What works?
  • Line, Points, Mesh, InstancedMesh
  • Geometries [Box, Sphere, Plane, Cylindrical, Capsule, Tube, ++]
  • Lights [Ambient, Directional, Point, Spot, Hemi, RectArea]
  • Raycasting [Mesh, Line, Points]
  • 2D/3D Textures, 2D/3D text, Sprites, RenderTarget, CubeMaps
  • Transparency, Transmission, Shadows
  • Environment maps, including PMREM
  • Animation, Morphtargets, Bones
  • Controls [Orbit, Fly, Drag, Transform]
  • Water and Sky shaders
  • Built-in text rendering and font loading [typeface.json, TTF]
  • Built-in model loaders [Binary STL, OBJ/MTL, GLTF, COLLADA, USD, FBX, SVG, URDF]
  • Basic Audio support using miniaudio
  • Generic model loader based on Assimp
  • Easy integration with Dear ImGui
  • OpenGL 3.3 and WebGPU raster backends
  • Real-time path tracing (on Vulkan backend)

Builds on Windows, Linux, MacOS, MinGW and with Emscripten.

And ROS2 users: threepp is a great fit for your visualisation needs.

But, but why?

Because C++ deserves nice things too.

Also, because fun.

How to build

threepp comes bundled with all required core dependencies.

Use CMake for project configuration and building.

Do note that you may also use a system installation of GLFW3 if you want or have issues with the bundled setup by passing -DTHREEPP_USE_EXTERNAL_GLFW=ON to CMake.

Windows
cmake . -A x64 -B build -DCMAKE_BUILD_TYPE="Release"
cmake --build build --config "Release"
Unix
cmake . -B build -DCMAKE_BUILD_TYPE="Release"
cmake --build build
Building examples with Emscripten

Pass to CMake:

-DCMAKE_TOOLCHAIN_FILE="[path to emscripten]\emsdk\upstream\emscripten\cmake\Modules\Platform\Emscripten.cmake"

This will generate .html versions of a subset of the examples to be loaded in a browser.
You can run a collection of them online here.

Optional downstream dependencies

When consuming threepp in your own application, some headers will require additional dependencies to compile.

Header Dependency Description
AssimpLoader assimp Import a wide variety of different 3D formats
ImguiContext imgui ImGUI utility

Implementation notes

In general, you'll find that math classes are value types, while threepp expect smart pointers for other types. For convenience, geometries, materials, etc. have a static ::create function that returns a std::shared_ptr. Thus, you don't necessarily need to handle memory explicitly using threepp. Furthermore, materials, geometries and textures are automatically disposed of when they go out of scope. Yay!

Example

#include "threepp/threepp.hpp"

using namespace threepp;

auto createBox(const Vector3& pos, const Color& color) {
    auto geometry = BoxGeometry::create();
    auto material = MeshPhongMaterial::create();
    material->color = color;
    
    auto box = Mesh::create(geometry, material);
    box->position.copy(pos);
    
    return box;
}

auto createPlane() {
    auto planeGeometry = PlaneGeometry::create(5, 5);
    auto planeMaterial = MeshLambertMaterial::create();
    planeMaterial->color = Color::gray;
    planeMaterial->side = Side::Double;
    
    auto plane = Mesh::create(planeGeometry, planeMaterial);
    plane->position.y = -1;
    plane->rotateX(math::degToRad(90));
    
    return plane;
}

int main() {

    Canvas canvas{"Demo"};
    GLRenderer renderer{canvas};

    auto scene = Scene::create();
    auto camera = PerspectiveCamera::create(75, canvas.aspect(), 0.1f, 100.f);
    camera->position.z = 5;
    
    OrbitControls controls{*camera, canvas};

    auto light = HemisphereLight::create();
    scene->add(light);

    auto plane = createPlane();
    scene->add(plane);
    
    auto group = Group::create();
    group->add(createBox({-1, 0, 0}, Color::green));
    group->add(createBox({1, 0, 0}, Color::red));
    scene->add(group);

    canvas.onWindowResize([&](WindowSize size) {
        camera->aspect = size.aspect();
        camera->updateProjectionMatrix();
        renderer.setSize(size);
    });
    
    Clock clock;
    canvas.animate([&] {
        
        const auto dt = clock.getDelta();
        group->rotation.y += 1.f * dt;

        renderer.render(*scene, *camera);
    });
}

Consuming threepp

Threepp is mainly available as a CMake package and can be consumed in a number of ways. It's also available as a Conan package, so it can be consumed using conan or xmake.

CMake FetchContent (recommended)

threepp is compatible with CMake's FetchContent:

include(FetchContent)
set(THREEPP_BUILD_TESTS OFF)
set(THREEPP_BUILD_EXAMPLES OFF)
FetchContent_Declare(
        threepp
        GIT_REPOSITORY https://github.com/markaren/threepp.git
        GIT_TAG tag_or_branch   # use a tag/branch (required for GIT_SHALLOW)
        GIT_SHALLOW TRUE        # fetch only the tip to keep the download small
)
FetchContent_MakeAvailable(threepp)
#...
target_link_libraries(main PUBLIC threepp::threepp)

This is the preferred approach, as it enables users to update the targeted threepp version at will.

With THREEPP_BUILD_EXAMPLES and THREEPP_BUILD_TESTS off (as above), only the library is fetched. The example/test assets (models, textures, fonts, sounds, …) live in a separate threepp_data repository, fetched automatically only when examples or tests are enabled — library consumers never download them. For local development against a working copy of the assets, configure with -DFETCHCONTENT_SOURCE_DIR_THREEPP_DATA=/path/to/threepp_data.

Tip: Since threepp examples bundles imgui, you can link against it by including:

add_subdirectory("${threepp_SOURCE_DIR}/examples/external")

An example is provided here.

See also this demo, which additionally uses WxWidgets as the Window system.

Using Conan

Example conanfile.py :

from conan import ConanFile
from conan.tools.cmake import CMake, cmake_layout


class ExampleRecipe(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    generators = "CMakeDeps", "CMakeToolchain"

    def requirements(self):
        self.requires("threepp/0.0.20260310")

    def layout(self):
        cmake_layout(self)

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

Xmake

Example xmake.lua file:

add_rules("mode.debug", "mode.release")
add_requires("imgui", {configs = {glfw_opengl3 = true}}) -- optional dependency for UI widgets
add_requires("assimp") -- optional dependency for importing assembly models (.glb/.dae)
add_requires("conan::threepp/0.0.20260310", {
    alias = "threepp",
    configs = {
        settings = {"compiler.cppstd=20"}
    }
})
target("example")
set_kind("binary")
add_files("src/*.cpp")
add_packages("imgui", "threepp", "assimp")
set_languages("c++20")

Screenshots

Fonts Ocean Spline Editor SVG UI colnav Shadows Crane FlyControls Optimization Bistro Animation Lidar Water+sky MotorController SVG Depth sensor Shooter Chess