3

I want to recreate the field of view effect in the game Among Us in Unity by using the Universal Pipeline and 2D Lighting system.

But when I use 2D light and Shadow, I still see the character in the shadow, and I don't want to have the shadow completely black because I want to recreate the look as in the game Among Us where the background still has some light on it.

Here is what I mean by Among Us Field of View Among Us in Unity Field of View

As you can see there are shadows, the background is lightly light, and the character's body is half in shadow which is invisible and half in the light where they are visible.

Here is What I mean By Player is Blacked Out enter image description here

There are a couple of YouTube videos about it, but the view becomes sharp and there is no fallout at the edges of the view as with 2D lights. Which really gives it a nice look.

I need help with that, is there away to do that in Unity using 2D Lights and Shadows? And if so how do I go about doing that?

Thank you in advance for your help, all is appreciated.

2
  • 1
    I'm not sure you can achieve this with normal shadows, it looks more like a mask.
    – Iggy
    Jan 13, 2021 at 6:26
  • But how would I even go about that? I was able to generate a mesh that would do exactly as in Among us where it would hide and reveal and all that. But I couldn't achieve the fersnal effect or like the fallout of the edges as in Among us. Which throws the game off. Jan 13, 2021 at 17:21

2 Answers 2

5

It took me a while to figure it out, and tbh I wasn't the one who figured it out, I got help from someone on the Unity Discord server. He went into the Sprite-Lit-Default and changed some code that accounts for the luminance, now I don't know anything about shader programming so I won't be able to explain it much. But he created a .hlsl file with the same functionality as the CombinedShapeLightShared.hlsl file which Unity uses to calculate lights, can be found here, but added some argument that accounts for luminance. Now here are the file content of both the .shader file and the .hlsl file.

But YOU NEED TO KEEP BOTH FILES IN THE SAME FOLDER, OTHERWISE IT WONT WORK. ALSO KEEP THE FILE NAMES THE SAME, UNLESS YOU KNOW WHAT YOU ARE DOING

File name HideInShadow.shader

