Skip to content

Instantly share code, notes, and snippets.

@phi-lira
Last active April 4, 2024 13:42
Show Gist options
  • Save phi-lira/225cd7c5e8545be602dca4eb5ed111ba to your computer and use it in GitHub Desktop.
Save phi-lira/225cd7c5e8545be602dca4eb5ed111ba to your computer and use it in GitHub Desktop.
Template shader to use as guide to create Universal Pipeline ready shaders. This shader works with Universal Render Pipeline 7.1.x and above.
// When creating shaders for Universal Render Pipeline you can you the ShaderGraph which is super AWESOME!
// However, if you want to author shaders in shading language you can use this teamplate as a base.
// Please note, this shader does not necessarily match perfomance of the built-in URP Lit shader.
// This shader works with URP 7.1.x and above
Shader "Universal Render Pipeline/Custom/Physically Based Example"
{
Properties
{
// Specular vs Metallic workflow
[HideInInspector] _WorkflowMode("WorkflowMode", Float) = 1.0
[MainColor] _BaseColor("Color", Color) = (0.5,0.5,0.5,1)
[MainTexture] _BaseMap("Albedo", 2D) = "white" {}
_Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5
_Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5
_GlossMapScale("Smoothness Scale", Range(0.0, 1.0)) = 1.0
_SmoothnessTextureChannel("Smoothness texture channel", Float) = 0
[Gamma] _Metallic("Metallic", Range(0.0, 1.0)) = 0.0
_MetallicGlossMap("Metallic", 2D) = "white" {}
_SpecColor("Specular", Color) = (0.2, 0.2, 0.2)
_SpecGlossMap("Specular", 2D) = "white" {}
[ToggleOff] _SpecularHighlights("Specular Highlights", Float) = 1.0
[ToggleOff] _EnvironmentReflections("Environment Reflections", Float) = 1.0
_BumpScale("Scale", Float) = 1.0
_BumpMap("Normal Map", 2D) = "bump" {}
_OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0
_OcclusionMap("Occlusion", 2D) = "white" {}
_EmissionColor("Color", Color) = (0,0,0)
_EmissionMap("Emission", 2D) = "white" {}
// Blending state
[HideInInspector] _Surface("__surface", Float) = 0.0
[HideInInspector] _Blend("__blend", Float) = 0.0
[HideInInspector] _AlphaClip("__clip", Float) = 0.0
[HideInInspector] _SrcBlend("__src", Float) = 1.0
[HideInInspector] _DstBlend("__dst", Float) = 0.0
[HideInInspector] _ZWrite("__zw", Float) = 1.0
[HideInInspector] _Cull("__cull", Float) = 2.0
_ReceiveShadows("Receive Shadows", Float) = 1.0
// Editmode props
[HideInInspector] _QueueOffset("Queue offset", Float) = 0.0
}
SubShader
{
// With SRP we introduce a new "RenderPipeline" tag in Subshader. This allows to create shaders
// that can match multiple render pipelines. If a RenderPipeline tag is not set it will match
// any render pipeline. In case you want your subshader to only run in LWRP set the tag to
// "UniversalRenderPipeline"
Tags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline" "IgnoreProjector" = "True"}
LOD 300
// ------------------------------------------------------------------
// Forward pass. Shades GI, emission, fog and all lights in a single pass.
// Compared to Builtin pipeline forward renderer, LWRP forward renderer will
// render a scene with multiple lights with less drawcalls and less overdraw.
Pass
{
// "Lightmode" tag must be "UniversalForward" or not be defined in order for
// to render objects.
Name "StandardLit"
Tags{"LightMode" = "UniversalForward"}
Blend[_SrcBlend][_DstBlend]
ZWrite[_ZWrite]
Cull[_Cull]
HLSLPROGRAM
// Required to compile gles 2.0 with standard SRP library
// All shaders must be compiled with HLSLcc and currently only gles is not using HLSLcc by default
#pragma prefer_hlslcc gles
#pragma exclude_renderers d3d11_9x
#pragma target 2.0
// -------------------------------------
// Material Keywords
// unused shader_feature variants are stripped from build automatically
#pragma shader_feature _NORMALMAP
#pragma shader_feature _ALPHATEST_ON
#pragma shader_feature _ALPHAPREMULTIPLY_ON
#pragma shader_feature _EMISSION
#pragma shader_feature _METALLICSPECGLOSSMAP
#pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
#pragma shader_feature _OCCLUSIONMAP
#pragma shader_feature _SPECULARHIGHLIGHTS_OFF
#pragma shader_feature _GLOSSYREFLECTIONS_OFF
#pragma shader_feature _SPECULAR_SETUP
#pragma shader_feature _RECEIVE_SHADOWS_OFF
// -------------------------------------
// Universal Render Pipeline keywords
// When doing custom shaders you most often want to copy and past these #pragmas
// These multi_compile variants are stripped from the build depending on:
// 1) Settings in the LWRP Asset assigned in the GraphicsSettings at build time
// e.g If you disable AdditionalLights in the asset then all _ADDITIONA_LIGHTS variants
// will be stripped from build
// 2) Invalid combinations are stripped. e.g variants with _MAIN_LIGHT_SHADOWS_CASCADE
// but not _MAIN_LIGHT_SHADOWS are invalid and therefore stripped.
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile _ _SHADOWS_SOFT
#pragma multi_compile _ _MIXED_LIGHTING_SUBTRACTIVE
// -------------------------------------
// Unity defined keywords
#pragma multi_compile _ DIRLIGHTMAP_COMBINED
#pragma multi_compile _ LIGHTMAP_ON
#pragma multi_compile_fog
//--------------------------------------
// GPU Instancing
#pragma multi_compile_instancing
#pragma vertex LitPassVertex
#pragma fragment LitPassFragment
// Including the following two function is enought for shading with Universal Pipeline. Everything is included in them.
// Core.hlsl will include SRP shader library, all constant buffers not related to materials (perobject, percamera, perframe).
// It also includes matrix/space conversion functions and fog.
// Lighting.hlsl will include the light functions/data to abstract light constants. You should use GetMainLight and GetLight functions
// that initialize Light struct. Lighting.hlsl also include GI, Light BDRF functions. It also includes Shadows.
// Required by all Universal Render Pipeline shaders.
// It will include Unity built-in shader variables (except the lighting variables)
// (https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html
// It will also include many utilitary functions.
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// Include this if you are doing a lit shader. This includes lighting shader variables,
// lighting and shadow functions
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
// Material shader variables are not defined in SRP or LWRP shader library.
// This means _BaseColor, _BaseMap, _BaseMap_ST, and all variables in the Properties section of a shader
// must be defined by the shader itself. If you define all those properties in CBUFFER named
// UnityPerMaterial, SRP can cache the material properties between frames and reduce significantly the cost
// of each drawcall.
// In this case, for sinmplicity LitInput.hlsl is included. This contains the CBUFFER for the material
// properties defined above. As one can see this is not part of the ShaderLibrary, it specific to the
// LWRP Lit shader.
#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
float2 uvLM : TEXCOORD1;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float2 uv : TEXCOORD0;
float2 uvLM : TEXCOORD1;
float4 positionWSAndFogFactor : TEXCOORD2; // xyz: positionWS, w: vertex fog factor
half3 normalWS : TEXCOORD3;
#if _NORMALMAP
half3 tangentWS : TEXCOORD4;
half3 bitangentWS : TEXCOORD5;
#endif
#ifdef _MAIN_LIGHT_SHADOWS
float4 shadowCoord : TEXCOORD6; // compute shadow coord per-vertex for the main light
#endif
float4 positionCS : SV_POSITION;
};
Varyings LitPassVertex(Attributes input)
{
Varyings output;
// VertexPositionInputs contains position in multiple spaces (world, view, homogeneous clip space)
// Our compiler will strip all unused references (say you don't use view space).
// Therefore there is more flexibility at no additional cost with this struct.
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
// Similar to VertexPositionInputs, VertexNormalInputs will contain normal, tangent and bitangent
// in world space. If not used it will be stripped.
VertexNormalInputs vertexNormalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);
// Computes fog factor per-vertex.
float fogFactor = ComputeFogFactor(vertexInput.positionCS.z);
// TRANSFORM_TEX is the same as the old shader library.
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
output.uvLM = input.uvLM.xy * unity_LightmapST.xy + unity_LightmapST.zw;
output.positionWSAndFogFactor = float4(vertexInput.positionWS, fogFactor);
output.normalWS = vertexNormalInput.normalWS;
// Here comes the flexibility of the input structs.
// In the variants that don't have normal map defined
// tangentWS and bitangentWS will not be referenced and
// GetVertexNormalInputs is only converting normal
// from object to world space
#ifdef _NORMALMAP
output.tangentWS = vertexNormalInput.tangentWS;
output.bitangentWS = vertexNormalInput.bitangentWS;
#endif
#ifdef _MAIN_LIGHT_SHADOWS
// shadow coord for the main light is computed in vertex.
// If cascades are enabled, LWRP will resolve shadows in screen space
// and this coord will be the uv coord of the screen space shadow texture.
// Otherwise LWRP will resolve shadows in light space (no depth pre-pass and shadow collect pass)
// In this case shadowCoord will be the position in light space.
output.shadowCoord = GetShadowCoord(vertexInput);
#endif
// We just use the homogeneous clip position from the vertex input
output.positionCS = vertexInput.positionCS;
return output;
}
half4 LitPassFragment(Varyings input) : SV_Target
{
// Surface data contains albedo, metallic, specular, smoothness, occlusion, emission and alpha
// InitializeStandarLitSurfaceData initializes based on the rules for standard shader.
// You can write your own function to initialize the surface data of your shader.
SurfaceData surfaceData;
InitializeStandardLitSurfaceData(input.uv, surfaceData);
#if _NORMALMAP
half3 normalWS = TransformTangentToWorld(surfaceData.normalTS,
half3x3(input.tangentWS, input.bitangentWS, input.normalWS));
#else
half3 normalWS = input.normalWS;
#endif
normalWS = normalize(normalWS);
#ifdef LIGHTMAP_ON
// Normal is required in case Directional lightmaps are baked
half3 bakedGI = SampleLightmap(input.uvLM, normalWS);
#else
// Samples SH fully per-pixel. SampleSHVertex and SampleSHPixel functions
// are also defined in case you want to sample some terms per-vertex.
half3 bakedGI = SampleSH(normalWS);
#endif
float3 positionWS = input.positionWSAndFogFactor.xyz;
half3 viewDirectionWS = SafeNormalize(GetCameraPositionWS() - positionWS);
// BRDFData holds energy conserving diffuse and specular material reflections and its roughness.
// It's easy to plugin your own shading fuction. You just need replace LightingPhysicallyBased function
// below with your own.
BRDFData brdfData;
InitializeBRDFData(surfaceData.albedo, surfaceData.metallic, surfaceData.specular, surfaceData.smoothness, surfaceData.alpha, brdfData);
// Light struct is provide by LWRP to abstract light shader variables.
// It contains light direction, color, distanceAttenuation and shadowAttenuation.
// LWRP take different shading approaches depending on light and platform.
// You should never reference light shader variables in your shader, instead use the GetLight
// funcitons to fill this Light struct.
#ifdef _MAIN_LIGHT_SHADOWS
// Main light is the brightest directional light.
// It is shaded outside the light loop and it has a specific set of variables and shading path
// so we can be as fast as possible in the case when there's only a single directional light
// You can pass optionally a shadowCoord (computed per-vertex). If so, shadowAttenuation will be
// computed.
Light mainLight = GetMainLight(input.shadowCoord);
#else
Light mainLight = GetMainLight();
#endif
// Mix diffuse GI with environment reflections.
half3 color = GlobalIllumination(brdfData, bakedGI, surfaceData.occlusion, normalWS, viewDirectionWS);
// LightingPhysicallyBased computes direct light contribution.
color += LightingPhysicallyBased(brdfData, mainLight, normalWS, viewDirectionWS);
// Additional lights loop
#ifdef _ADDITIONAL_LIGHTS
// Returns the amount of lights affecting the object being renderer.
// These lights are culled per-object in the forward renderer
int additionalLightsCount = GetAdditionalLightsCount();
for (int i = 0; i < additionalLightsCount; ++i)
{
// Similar to GetMainLight, but it takes a for-loop index. This figures out the
// per-object light index and samples the light buffer accordingly to initialized the
// Light struct. If _ADDITIONAL_LIGHT_SHADOWS is defined it will also compute shadows.
Light light = GetAdditionalLight(i, positionWS);
// Same functions used to shade the main light.
color += LightingPhysicallyBased(brdfData, light, normalWS, viewDirectionWS);
}
#endif
// Emission
color += surfaceData.emission;
float fogFactor = input.positionWSAndFogFactor.w;
// Mix the pixel color with fogColor. You can optionaly use MixFogColor to override the fogColor
// with a custom one.
color = MixFog(color, fogFactor);
return half4(color, surfaceData.alpha);
}
ENDHLSL
}
// Used for rendering shadowmaps
UsePass "Universal Render Pipeline/Lit/ShadowCaster"
// Used for depth prepass
// If shadows cascade are enabled we need to perform a depth prepass.
// We also need to use a depth prepass in some cases camera require depth texture
// (e.g, MSAA is enabled and we can't resolve with Texture2DMS
UsePass "Universal Render Pipeline/Lit/DepthOnly"
// Used for Baking GI. This pass is stripped from build.
UsePass "Universal Render Pipeline/Lit/Meta"
}
// Uses a custom shader GUI to display settings. Re-use the same from Lit shader as they have the
// same properties.
CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitShader"
}
@tehuster
Copy link

tehuster commented Feb 28, 2020

Can somebody explain why properties i add to this template do not show up in the inspector? Am i missing something? Nonetheless, thanks for the template!
Edit: I think i found it: CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitShader"

@dzitsiuk
Copy link

If I want to refer to the builtin pipeline what should I put here:
Tags{"RenderType" = "Opaque" "RenderPipeline" = "Bultin?" }
Thanks.

"RenderPipeline" = ""

This used to do the trick in previous versions of Unity, but does not work in Unity 2019.3.5f1+.

@erickweil
Copy link

Does this allow us to use ComputeBuffers and draw with a Graphics.DrawProceduralIndirect call? is so how that could be accomplished?

Alternatively, there is any way to make a geometry shader with this?

@Dan3dd
Copy link

Dan3dd commented Apr 18, 2020

Hello! I found a prolem.
When cascades is enabled you should calculate shadow coords per pixel to avoid artefacts.

line 178 , #if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
line 217, #if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)

line 275 Light mainLight = GetMainLight(input.shadowCoord);

replace to
float4 shadowCoord;
#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
shadowCoord = input.shadowCoord;
#elif defined(MAIN_LIGHT_CALCULATE_SHADOWS)
shadowCoord = TransformWorldToShadowCoord(positionWS);
#else
shadowCoord = float4(0, 0, 0, 0);
#endif
Light mainLight = GetMainLight(shadowCoord);

