Skip to main content

vcti-shader-pick

The pick shader feature: the fragment-stage pass that writes identifiers for the geometry at each pixel.

Overview

A viewer has to answer "what did the user just click on", and the only thing that knows which surface covers which pixel is the rasterizer. So the answer is produced the same way the picture is: by drawing. A pick pass renders the scene to an integer target and writes, at every pixel, identifiers for the geometry that landed there — plus how far away it was.

Reading one texel back then answers the click. What those identifiers mean is not this package's business: one names the atom, and the other is opaque, resolved by whoever owns the source model. That is what lets the same pass serve an element, a face, a triangle, or something not yet invented, without changing a line of shader source.

vcti-shader-pick ships the Slang that packs a texel, a Python mirror of the same layout for decoding a readback, and the ShaderDefinition saying what the feature is. Everything here is a declaration or fixed shader source; nothing compiles, runs, or reads back a shader.

Installation

pip install vcti-shader-pick

Requires Python 3.12, 3.13, or 3.14, matching vcti-shader-base. Nothing native is built on that path, and nothing native is built by [test] either — only the [gl] extra pulls a GL binding, and only on 3.14 does that compile from source for want of a cp314 wheel.

In requirements.txt

vcti-shader-pick>=1.0.0

In pyproject.toml dependencies

dependencies = [
    "vcti-shader-pick>=1.0.0",
]

Quick Start

What a build step binds

The feature declares two per-vertex identifiers and one integer target. Both identifiers must reach the fragment stage as flat varyings — GLSL ES requires that of every integer varying, and it is what keeps them exact:

from vcti.shader.pick import fragment_inputs, fragment_outputs, fragment_uniforms

inputs = fragment_inputs()
assert [attribute.name for attribute in inputs] == ["a_atomId", "a_pickIndex"]
assert inputs[0].semantic == "atom-id"

assert fragment_outputs()[0].name == "pickTarget"
assert fragment_uniforms() == ()

a_atomId is declared identically by another atom-aware feature, so the two merge into one attribute when they are composed. a_pickIndex is this feature's own, and is opaque to everything in the shader.

Reading a click back

The target is four unsigned 32-bit channels. Clear it to PICK_EMPTY before the pass, and draw the pass depth-tested, unblended and single-sampled — each of those is the caller's to get right, and each fails silently, which design.md explains. Then decode the texel under the cursor:

from vcti.shader.pick import PICK_EMPTY, decode, float_to_bits

nothing = decode((PICK_EMPTY, PICK_EMPTY, PICK_EMPTY, PICK_EMPTY))
assert nothing is None

hit = decode((7, 4_210_001, float_to_bits(12.5), 0))
assert hit is not None
assert hit.atom_id == 7
assert hit.pick_index == 4_210_001
assert hit.depth == 12.5

The depth is measured along the camera's forward axis — -z in view space, positive in front of the camera — not from the eye to the point. Give it the pixel's ray, and reconstruct_view_position returns the point:

from vcti.shader.pick import reconstruct_view_position

# A perspective ray: the origin is the eye, at the view-space origin.
point = reconstruct_view_position(12.5, (0.0, 0.0, 0.0), (0.3, 0.2, -1.0))
assert point == (3.75, 2.5, -12.5)

# An orthographic ray: parallel, each starting at its own point.
point = reconstruct_view_position(12.5, (2.0, 1.0, -0.1), (0.0, 0.0, -1.0))
assert point == (2.0, 1.0, -12.5)

The ray is yours to build — inverting the projection and knowing which way the viewport's Y runs are the caller's, because the caller owns the camera. What the depth means is this package's, which is why the inverse ships here rather than being a formula every caller re-derives. direction need not be normalized, and the origin need not sit on the camera plane: a ray unprojected to the near plane works as given.

A Euclidean distance from the eye would have cost the same channel and been wrong for any ray not starting at a single shared point.

Note that depth is reinterpreted from its bits rather than converted:

from vcti.shader.pick import bits_to_float

assert bits_to_float(float_to_bits(12.5)) == 12.5
assert float_to_bits(1.0) > 1_000_000_000        # the same bits read as an int

Selecting a pipeline

from vcti.shader.pick import DEFINITION

assert DEFINITION.id == "pick"
assert DEFINITION.role.value == "fragment"
assert DEFINITION.capabilities == ("pick",)

The texel

Channel Holds
R a_atomId
G a_pickIndex
B the view depth, as reinterpreted float32 bits
A reserved, written as zero

An integer target rather than a float one because identifiers must survive exactly: a float32 carries consecutive integers only to 2²⁴, above which one arrives as its neighbour and resolves to different geometry — a failure that looks like a mis-click rather than like corruption.

API surface

Name What it is
DEFINITION the ShaderDefinition a build step imports to compose this feature
SLANG_DIR the installed Slang directory
fragment_inputs, fragment_outputs, fragment_uniforms what a build step binds
decode, is_empty, Pick read a texel of a pick readback
bits_to_float, float_to_bits the bit reinterpretation, on its own
reconstruct_view_position a ray and a depth to the view-space point
PICK_EMPTY, PICK_RESERVED, CHANNELS the layout, as data
TARGET_NAME, TARGET_TYPE, TARGET_FORMAT the output's name, GLSL type, and the attachment it needs

Dependencies

vcti-shader-base is the only runtime dependency — declaring a feature is pure data. vcti-shader-compiler>=4.0.0 and numpy are test-only, and a separate gl extra adds the GL binding the shader tests need to execute rather than skip.

Documentation

If you want to… Read
Get started using the package Quick Start above
Understand the texel contract and the decisions behind it docs/design.md
Navigate or modify the source, including the Slang docs/source-guide.md

The full API reference is generated from the source docstrings and published in the unified VCollab docs.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vcti_shader_pick-1.0.0.tar.gz (34.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vcti_shader_pick-1.0.0-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

Details for the file vcti_shader_pick-1.0.0.tar.gz.

File metadata

  • Download URL: vcti_shader_pick-1.0.0.tar.gz
  • Upload date:
  • Size: 34.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for vcti_shader_pick-1.0.0.tar.gz
Algorithm Hash digest
SHA256 2178b918979576fe58dea75dfb6ddbd8d0205e4d4b5780285c9a6bd25c225d26
MD5 9f9a69ad40fac005022301be99687969
BLAKE2b-256 4010e4ac46e2c14c7b422bad7f40f865b7a17918c4ede333ea3ae98a39b9dba1

See more details on using hashes here.

Provenance

The following attestation bundles were made for vcti_shader_pick-1.0.0.tar.gz:

Publisher: release.yml on vcollab/vcti-python-shader-pick

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vcti_shader_pick-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for vcti_shader_pick-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 827109a90fa9944f6e3550b6eb59acaf7a28555f3f2c612577c7f0fb2c78d2cd
MD5 cef7b290392fe99dbe739f9fc979b1b8
BLAKE2b-256 ceb51dbecbfa804be8d746a09014ae7a01655d3c504581015706b22aa0d3abf0

See more details on using hashes here.

Provenance

The following attestation bundles were made for vcti_shader_pick-1.0.0-py3-none-any.whl:

Publisher: release.yml on vcollab/vcti-python-shader-pick

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page