RTC_GEOMETRY_TYPE_CURVE(3)

πŸ’– RTC_GEOMETRY_TYPE_*_CURVE β€” Embree Ray Tracing Kernels 4 (part 1) πŸ’–

ok so bestie, we're in the embree4 manual, section 3, and today's topic is curve geometry. it's giving "i have a lot of hair strands and i need them rendered" energy. let's get into it. πŸŽ€


✨ NAME ✨

Lowkey this one geometry type is actually a whole squad of them, all curve geometries, each with its own whole personality:

  • RTC_GEOMETRY_TYPE_FLAT_LINEAR_CURVE β€” flat curve geometry with linear basis
  • RTC_GEOMETRY_TYPE_FLAT_BEZIER_CURVE β€” flat curve geometry with cubic BΓ©zier basis
  • RTC_GEOMETRY_TYPE_FLAT_BSPLINE_CURVE β€” flat curve geometry with cubic B-spline basis
  • RTC_GEOMETRY_TYPE_FLAT_HERMITE_CURVE β€” flat curve geometry with cubic Hermite basis
  • RTC_GEOMETRY_TYPE_FLAT_CATMULL_ROM_CURVE β€” flat curve geometry with Catmull-Rom basis
  • RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_BEZIER_CURVE β€” flat normal oriented curve geometry with cubic BΓ©zier basis
  • RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_BSPLINE_CURVE β€” flat normal oriented curve geometry with cubic B-spline basis
  • RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_HERMITE_CURVE β€” flat normal oriented curve geometry with cubic Hermite basis
  • RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_CATMULL_ROM_CURVE β€” flat normal oriented curve geometry with Catmull-Rom basis
  • RTC_GEOMETRY_TYPE_CONE_LINEAR_CURVE β€” capped cone curve geometry with linear basis β€” discontinuous at edge boundaries
  • RTC_GEOMETRY_TYPE_ROUND_LINEAR_CURVE β€” capped cone curve geometry with linear basis and spherical ending
  • RTC_GEOMETRY_TYPE_ROUND_BEZIER_CURVE β€” swept surface curve geometry with cubic BΓ©zier basis
  • RTC_GEOMETRY_TYPE_ROUND_BSPLINE_CURVE β€” swept surface curve geometry with cubic B-spline basis
  • RTC_GEOMETRY_TYPE_ROUND_HERMITE_CURVE β€” swept surface curve geometry with cubic Hermite basis
  • RTC_GEOMETRY_TYPE_ROUND_CATMULL_ROM_CURVE β€” swept surface curve geometry with Catmull-Rom basis

periodt. fifteen of them. πŸ‘‘


🌸 SYNOPSIS 🌸

you pull in the header like this:

#include <embree4/rtcore.h>

and then you make a geometry, and you have options, bestie:

rtcNewGeometry(device, RTC_GEOMETRY_TYPE_FLAT_LINEAR_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_FLAT_BEZIER_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_FLAT_BSPLINE_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_FLAT_HERMITE_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_FLAT_CATMULL_ROM_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_BEZIER_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_BSPLINE_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_HERMITE_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_CATMULL_ROM_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_CONE_LINEAR_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_ROUND_LINEAR_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_ROUND_BEZIER_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_ROUND_BSPLINE_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_ROUND_HERMITE_CURVE);
rtcNewGeometry(device, RTC_GEOMETRY_TYPE_ROUND_CATMULL_ROM_CURVE);

πŸ’… DESCRIPTION πŸ’…

so. curves with per vertex radii? fully supported. and not just one basis β€” we got linear, cubic BΓ©zier, cubic B-spline, and cubic Hermite bases. iconic behavior honestly.

you create one of these curve geometries by passing one of these bad boys to rtcNewGeometry:

  • RTC_GEOMETRY_TYPE_FLAT_LINEAR_CURVE
  • RTC_GEOMETRY_TYPE_FLAT_BEZIER_CURVE
  • RTC_GEOMETRY_TYPE_FLAT_BSPLINE_CURVE
  • RTC_GEOMETRY_TYPE_FLAT_HERMITE_CURVE
  • RTC_GEOMETRY_TYPE_FLAT_CATMULL_ROM_CURVE
  • RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_FLAT_BEZIER_CURVE
  • RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_FLAT_BSPLINE_CURVE
  • RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_FLAT_HERMITE_CURVE
  • RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_FLAT_CATMULL_ROM_CURVE
  • RTC_GEOMETRY_TYPE_CONE_LINEAR_CURVE
  • RTC_GEOMETRY_TYPE_ROUND_LINEAR_CURVE
  • RTC_GEOMETRY_TYPE_ROUND_BEZIER_CURVE
  • RTC_GEOMETRY_TYPE_ROUND_BSPLINE_CURVE
  • RTC_GEOMETRY_TYPE_ROUND_HERMITE_CURVE
  • RTC_GEOMETRY_TYPE_ROUND_CATMULL_ROM_CURVE