PS URP version is 7.3.1. And I found a comment about it:

Sampling shadows from the Main Light

In previous versions of URP, if shadow cascades were enabled for the main Light, shadows would be resolved in a screen space pass. The pipeline now always resolves shadows while rendering opaques or transparent objects. This allows for consistency and solved many issues regarding shadows.

If have custom hlsl shaders and sample _ScreenSpaceShadowmapTexture texture, you must upgrade them to sample shadows by using the GetMainLight function instead.

@ColinLeung-NiloCat
Copy link

ColinLeung-NiloCat commented May 3, 2020

Dan3dd, the fix works, thanks.

I replace the original line 275 to your code, and it fixed cascade shadow problem in URP 7.3

Now this shader looks the same as the default URP lit shader

@razhollander
Copy link

Thanks a lot man!
Some changes if you are using the older "Lightweight Render Pipeline":

  1. Line 331:
    Before: CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitShader"
    After: CustomEditor "UnityEditor.Rendering.LWRP.ShaderGUI.LitShader"

  2. All the lines which start with: UsePass "Universal Render Pipeline/...."
    Change the word "Universal" to "Lightweight"

  3. All the line which start with: #include "Packages/com.unity.render-pipelines.universal/..."
    Change the word "universal" to "lightweight"

  4. Line 60:
    Before: Tags{"RenderType" = "Opaque" "RenderPipeline" = "LightweightPipeline" "IgnoreProjector" = "True"}
    After: Tags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline" "IgnoreProjector" = "True"}

  5. Line 72:
    Before: Tags{"LightMode" = "UniversalForward"}
    After: Tags{"LightMode" = "LightweightForward"}

