I am not really very familiar with shaders, but I have been using the shader below to swap sprites colors at runtime to recolor them for cosmetic variety. The shader works great but it is unlit and I want to try and use it in a project with lit sprites, and can't figure out how I should modify it to do so. I was wondering if someone could take a look at the shader and let me know if there is an easy way to modify it to make it a surface shader but still keep the render texture color swapping. I have taken a look at the default Unity sprite diffuse shader but am not sure what parts of it I should include in this shader to make it into a lit shader. Here is the shader: Code (CSharp): Shader "Sprites/BatchingColorSwap" { Properties { [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {} _SwapTex("Color Data", 2D) = "transparent" {} _Color("Tint", Color) = (1,1,1,1) [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 } SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" "CanUseSpriteAtlas" = "True" } Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile _ PIXELSNAP_ON #pragma multi_compile_instancing #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; half2 texcoord : TEXCOORD0; }; fixed4 _Color; v2f vert(appdata_t IN) { v2f OUT; OUT.vertex = UnityObjectToClipPos(IN.vertex); OUT.texcoord = IN.texcoord; OUT.color = IN.color * _Color; #ifdef PIXELSNAP_ON OUT.vertex = UnityPixelSnap(OUT.vertex); #endif return OUT; } sampler2D _MainTex; sampler2D _AlphaTex; float _AlphaSplitEnabled; sampler2D _SwapTex; fixed4 SampleSpriteTexture(float2 uv) { fixed4 color = tex2D(_MainTex, uv); if (_AlphaSplitEnabled) color.a = tex2D(_AlphaTex, uv).r; return color; } fixed4 frag(v2f IN) : SV_Target { fixed4 c = SampleSpriteTexture(IN.texcoord); fixed4 swapCol = tex2D(_SwapTex, float2(c.x, IN.color.a * (255.0 / 256.0) + (0.5 / 255.0))); fixed4 final = lerp(c, swapCol, swapCol.a) * IN.color; final.a = c.a; final.rgb *= c.a; return final; } ENDCG } } } The shader was from this tutorial originally then I modified it to make it batchable based on this thread.
It's been a year since this thread has been made and I figured I'd try to respond with what I've found. I've been trying to tackle the same issue with almost the exact same shader from that tutorial. I want to precursor and say I've not been able to find a full solution, but maybe my progress might help someone else to figure what's wrong. I took unity's base Sprite-Lit-Default shader to see if I can add the shader to it. I found that I was able to both add a pass for the shader and write a HLSL version of the shader which allowed it to "work". Atleast, the palette swap works but it doesn't respond to lighting. I tried digging into the "CombinedShapeLightShared.HLSL" and create a custom one to work with lighting but that also didn't seem to help. I also tried adding a Grab Pass after the first pass (palette swap) and using that texture to show the lighting on top of it in the next pass. That didn't work either. Here's the shader script I've made so far: Code (CSharp): Shader "Universal Render Pipeline/2D/PaletteSwap" { Properties { _MainTex("Diffuse", 2D) = "white" {} _MaskTex("Mask", 2D) = "white" {} _NormalMap("Normal Map", 2D) = "bump" {} _Color ("Tint", Color) = (1,1,1,1) [PerRendererData] _SwapTex("Color Data", 2D) = "transparent" {} [PerRendererData] _ColorIndex("Color Index", Int) = 0 [PerRendererData] _TotalPalettes("Total Palettes", Range(1, 255)) = 1 [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 // 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" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" "IgnoreProjector"="True" } Blend SrcAlpha OneMinusSrcAlpha Cull Off ZWrite Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile _ PIXELSNAP_ON #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; half2 texcoord : TEXCOORD0; }; fixed4 _Color; uniform float _ColorIndex; v2f vert(appdata_t IN) { v2f OUT; OUT.vertex = UnityObjectToClipPos(IN.vertex); OUT.texcoord = IN.texcoord; OUT.color = IN.color * _Color; #ifdef PIXELSNAP_ON OUT.vertex = UnityPixelSnap (OUT.vertex); #endif return OUT; } sampler2D _MainTex; sampler2D _AlphaTex; float _AlphaSplitEnabled; uniform int _TotalPalettes; sampler2D _SwapTex; fixed4 SampleSpriteTexture (float2 uv) { fixed4 color = tex2D (_MainTex, uv); if (_AlphaSplitEnabled) color.a = tex2D (_AlphaTex, uv).r; return color; } fixed4 frag(v2f IN) : SV_Target { //Decimal Index is the float version of a y value between 0 - 1 //So in order to find each index we have to get each sixth (6 colors) //I take away 1/12 in order to be safe of rounding errors float decimalIndex = (_ColorIndex + 1.0)/(float)_TotalPalettes - 1.0/((float)_TotalPalettes * 2); fixed4 c = SampleSpriteTexture (IN.texcoord); fixed4 swapCol = tex2D(_SwapTex, float2(c.x, decimalIndex)); fixed4 final = lerp(c, swapCol, swapCol.a) * IN.color; final.a = c.a; final.rgb *= c.a; return final; } ENDCG } //GrabPass{ "_GrabTex" } 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); TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap); half4 _MainTex_ST; half4 _NormalMap_ST; SAMPLER(sampler_AlphaTex); float _AlphaSplitEnabled; uniform int _TotalPalettes; uniform float _ColorIndex; SAMPLER(sampler_SwapTex); //TEXTURE2D(_GrabTex); //SAMPLER(sampler_GrabTex); //half4 _GrabTex_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); float4 clipVertex = o.positionCS / o.positionCS.w; o.lightingUV = ComputeScreenPos(clipVertex).xy; o.color = v.color; return o; } #include "CombinedShapeLightShared_CustomPaletteSwap.hlsl" float4 SampleSpriteTexture(float2 uv) { float4 color = tex2D(sampler_MainTex, uv); if(_AlphaSplitEnabled) color.a = tex2D(sampler_AlphaTex, uv).r; return color; } half4 CombinedShapeLightFragment(Varyings i) : SV_Target { float decimalIndex = (_ColorIndex + 1.0)/(float)_TotalPalettes - 1.0/((float)_TotalPalettes * 2.0); float4 c = SampleSpriteTexture(i.uv); float4 swapCol = tex2D(sampler_SwapTex, float2(c.x, decimalIndex)); float4 final = lerp(c, swapCol, swapCol.a) * i.color; final.a = c.a; final.xyz *= c.a; float4 main = i.color * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv); final = final * main; half4 mask = SAMPLE_TEXTURE2D(_MaskTex, sampler_MaskTex, i.uv); return CombinedShapeLightShared(main, final, 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.uv = attributes.uv; 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.uv = attributes.uv; 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" } Here's the custom HLSL file I added in with it: Code (CSharp): #if !defined(COMBINED_SHAPE_LIGHT_PASS) #define COMBINED_SHAPE_LIGHT_PASS half _HDREmulationScale; half _UseSceneLighting; half4 _RendererColor; half4 CombinedShapeLightShared(half4 color, half4 swapColor, half4 mask, half2 lightingUV) { if (color.a == 0.0) discard; //color = color * _RendererColor; // This is needed for sprite shape color = swapColor * _RendererColor; #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); #endif finalOutput.a = color.a; finalOutput = finalOutput *_UseSceneLighting + (1 - _UseSceneLighting)*color; return max(0, finalOutput); } #endif If anyone has any leads on how to make this work or on how to combine 2d lighting with a custom shader I'd love any more advice.
I figured out how to implement the palette swapping into URP, the issue had to do with properly porting from CGPROGRAM to HLSL and injecting that into the existing Default Sprite Lit shader. This site helped me understand how to properly write a shader in HLSL and compare it to the previous shader in CGPROGRAM. I'll include the shader below. Keep in mind I added the custom HLSL "CombinedShapeLightShared" above but this can be done without changing or creating the custom HLSL. The #include line needs to be changed if you use the older one though. Code (CSharp): Shader "Universal Render Pipeline/2D/PaletteSwap" { Properties { _MainTex("Diffuse", 2D) = "white" {} _MaskTex("Mask", 2D) = "white" {} _NormalMap("Normal Map", 2D) = "bump" {} _Color ("Tint", Color) = (1,1,1,1) [PerRendererData] _SwapTex("Color Data", 2D) = "transparent" {} [PerRendererData] _ColorIndex("Color Index", Int) = 0 [PerRendererData] _TotalPalettes("Total Palettes", Range(1, 255)) = 1 [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 // 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" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" "IgnoreProjector"="True" } Blend SrcAlpha OneMinusSrcAlpha Cull Off ZWrite Off HLSLINCLUDE CBUFFER_START(UnityPerMaterial) uniform int _ColorIndex; uniform float _TotalPalettes; CBUFFER_END ENDHLSL 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); TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap); half4 _MainTex_ST; half4 _NormalMap_ST; TEXTURE2D(_AlphaTex); SAMPLER(sampler_AlphaTex); half4 _AlphaTex_ST; float _AlphaSplitEnabled; TEXTURE2D(_SwapTex); SAMPLER(sampler_SwapTex); half4 _SwapTex_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); float4 clipVertex = o.positionCS / o.positionCS.w; o.lightingUV = ComputeScreenPos(clipVertex).xy; o.color = v.color; return o; } #include "CombinedShapeLightShared_CustomPaletteSwap.hlsl" float4 SampleSpriteTexture(float2 uv) { half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv); if(_AlphaSplitEnabled) color.a = SAMPLE_TEXTURE2D(_AlphaTex, sampler_AlphaTex, uv).r; return color; } half4 CombinedShapeLightFragment(Varyings i) : SV_Target { float decimalIndex = (_ColorIndex + 1.0)/(float)_TotalPalettes - 1.0/((float)_TotalPalettes * 2.0); float4 c = SampleSpriteTexture(i.uv); half4 swapCol = SAMPLE_TEXTURE2D(_SwapTex, sampler_SwapTex, float2(c.x, decimalIndex)); float4 final = lerp(c, swapCol, swapCol.a) * i.color; final.a = c.a; final.xyz *= c.a; float4 main = i.color * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv); half4 mask = SAMPLE_TEXTURE2D(_MaskTex, sampler_MaskTex, i.uv); return CombinedShapeLightShared(final, 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.uv = attributes.uv; 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.uv = attributes.uv; 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" } Here is the shader without any 2D lighting and just in HLSL: Code (CSharp): Shader "Custom/HLSLPaletteSwap" { Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} _Color ("Color", Color) = (1,1,1,1) [PerRendererData] _SwapTex("Color Data", 2D) = "transparent" {} [PerRendererData] _ColorIndex("Color Index", Int) = 0 [PerRendererData] _TotalPalettes("Total Palettes", Range(1, 255)) = 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" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" "IgnoreProjector"="True" } Blend SrcAlpha OneMinusSrcAlpha Cull Off Lighting Off ZWrite Off HLSLINCLUDE CBUFFER_START(UnityPerMaterial) uniform int _ColorIndex; uniform float _TotalPalettes; CBUFFER_END ENDHLSL Pass { Tags { "LightMode" = "Universal2D" } HLSLPROGRAM #pragma vertex UnlitPassVertex #pragma fragment UnlitPassFragment struct Attributes { float4 positionOS : POSITION; float4 color : COLOR; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float4 color : COLOR; float2 uv : TEXCOORD0; }; TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); half4 _MainTex_ST; TEXTURE2D(_AlphaTex); SAMPLER(sampler_AlphaTex); half4 _AlphaTex_ST; float _AlphaSplitEnabled; //uniform int _TotalPalettes; //uniform float _ColorIndex; TEXTURE2D(_SwapTex); SAMPLER(sampler_SwapTex); half4 _SwapTex_ST; Varyings UnlitPassVertex(Attributes v) { Varyings o = (Varyings)0; o.positionCS = TransformObjectToHClip(v.positionOS); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.color = v.color; return o; } half4 SampleSpriteTexture(float2 uv) { half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv); if(_AlphaSplitEnabled) color.a = SAMPLE_TEXTURE2D(_AlphaTex, sampler_AlphaTex, uv).r; return color; } half4 UnlitPassFragment(Varyings i) : SV_Target { half decimalIndex = (_ColorIndex + 1.0)/(float)_TotalPalettes - 1.0/((float)_TotalPalettes * 2.0); half4 c = SampleSpriteTexture(i.uv); half4 swapCol = SAMPLE_TEXTURE2D(_SwapTex, sampler_SwapTex, float2(c.x, decimalIndex)); half4 final = lerp(c, swapCol, swapCol.a) * i.color; final.a = c.a; final.xyz *= c.a; return final; } ENDHLSL } } } I hope this helps anyone in the future!