and then you gotta feed it buffers, because geometry doesn't just manifest:

  • the curve indices go in an index buffer (RTC_BUFFER_TYPE_INDEX)
  • the curve vertices go in a vertex buffer (RTC_BUFFER_TYPE_VERTEX)
  • if you're on the Hermite basis, you also need a tangent buffer (RTC_BUFFER_TYPE_TANGENT)
  • for normal oriented curves, a normal buffer (RTC_BUFFER_TYPE_NORMAL)
  • and for normal oriented Hermite curves, a normal derivative buffer (RTC_BUFFER_TYPE_NORMAL_DERIVATIVE) on top of all that
head over to rtcSetGeometryBuffer and rtcSetSharedGeometryBuffer for the whole how-to on setting buffers. that's the lore. πŸ“š

the index buffer, bestie: it's an array of 32-bit indices (RTC_FORMAT_UINT format). each one points to the first control vertex in the vertex buffer β€” but also to the first tangent in the tangent buffer, and the first normal in the normal buffer, if those buffers are present. multitasking queen. πŸ’

the vertex buffer stores each control vertex as a single precision position and radius, written in (x, y, z, r) order in memory, RTC_FORMAT_FLOAT4 format. the number of vertices is inferred from the size of this buffer β€” no, you don't declare it separately. and yes, the radii may be smaller than zero, but the interpolated radii should always be greater or equal to zero. keep that in mind, ok?

same deal for the others:

  • the tangent buffer stores the derivative of each control vertex, x, y, z, r order, RTC_FORMAT_FLOAT4 format
  • the normal buffer stores a single precision normal per control vertex, x, y, z order, RTC_FORMAT_FLOAT3 format

🌈 Linear Basis 🌈

for the linear basis, the indices point to the first of 2 consecutive control points in the vertex buffer. first control point = the start, second control point = the end of the line segment.

cute little hack for y'all building hair strands in this basis: the end-point can be shared with the start of the next line segment. memory efficiency? we love her. 🫢

also, for the linear basis, you optionally can provide a flags buffer of type RTC_BUFFER_TYPE_FLAGS. it holds bytes that encode whether the left neighbor segment (the RTC_CURVE_FLAG_NEIGHBOR_LEFT flag) and/or the right neighbor segment (the RTC_CURVE_FLAG_NEIGHBOR_RIGHT flags) exist β€” check [RTCCurveFlags] for that. if this buffer isn't set, then the left/right neighbor bits get automatically calculated based on the index buffer, like:

  • left segment exists if segment(id-1)+1 == segment(id)
  • right segment exists if segment(id+1)-1 == segment(id)

here's the mental model:

  • a left neighbor segment is assumed to end at the start vertex of the current segment, and to start at the previous vertex in the vertex buffer.
  • a right neighbor segment is assumed to start at the end vertex of the current segment, and to end at the next vertex in the vertex buffer.
⚠️ real talk: only when the left and right bits are properly specified can the current segment properly attach to its left and/or right neighbor. otherwise the touching area may not get rendered properly. so don't skip this. fr fr. 😭

πŸ¦‹ BΓ©zier Basis πŸ¦‹

for the cubic BΓ©zier basis, the indices point to the first of 4 consecutive control points in the vertex buffer. these control points use the cubic BΓ©zier basis, where:

  • the first control point = the start point of the curve
  • the 4th control point = the end point of the curve

and the slay detail: the Bézier basis is interpolating, so the curve does go exactly through the first and fourth control vertex. it hits different when the math just agrees with you. ✨


🧚 B-spline Basis 🧚

for the cubic B-spline basis, the indices point to the first of 4 consecutive control points in the vertex buffer. these control points make up a cardinal cubic B-spline, with an implicit equidistant knot vector.

not interpolating, bestie. the curve does in general not go through any of the control points directly. that's not a bug, that's the whole vibe.

the big advantage though?? 3 control points can be shared for two continuous neighboring curve segments. like, the curves (p0,p1,p2,p3) and (p1,p2,p3,p4) are C1 continuous. this makes it a great choice for constructing continuous multi-segment curves, because memory consumption can be kept minimal. economical queen. πŸ’œ


πŸ“ Hermite Basis πŸ“

for the cubic Hermite basis, the indices point to the first of 2 consecutive points in the vertex buffer, and the first of 2 consecutive tangents in the tangent buffer. two points + two tangents = one cubic Hermite curve. simple math, gorgeous result.

this basis is interpolating, so it does exactly go through the first and second control point, and the first order derivative at the begin and end matches exactly the value specified in the tangent buffer. no cap.

connecting two segments continuously? the end point and tangent of the previous segment can be shared. and here's a fun one: different versions of Catmull-Rom splines can be easily constructed using the Hermite basis, by calculating a proper tangent buffer from the control points. DIY moment. πŸŽ€


⭐ Catmull-Rom Basis ⭐

for the Catmull-Rom basis, the indices point to the first of 4 consecutive control points in the vertex buffer. this basis goes through p1 and p2, with tangents (p2-p0)/2 and (p3-p1)/2. that's the whole thing. short, sweet, mathematically blessed. πŸ”₯


πŸ’– Flat Curves πŸ’–