@jhm-ciberman
Copy link

Hello. Is there any version of this template that is closer to the "Simple Lit" shader? I'm not planning to use a lot of features of the full "Lit" shader, only the ones in the Simple Lit.

@MagistrAVSH
Copy link

Hello. Is there any version of this template that is closer to the "Simple Lit" shader? I'm not planning to use a lot of features of the full "Lit" shader, only the ones in the Simple Lit.

My "Simple Lit" VertexLit shader - may be can help.
https://gist.github.com/MagistrAVSH/6fb842b3804bf1ff1a03a5bc759c5c57

@dzitsiuk
Copy link

How can I access vertex color in this shader?

@hickVieira
Copy link

Thank you for this template! I have a small issue though, I can't activate alpha testing on a custom shader I made using this. I tried setting _ALPHATEST_ON to true but it doenst work.

@neon-age
Copy link

How to make it work with Hybrid Renderer V2?

@smkplus
Copy link

smkplus commented Sep 5, 2020

I made a material property block shader for URP

https://gist.github.com/smkplus/7c1094ebaf34ec73f9aceec1a72b0800

This would allow you to set a property as instanced in Shader Graph. This lets you draw multiple objects with the same material, and slightly different properties. For example, if you want to change the color.

For more information see: https://docs.unity3d.com/ScriptReference/MaterialPropertyBlock.html
https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstanced.html

