Open-source project
sergcpp/Ray avatar
sergcpp/Ray

Ray and an embeddable physically based renderer for C++

Embeddable physically based renderer

555 stars18 forksC++MIT

At a glance

What is it?
Ray is a cross-platform, semi-realtime physically based renderer written in C++ that you embed as a library or build standalone, with unidirectional path tracing, Vulkan and DirectX 12 backends, a CPU path accelerated with SSE, AVX and NEON, and a neural denoiser ported from OpenImageDenoise. The interesting design choice is that the renderer object owns its own scene and its own storage, and the interesting constraint is that it renders still frames rather than animating them.
Who is it for?
Ray is a good fit for a C++ application that needs physically based still images as part of its own product, such as a previews pipeline, a procedural tool or a graphics feature, because it is designed to be embedded with add_subdirectory and it produces final-quality frames rather than approximations.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A renderer as a library, not a standalone application

Ray is described as an embeddable cross-platform semi-realtime physically based renderer, and the intended use is stated plainly: to add it as a submodule to an existing project.

That framing distinguishes it from the many path tracers that ship as a command line tool you invoke on a scene file. Ray is a library, and the sample application is a demonstration of the API rather than the product. The repository layout supports that reading, with Ray.h, Ray.cpp, RendererBase.cpp and RendererBase.h and SceneBase.h at the root, alongside internal/, first-party/ and third-party/ directories and a samples/ directory whose entries are numbered by topic.

The renderer itself is a unidirectional path tracer. The feature list names light tree NEE and skyportals, diffuse, glossy, refractive and multi-layer Principled BSDFs, filmic and AgX tonemapping, a built-in procedural sky model, and automatic texture compression. The scene list in the samples directory names the progression: basic, texturing, multithreading, principled, denoising and physical sky.

The topic tags are bvh, path-tracer, raytracing, gpu, rtx, vulkan and directx12, which tells you the acceleration structure and the hardware targets.

The audience is a C++ developer who wants physically based rendering inside an application they already have. That could be a preview generator, a procedural content tool, a visualisation component, or a research harness. The licence is MIT, the default branch is master, the last push was on 2026-10-18, and the recent tags are v0.4.0 from 2025-02-26, v0.3.0 from 2023-12-04 and v0.2.0 from the same day.

That gap between the last tag and the last push is worth noting. There has been a great deal of work since v0.4.0 that is not visible in a release list, so a consumer pinning to a tag is pinning to code from early 2025.

The API shape: settings, renderer, scene, materials

The usage example is the best description of the API, because the object lifetimes it shows are the design.

You start with a settings structure, Ray::settings_t, where you set the initial frame resolution. The comment notes that the resolution can be changed later, so the settings object is about the initial configuration rather than a permanent constraint.

You then call Ray::CreateRenderer with those settings and a log function pointer, and the result is a Ray::RendererBase pointer. The comment there is the important one: an additional Ray::eRendererType parameter can be passed, and the Vulkan GPU renderer is created by default. So the backend is a parameter of renderer creation rather than a compile-time choice.

Next comes the scene, and the comment explains why the scene comes from the renderer rather than being constructed independently: each renderer has its own storage implementation, RAM or GPU-RAM, so the renderer itself should create the scene object. The call is renderer->CreateScene(), returning a Ray::SceneBase pointer. That coupling is what lets the same scene-building code work against CPU and GPU backends, and it is also a constraint, because the scene cannot outlive or be shared between renderers.

The scene then takes an environment description with the environment colour set per channel, and materials are added through shading node descriptions. The type field selects the BSDF, Diffuse or Emissive in the example, the base colour is set per channel, and for the emissive material there is a strength value and an importance_sample flag whose comment says to use NEE for this light source.

That last flag is a small but telling detail. Next event estimation is a sampling optimisation, and exposing it as a per-material switch means a caller can decide which lights are sampled directly rather than found by chance. The API is a renderer written by someone who cares about the sampling, not a wrapper around a black box.

CPU and GPU backends, and the vectorised CPU path

The feature list describes two quite different execution paths, and the difference between them is the main thing to understand about the library.

The CPU backend is accelerated using SSE, AVX and NEON extensions. NEON is the ARM vector instruction set, and its presence alongside the x86 extensions tells you the CPU path is meant to be usable on Apple silicon and ARM machines generally, not only on desktops.

