# Volumetric Spotlight 2 â?" Construct 3 layer effect
A physically-based volumetric spotlight beam for 3D layers: cone-clipped ray
march, Beer-Lambert transmittance, anisotropic scattering, dust, and
flashlight-style surface tint/darkness. Companion to the **Frag Light V2**
surface lighting addon (use both with the same light values for the full look).
`src/` is the frozen v1 addon (kept for old projects); everything below is
about **v2** (`src-v2/`, addon id `mikal-volumetric-spot-v2`).
## Install
Install `dist/mikal-volumetric-spot-v2-*.c3addon` via the C3 addon manager
(or point developer mode at the `src-v2/` folder). Add the effect to the
**3D layer that contains your scene** â?" it is a layer effect, not an object
effect. It reads the depth buffer, so the layer must render with depth.
## Setup â?" feed the camera every tick
The shader rebuilds each pixel's world ray from camera uniforms; they must
match C3's real camera every tick or the beam won't line up with geometry.
Add an *Every tick* event with *Set layer effect parameter*:
| Param | Value |
|---|---|
| 0, 1, 2 | `3DCamera.CameraX`, `.CameraY`, `.CameraZ` |
| 3, 4, 5 | `3DCamera.LookVectorX`, `.LookVectorY`, `.LookVectorZ` |
| 6 | camera FOV in **radians** (default 0.7854 = 45A°, no expression exists â?" use your configured value) |
| 7, 8, 9 | `3DCamera.UpX`, `.UpY`, `.UpZ` |
Every parameter's editor label ends with its event index (`Camera X - 0`).
## The light
Position 24â?"26, direction 27â?"29, cone half-angle **in degrees** at 31.
**Z sign rule**: with the C3-native camera feed above, enter light Z (26) and
direction Z (29) **negated** â?" a light 100 units above the floor is `-100`;
a light on the floor plane is `0`. If the beam appears mirrored or buried
behind geometry, check these signs first.
Starting values: Density 0.01 (beam brightness), Extinction 0.0002â?"0.001
(background absorb â?" subtle!), Intensity/Exposure for overall punch.
## Flashlight look (params 41â?"43)
- **Extinction Tint (41)** â?" absorption takes the light's color, so bright
backgrounds tint instead of staying white. Needs Extinction > 0.
- **Outside Darkness (42)** â?" everything the beam doesn't hit goes black
(judged at the surface each pixel shows; backfaces count as not hit).
- **Surface Tint (43)** â?" surfaces the beam lands on take the light color,
shaded by a simple depth-based normal; empty space is never colored.
All three at 0 by default. Darkness + Surface Tint at 100% = full
flashlight-in-the-dark.
## Scripting sample
`scripting/volumetricLight.ts` is a small helper â?" copy it into your C3
project's Scripts folder. It sets parameters by index and **handles the
light-Z sign flip for you**, so you pass ordinary z-elevations:
```ts
import { VolumetricLight } from "./volumetricLight.ts"; // ".js" from plain JS
let light: VolumetricLight;
runOnStartup(async runtime => {
runtime.addEventListener("beforeprojectstart", () => {
light = new VolumetricLight(runtime.layout.getLayer("Layer 0"));
light.setPosition(200, 100, 300); // 300 above the floor
light.setConeAngle(25); // half-angle, degrees
runtime.addEventListener("tick", () => {
const target = runtime.objects.Sprite.getFirstInstance();
if (target) light.aimAt(target.x, target.y, target.totalZ);
});
});
});
```
The constructor finds the effect by name (default `"VolumetricSpotlight2"` â?"
pass yours if renamed). `aimAt` reads the light's current position from the
effect, so it works no matter where the position was set. The camera params
(0â?"9) still need the event-sheet feed above. `scripting/aimAtSprite.ts` is
this same sample as a standalone file.
## Debug modes (param 22)
1 depth bands A· 2 world grid A· 3 scatter only A· 4 cone segment A· 6 phase term.
Each replaces the output â?" use to verify camera/depth alignment first.