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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2178b918979576fe58dea75dfb6ddbd8d0205e4d4b5780285c9a6bd25c225d26
|
|
| MD5 |
9f9a69ad40fac005022301be99687969
|
|
| BLAKE2b-256 |
4010e4ac46e2c14c7b422bad7f40f865b7a17918c4ede333ea3ae98a39b9dba1
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vcti_shader_pick-1.0.0.tar.gz -
Subject digest:
2178b918979576fe58dea75dfb6ddbd8d0205e4d4b5780285c9a6bd25c225d26 - Sigstore transparency entry: 2871105379
- Sigstore integration time:
-
Permalink:
vcollab/vcti-python-shader-pick@de24e116153f3ac582cba64d40c3f68fd0acc30f -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/vcollab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@de24e116153f3ac582cba64d40c3f68fd0acc30f -
Trigger Event:
push
-
Statement type:
File details
Details for the file vcti_shader_pick-1.0.0-py3-none-any.whl.
File metadata
- Download URL: vcti_shader_pick-1.0.0-py3-none-any.whl
- Upload date:
- Size: 14.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
827109a90fa9944f6e3550b6eb59acaf7a28555f3f2c612577c7f0fb2c78d2cd
|
|
| MD5 |
cef7b290392fe99dbe739f9fc979b1b8
|
|
| BLAKE2b-256 |
ceb51dbecbfa804be8d746a09014ae7a01655d3c504581015706b22aa0d3abf0
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vcti_shader_pick-1.0.0-py3-none-any.whl -
Subject digest:
827109a90fa9944f6e3550b6eb59acaf7a28555f3f2c612577c7f0fb2c78d2cd - Sigstore transparency entry: 2871105954
- Sigstore integration time:
-
Permalink:
vcollab/vcti-python-shader-pick@de24e116153f3ac582cba64d40c3f68fd0acc30f -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/vcollab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@de24e116153f3ac582cba64d40c3f68fd0acc30f -
Trigger Event:
push
-
Statement type: