Table of Contents

HDRP features

HDRP's Lit shader has features beyond the standard surface: material types such as subsurface scattering and iridescence, clear coat, refraction, distortion, tessellation and ray tracing. Shader Script shaders can use them through the attributes and output fields described on this page. They are in the Ahilevich.ShaderScript.Hdrp namespace and have no effect on URP, so a shader that uses them still works there.

using Ahilevich.ShaderScript;
using Ahilevich.ShaderScript.Hdrp;

Most of these features take a mode: a fixed mode applies to every material, and Override adds the setting to the Material Inspector so that each material chooses.

Material types

[Material(MaterialType)] sets the material types the shader supports. Each type describes a different kind of surface and uses its own output fields:

Type Surfaces Output fields
Standard (default) Most surfaces, with the metallic workflow. metallic, smoothness
SpecularColor Surfaces with a specular color workflow. specularColor instead of metallic
Anisotropy Brushed metal, hair: highlights stretched along a direction. anisotropy, tangentWS for the direction
Iridescence Soap bubbles, oil slicks: colors that shift with the viewing angle. iridescenceMask, iridescenceThickness
SubsurfaceScattering Skin, wax, marble: light scattering under the surface. subsurfaceMask, diffusionProfile; with transmission, transmissionMask, thickness
Translucent Leaves, paper: light passing through thin surfaces. transmissionMask, thickness, diffusionProfile

Combine several types to let each material choose one of them in the Material Inspector:

[Shader, Material(MaterialType.Standard | MaterialType.SubsurfaceScattering)]
public class Skin : ShaderScript.Lit
{
    public DiffusionProfile Profile { get; }
    [Range(0, 1)] public float SubsurfaceMask { get; } = 1;

    public override void Vert(VertexInput IN, ref VertexOutput OUT) { }

    public override void Frag(FragmentInput IN, ref LitFragmentOutput OUT)
    {
        OUT.albedo = (half3)new float3(0.8f, 0.6f, 0.5f);
        OUT.subsurfaceMask = SubsurfaceMask;
        OUT.diffusionProfile = Profile;
    }
}

Subsurface scattering and translucency need an HDRP Diffusion Profile asset: declare a property of type DiffusionProfile, assign it to OUT.diffusionProfile, and pick the profile in the Material Inspector. Writing the fields of a type the material doesn't use costs nothing: they are simply ignored.

Clear coat

[ClearCoat(ClearCoatMode)] adds a thin glossy layer over the material, like car paint or lacquered wood. Write its strength, from 0 to 1, to OUT.coatMask. It works with every material type.

Mode Effect
Off (default) No clear coat; the compiler warns that coatMask is ignored.
On Always on.
Override Each material turns it on or off in the Inspector.
Custom The shader decides itself, by enabling the _MATERIAL_FEATURE_CLEAR_COAT keyword in its Inspector plugin.

Refraction

[Refraction(RefractionModel)] bends and tints the view through a transparent surface, like glass or water. It's visible only on transparent materials. Write the index of refraction to OUT.ior (1.5 for glass, 1.33 for water), the absorption color to OUT.transmittanceColor and the distance at which it's reached to OUT.atDistance.

Model Shape
None (default) No refraction.
Planar A flat surface with a thickness, like a window pane. Uses OUT.thickness.
Sphere A filled sphere, like a glass ball. Uses OUT.thickness.
Thin A very thin surface, like a soap bubble.
Override Each material chooses.
[Shader]
[Surface(SurfacePreset.Transparent), Blend(BlendPreset.Alpha)]
[Refraction(RefractionModel.Planar)]
public class Glass : ShaderScript.Lit
{
    public override void Vert(VertexInput IN, ref VertexOutput OUT) { }

    public override void Frag(FragmentInput IN, ref LitFragmentOutput OUT)
    {
        OUT.albedo = (half3)new float3(1, 1, 1);
        OUT.smoothness = 0.95f;
        OUT.alpha = 0.1f;
        OUT.ior = 1.5f;
        OUT.thickness = 0.05f;
        OUT.transmittanceColor = new float3(0.8f, 1, 0.9f);
        OUT.atDistance = 0.5f;
    }
}

Distortion

[Distortion(...)] warps the image behind the object, for heat haze, shock waves or rippling water. It's visible only on transparent materials, and works in Lit and Unlit shaders. Write the screen-space offset to OUT.distortion and the amount of blur, from 0 to 1, to OUT.distortionBlur.