@janis2068
Copy link

this Shader is not working for URP with a 2D Renderer.
can someone help me?

@smkplus
Copy link

smkplus commented Sep 6, 2020

@smkplus
Copy link

smkplus commented Sep 6, 2020

@neon-age

there is no option to copy shader from fragment or vertex in Unity 2020

image

but you can copy generated shader in Unity 2020

image

@smkplus
Copy link

smkplus commented Sep 6, 2020

@neon-age
did you use Material Property Override in Hybrid Renderer V2?
I tried to use it in ECS/DOTS but I had some artifacts

@janis2068
Copy link

thanks guys

@janis2068
Copy link

Is it possible to make an stencil effect on this shader?

@smkplus
Copy link

smkplus commented Sep 9, 2020

Is it possible to make an stencil effect on this shader?

I didn't test it but I think it's possible
I explained about stencils here:
https://gamedev.stackexchange.com/questions/152824/how-can-i-create-a-see-behind-walls-effect/152825#152825

@AlganOngun
Copy link

If I want to refer to the builtin pipeline what should I put here:
Tags{"RenderType" = "Opaque" "RenderPipeline" = "Bultin?" }
Thanks.

You dont need dont write RenderPipeline tag

@neon-age
Copy link

neon-age commented Sep 10, 2020

How to make it work with Hybrid Renderer V2?

Holy moly, only these lines were missing:
image

But now i'm having huge artifact with cascades
image
Edit: @Dan3dd fix works like a charm, thanks!

line 275 Light mainLight = GetMainLight(input.shadowCoord);

replace to
float4 shadowCoord;
#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
shadowCoord = input.shadowCoord;
#elif defined(MAIN_LIGHT_CALCULATE_SHADOWS)
shadowCoord = TransformWorldToShadowCoord(positionWS);
#else
shadowCoord = float4(0, 0, 0, 0);
#endif
Light mainLight = GetMainLight(shadowCoord);

@trzy
Copy link

trzy commented Feb 23, 2021

This has been very helpful but Is this template still valid for e.g. Unity 2020.2? I'm running into some issues using it:

  • I don't think _NORMALMAP is defined anymore.
  • I'm unable to add inspector properties (which then would be placed in a second CBUFFER that I define in the shader).

I've been trying to add to this shader and the above two issues have me puzzled. I removed any preprocessor guards for _NORMALMAP and make it always active. But I cannot get any new properties to show up in the inspector when added to the property block at the top.

@guischmitd
Copy link

@trzy Not sure if this helps but seems to me that UT finally added some documentation to aid in writing custom shaders for URP

@andrew-raphael-lukasik
Copy link

Inspector in 2021.1.3f1 throws exception expecting these 5 lines to be included in the Properties block:

        _DetailMask("Detail Mask", 2D) = "white" {}
        _DetailAlbedoMapScale("Scale", Range(0.0, 2.0)) = 1.0
        _DetailAlbedoMap("Detail Albedo x2", 2D) = "linearGrey" {}
        _DetailNormalMapScale("Scale", Range(0.0, 2.0)) = 1.0
        [Normal] _DetailNormalMap("Normal Map", 2D) = "bump" {}

@OwenMagelssen
Copy link

@trzy @andrew-raphael-lukasik The custom inspector code referenced on line 331 has probably changed and is causing the errors and is why your new properties aren't showing up. I haven't tried to figure out what changed, but if you don't mind the default inspector, just comment out line 331.

@0lento
Copy link

0lento commented Nov 29, 2021

This also currently doesn't work on URP deferred for obvious reasons :)

@HelixSnake
Copy link

FYI if you want additional lights to have shadows in the newer versions of unity, replace

Light light = GetAdditionalLight(i, positionWS);
with
Light light = GetAdditionalLight(i, positionWS, half4(1, 1, 1, 1));

GetAdditionalLight needs a shadowmask input in order for additional lights to cast shadows.

@Waffle1434
Copy link

Waffle1434 commented Jun 18, 2022

CustomEditor is causing the inspector to be completely broken and throw null reference errors in console (URP 10.9.0).
Temporary solution is to comment out: //CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitShader"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment