Skip to content

VRSL DMX: Creating Custom DMX Shaders

AcChosen edited this page Feb 17, 2023 · 23 revisions

CG Includes & Properties

VRSLDMX.cginc

VRSL DMX is intended to be used more like a data protocol rather than a discrete set of shaders and tools. As such, any shader property can be controlled by DMX, whether it's lighting, vertex transforms, screen space, etc. If it's a shader property, it can be controlled by VRSL. This page discusses what you need to add to your custom shaders to enable VRSL DMX functionality.

Here is the video tutorial version of this page.

VRSL DMX works similarly to audiolink in that there is a single CGINC file that you need to include to start creating DMX-compatible world shaders. The file is called VRSLDMX.cginc and you can use it with this strip of code:

#include "Packages/com.acchosen.vr-stage-lighting/Runtime/Shaders/VRSLDMX.cginc"

This file contains a few instanced defined properties, standard defined properties, and some functions needed to read DMX from the grid node. Once you have this file in, there are some properties you may want to define first before you get started writing.

Example Code

It is recommended, if possible, to get the DMX data in the vertex shader rather than the fragment shader. This is because since there is no UV mapping involved when sampling, it is generally faster to sample the data in the vertex shader and send it to the fragment shader through the v2f struct. (If it's not possible, all of the functions will still work in the fragment shader as normal).

In this example, we created a custom parameter in the v2f struct with float4 finalColor : TEXCOORD1;, which simply stores the final emission color of our DMX shader. We then use isDMX() to determine if we are using DMX at runtime, and adjust the final output of finalColor with dmx data accordingly.

Shader "Unlit/VRSL-TutorialV2"
{
    Properties
    {
    _MainTex ("Texture", 2D) = "white" {}
    [Toggle] _EnableDMX ("Enable Stream DMX/DMX Control", Int) = 0
    [Toggle] _NineUniverseMode ("Extended Universe Mode", Int) = 0
    [Toggle] _EnableVerticalMode ("Enable Vertical Mode", Int) = 0
    [Toggle] _EnableCompatibilityMode ("Enable Compatibility Mode", Int) = 0

    [Toggle] _EnableStrobe ("Enable Strobe", Int) = 0
    [Header(Base Properties)]
    [HDR] _Emission ("Base DMX Emission Color", Color) = (1,1,1,1)
    _GlobalIntensity ("Global Intensity", Range(0,1)) = 1
    _FinalIntensity ("Final Intensity", Range(0,1)) = 1

    // Use this for World Shaders
    _DMXChannel ("Starting DMX Channel", Int) = 1  //raw dmx channel
            
    //Use these for Avatar Shaders
    // _Channel ("DMX Channel", Int) = 1
    // _Universe ("DMX Universe", Int) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                nointerpolation float4 finalColor : TEXCOORD1; //final output color from DMX
            };
            //Main CGINC File
            #include "Packages/com.acchosen.vr-stage-lighting/Runtime/Shaders/VRSLDMX.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            int _Channel, _Universe;

            //Channel Mappings
            //intensity = ch1
            //rgb = ch2, ch3, ch4
            //strobe = ch5

            //always read DMX data from the vertex shader
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);

                //Use this for Avatar Shaders
                //int dmx = ConvertToRawDMXChannel(_Channel, _Universe);

                //Use this for World Shaders
                int dmx = GetDMXChannel();

                float intensity = ReadDMX(dmx, _Udon_DMXGridRenderTexture); // 0-1
                float4 color = GetDMXColor(dmx+1);
                float strobe = GetStrobeOutput(dmx + 4);
                o.finalColor = (intensity * color) * strobe;
                
                o.finalColor = isDMX() ? o.finalColor : getBaseEmission();
                o.finalColor *= getGlobalIntensity() * getFinalIntensity();
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture, multiply by dmx final output.
                fixed4 col = tex2D(_MainTex, i.uv) * i.finalColor;
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }   
}

This way, our fragment shader only needs to grab i.finalColor to get the final output of our DMX color.

You do not need to only send color data, you can also send the dmx data values themselves if needed through the same method.

The Main Properties

These two are the two properties most important:

  • _DMXChannel ("Starting DMX Channel", Int) = 1

    • The _DMXChannel instanced property is where you'll set the starting DMX Channel for your DMX shader. DMX Channels start reading from 1, so 1 is the default. This is more of a "raw" channel number and should be used only for world shaders when its controlled by the VRStageLighting_DMX_Static script.
  • _Channel ("Starting DMX Channel", Int) = 1

    • An alternative to "_DMXCHannel" for avatar shaders. Use this to manually set the proper dmx512 channel.
  • _Universe("Starting DMX Universe", Int) = 1

    • An alternative to "_DMXCHannel" for avatar shaders. Use this in conjunction with _Channel to manually set the universe for the dmx512 channel.
  • [NoScaleOffset] _DMXGridRenderTexture("DMX Grid Render Texture (To Control Lights)", 2D) = "white" {}

    • The _DMXGridRenderTexture is where the DMX Grid render texture will actually go. You can find the render textures in: Packages/com.acchosen.vr-stage-lighting/Runtime/Textures/RTs. There you can find the horizontal, vertical, and legacy versions of the textures. I recommend using the DMXRTViewer-Interpolated-Color+Intensity prefixed render textures as those are the slighting smoothed versions of the RAW render texture counterparts.

These two properties are required for all world-based DMX shaders (for now).

The Extra Properties

Here are some extra properties that are not required but are still highly recommended to have in your shader to enable standard features:

  • [Toggle] _EnableDMX ("Enable Stream DMX/DMX Control", Int) = 0

    • A simple instanced int toggle property that can be used to enable/disable DMX functionality at runtime. This toggle can be controlled by the VRSL DMX Udon Script.
  • [NoScaleOffset] _DMXGridStrobeTimer("DMX Grid Strobe Texture (To Control Strobing)", 2D) = "white" {}

    • The render texture property that enables the strobing functionality of VRSL DMX. If you want to enable the strobing function, this texture is required. You can find them in the same render texture folder as the standard DMX render textures and they have the prefix DMXRTViewer-StrobeTimings-.
  • [Toggle] _NineUniverseMode ("Extended Universe Mode", Int) = 0

    • An instanced toggle for nine/extended universe mode. This toggle is required for your shader to be used in extended universe mode. This toggle can be controlled by the VRSL Control Panel.
  • [Toggle] _EnableVerticalMode ("Enable Vertical Mode", Int) = 0

    • An instanced toggle for vertical mode. This toggle is required for your shader to be used in vertical mode. This toggle can be controlled by the VRSL Control Panel.
  • [Toggle] _EnableCompatibilityMode ("Enable Compatibility Mode", Int) = 0

    • An instanced toggle for legacy mode. This toggle is required for your shader to be used in legacy mode. This toggle can be controlled by the VRSL Control Panel.
  • [Toggle] _EnableStrobe ("Enable Strobe", Int) = 0

    • An instanced toggle for strobe functionality. Use this property if you want to have an instanced toggle for the strobing feature. This toggle can be controlled by the VRSL DMX Udon Script.
  • [HDR] _Emission ("Base DMX Emission Color", Color) = (1,1,1,1)

    • An instanced base emission color. Multiple your DMX color output by this to change the shader's base starting color. This color can be controlled by the VRSL DMX Udon Script.
  • _GlobalIntensity ("Global Intensity", Range(0,1)) = 1

    • An instanced 0-1 float value intended to control the base intensity of your DMX color output. This value can be controlled by the VRSL DMX Udon Script.
  • _FinalIntensity ("Final Intensity", Range(0,1)) = 1

    • An instanced 0-1 float value intended to control the base intensity of your DMX color output. Functionally, this is no different than Global Intensity, however, it is included in case you needed the base intensity to be controlled by more than one external script or animation. This value can be controlled by the VRSL DMX Udon Script.

The DMX Functions

There are a few functions inside of the VRSLDMX.cginc script that you will need to use to enable DMX compatibility.

The general use case, however, will be to use the ReadDMX() function with GetDMXChannel() and _DMXGridRenderTexture as your input. This will read DMX data from the _DMXGridRenderTexture at the specified DMX channel. The general workflow is to increment the DMX channel value to traverse the grid and sample from nearby channels to create a "fixture profile" for your shader that you can then mimic inside of the DMX software of your choosing.

The Main Functions

  • ReadDMX(uint DMXChannel, sampler2D _Tex)

    • Returns a 0-1 value from a specified DMX channel from the provided texture. Use this with GetDMXChannel() as input. You can increment GetDMXChannel() to get nearby channels to sample from for different properties. It is recommended to use the standard _DMXGridRenderTexture as the texture input.
  • GetDMXChannel()

    • Returns the instanced DMX channel property of your shader as a uint. Use this for world shaders.
  • ConvertToRawDMXChannel(int chan, int universe)

    • Returns a conversion of the standard dmx channel and universe properties that you use for avatar shaders. Input the _Channel and _Universe properties to convert it to its "raw" dmx channel format. This is the avatar shader equivalent for GetDMXChannel()

The Standard Functions

  • GetStrobeOutput(uint DMXChannel)

    • Returns an alternating 0-1 value with the rate controlled by the specified DMX Channel value. Multiply the output of this function by any other value to make it strobe from its current value to 0 and back.
  • GetDMXColor(uint DMXChannel)

    • This function callsReadDMX() 3 times, at 3 consecutive DMX increments (DMXChannel, DMXChannel + 1, DMXChannel + 2) to get 3 DMX channels for red, green, and blue. It then combines those values into a color (float4) that it returns.

The Property Functions

  • isDMX()

    • Returns the instanced toggle property, _EnableDMX.
  • getBaseEmission()

    • Returns the instanced color property, _Emission.
  • getGlobalIntensity()

    • Returns the instanced float property, _GlobalIntensity .
  • getFinalIntensity()

    • Returns the instanced float property, _FinalIntensity.
  • isStrobe()

    • Returns the instanced toggle property, _EnableStrobe.
  • getNineUniverseMode()

    • Returns the instanced toggle property, _NineUniverseMode.

Adding it to the VRSL DMX Script

To add your custom DMX shader/material to the VRSL DMX Script, VRStageLighting_DMX_Static, you will need to add the script to a game object, add your material to your target mesh object, and then add that object's mesh renderer to the obj renderers array at the bottom of the script. This will enable basic instancing functionality as well as allow your custom shader to appear as a fixture in the VRSL Control Panel.