put mask in each color value range 0-255

hey I’m trying to get a mask value (0 or 1) over the whole range of an RGB channel. so R would have 255 mask G would have 255 and so on.

i ran into 2 problems:

  1. precision, seem that R*255 doesn’t output the correct value for most values.
    i have this line to check if there a mask on a certain value
half channel = (round(mask.r*255.0)/_Divider == 1.0);

where _Divider is the color value i used
for example if i draw some thing with color r=100 g=0 b=0, _Divider = 100 but it doesn’t show, i have to put it to 99 to make it show while the same with r=101 and _Divider set to 101 works fine.


problem 2:
obviously i don’t want to flood my shader with 765 divider values and channels in the shader to get the mask values :slight_smile: any ideas how i could put this into one algorithm?

I’m just testing if it’s even possible to do atm.

full shader

Shader "Custom/DecalLayers" {
    Properties {
        _Divider("divide by", int) = 100
        _Divider1("divide by", int) = 101
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _MaskTex ("Mask (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _MaskTex;

        struct Input {
            float2 uv_MainTex;
        };

        int _Divider;
        int _Divider1;
        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            float4 mask = tex2D (_MaskTex, IN.uv_MainTex);
            half channel = (round(mask.r*255.0)/_Divider == 1.0);
            half channel1 = (round(mask.r*255.0)/_Divider1 == 1.0);
            //half channel = (round((mask.r+1.0/255.0)*256.0)/_Divider == 1);
            //half channel1 = (round((mask.r+1.0/255.0)*256.0)/_Divider1 == 1);
            o.Albedo = lerp(c.rgb, _Color, (channel+channel1));
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

What format is the texture asset? For this to work you’ll need to have the texture be uncompressed, bypass sRGB, and either Alpha8 or R8 formats for a single channel. Alternatively it could be RGB24 or RGBA32, but those are quite wasteful if all you need is one channel for this example at least.

If the above is true the values should come out as close to perfect as floating point math allows.

Maybe try:

half channel = (int)(mask.r * 255.0 + 0.5) == _Divider ? 1 : 0;

aha ok that kind of seems to work! Thanks again @bgolus

void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            float4 mask = tex2D (_MaskTex, IN.uv_MainTex);
            for(int i=1; i<=255; i++)
            {
                _Channel += (int)(mask.r * 255.0 + 0.5) == _Divider[i] ? 1 : 0;
            }
            o.Albedo = lerp(c.rgb, _Color, _Channel);
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }

strangely enough only 255 itself doesn’t seem to work?


also for my second problem setting an array of values seems to do it. is there a better way then a for loop to go through it?

One other thing, you’ll want your textures to be point sampled. Otherwise you’ll be getting intermediate values from the bilinear texture sampling.

As for problem #2, there are a few ways to go about it to make it faster. However I’m more curious what your end goal is rather than what you’re trying to do right now. With what your example code is doing you could replicate your loop with just this one line:

fixed channel = mask.r > 0.0 ? 1.0 : 0.0;

Or even faster, this:

fixed channel = saturate(mask.r * 255.0);

If any pixel is a value of the mask texture greater than 0 then channel = 1. But if that’s works for you then there’s no reason not to just use a texture with only 0/255 and 255/255 values and keep the bilinear filtering. I suspect there’s more to this than your example, but I have no idea why you would ever need 765 masks in a single shader. If that’s really what you need, then there’s kind of no getting around needing to pass an array of that many values to the shader.

If the end goal is to be able to arbitrarily assign colors to different parts of the shader based on a mask then you could use a look up texture for each mask channel. Have a 3x256 texture with color values assigned to match the color you want for each mask value, and remap the mask to be a UV look up into that texture.

But with out knowing what you’re doing I’m not sure why you don’t just use a tint texture with the wanted color values already assigned to each pixel which is way faster than this extra indirection, plus it would let you have 16 million colors and not just 765.

the goal is to be able to gradually build up masks or “animate” masks, for example a player getting wet from rain or a car getting dirty from mud. any case where i need a lot of masks to be turned on/off separately. The idea is to add intensity for a mask in two other channels i think, so R and G will be masking values and B and A will be their respective intensity values.
The masks will be sampling from and other texture maybe scroll uv’s in cases like the rain.
For now i don’t have a clue as to how expensive it is or if there are better ways, this is the only thing i could come up with.

Generally speaking you don’t need that many masks for these kinds of effects. Getting too granular with it means the inherent inaccuracy of trying to match a pre-drawn mask to “hit” locations becomes more apparent. My general recommendation for something like a player getting wet is to have a gradient that goes roughly from their feet to their head and just use that with some heavy slop. If the character gets water on their head or goes horizontal at all in water just make their entire body wet. This is how most AAA games work. There may be a separate “drying pattern” gradient texture they can use to fade it out, but that’s about it. Same goes for most racing games, dirt, snow, etc. tend to just build up over the whole vehicle and not super localized. If you want more detail than that, or you need really accurate splattering then a procedural approach with render textures and projected splatter textures may be a better approach, or a per vertex / low res “coverage” texture with some kind of higher detail noise on top to hide the low resolution shapes.

@bgolus thanks for the tips, I’m using it for a dynamic injury system, where the mask needs to be activated at the point of impact (punches are targeted, so it needs to be precise).