the RTC_GEOMETRY_TYPE_FLAT_* flat mode is a fast mode designed to render distant hair. key word: distant. πŸ‘€

in this mode the curve is rendered as a connected sequence of ray facing quads. individual quads are considered to have subpixel size, and zooming onto the curve might show geometric artifacts. so like, don't zoom in and then act surprised, ok?

the number of quads to subdivide into can be specified through the rtcSetGeometryTessellationRate function. and by default, the tessellation rate is 4. 🎚️


πŸ‘‘ Normal Oriented Curves πŸ‘‘

the RTC_GEOMETRY_TYPE_NORMAL_ORIENTED_* mode is a mode designed to render blades of grass. 🌱 yes, specifically grass. main character energy for your lawn.

in this mode a vertex spline has to get specified as for the previous modes, but additionally a normal spline is required. and if the Hermite basis is used, then the RTC_BUFFER_TYPE_NORMAL and RTC_BUFFER_TYPE_NORMAL_DERIVATIVE buffers have both to be set. both. not one. both. πŸ’€

the curve is rendered as a flat band whose:

  • center approximately follows the provided vertex spline
  • half width approximately follows the provided radius spline
  • normal orientation approximately follows the provided normal spline

notice all the "approximately"s? yeah. that's the deal. moving on.

🌸 how the intersection actually works 🌸

to intersect the normal oriented curve, we perform a newton-raphson style intersection of a ray with a tensor product surface of:

  • a linear basis (perpendicular to the curve), and
  • a cubic BΓ©zier basis (along the curve)

we use a guide curve and its derivatives to construct the control points of that surface. the guide curve is defined by a sweep surface, defined by sweeping a line centered at the vertex spline location along the curve.

at each parameter value:

  • the half width of the line matches the radius spline
  • the direction matches the cross product of the normal from the normal spline and tangent of the vertex spline
⚠️ heads up, and this one actually matters: this construction does not work when the provided normals are parallel to the curve direction. for this reason, the provided normals should best be kept as perpendicular to the curve direction as possible. also, we further assume second order derivatives of the center curve to be zero for this construction β€” otherwise, very large curvatures occurring in corner cases can thicken the constructed curve significantly. so like, don't do that. πŸ™ˆ

ok that's part 1 done, keep reading bestie, there's more πŸ‘€

✨ Round Curves ✨

Okay bestie, when you're rolling with RTC_GEOMETRY_TYPE_ROUND_* round mode, Embree renders an actual real geometric surface for the curve. It's more expensive, yes, but it lets you do those extreme closeup views without the curve looking crunchy. πŸ’…

For the linear basis, round mode renders a cone that tangentially touches a start-sphere and an end-sphere. The start sphere gets rendered when no previous segments is indicated by the neighbor bits. The end sphere is always rendered, but the parts that lie inside the next segment are clipped away (if that next segment exists). This way a curve is closed on both ends and the interior will render properly as long as only neighboring segments penetrate into a segment. For this to work properly, it is important that the flags buffer is properly populated with neighbor information.

🚩 Not me warning you: if the flags buffer isn't properly populated with neighbor info, the whole closed-curve situation gets messy. Do not skip this.

For the cubic polynomial bases, round mode renders a sweep surface by sweeping a varying radius circle tangential along the curve. As a limitation, the radius of the curve has to be smaller than the curvature radius of the curve at each location on the curve. So if your radius is giving chonk, that's a no. 😭

The intersection with the curve segment stores the parametric hit location along the curve segment as u-coordinate (range 0 to +1). ✨

For flat curves, the v-coordinate is set to the normalized distance in the range -1 to +1. For normal oriented curves the v-coordinate is in the range 0 to 1. For the linear basis and in round mode the v-coordinate is set to zero. πŸ‘‘

In flat mode, the geometry normal Ng is set to the tangent of the curve at the hit location. In round mode and for normal oriented curves, the geometry normal Ng is set to the non-normalized geometric normal of the surface. No cap. πŸ’―

For multi-segment motion blur, the number of time steps must be first specified using the rtcSetGeometryTimeStepCount call. Then a vertex buffer for each time step can be set using different buffer slots, and all these buffers must have the same stride and size. For the Hermite basis also a tangent buffer has to be set for each time step and for normal oriented curves a normal buffer has to get specified for each time step. So many buffers, but we love a well-prepared setup. 🫢

Also see tutorials [Hair] and [Curves] for examples of how to create and use curve geometries. πŸ¦‹


πŸ’€ EXIT STATUS πŸ’€

On failure NULL is returned and an error code is set that can be queried using rtcGetDeviceError. So if you see NULL, go ask rtcGetDeviceError what's wrong. 😭


πŸ‘€ SEE ALSO πŸ‘€

  • [rtcNewGeometry]
  • [RTCCurveFlags]

That's the whole page, bestie! πŸ’– Source/version: Embree Ray Tracing Kernels 4, man page RTC_GEOME...PE_*CURVE(3). Date: the one in the footer, and it's iconic. Periodt. πŸ’…βœ¨

πŸ“¦ where this page comes from