Search Unity

Combine 2 greyscale textures to output alpha channel based on combination of both

Discussion in 'Shaders' started by guto-thomas, Oct 7, 2015.

  1. guto-thomas

    guto-thomas

    Joined:
    Mar 12, 2012
    Posts:
    25
    Hi everyone,

    The title may seem a little bit weird and badly explained, but what I'm trying to do is the following:

    I have two textures, one represents the areas that are currently active and the other represents the areas that have been explored, but aren't necessarily active. What I want to do is to combine both, but I want to change the alpha value of the explored area to be something like 0.5 and force the active areas to be fully transparent. Using pictures:
    exploredAreas.png

    This image represents explored areas. I want the alpha to be controlled. In other words, I want to change the black to a greyish colour and generate alpha based on that.

    activeAreas.png

    This image represents active areas. I want to multiply this by the explored areas, so that active areas are fully transparent.

    outputTexture.png

    This is the texture output. I combined both, generated alpha based on the amount of black and multiplied the final texture by black (The white is actually alpha on this image).

    I was thinking of something like: _Color * ((exploredAreas + float4(1,1,1,1) * greyishFactor) * activeAreas);

    Hope somebody can point me in the right direction here.

    Thank you very much! :)
     
    Last edited: Oct 7, 2015
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Learn the wonders of lerp, min, and max.

    Code (csharp):
    1. // blend between transparent grey and opaque black based on the explored areas alpha
    2. fixed4 color = lerp(fixed4(0.5,0.5,0.5,0.5), fixed4(0,0,0,1), exploredareas);
    3.  
    4. // Force the active area to be completely transparent by choosing the lowest alpha value between the previous lerp and the active area. We don't care about color since it's going to be transparent.
    5. color.a = min(color.a, activearea);
    6.  
    7. // you could also just multiply the alpha by the active area texture instead for a slightly different look. Do one or the other, or both if you really want to.
    8. // color.a *= activearea;
    9.  
     
  3. guto-thomas

    guto-thomas

    Joined:
    Mar 12, 2012
    Posts:
    25
    Hi bgolus,

    Thank you very much for your answer. I tried and it works just fine! :)

    What I am wondering now is how do I tell the shader to use greyscale as alpha, instead of the regular alpha channel.

    Do I need to add a Blend property with a specific Src and Dst alpha? Which one would work best? I tried a lot of combinations but none of them worked the way I wanted. It looks like the parts that should be fully opaque remain with some kind of transparency and I can see what's below them.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    When you read a texture in a shader you just get a four component vector, ie: "fixed4 color = texture2D(_MyTexture, i.uv);". You can just access a single component of that vector with "x y z w" or "r g b a", ie: "fixed colorAlpha = color.a" or "fixed colorAlpha = color.w".

    Though I assume you're generating the mask textures from in script using Texture2D() and SetPixels()? Is there a reason you're not using TextureFormat.Alpha8 for them? Or pack both masks into a single RGB24 (which is a little more memory as it has an extra channel but slightly faster shader as you only need one texture lookup).

    As for the blend mode, you probably want your basic alpha blend.
    Code (csharp):
    1. Blend SrcAlpha OneMinusSrcAlpha
    Unless you're doing this as a post process shader, in which case you should be doing the blending in-shader an
    Code (csharp):
    1. float4 original = tex2D(_MainTex, i.uv);
    2. fixed4 newColor = // the mask color stuff
    3. return float4(lerp(newColor.rgb, original.rgb, newColor.a), original.a);
     
    Last edited: Oct 8, 2015
  5. guto-thomas

    guto-thomas

    Joined:
    Mar 12, 2012
    Posts:
    25
    Thank you for your explanation and your time. :)
    So about the Alpha8 type, there is no particular reason I wasn't using it. In fact, I just changed to this type.
    I created the shader and it should be working fine, but there's no Alpha at all. I can see the textures are being filled in real time. Below is my code and a SS.
    I can't use just one texture to do this. It would take a huge effort computationally speaking, so I thought a shader could do the job faster, even though I have to feed two textures.

    Code (CSharp):
    1. Shader "Custom/FogOfWar" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _HalfFog ("Albedo (RGBA)", 2D) = "white" {}
    5.         _NoFog ("Albedo (RGBA)", 2D) = "white" {}
    6.         _InactiveAreaFogStrength ("Inactive Areas Fog", Range (0, 1)) = 0.5
    7.     }
    8.     SubShader {
    9.         Tags { "RenderType"="Transparent" "Geometry"="Geometry-10" }
    10.          Blend SrcAlpha OneMinusSrcAlpha
    11.        
    12.         CGPROGRAM
    13.         // Physically based Standard lighting model, and enable shadows on all light types
    14.         #pragma surface surf Standard fullforwardshadows
    15.         //#pragma surface surf NoLighting
    16.  
    17.         // Use shader model 3.0 target, to get nicer looking lighting
    18.         #pragma target 3.0
    19.  
    20.         sampler2D _HalfFog;
    21.         sampler2D _NoFog;
    22.  
    23.         struct Input {
    24.             float2 uv_HalfFog;
    25.             float2 uv_NoFog;
    26.         };
    27.  
    28.         fixed4 _Color;
    29.         float _InactiveAreaFogStrength;
    30.  
    31.         void surf (Input IN, inout SurfaceOutputStandard o)
    32.         {
    33.             float halfFogAlpha = lerp (0, _InactiveAreaFogStrength, tex2D(_HalfFog, IN.uv_HalfFog).a);
    34.             float noFogAlpha = tex2D(_NoFog, IN.uv_NoFog).a;
    35.             o.Albedo = _Color.rgb;    
    36.             o.Alpha = noFogAlpha * halfFogAlpha;
    37.         }
    38.         ENDCG
    39.     }
    40.     FallBack "Diffuse"
    41. }
    42.  
    Screenshot 2015-10-08 03.04.13.png

    In this picture you can see the textures (type is Alpha8). I'm passing them as Color structure with r, g and b as being 0 and alpha varying. Is that correct? it looks like it is, though no good output.

    Thanks for your help! :)
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You probably shouldn't be using a surface shader for this. Surface shaders are generally for making it easier to use various lighting models (lambert, blinn, standard). This is an effect that is intentionally unlit so it's just wasted computation.

    However if you just want to get something working the reason you're not seeing anything is by default surface shaders are opaque and disregard any alpha values that are set. See http://docs.unity3d.com/Manual/SL-SurfaceShaders.html

    Try adding alpha or keepalpha to the end of your #pragma surface line, then start reading up on vert / frag shaders.
    http://docs.unity3d.com/Manual/SL-ShaderPrograms.html
     
  7. guto-thomas

    guto-thomas

    Joined:
    Mar 12, 2012
    Posts:
    25
    Hi bgolus,

    Thank you very much! It worked, though the areas that should be completely opaque are having a bit of transparency*. Don't know why... To be honest I really don't know what I should be using if not the function surf.

    *I noted that this is due the fact that when I initialize a Texture2D, all the pixels are a bit transparent. The pixel 0, 0 of a just initialized Alpha8 Texture2D gives me RGBA(1.000, 1.000, 1.000, 0.804). Is there a way to get, let's say, a texture with all the pixels being opaque white without having to loop through the whole thing? For now I solved this by multiplying the alpha by a constant in my shader, which should not bring performance down by a noticeable amount.

    Just adding "alpha" at the end of the #pragma line did the job. :)
     
    Last edited: Oct 9, 2015
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I have no idea how they initialize textures, but it shouldn't be too slow to set the whole image using SetPixels32();

    edit: the pixels values are considered "undefined", so basically it could be anything depending on the system it's created on.
     
    Last edited: Oct 9, 2015