Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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.LitShaderGUI"
}
@EIZENHORN91

This comment has been minimized.

Copy link

EIZENHORN91 commented Oct 12, 2018

Hey just FYI - line 300 "lightmapUV" must be renamed to uvLM (as in Varyings) :)

@RamiroOliva

This comment has been minimized.

Copy link

RamiroOliva commented Oct 15, 2018

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

@phi-lira

This comment has been minimized.

Copy link
Owner Author

phi-lira commented Nov 16, 2018

Hey just FYI - line 300 "lightmapUV" must be renamed to uvLM (as in Varyings) :)

Thanks for catching that. Fixed.

@phi-lira

This comment has been minimized.

Copy link
Owner Author

phi-lira commented Nov 16, 2018

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

"RenderPipeline" = ""

@jean-moreno

This comment has been minimized.

Copy link

jean-moreno commented Jan 9, 2019

Thanks for that, it's a very useful reference!

Small things:
line 167, typo in the comment: xyx => xyz
line 241, there's a normalize(normalWS); that isn't assigned
line 267, typo: brighest => brightest

@phi-lira

This comment has been minimized.

Copy link
Owner Author

phi-lira commented Jan 24, 2019

Thanks! Updated!

@niguiecandy

This comment has been minimized.

Copy link

niguiecandy commented Feb 7, 2019

It seems like this shader only works correctly in the editor.
It fallbacks to error shader when built using Unity2018.3.3f1, with both LWRP v4.6.0-Preview and v4.9.0-Preview.
Tested with iMac late 2015

@MartRimm

This comment has been minimized.

Copy link

MartRimm commented Mar 13, 2019

Same problem as niguiecandy, frame debugger shows the shader as pass #0 and pink. Adding this shader to always included shaders in a build will freeze the build process and unity indefinitely and you have to manually end task on unity.
EDIT:
Have to remove line 323 before building.
// Used for Baking GI. This pass is stripped from build.
UsePass "Lightweight Render Pipeline/Lit/Meta"

@NoxMortem

This comment has been minimized.

Copy link

NoxMortem commented Apr 18, 2019

I added this example to our project in 2019.1 and it shows the following errors:
undeclared identifier '_MainTex_ST' D3D 198
which is
output.uv = TRANSFORM_TEX(input.uv, _MainTex);

@Ittaimann

This comment has been minimized.

Copy link

Ittaimann commented May 3, 2019

@NoxMortem looks like there was a change to LitInput.hlsl where anything saying _Main got changed to _BaseMap. so its should be _BaseMap_ST

@Pyromuffin

This comment has been minimized.

Copy link

Pyromuffin commented May 10, 2019

To fix this for 5.7.2 you need to change
_Glossiness("Smoothness", Range(0.0, 1.0)) = 0.5
to
_Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5
and
_Color("Color", Color) = (0.5,0.5,0.5,1)
_MainTex("Albedo", 2D) = "white" {}
to
_BaseColor("Color", Color) = (0.5,0.5,0.5,1)
_BaseMap("Albedo", 2D) = "white" {}

and at the bottom change

CustomEditor "UnityEditor.Experimental.Rendering.LightweightPipeline.LitShaderGUI"
to
CustomEditor "UnityEditor.Rendering.LWRP.ShaderGUI.LitShader"

these changes got most of the features working. I think it's still missing a few things from digging through the Editor scripts, but I'm not exactly sure.

@phi-lira

This comment has been minimized.

Copy link
Owner Author

phi-lira commented Jul 8, 2019

I've updated the Shader to work with LWRP 5.7.2+ (version LWRP is out of preview). It should work now. Bear in mind this is a example shader only. It's not meant to be feature compatible with LWRP Lit shader.

@rustinlee

This comment has been minimized.

Copy link

rustinlee commented Jul 11, 2019

Thanks for the update! For anyone trying to get the Lit GUI working for access to your properties from the inspector, you still need to change UnityEditor.Experimental.Rendering.LightweightPipeline.LitShaderGUI to UnityEditor.Rendering.LWRP.ShaderGUI.LitShader.

@rustinlee

This comment has been minimized.

Copy link

rustinlee commented Jul 22, 2019

Also worth noting that having UNITY_VERTEX_INPUT_INSTANCE_ID in your vertex attributes struct isn't enough on its own to compile a working GPU instancing enabled version of the shader, instance transform matrices will be broken unless you call UNITY_SETUP_INSTANCE_ID(input); in LitPassVertex.

@Kolyasisan

This comment has been minimized.

Copy link

Kolyasisan commented Jul 23, 2019

"Please note, this shader does not match perfomance of the built-in LWRP Lit shader."

Wait, why?

@edgelesscorner

This comment has been minimized.

Copy link

edgelesscorner commented Jul 28, 2019

Also worth noting that having UNITY_VERTEX_INPUT_INSTANCE_ID in your vertex attributes struct isn't enough on its own to compile a working GPU instancing enabled version of the shader, instance transform matrices will be broken unless you call UNITY_SETUP_INSTANCE_ID(input); in LitPassVertex.

Very helpful thanks @rustinlee!
Though sadly this seems to break some operations.

@gustavolsson

This comment has been minimized.

Copy link

gustavolsson commented Jul 29, 2019

Heads-up: FallBack "Hidden/InternalErrorShader" will either break your build or if you include the shader in always include shaders as I did, make it impossible to build the game. Just remove the line. Thanks @phi-lira for a nice example!

@phi-lira

This comment has been minimized.

Copy link
Owner Author

phi-lira commented Jan 9, 2020

Updated template for UniversalRenderPipeline. There was an issue about including Fallback "Hidden/InternalError" in always include shaders. This issue is fixed. I also removed the fallback error from the template.

@leimiles

This comment has been minimized.

Copy link

leimiles commented Jan 17, 2020

I found an issue wonder if it only happens to me: I can't see normal map effect with this shader, but if I change shader to URP/Lit, then change back to this shader, normal map works, I don't know why is this.. : )

@13Flo

This comment has been minimized.

Copy link

13Flo commented Jan 18, 2020

Super helpful. Thank you for sharing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.