Better Shaders - An improved Surface Shader system for Standard, URP, HDRP

Better Shaders is a system which targets the following goals:

  • Write Lit Shaders in a simple format, akin to Unity’s Surface Shader system
  • Shaders automatically compile to Standard, URP, and HDRP pipeline
  • Shaders can be distributed and packaged for the asset store without Better Shaders
  • Shader creation is more modular for developers and users of those shaders

If you’ve written a surface shader, Better Shaders will feel right at home to you, and surface shaders can be quickly ported to the system and immediately compile in URP, Standard, and HDRP. Better Shaders cleans up many of the quirks of the surface shader abstraction, massively improves what you can do with includes via it’s subshader system, and even lets users graphically stack existing shaders on top of each other to create new shaders.

For technical information about how this works, consult the online Documentation.

Stackable Shaders

Going beyond a surface shader replacement system, Better Shaders allows users to stack shaders together to create complex effects. For instance, this image was created by stacking these shaders on top of each other:

Let’s break down this example. At the top of the stack is the LitTessellation shader, which internally inherits from the Lit shader, and only implements a function to do the tessellation itself. Next we stack a moss effect onto it, which gives us moss based on the angle/height of the height texture. A puddles effect is applied after, followed by a UVShear modifier, which bends the UV coordinates for the two wind effects below it.

The same can be done via code:

BEGIN_SUBSHADER
   "LitTessellation.surfshader"
   "Stackable_Moss"
   "Stackable_Puddles"
   "Stackable_UVShear"
   "Stackable_Wind"
   "Stackable_Wind"
END_SUBSHADER

This lets you write and combine effects in a modular way, from multiple authors. Want to add Vegetation Studio support to your new shader? Simply subshader in its code, or stack it. Sub Shaders don’t just include code, but also properties, cbuffer entries, local keywords, etc. And you can even write them to allow multiple inclusion if you need to stack them more than once!

Better Shaders also includes custom Material Property Drawers to make your materials look better, and works with Shader Graph Markdown. You can even write Custom Editors for individual shaders, and they will be automatically combined into one editor for stacked shaders.

30 Likes

Hi, I like the idea as it prevents reinventing the wheel over and over again. However, do you combine the subshaders somehow (to a single shader file) and/or do we have some performence drawbacks when using it?

There’s no overhead added by stacking shaders - they get combined into one shader, and the boilerplate that makes it possible gets stripped by the compiler.

2 Likes

Nice! :slight_smile:

Am i missing something or is there a release date for when this will be available?

I’ll be submitting fairly soon.

1 Like

Submitted…

1 Like

Look forward to trying it out, As usuall, amazing work!

And it’s out:

7 Likes

@jbooth_1 you are the answer to my prayers.
I was seriously going to stay on built-in forward render forever.
The shader graph is a joke (it doesn’t even have a final color modifier), and direct coding for URP and HDRP is a total nightmare.
Your plugin could change that completely.
I’ll read the docs in depth.

Does Better Shaders also work for HDRP Unlit shaders? I have a pretty complex unlit atmosphere shader that works in HDRP, but I’m not able to get _ENABLE_FOG_ON_TRANSPARENT working properly with it. I tried porting the shader to a custom Shader Graph node, as HDRP Unlit Shader Graph shaders have a “Receive Fog” toggle, but I was unsuccessful. Is this something Better Shaders supports?

Well it supports unlit shaders. Not sure about the fog issue though…

Ok I read the docs, but it seems Better Shaders doesn’t support custom lighting functions like surface shaders.
It wouldn’t be a big deal for me except for two things: anisotropic shading for hair and “wrap around” diffuse to simulate SSS (with color tint ideally).
Another advantage of custom lighting functions is to use a simpler shading than PBR for performance.
So, is there any way to implement that with Better Shaders?

Great work by the way! Unity should hire you to fix their SRP mess.

I mean you can make an unlit shader and pipe anything you want into albedo. This is certainly viable for URP or Standard Lighting, where you have forward renderer’s and easy access to light data. Or you can write your own templates, for a custom SRP even.

The problem with having a custom lighting function is it would be entirely different for each pipeline, and by the time you’re done with it, it wouldn’t be much less code than just implementing your lighting in an unlit shader. That said, the whole point of the surface shader concept was to protect the user from the complexity of complex lighting models.

So I added abstractions for grab pass/scene color and depth buffer lookups. On standard pipeline, it does a grab pass- on SRPs it samples the opaque texture, which doesn’t have the overhead of GrabPass. Looks like this:

BEGIN_OPTIONS
   GrabPass { "_Grab" }
   Alpha "Blend"
   Workflow "Unlit"
END_OPTIONS

BEGIN_CODE
    void SurfaceFunction(inout Surface o, ShaderData d)
    {
        o.Albedo = 1 - GetSceneColor(d.screenUV);
        o.Alpha = 1;
    }
END_CODE

//The depth texture API is:
float GetSceneDepth(float2 screenUV);
float GetLinear01Depth(float2 screenUV);
float GetLinearEyeDepth(float2 screenUV);

10 Likes

Hello, @jbooth_1 !
Will you plan to support 2021.1/2021.2 or only LTS?
I am working with URP and HDRP in one project do i need to be aware of some caveats related to better shader system?
If I use scripts for switching pipeline back and forth in editor will it recompile shaders accordingly?

Official support is tied to LTS versions, though I may provide support for tech releases to enterprise customers on a case by case basis.

Better shaders checks for the presence of the render pipeline via an asmdef file and sets a define for which pipeline to use, and compiles shaders for that pipeline. If you have both pipelines installed, it would compile the shader for which ever pipeline it detects first, so you would need to modify the system to determine which pipeline is currently active, and figure out a way to detect when that changes. As far as I know Unity doesn’t have any kind of hooks for that stuff. If that was general enough to handle all cases and wasn’t brittle, it would be useful to merge that change back.

3 Likes

Does it supports the URP 2DRenderer ?

Hi Jason,

A few questions, does this cover all possible shader scenarios, e.g does it do grab pass (for refraction) and camera depth sampling in all pipelines automatically and how is this handled ? e.g. do i write the methods in Standard pipeline and convert automatically to URP and HDRP ?

Also does it support changing the vertex shader and then define the same changes in the shadow passes, e.g for leaf shaders that need wind ? Will the modified shadow casting pass also convert to URP - HDRP similar passes ?

Do these functionalities have example shaders implemented for direct use ?

Thanks

In the case of depth sampling and grab pass, there are functions you can call that return the appropriate data for each pipeline. In the case of grab pass, this is very different on SRPs than Built in, since SRPs don’t really have grab pass but rather an opaque texture that can be sampler. There is also an example of this API like 2 messages above yours.

By default the vertex shader modifications are used in all passes, but if you want to do different things in different passes you can #if _PASSSHADOW to do pass specific code.

All the documentation is available online, so you can see all of this for yourself.