Shader "Universal Render Pipeline/2D/HideInShadow"
{
    Properties
    {
        _MainTex("Diffuse", 2D) = "white" {}
        _MaskTex("Mask", 2D) = "white" {}
        _NormalMap("Normal Map", 2D) = "bump" {}

        // Legacy properties. They're here so that materials using this shader can gracefully fallback to the legacy sprite shader.
        [HideInInspector] _Color("Tint", Color) = (1,1,1,1)
        [HideInInspector] _RendererColor("RendererColor", Color) = (1,1,1,1)
        [HideInInspector] _Flip("Flip", Vector) = (1,1,1,1)
        [HideInInspector] _AlphaTex("External Alpha", 2D) = "white" {}
        [HideInInspector] _EnableExternalAlpha("Enable External Alpha", Float) = 0
    }

    HLSLINCLUDE
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    ENDHLSL

    SubShader
    {
        Tags {"Queue" = "Transparent" "RenderType" = "Transparent" "RenderPipeline" = "UniversalPipeline" }

        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off
        ZWrite Off

        Pass
        {
            Tags { "LightMode" = "Universal2D" }
            HLSLPROGRAM
            #pragma vertex CombinedShapeLightVertex
            #pragma fragment CombinedShapeLightFragment
            #pragma multi_compile USE_SHAPE_LIGHT_TYPE_0 __
            #pragma multi_compile USE_SHAPE_LIGHT_TYPE_1 __
            #pragma multi_compile USE_SHAPE_LIGHT_TYPE_2 __
            #pragma multi_compile USE_SHAPE_LIGHT_TYPE_3 __

            struct Attributes
            {
                float3 positionOS   : POSITION;
                float4 color        : COLOR;
                float2  uv           : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct Varyings
            {
                float4  positionCS  : SV_POSITION;
                half4   color       : COLOR;
                float2  uv          : TEXCOORD0;
                half2   lightingUV  : TEXCOORD1;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            #include "Packages/com.unity.render-pipelines.universal/Shaders/2D/Include/LightingUtility.hlsl"

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            TEXTURE2D(_MaskTex);
            SAMPLER(sampler_MaskTex);
            half4 _MainTex_ST;

            #if USE_SHAPE_LIGHT_TYPE_0
            SHAPE_LIGHT(0)
            #endif

            #if USE_SHAPE_LIGHT_TYPE_1
            SHAPE_LIGHT(1)
            #endif

            #if USE_SHAPE_LIGHT_TYPE_2
            SHAPE_LIGHT(2)
            #endif

            #if USE_SHAPE_LIGHT_TYPE_3
            SHAPE_LIGHT(3)
            #endif

            Varyings CombinedShapeLightVertex(Attributes v)
            {
                Varyings o = (Varyings)0;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                o.positionCS = TransformObjectToHClip(v.positionOS);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.lightingUV = ComputeNormalizedDeviceCoordinates(o.positionCS);
                o.color = v.color;
                return o;
            }

            #include "CombinedShapeLightSharedHidden.hlsl"

            half4 CombinedShapeLightFragment(Varyings i) : SV_Target
            {
                half4 main = i.color * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
                half4 mask = SAMPLE_TEXTURE2D(_MaskTex, sampler_MaskTex, i.uv);

                return CombinedShapeLightShared(main, mask, i.lightingUV);
            }
            ENDHLSL
        }

        Pass
        {
            Tags { "LightMode" = "NormalsRendering"}
            HLSLPROGRAM
            #pragma vertex NormalsRenderingVertex
            #pragma fragment NormalsRenderingFragment

            struct Attributes
            {
                float3 positionOS   : POSITION;
                float4 color        : COLOR;
                float2 uv           : TEXCOORD0;
                float4 tangent      : TANGENT;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct Varyings
            {
                float4  positionCS      : SV_POSITION;
                half4   color           : COLOR;
                float2  uv              : TEXCOORD0;
                half3   normalWS        : TEXCOORD1;
                half3   tangentWS       : TEXCOORD2;
                half3   bitangentWS     : TEXCOORD3;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            TEXTURE2D(_NormalMap);
            SAMPLER(sampler_NormalMap);
            half4 _NormalMap_ST;  // Is this the right way to do this?

            Varyings NormalsRenderingVertex(Attributes attributes)
            {
                Varyings o = (Varyings)0;
                UNITY_SETUP_INSTANCE_ID(attributes);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                o.positionCS = TransformObjectToHClip(attributes.positionOS);
                o.uv = TRANSFORM_TEX(attributes.uv, _NormalMap);
                o.color = attributes.color;
                o.normalWS = TransformObjectToWorldDir(float3(0, 0, -1));
                o.tangentWS = TransformObjectToWorldDir(attributes.tangent.xyz);
                o.bitangentWS = cross(o.normalWS, o.tangentWS) * attributes.tangent.w;
                return o;
            }

            #include "Packages/com.unity.render-pipelines.universal/Shaders/2D/Include/NormalsRenderingShared.hlsl"

            half4 NormalsRenderingFragment(Varyings i) : SV_Target
            {
                half4 mainTex = i.color * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
                half3 normalTS = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, i.uv));
                return NormalsRenderingShared(mainTex, normalTS, i.tangentWS.xyz, i.bitangentWS.xyz, i.normalWS.xyz);
            }
            ENDHLSL
        }
        Pass
        {
            Tags { "LightMode" = "UniversalForward" "Queue"="Transparent" "RenderType"="Transparent"}

            HLSLPROGRAM
            #pragma vertex UnlitVertex
            #pragma fragment UnlitFragment

            struct Attributes
            {
                float3 positionOS   : POSITION;
                float4 color        : COLOR;
                float2 uv           : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct Varyings
            {
                float4  positionCS      : SV_POSITION;
                float4  color           : COLOR;
                float2  uv              : TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            float4 _MainTex_ST;

            Varyings UnlitVertex(Attributes attributes)
            {
                Varyings o = (Varyings)0;
                UNITY_SETUP_INSTANCE_ID(attributes);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                o.positionCS = TransformObjectToHClip(attributes.positionOS);
                o.uv = TRANSFORM_TEX(attributes.uv, _MainTex);
                o.color = attributes.color;
                return o;
            }

            float4 UnlitFragment(Varyings i) : SV_Target
            {
                float4 mainTex = i.color * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
                return mainTex;
            }
            ENDHLSL
        }
    }

    Fallback "Sprites/Default"
}

File name CombinedShapeLightSharedHidden.hlsl

#if !defined(COMBINED_SHAPE_LIGHT_PASS)
#define COMBINED_SHAPE_LIGHT_PASS

half _HDREmulationScale;
half _UseSceneLighting;
half4 _RendererColor;

half4 CombinedShapeLightShared(half4 color, half4 mask, half2 lightingUV)
{
    if (color.a == 0.0)
        discard;

    color = color * _RendererColor; // This is needed for sprite shape

#if USE_SHAPE_LIGHT_TYPE_0
    half4 shapeLight0 = SAMPLE_TEXTURE2D(_ShapeLightTexture0, sampler_ShapeLightTexture0, lightingUV);

    if (any(_ShapeLightMaskFilter0))
    {
        half4 processedMask = (1 - _ShapeLightInvertedFilter0) * mask + _ShapeLightInvertedFilter0 * (1 - mask);
        shapeLight0 *= dot(processedMask, _ShapeLightMaskFilter0);
    }

    half4 shapeLight0Modulate = shapeLight0 * _ShapeLightBlendFactors0.x;
    half4 shapeLight0Additive = shapeLight0 * _ShapeLightBlendFactors0.y;
#else
    half4 shapeLight0Modulate = 0;
    half4 shapeLight0Additive = 0;
#endif

#if USE_SHAPE_LIGHT_TYPE_1
    half4 shapeLight1 = SAMPLE_TEXTURE2D(_ShapeLightTexture1, sampler_ShapeLightTexture1, lightingUV);

    if (any(_ShapeLightMaskFilter1))
    {
        half4 processedMask = (1 - _ShapeLightInvertedFilter1) * mask + _ShapeLightInvertedFilter1 * (1 - mask);
        shapeLight1 *= dot(processedMask, _ShapeLightMaskFilter1);
    }

    half4 shapeLight1Modulate = shapeLight1 * _ShapeLightBlendFactors1.x;
    half4 shapeLight1Additive = shapeLight1 * _ShapeLightBlendFactors1.y;
#else
    half4 shapeLight1Modulate = 0;
    half4 shapeLight1Additive = 0;
#endif

#if USE_SHAPE_LIGHT_TYPE_2
    half4 shapeLight2 = SAMPLE_TEXTURE2D(_ShapeLightTexture2, sampler_ShapeLightTexture2, lightingUV);

    if (any(_ShapeLightMaskFilter2))
    {
        half4 processedMask = (1 - _ShapeLightInvertedFilter2) * mask + _ShapeLightInvertedFilter2 * (1 - mask);
        shapeLight2 *= dot(processedMask, _ShapeLightMaskFilter2);
    }

    half4 shapeLight2Modulate = shapeLight2 * _ShapeLightBlendFactors2.x;
    half4 shapeLight2Additive = shapeLight2 * _ShapeLightBlendFactors2.y;
#else
    half4 shapeLight2Modulate = 0;
    half4 shapeLight2Additive = 0;
#endif

#if USE_SHAPE_LIGHT_TYPE_3
    half4 shapeLight3 = SAMPLE_TEXTURE2D(_ShapeLightTexture3, sampler_ShapeLightTexture3, lightingUV);

    if (any(_ShapeLightMaskFilter3))
    {
        half4 processedMask = (1 - _ShapeLightInvertedFilter3) * mask + _ShapeLightInvertedFilter3 * (1 - mask);
        shapeLight3 *= dot(processedMask, _ShapeLightMaskFilter3);
    }

    half4 shapeLight3Modulate = shapeLight3 * _ShapeLightBlendFactors3.x;
    half4 shapeLight3Additive = shapeLight3 * _ShapeLightBlendFactors3.y;
#else
    half4 shapeLight3Modulate = 0;
    half4 shapeLight3Additive = 0;
#endif

    half4 finalOutput;
#if !USE_SHAPE_LIGHT_TYPE_0 && !USE_SHAPE_LIGHT_TYPE_1 && !USE_SHAPE_LIGHT_TYPE_2 && ! USE_SHAPE_LIGHT_TYPE_3
    finalOutput = color;
#else
    half4 finalModulate = shapeLight0Modulate + shapeLight1Modulate + shapeLight2Modulate + shapeLight3Modulate;
    half4 finalAdditve = shapeLight0Additive + shapeLight1Additive + shapeLight2Additive + shapeLight3Additive;
    finalOutput = _HDREmulationScale * (color * finalModulate + finalAdditve);

    half luminance = max(finalModulate.r, max(finalModulate.g, finalModulate.b));
    finalOutput.a = min(luminance * 2, color.a);
#endif

    finalOutput = finalOutput *_UseSceneLighting + (1 - _UseSceneLighting)*color;
    return max(0, finalOutput);
}
#endif

The part that he added is that last part where it starts at half luminance....

After copying them into your own files, you can then use the .shader file to add it to a material and then apply it to the objects you want to disappear in shadows. So if the light hits the object it will appear, but it will appear gradually as the light source gets closer to that object, the only downside to this is there must be a light source in the game so that it can use to calculate the luminance, otherwise the object will just default back to a luminance of 1, meaning completely visible. But, I think if you want to make an object disappear in the shadows then you're most likely using a light source. This is how it looks like:

Gif

Of course, you play with the light source and the intensity to get the result you want.

If you have any questions I guess you can comment or something and I will help once I see the comment.

4
  • Is this solution mobile friendly?
    – Doh09
    Jun 1, 2021 at 8:15
  • 1
    @Doh09 tbh I never tried it, but it should work on high-end phones as this ONLY works with the Universal Pipeline enabled. So old phones that won't be able to support 2D light rendering won't be able to support this. But hey, trial never hurt, so try it on like a test project and deploy it to a phone and try it out. Jun 1, 2021 at 23:00
  • I am using it on mobile. No fps drops were detected.
    – amira
    Jul 23, 2021 at 20:02
  • Oh thats great to hear @amira Jul 23, 2021 at 20:21
2

You could try find a solution using the target sorting layer and alpha blending.

What I mean is to ensure that the object the flashlight will partially reveal is not overlapping unless that particular light source is showing it. A bit like in the example image below.

Click here to read more.

enter image description here

I think it would otherwise have to be done with a mask or a shader. I have once done a similar thing in a 3D environment but never in a 2D one, please see this solution for possible inspiration: unity3d trouble with ship floating on water

My suggestion is to look more into 2D shaders to try see if you can find a solution that way.

6
  • 1
    Thank you for that, I did try with the sorting layer, and it just creates a blacked out version of the character. But i didnt try it with the Alpha Blending so i will try that out now and i will get back to you. Jan 13, 2021 at 15:31
  • I looked into what you shared, but it still doesnt do anything. As for the "ship floating on water" post, i didnt really understand much, am not good with shaders what so ever. And I have been looking into 2D Shaders but still nothing. Do you know if it's possible to detect shadows in 2D Shaders? I tried to look into that but couldnt find anything. @Doh09 Jan 14, 2021 at 4:25
  • To my knowledge shaders can use texture information to change what is being displayed. I have not dealt much in shader programming as that is a bit its own thing from how game logic is otherwise made, but i do see it used for purposes resembling your needs. Unity has a tool called Shadergraph meant to simplify making shaders. Check for example this video: m.youtube.com/watch?v=5dzGj9k8Qy8 i can also add that i once implemented a 3d shader that could partially hide a character based on world coordinates.
    – Doh09
    Jan 15, 2021 at 8:07
  • In the ship example there is a "lens" looking through the water, see the bottom screenshot there. I believe it might be possible to make a similar 2d lens, where the flashlight is the lens. This way the player could be hidden but if the lens is over him he is shown. What the ship example does is to affect the order in which pixels are rendered for a specific part of the screen where the lens is. This is what you likely also need. Do note that 2d has layers, maybe you can work with those too.
    – Doh09
    Jan 15, 2021 at 8:11
  • I watched the video you sent, i thank you for that. Going with what Brackeys did, is there away to get the light information on the material/player and use that so that if he has a certain amount of light on him, increase that section's alpha. But, the other sections just reduce the alpha accordingly. So I guess what am trying to say is there a way to get light information or luminance on the player? @Doh09 Jan 20, 2021 at 15:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.