Embree API Example

On this page you find a simple example application (a Embree 'hello world', if you will), to demonstrate how easy it is to use Embree. The application creates a triangle and a ray and performs a ray-triangle intersection using the Embree API. The full source code is shown below and afterwards small snippets of the code will be discussed in more detail. Build instructions can be found at the bottom of the page. Please refer to the documentation for more information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Copyright 2009-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

#include <embree3/rtcore.h>
#include <limits>
#include <iostream>

int main()
{
  RTCDevice device = rtcNewDevice(NULL);
  RTCScene scene   = rtcNewScene(device);
  RTCGeometry geom = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_TRIANGLE);

  float* vb = (float*) rtcSetNewGeometryBuffer(geom,
      RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, 3*sizeof(float), 3);
  vb[0] = 0.f; vb[1] = 0.f; vb[2] = 0.f; // 1st vertex
  vb[3] = 1.f; vb[4] = 0.f; vb[5] = 0.f; // 2nd vertex
  vb[6] = 0.f; vb[7] = 1.f; vb[8] = 0.f; // 3rd vertex

  unsigned* ib = (unsigned*) rtcSetNewGeometryBuffer(geom,
      RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, 3*sizeof(unsigned), 1);
  ib[0] = 0; ib[1] = 1; ib[2] = 2;

  rtcCommitGeometry(geom);
  rtcAttachGeometry(scene, geom);
  rtcReleaseGeometry(geom);
  rtcCommitScene(scene);

  RTCRayHit rayhit; 
  rayhit.ray.org_x  = 0.f; rayhit.ray.org_y = 0.f; rayhit.ray.org_z = -1.f;
  rayhit.ray.dir_x  = 0.f; rayhit.ray.dir_y = 0.f; rayhit.ray.dir_z =  1.f;
  rayhit.ray.tnear  = 0.f;
  rayhit.ray.tfar   = std::numeric_limits<float>::infinity();
  rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
  
  RTCIntersectContext context;
  rtcInitIntersectContext(&context);

  rtcIntersect1(scene, &context, &rayhit);

  if (rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID) {
    std::cout << "Intersection at t = " << rayhit.ray.tfar << std::endl;
  } else {
    std::cout << "No Intersection" << std::endl;
  }

  rtcReleaseScene(scene);
  rtcReleaseDevice(device);
}
hello_embree.cpp

Step by step

We first create several Embree objects: A Embree device with a default configuration (see sec. 5.1 and 7.1 in the documention for details), a Embree scene, and a Embree geometry to represent our triangle.

1
2
3
RTCDevice device = rtcNewDevice(NULL);
RTCScene scene = rtcNewScene(device);
RTCGeometry geometry = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_TRIANGLE);
Embree device, scene, and geometry.

A scene is a collection of geometry objects. Scenes are what the intersect / occluded API functions work on. You can think of a scene as an acceleration structure, e.g. a bounding-volume hierarchy.

Next, we instruct Embree to create vertex and index buffers into which we copy the geomety data. For complex scenes, shared buffers are often better choice but special care must be taken to ensure proper alignment and padding. This is described in more detail in the API documentation.

1
2
3
4
5
6
7
8
9
float* vb = (float*) rtcSetNewGeometryBuffer(geom,
    RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, 3*sizeof(float), 3);
vb[0] = 0.f; vb[1] = 0.f; vb[2] = 0.f; // 1st vertex
vb[3] = 1.f; vb[4] = 0.f; vb[5] = 0.f; // 2nd vertex
vb[6] = 0.f; vb[7] = 1.f; vb[8] = 0.f; // 3rd vertex

unsigned* ib = (unsigned*) rtcSetNewGeometryBuffer(geom,
    RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, 3*sizeof(unsigned), 1);
ib[0] = 0; ib[1] = 1; ib[2] = 2;
Vertex and index buffers.

After setting the geometry data the geometry can be committed and attached to the scene. Embree objects such as RTCDevice, RTCScene and RTCGeometry are reference-counted. This means, that the scene takes ownership of the geometry when we attach it and we can release the geometry handle. The API function rtcAttachGeometry returns a geometry ID which can be used identify intersected objects when the scene contains multiple geometry objects. We finish the setup by committing the scene, after which the scene can be intersected.

1
2
3
4
rtcCommitGeometry(geometry);
rtcAttachGeometry(scene, geometry);
rtcReleaseGeometry(geometry);
rtcCommitScene(scene);
Attach and commit

To perform a ray intersect, we obviously have to create a ray first. Embree also expects an intersection context which can be used for advanced features such as intersection filters and instancing.

1
2
3
4
5
6
7
8
9
RTCRayHit rayhit; 
rayhit.ray.org_x  = 0.f; rayhit.ray.org_y = 0.f; rayhit.ray.org_z = -1.f;
rayhit.ray.dir_x  = 0.f; rayhit.ray.dir_y = 0.f; rayhit.ray.dir_z =  1.f;
rayhit.ray.tnear  = 0.f;
rayhit.ray.tfar   = std::numeric_limits<float>::infinity();
rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID;

RTCIntersectContext context;
rtcInitIntersectContext(&context);
Intersecting the triangle.

We set the ray values tnear to 0 and tfar to infinity to indicate that the ray starts at origin (org_x, org_y, org_z) and is unbounded. We also set geomID to RTC_INVALID_GEOMETRY_ID. If an intersection occured, geomID will contain the ID of the geometry that has been intersected. In this case, the value tfar will contain the ray parameter which we can compute the point at which the ray and triangle intersect as (org_x, org_y, org_z) + t * (dir_x, dir_y, dir_z).

Now we can call Embree's intersect function and check if the ray intersects the triangle.

1
2
3
4
5
6
7
rtcIntersect1(scene, &context, &rayhit);

if (rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID) {
  std::cout << "Intersection at t = " << rayhit.ray.tfar << std::endl;
} else {
  std::cout << "No Intersection" << std::endl;
}
Performing a ray intersection with the scene.

At the end of an application all resources that where allocated through Embree should be released. Note that the geometry will be released automatically, when the (last) scene the geometry is attached to is released.

1
2
rtcReleaseScene(scene);
rtcReleaseDevice(device);
Be neat and clean up.

Building the example

To build and run the example application you can download the latest Embree release, the hello_embree.cpp source code, and a simple CMakeLists.txt file.

After extracting the Embree release archive and placing hello_embree.cpp and CMakeLists.txt into the same folder, you can run

1
2
3
cmake -B build -Dembree_DIR=/path/to/extracted/embree/release/lib/cmake/embree-VERSION
cmake --build build
./build/hello_embree
Build and run example on Unix-based systems.

on Unix-based systems or

1
2
3
cmake -B build -Dembree_DIR=C:\path\to\extracted\embree\release\lib\cmake\embree-VERSION
cmake --build build
build\Debug\hello_embree.exe
Build and run example on Windows.

in a developer command prompt for Visual Studio on windows.

The CMake variable embree_DIR has to be set to the directory which contains the embree-config.cmake file in the extracted release archive.

If everything went smoothly you should get the following output

1
Intersection at t = 1
Output of the example application.

Next steps

Some suggestions on how to continue from here:

  • Modify the ray such that it does not intersect the triangle.
  • Add another triangle to the scene.
  • Add a RTC_GEOMETRY_TYPE_QUAD geometry to the scene and check if a ray intersects the correct geometry by inspecting the geomerty IDs.
  • Take a look at the API documentation.
  • Take a look at the Embree tutorials.