The attribute takes four settings, each fixed or Override:

Setting Values
DistortionEnableMode Off (default), On, Override.
DistortionBlendMode How overlapping distortions combine: Add (default), Multiply, Replace.
DistortionDepthTestMode Whether objects in front hide the distortion: On (default), Off.
DistortionOnlyMode On renders only the distortion, for invisible effects like heat haze; Off (default) renders the object too.
[Shader]
[Surface(SurfacePreset.Transparent), Blend(BlendPreset.Alpha)]
[Distortion(DistortionEnableMode.On, DistortionBlendMode.Add, DistortionDepthTestMode.On, DistortionOnlyMode.On)]
public class HeatHaze : ShaderScript.Unlit
{
    public override void Vert(VertexInput IN, ref VertexOutput OUT) { }

    public override void Frag(FragmentInput IN, ref UnlitFragmentOutput OUT)
    {
        float2 uv = IN.uv0;
        float wave = math.sin(uv.y * 40 + Time.y * 5);
        OUT.distortion = new float2(wave, 0) * 0.01f;
    }
}

Depth offset

[DepthOffset(DepthOffsetMode, conservative)] lets Frag move each pixel's depth along the view direction, by OUT.depthOffset world units. It makes per-pixel displacement, such as parallax occlusion mapping, intersect other geometry correctly.

With the default Auto mode, depth offset is on when the shader writes OUT.depthOffset. Depth offset disables some GPU depth optimizations, so use it only where needed. Keep conservative true when the offset only pushes pixels away from the camera, which keeps early depth testing working.

Tessellation

[Tessellation(TessellationMode.On, TessellationShapeMode)] subdivides the mesh into smaller triangles on the GPU, for smooth silhouettes and detailed displacement. Override Displace to move the new vertices, typically along the normal by a height map:

[Shader, Tessellation(TessellationMode.On, TessellationShapeMode.Phong)]
public class Terrain : ShaderScript.Lit
{
    [NoScaleOffset] public Texture2D HeightMap { get; } = Texture2D.Black;
    [Range(0, 1)] public float Height { get; } = 0.2f;

    public override void Vert(VertexInput IN, ref VertexOutput OUT) { }

    public override void Displace(TessellationInput IN, ref TessellationOutput OUT)
    {
        float2 uv = IN.uv0;
        float height = HeightMap.SampleLevel(uv, 0).r;
        OUT.positionWS = IN.positionWS + IN.normalWS * height * Height;
    }

    public override void Frag(FragmentInput IN, ref LitFragmentOutput OUT)
    {
        OUT.albedo = (half3)new float3(0.5f, 0.5f, 0.5f);
    }
}

Phong shaping rounds off low-polygon models; None keeps the new vertices on the original triangles. The subdivision amount, the distances at which it fades out and the other parameters become per-material settings in the Material Inspector; the attribute's parameters set their defaults. Tessellation can't be turned on or off per material.

In Displace, positions and normals are in world space, and IN.positionWS is an absolute position. Sample textures there with SampleLevel. Custom interpolators (interp0 to interp3) are 0 in tessellated passes.

Ray tracing

Shader Script shaders work with HDRP's ray tracing: ray-traced reflections, global illumination, shadows, subsurface scattering, recursive rendering and path tracing. The ray tracing passes are generated automatically; set up ray tracing in the HDRP asset and volumes as for HDRP's own shaders.

In ray tracing passes, there are no neighboring pixels, so a few things behave differently:

  • Texture2D.Sample reads the most detailed mipmap.
  • ShaderApi.ddx, ddy and fwidth return 0, and the procedural shapes lose their edge smoothing.
  • ShaderApi.clip doesn't discard pixels, so ray-traced effects see the surface as solid. Use OUT.alphaClipThreshold with alpha clipping instead, which works in ray tracing too.

Emission

HDRP uses physical light units and camera exposure. An emission value that looks bright on URP looks dark on HDRP, so scale emission up on HDRP, for example with IsHdrp. ShaderLib.ApplyExposureWeight controls how much the camera's exposure affects the emission, like HDRP's own Exposure Weight setting: with a weight of 0, the emission keeps its brightness in both dark and bright scenes, which suits screens and signs.