Skip to content

Feat/raycast skeleton - #227

Open
seankmartin wants to merge 33 commits into
masterfrom
feat/raycast-skeleton
Open

Feat/raycast skeleton#227
seankmartin wants to merge 33 commits into
masterfrom
feat/raycast-skeleton

Conversation

@seankmartin

@seankmartin seankmartin commented Aug 24, 2026

Copy link
Copy Markdown

Summary:
This PR adds two new modes to 3D skeleton rendering, cylinders, and cylinders and balls. These are based on a two step process, the first is in the vertex shader - to find the screen space quad which bounds the object. Then in the fragment shader, do an analytic raycast, so solve whether the ray hits and where it hits by regular geometry and algebra (as opposed to a ray march). This rendering mode will be more expensive than lines/lines and points but since each cylinder and each ball is represented by two triangles it still be performant enough in most cases. Here is a drawing of the cases:

Sphere:
image

Cylinder (non tapered) - unused, replaced by cone due to depth shrinking and needing to support nodes with variable radius, kept for illustration as likely easier to understand than the cone:
image

Cone (we still do an axial test similar to the cylinder shown above, and a cheaper test before the quadratic - but this is to illustrate the core test for a hit):
image

We needed more varyings in the shaders to allow this, and since user defined attributes also become varyings we packed the varyings where possible.

Other changes

Line clipping

Cylinders and balls handle avoiding self intersections. But lines and points did not. To allow the rendering modes to be more consistent, this introduces a method for lines to be aware of points drawn at the end of the lines.

old
image

new
image

Reorder skeleton drawing to reduce shader switching

The drawing loop before this PR was for each skeleton:

  1. Set edge shader program
  2. Draw lines for a skeleton
  3. Set points shader program
  4. Draw points for a skeleton

This causes a shader program switch every skeleton, and can be worse for cylinders and balls because the shaders are more complex and because the rendering takes longer overall so this potential hit from program switching is more noticeable since the leeway is reduced. This proposes to change the drawing loop to:

  1. Set edge shader program
  2. Loop over skeletons and draw lines/cylinders
  3. Set point shader program
  4. Loop over skeletons and draw points/balls

Which is two program sets regardless of the number of skeletons. The skeletons are not depth sorted so drawing all the edges first wouldn't present any issues.

This is to allow to avoid intersecting points at the ends of the lines
Old calls just pass gl fragcoord.z as this depth
These are screen space camera facing quads where geometry hit/miss, depth, and lighting
contribution are determined via raycast (and generally intended to be an analytic
raycast for speed not a raymarch)
The old cylinder used an OBB but the new one doesn't and AABB is faster and simpler
This is formed by:
1. Find AABB of the sphere. Project the 8 corners of the AABB into screen space and then draw 6 verts (two tris)
   to represent the quad in screen space which covers the AABB as the vertex shader. For now each invocation
   redoes the projection, which could possibly be avoided but seems a small win. Rest happens in frag shader.
2. Find the ray from the eye (camera) to the fragment, in model space.
3. With this ray, see if a part of the ray forms a chord through the sphere. To do this,
   get the shortest distance from the ray to the sphere center (which is the
   perpendicular) and see if the right angled triangle that would be formed between a
   radial line from the sphere center and the perpendicular could exist.
4. If the above holds, so there is a chord, ensure that the ray doesn't need to travel
   backwards behind the near plane to form the chord (which would be negative hit distance)
5. If no hit, discard. If hit, continue with regular depth and lighting calculation
   based on the hit distance.
the analogy here is similar to when a camera
in a game goes behind a wall, often you
then don't render the wall because you're inside the wall.
We'd have the same issue of the camera getting trapped inside a sphere if the
sphere was big enough.
Follows a very similar process to the AABB, where we provide the parameterization of the
OBB to the vertex shader, and construct the projection to clip space of the model space
OBB to find a screen space quad which covers the screen space (depth clipped) OBB.
However the calculations are a bit more complex since we have a OBB instead of an AABB.
Adds a raycast cylinder building on the OBB form in the generic file.

The vertex shader setup is quite small with the generic helper.
For the fragment shader we split the problem into two parts. We start by
considering
the line that represents the primary axis going through the center of the cylinder, which we
have from its two endpoints.
Then for this axis line, we find the plane perpendicular to the axis line. We check in that
plane in a very similar manner to whether in our sphere code the radial line and the
perpendicular could form a right angled triangle or not to see if we have a hit with the
cylinder in the plane. If we do, then we just need to check if the hit lies outside the
ends of the cylinder when viewed along the main axis running through the center of the
cylinder. If it is within a sufficient distance, we can consider the ray to be a
hit. To split into the axial component and the planar component we use the dot
product.
The hit point is fairly simple from there in the plane, and for the normal, since we constructed the
plane to be perpendicular to the axis line, we can use the information about the
orientation of the plane to determine the normal as the normal will be in the
same direction as the in-plane radial line starting from the cylinder central axis line.
Should but perf improvements, which is helpful with the cylinder rendering
Also combines the rendering tests together
Comment thread src/skeleton/frontend.ts Outdated
Comment thread src/webgl/lines.ts Outdated
was accidentally double multiply alpha
also fixes the fact that the edge shader had no emitRGBA, which caused both shaders to
fail so you'd see nothing. The node and edge shaders now run independently so if either
compile it works
In the future we should likely allow some ability in skeleton shaders to easily indicate
that something is targetted at lines, and something else at nodes - but this PR is
already very large and I don't want to blow out the scope too far.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants