DMX in Your Own Shaders - towneh/vrsl-urp GitHub Wiki
DMX in Your Own Shaders
A world's own shader can react to the desk: a screen that takes its colour from a fixture's
channels, a logo that strobes with the rig, a laser of your own. This page covers the include
that reads the DMX, the values a fixture component hands your shader, and porting a shader
written against VR Stage Lighting's VRSLDMX.cginc.
The include
#include "Packages/town.mr.vrsl-urp/Runtime/Shaders/VRSLDMX.hlsl"
Put it after URP's Core.hlsl, or on its own (it includes Core.hlsl and core's Color.hlsl
itself). It reads what the DMX manager reads: the video grid the manager's DMX Input is set
to, Horizontal, Vertical or Legacy, or the channels a stream sends under Stream data. A
shader using it stays in step with the package's own fixtures, whichever input the world is on.
It declares only the manager's own globals, every one named _VRSLU_… or _VRSLDMX…. It
declares no material properties, no UnityPerMaterial and no instancing buffer, so your shader
keeps its own layout and stays SRP Batcher compatible, and it sits beside URP's
LitInput.hlsl and DeclareDepthTexture.hlsl in either order.
The package's other includes, under Runtime/Shaders/Shared/, are its own. They declare VRSL's
material properties and the depth texture, so they won't compile beside URP's Lit inputs, and
they change between releases. Use VRSLDMX.hlsl.
Under Stream data the channels come from a GPU buffer, which needs shader model 4.5. Add
#pragma target 4.5 to the pass. Below it the reads take the grid only, which is black under
Stream data.
The four reads
Every read takes an absolute channel number, the one a fixture component writes as
_DMXChannel (below), and returns a value from 0 to 1. Each samples mip 0 of a grid texture,
or loads from a buffer under Stream data, so all four work in the vertex stage as well as the
fragment stage, and a shader can move geometry with them.
| Function | Returns |
|---|---|
half VRSLDMX_Channel(uint channel) |
The channel's value. On a grid this is the smoothed value, which eases from one value to the next over a few frames, as the package's fixtures do |
half VRSLDMX_ChannelRaw(uint channel) |
The channel's value as it arrived, before smoothing: it jumps in the frame the desk changes it. Under Stream data, which has no smoothing, the same as VRSLDMX_Channel |
half VRSLDMX_Strobe(uint channel) |
1 open, 0 closed, for a strobe channel. Held open while the channel is below 0.2 and while strobe is switched off on the manager |
half4 VRSLDMX_Color(uint channel) |
A 13-channel fixture's colour: red, green and blue (+7, +8, +9) times the dimmer (+5), alpha 1. Pass the fixture's first channel |
VRSLDMX_Color applies no brightness of its own. Multiply by whatever intensity your shader
uses.
Pick per channel which of the first two to read. A dimmer read smoothed fades where the desk
cuts; a speed that multiplies _Time.y should be read raw, or every ease sweeps the pattern
through the whole change in speed times the time since the world loaded.
Functions starting vrsldmx_ (lower case) are the package's own and may change.
What a channel at the edge reads
A grid carries a fixed number of channels: Horizontal 1560 (three universes), Vertical 871, Legacy 650. A channel past that, and channel 0, reads as a channel at 0: value 0 and strobe open. Under Stream data a channel past what the source sends reads the same way.
A universe is 520 flat channels: 512 slots and 8 of padding, because the grid is 13 channels wide and 512 doesn't divide by 13. Universe 2's channel 1 is flat channel 521. The fixture component does this sum for you.
The reads take the standard encoding. A grid carrying nine universes, three per colour component, isn't something the public reads follow.
Driving it from a fixture component
VRSLDMXStatic writes its settings onto every renderer in its Obj Renderers
list through a material property block, whatever shader the renderer has. Put your renderer on
that list, declare the properties you use, and your shader gets the fixture's address and
switches. The ones a DMX shader usually wants:
| Property | What it holds |
|---|---|
_DMXChannel |
The fixture's first channel, absolute: from Dmx Channel and Dmx Universe, or the sector under Use Legacy Sector Mode. Pass it straight to the reads |
_EnableDMX |
1 when Enable DMX Channels is on. With it off, the fixture is meant to show its own colour rather than the desk's |
_EnableStrobe |
1 when Enable Strobe is on |
_Emission |
Light Color Tint, the fixture's own colour (HDR) |
_GlobalIntensity |
Global Intensity, 0 to 1, for animating or scripting brightness |
_FinalIntensity |
Final Intensity, 0 to 1, the ceiling on the above, for a viewer's brightness setting |
_NineUniverseMode |
1 when Nine Universe Mode is on. The public reads don't use it |
It writes the rest of its settings too (_PanInvert, _TiltInvert, _EnableSpin,
_EnableFineChannels, _ProjectionSelection, _FixtureRotationX, _FixtureBaseRotationY,
_EmissionDMX, _ConeWidth, _ConeLength, _MaxConeLength, _MaxMinPanAngle,
_MaxMinTiltAngle, _LegacyGoboRange), for shaders that want them.
Five things to watch:
- With Final Intensity Component Mode on,
_FinalIntensitydepends on the renderer's GameObject name: one whose name contains "Volume" or "Flare" gets Final Intensity Volumetric, "Project" gets Final Intensity Projection, and anything else Final Intensity Fixture. - The block replaces the renderer's whole property block each time the component updates, so a value your own script set on the same renderer through a property block is lost.
- A renderer with a property block draws outside the SRP Batcher, even though the shader is compatible. That's the cost of driving it from the component.
- The switch is
_EnableDMX. A shader ported from one that switched on_EnableOSCshould read_EnableDMXinstead. - Declare
_DMXChannelas a float and take(uint) round(_DMXChannel), which reads it correctly whichever way Unity stores it.
A fragment that glows in the fixture's colour, strobes with it and follows its intensity settings:
CBUFFER_START(UnityPerMaterial)
float _DMXChannel;
float _EnableDMX;
float _EnableStrobe;
float4 _Emission;
float _GlobalIntensity;
float _FinalIntensity;
CBUFFER_END
half3 Glow()
{
uint first = (uint) round(_DMXChannel);
half3 colour = _EnableDMX > 0.5 ? VRSLDMX_Color(first).rgb : _Emission.rgb;
half strobe = _EnableDMX > 0.5 && _EnableStrobe > 0.5 ? VRSLDMX_Strobe(first + 6) : 1.0;
return colour * strobe * _GlobalIntensity * _FinalIntensity;
}
The 13-channel layout the offsets come from is in the Fixture Configuration Reference: pan +0 and +1, tilt +2 and +3, zoom +4, dimmer +5, strobe +6, red, green and blue +7 to +9, gobo spin +10, gobo select +11 and movement smoothing +12.
Porting from VR Stage Lighting
A shader written against VR Stage Lighting's VRSLDMX.cginc changes its include line (the
package path, and .hlsl for .cginc) and its reads:
| VR Stage Lighting | Here |
|---|---|
ReadDMX(ch, _Udon_DMXGridRenderTexture) |
VRSLDMX_Channel(ch) |
ReadDMX(ch, …RAW…), the unsmoothed grid |
VRSLDMX_ChannelRaw(ch) |
GetStrobeOutput(ch) |
VRSLDMX_Strobe(ch). The old function also held strobe open when _EnableDMX or _EnableStrobe was off; check those in your shader, as the example above does |
GetImmediateStrobeOutput(ch) |
VRSLDMX_Strobe(ch) |
GetDMXColor(ch) |
Not the same function. The old one read ch, ch + 1 and ch + 2 with no dimmer: write half3(VRSLDMX_Channel(ch), VRSLDMX_Channel(ch + 1), VRSLDMX_Channel(ch + 2)). VRSLDMX_Color takes the fixture's first channel and applies the dimmer |
ConvertToRawDMXChannel(chan, universe) |
Not needed: the fixture component writes the absolute channel |
What doesn't carry over:
- Texture arguments and the
_Udon_DMXGrid*textures. The manager publishes its own grid globals and the reads find them; a material no longer points at a grid texture. Under Stream data there is no grid at all, and the reads still work. _EnableCompatibilityModeand_EnableVerticalMode. The manager's DMX Input chooses the layout for every shader at once.- The
Propsinstancing buffer and the getters on it (isDMX(),getGlobalIntensity()and the rest). Declare the properties you read in your ownUnityPerMaterial. - The
IFmacro. Use the ternary operator. - Pan and tilt (
GetPanValue,GetTiltValue,calculateRotations) andReadDMXRawon the timer textures. Movement and the spin and strobe phases aren't part of the include. A shader with its own movement channels reads them as plain values withVRSLDMX_Channel.
A shader that worked out cells on the grid itself, or applied its own gamma step to a read, gets the package's decode instead: every channel from its own cell, as the package's fixtures read it. Expect small differences from the old maths.
Grid Textures and the raw grid
The raw read reads the Raw slot of the manager's Grid Textures asset, the grid as it arrives from the video. The package's own asset fills it for all three layouts. In a Grid Textures asset of your own, fill Raw with the render texture your video lands in; left empty, the raw read gets the smoothed values instead. A camera that renders into a Raw texture gets no VRSL lighting, as one rendering into the other grid textures doesn't.