Powered by

WastedSpace Colorspace Studio CMS

Copyright © 2018


Unity 3D Normal Map Sprites

Thursday, December 11, 2014 11:27 AM

So a while ago my friend was talking about having highlights on the player for the game InTheShadows. At first I thought it would be too much work, working in Unity 4x, there was no shader available to do this easily. I've never worked on shaders before and didnt know where to begin so I researched online and put together some bits of code to make a single shader that had all the features I needed.

 

Without normal map / With normal map

 

So turns out adding normal map was pretty easy, including the process of generating the maps and adding the effect without having to mess with my animation system or any actual game code, beside shaders.

 

So lets start with generating the normal map. For that I use Nvidia normal map filter tool for Photoshop. If you are not using Photoshop, there is a plug-in equivalent available for gimp. Its free and easy to use. At the resolution my sprite are it does make some error that need to be manually corected but otherwise it does a pretty good job

 

Filtered single sprite

 

This is the sprite shader. You can copy and paste it in an empty file and add the extention .shader. It has pixel snapnormal mapping and normal mapping intensity control. I can't take credit for most of it, I wish I remember where I found all those lines of code. This is a prototype and need ajustement but it does what it need to be doing right now.

 

Shader "Sprites/Bumped Diffuse with Shadows"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        _BumpMap ("Normalmap", 2D) = "bump" {}
        _BumpIntensity ("NormalMap Intensity", Range (-1, 2)) = 1
        _BumpIntensity ("NormalMap Intensity", Float) = 1
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        _Cutoff ("Alpha Cutoff", Range (0,1)) = 0.5
    }

    SubShader
    {
        Tags
        { 
            "Queue"="AlphaTest" 
            "IgnoreProjector"="True" 
            "RenderType"="TransparentCutOut" 
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }
        LOD 300

        Cull Off
        Lighting On
        ZWrite On
        Fog { Mode Off }

        CGPROGRAM
        #pragma target 3.0
        #pragma surface surf Lambert alpha vertex:vert  alphatest:_Cutoff fullforwardshadows
        #pragma multi_compile DUMMY PIXELSNAP_ON 
        #pragma exclude_renderers flash

        sampler2D _MainTex;
        sampler2D _BumpMap;
        fixed _BumpIntensity;
        
        fixed4 _Color;

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            fixed4 color;
        };
        
        void vert (inout appdata_full v, out Input o)
        {
            #if defined(PIXELSNAP_ON) && !defined(SHADER_API_FLASH)
            v.vertex = UnityPixelSnap (v.vertex);
            #endif
            v.normal = float3(0,0,-1);
            v.tangent =  float4(1, 0, 0, 1);
            UNITY_INITIALIZE_OUTPUT(Input, o);
            o.color = _Color;
        }

        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * IN.color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            _BumpIntensity = 1 / _BumpIntensity;
            o.Normal.z = o.Normal.z * _BumpIntensity;
            o.Normal = normalize((half3)o.Normal);
        }
        ENDCG
    }

Fallback "Transparent/Cutout/Diffuse"
}

 

At first it didnt have the intensity slider, but it soon became apparent that in most case during game play there was just too much highlights going on. I wanted to have a relatively flat look in normal circumstance, exept when the player is near a light source other than the sun. 

 

 

To fix the intensity, I thought about having "intensity zones" using a gameobject with a gizmo. I can place intensity zones and control the intensity in the scenes. Making the code execute in edit mode makes it easier to tweak since I don't have to run the game to fine tune the affected spots.

 

 

As you can see, the light cube has a light, but there is also a gizmo (wire sphere) that represent the area of intensity for the normal map on the player. On the first image there is visually no normal map, has soon as the player enter the sphere the normal map start raising proportionally to the distance of the center of the sphere.

 

This technique works pretty well I think, and could be used in other situation. It also works with unity Mecanim system. 

 

 

I think this shader could be useful for others beginner unity devs like me, so feel free to use it!

 

 

Discussion

Are you accomplishing the shadow effect on the fence with this shader?
nt
The fence and all other tiles are actually 3D optimized voxel mesh. There is no normal map on those, only a simple diffuse shader. Only the characters and some other elements are 2D sprites. If you are talking about the shadow of the character on the fence, then yes, it will work with this shader, you just need to enable the shadow casting in debug mode on the sprite renderer.
Coming from someone who has little experience of Unity or C# this is an incredible share. Thank you so much.
nt
Thanks Simon! I have been looking for this for a while and didn't see anybody sharing exactly what I needed, I thought it could be useful to other people as well! Also I think the e-mail notification don't work on the website.
Way cool! I can't wait to get home tonight and give it a try.
Sebin
Thanks a lot! This is very informative and cool. Looking forward to playing your game
rusky
How did you get the grass to cast shadows? normal maps dont do that do they. I think the effect is really neat.
nt
@rusky The grass and other tiles casting shadows are in fact voxels. Almost everything in the environment is based on voxels. You can read more about it here Making art for In The Shadows
Eric
Are you using the spriteRenderer component for this? Or are you using a quad with a material attached? If it's the former, how do you apply your material to that?

nt
@Eric I am using a Sprite Renderer. The Sprite Renderer has a Material attribute to which you can assign a material that uses the shader in this post which will replace its default sprite material.
Dom
Just wanted to say thank you very much for sharing this. It's much appreciated.
Dylan
How do you make that nice pixelated grainy light fall off?

Leave a reply

Name ( required )
E-mail ( required, won't be published )
Wesite