Debugging shaders
Problems with a Shader Script shader show up in two places:
- While C# compiles. C# errors, and the problems Shader Script finds while translating the code, appear in your IDE and in the Console as compiler errors. Shader Script's own have codes starting with
SSG: see Diagnostics. While there are compile errors, Unity doesn't load the new code, so no shader is regenerated. - While Unity compiles the generated shader. The code translated fine, but the GPU shader compiler rejected it. These errors appear in the Console as
Shader error in 'Shader Scripts/MyShader': ....
This page is about the second kind, and about finding out why a shader that compiles doesn't look right.
The generated shader
To see the shader that Shader Script generated, select the .shaderscript asset and click View Generated Shader in the Inspector. The shader opens in your code editor.
Double-clicking a shader error in the Console opens the same file at the failing line.
The file is a copy made for reading, in the project's Temp/ShaderScript folder. Editing it has no effect: change the C# code instead.
Finding your code
The generated shader contains a SubShader for each installed render pipeline. In each, your code is in the HLSLINCLUDE block at the start: the input and output structures, the material properties, your types and methods, then Vert and Frag. The passes that follow are generated code that calls them.
Your code keeps its structure, and most names stay the same:
| In C# | In the generated shader |
|---|---|
Vert, Frag, local variables, parameters, fields |
Unchanged |
A local variable, parameter or field whose name HLSL reserves, such as point, sample or step |
point_, sample_, step_ |
Material property Strength |
_Strength |
Method Helper of the shader class |
GC_Helper |
Shared type Waves, and its method Sine |
GC_Waves, and GC_Waves::Sine |
Shared struct Ripple |
GC_Ripple |
const values, enum values |
Replaced by their values |
new Ripple() |
(GC_Ripple)0 |
For example:
public override void Frag(FragmentInput IN, ref UnlitFragmentOutput OUT)
{
float wave = Helper(Strength);
Ripple ripple = new Ripple();
ripple.height = Waves.Sine(wave);
...
becomes:
void Frag(FragmentInput IN, inout SSG_FragmentOutput OUT)
{
float wave = GC_Helper(_Strength);
GC_Ripple ripple = (GC_Ripple)0;
ripple.height = GC_Waves::Sine(wave);
...
Errors in some variants only
When Unity imports a shader, it compiles only the default variant. The other variants, for example the ones for a [KeywordSwitch] value or for transparent surfaces, are compiled when a material or a camera first uses them. An error in such a variant appears in the Console only then.
As with any shader in Unity, shader compiler warnings aren't printed to the Console: Unity lists them in the Inspector.
When a shader doesn't look right
A shader that compiles can still produce the wrong image. Some ways to find out why:
Show a value as a color. Write the value you want to check to
albedoand look at the result:OUT.albedo = (half3)(IN.normalWS * 0.5f + 0.5f); // the normal, mapped from -1..1 to 0..1The screen shows values from 0 to 1, so map others into that range first, as above. In a Lit shader, lighting changes the brightness. To see the value without lighting, set
albedoto black and write the value toemissioninstead, throughShaderLib.ApplyExposureWeightso that HDRP's camera exposure doesn't change it either:float3 value = IN.normalWS * 0.5f + 0.5f; OUT.albedo = new half3(0, 0, 0); OUT.emission = (half3)ShaderLib.ApplyExposureWeight(value, 0);Post-processing, such as tonemapping, still changes the colors on screen.
Check one part at a time. Replace a computation with a constant, such as
float mask = 1;, to see whether the problem is before or after it.Use Unity's Frame Debugger (Window > Analysis > Frame Debugger) to see which passes of the shader draw, with which keywords and property values. RenderDoc, which Unity can capture frames for, shows the inputs and outputs of each draw. Both show the generated shader, with the names described above.
Testing shared code on the CPU
Shared code is ordinary C#, and the shader types behave on the CPU as they do in a shader: vectors, matrices, the array types and the math functions all work. So shared code that uses only them can be tested with regular C# tests, for example with the Unity Test Framework:
[ShaderCode]
public static class MyMath
{
public static float Remap(float value, float min, float max)
{
return math.saturate((value - min) / (max - min));
}
}
// In an Edit Mode test assembly that references the shared code's assembly:
[Test]
public void Remap_MapsRange()
{
Assert.AreEqual(0f, MyMath.Remap(2f, 2f, 4f), 1e-6f);
Assert.AreEqual(0.5f, MyMath.Remap(3f, 2f, 4f), 1e-6f);
Assert.AreEqual(1f, MyMath.Remap(5f, 2f, 4f), 1e-6f);
}
What needs the GPU can't run on the CPU: textures can't be sampled there, and ShaderLib and most of ShaderApi throw an exception. Results can also differ slightly from the GPU's, which rounds differently and may compute half values with less precision.
Common problems
The material is magenta. The shader failed to compile: look for a shader error in the Console. The shader is also magenta when the project uses the Built-in Render Pipeline, which Shader Script doesn't support.
Changes to the C# code don't reach the shader. Check the Console for compile errors: while there are any, anywhere in the project, Unity doesn't load the new code.
A shader has no .shaderscript asset. Run Tools > Shader Script > Reimport All. This can happen when Shader Script is added to a project whose code was already compiled.
A shader is missing from the material's shader menu, or Shader.Find can't find it, while materials that already use it work. Run Tools > Shader Script > Reimport All. Unity's list of shader names can fall out of step with the imported shaders, and reimporting them rebuilds it.
If none of this helps, report the bug on GitHub. See Support.