The GPU backends are Vulkan and DirectX 12, with optional hardware raytracing. Vulkan is the primary target, since the Vulkan GPU renderer is the default in the example, and VulkanFunctions.h at the repository root suggests the Vulkan entry points are loaded dynamically rather than linked, which is what lets the same binary run on a machine without a Vulkan driver.

Ray tracing is described as optional hardware raytracing, and the RTX topic tag confirms the intent. This is a hybrid design. When a hardware ray tracing capable GPU is present, the traversal work can be pushed to the RT cores; otherwise the BVH traversal runs in software, which is where the SSE, AVX and NEON paths earn their keep.

Two more features belong to the GPU side. DNN denoising is a native port of OpenImageDenoise, accelerated using VK_KHR_cooperative_matrix, which is a Vulkan extension for matrix operations on the GPU and is exactly what an inference pass over a noise image needs. And spatial radiance caching is described as SHARC-like and optional, which is the technique of reusing cached radiance from nearby light samples to fill in paths that were terminated early.

The combination tells you what semi-realtime means here in practice. A path tracer that would normally need minutes per frame can use hardware traversal, a neural denoiser and a radiance cache to get to a usable frame in a fraction of the time, at the cost of being neither exactly physically correct nor fully real-time.

Building it: submodule, CMake, and the standalone samples

There are two ways to get Ray into a project, and the primary one is a CMake submodule.

The intended use is to add it as a submodule to an existing project:

bash
git submodule add https://github.com/sergcpp/Ray.git

And then in the CMakeLists.txt file:

cmake
add_subdirectory(Ray)

That is the whole integration, and it is the reason the library is described as embeddable. A single add_subdirectory and the renderer becomes part of your build.

Standalone samples can also be compiled and run, which is the route to take when you want to see what the thing does before committing to it. On Windows:

bash
git clone https://github.com/sergcpp/Ray.git
cd Ray
mkdir build && cd build/
cmake ..
msbuild ALL_BUILD.vcxproj /p:Configuration=Release

And on Linux and macOS:

bash
git clone https://github.com/sergcpp/Ray.git
cd Ray
mkdir build && cd build/
cmake .. -DCMAKE_BUILD_TYPE=Release && make

The two paths differ in two ways worth noticing. The Windows build uses msbuild against a generated Visual Studio project, and the Unix build passes a build type to CMake and drives make directly. And the Unix build is the one that would run on a Raspberry Pi or an ARM workstation, which is consistent with the NEON support in the CPU backend.

The samples directory shows what you get when you build standalone, and the names are the API tour: 00_basic, 01_texturing, 02_multithreading, 03_principled, 04_denoising and 05_physical_sky, each with a rendered .tga alongside its source directory. Reading them in order is the fastest way to learn the API, and the 02_multithreading sample in particular is where the thread pool usage is visible.

What a first render looks like in code

The complete example is worth walking through, because it shows the minimum sequence: configure, create renderer, create scene, set environment, add materials, add geometry.

c++
    #include <Ray/Ray.h>

    int main() {
        const int IMG_W = 256, IMG_H = 256;
        const int SAMPLE_COUNT = 64;

        // Initial frame resolution, can be changed later
        Ray::settings_t s;
        s.w = IMG_W;
        s.h = IMG_H;

        // Additional Ray::eRendererType parameter can be passed (Vulkan GPU renderer created by default)
        Ray::RendererBase *renderer = Ray::CreateRenderer(s, &Ray::g_stdout_log);

Four things are worth pulling out of that. The header is included as <Ray/Ray.h>, which confirms the include-directory convention that the add_subdirectory integration sets up for you. The log is passed as a function pointer, Ray::g_stdout_log, so logging is injectable and the library is not hard-wired to stdout. The scene is created from the renderer, as the earlier comment explains. And the settings object carries the frame resolution, with a comment that it can be changed later, so the renderer is not locked to one output size.

The example then sets the environment colour to zero on all three channels, adds three diffuse materials with different base colours and one emissive material with a strength of 100.0f and importance_sample set to true, and finally defines a test mesh as a flat float array where each vertex is position, normal and texture coordinate in turn, forming a floor, a back wall and a ceiling.

The material setup is a good illustration of the descriptor style. A Ray::shading_node_desc_t carries the BSDF type, the base colour channels and, for emissive materials, a strength, and each AddMaterial call returns a Ray::MaterialHandle. The same descriptor struct is reused and mutated between calls, with the base colour overwritten each time, which keeps the sample compact without any per-material allocation logic in the caller.

The sample count of 64 is also telling for a path tracer. That is a low number, which is exactly the regime where the denoiser and the radiance cache earn their place.

Where Ray is the wrong choice

Three limits follow from what the library is.

The first is that it renders still frames, not animation. The sample outputs are .tga files, the usage section is headed Image rendering, and the description calls the renderer semi-realtime. A renderer that can produce a single high-quality image in a fraction of the time a brute-force path tracer needs is not the same as a renderer that can produce a hundred frames of a moving camera, and nothing in the feature list suggests temporal accumulation, motion vectors or a frame sequence pipeline. For animation, a rasteriser with a rasterised shadow and reflection solution is the tool for the job, and for offline animation a batch path tracer that trades interactivity for correctness is the other.

The second is the version line. The most recent tag is v0.4.0 from 2025-02-26, while the last push was on 2026-10-18, and the version numbering has not moved past 0.4. Between those two dates the library has gained features, and a consumer who pins v0.4.0 and reads the current README may find the two do not match. There is a CHANGELOG.md at the root, and that is the file to consult for what changed.

The third is the dependency weight of a modern renderer. A Vulkan backend with cooperative matrix support, a neural denoiser ported from OpenImageDenoise, hardware ray tracing and multiple vector instruction sets is a substantial machine. The library is compatible with Windows including ARM, Linux and macOS, but the GPU path with its optional hardware ray tracing and VK_KHR_cooperative_matrix extension puts a floor under the hardware requirements that the CPU path does not.

The honest comparison is against the alternative of using an existing renderer as a subprocess. Ray's advantage is integration: no process boundary, direct access to the scene graph, and your own pipeline rather than a file format. The cost is that you inherit the whole dependency set, and you are responsible for the parts of the pipeline the library does not cover, including animation and the final output encoding.

Editorial conclusion

Ray is a good fit for a C++ application that needs physically based still images as part of its own product, such as a previews pipeline, a procedural tool or a graphics feature, because it is designed to be embedded with add_subdirectory and it produces final-quality frames rather than approximations. It is a poor fit for real-time rendering, since the description calls it semi-realtime and the samples are single frame writes, and a poor fit if you need an animation pipeline, because the sample set is still frames. Before adopting, run the standalone samples first, since the source tree is the documentation for the API, and check the version line, because the last tagged release is v0.4.0 from 2025-02-26 while the repository has moved on considerably since then.

Frequently asked questions

How do I add Ray to a CMake project?

Add it as a git submodule with `git submodule add https://github.com/sergcpp/Ray.git` and then add `add_subdirectory(Ray)` to your CMakeLists.txt file. The intended use is to embed it in an existing project rather than to run it as a separate application.

Which GPU backends does Ray support?

Vulkan and DirectX 12, with optional hardware raytracing. The Vulkan GPU renderer is created by default, and a Ray::eRendererType parameter can be passed to CreateRenderer to select a different one. There is also a CPU backend accelerated with SSE, AVX and NEON extensions.

Does Ray support real-time rendering?

Not in the conventional sense. It is described as a semi-realtime physically based renderer and the usage section covers image rendering, with single frame outputs in the samples directory. It is built for still images, with a denoiser and an optional spatial radiance cache reducing the time each frame needs.

What denoising does Ray use?

DNN denoising, described as a native port of OpenImageDenoise, accelerated using the VK_KHR_cooperative_matrix extension. The 04_denoising sample demonstrates it.

How do I build the Ray samples on Linux or macOS?

Clone the repository, create a build directory, run `cmake .. -DCMAKE_BUILD_TYPE=Release && make` in it. On Windows the equivalent is to run cmake and then `msbuild ALL_BUILD.vcxproj /p:Configuration=Release`.

What licence is Ray released under?

MIT, with the licence file at the repository root. The most recent tagged release is v0.4.0 from 2025-02-26, while the last push to the repository was on 2026-10-18, so the CHANGELOG.md at the root is the place to check what has changed since that tag.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. sergcpp/Ray on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sergcpp-ray.svg)](https://hysenlabs.com/projects/sergcpp-ray)

Was this useful?