How to sample HDR texture in shader?

I’ve written a custom skybox shader and the texture I’m using is an HDR formatted texture. Sampling the texture directly via tex2d clearly leads to wrong colors (super bright or saturated) versus whatever shader Unity applies to the standard skybox shaders…

How do I properly sample from an HDR texture?

(I saw other threads allude to using ‘8.0 * color.a * color.rgb’, but that doesn’t look right either.)

I tracked down the default skybox shader code. What I needed to do is call DecodeHDR and use some global parameters.

            half4 tex = tex2D (_MainTex, tc);
            half3 c = DecodeHDR (tex, _MainTex_HDR);
            c = c * _Tint.rgb * unity_ColorSpaceDouble.rgb;
1 Like

@briank did a great job tracking down code, but since this is an incomplete code snippet, I figured I’d fill in the blanks. Here’s a fragment shader that decodes HDR (from the built-in Skybox.shader):

    half4 skybox_frag (v2f i, sampler2D smp, half4 smpDecode)
    {
        half4 tex = tex2D (smp, i.texcoord);
        half3 c = DecodeHDR (tex, smpDecode);
        c = c * _Tint.rgb * unity_ColorSpaceDouble.rgb;
        c *= _Exposure;
        return half4(c, 1);
    }

and this is called with:

half4 frag (v2f i) : SV_Target
{
return skybox_frag(i,_FrontTex, _FrontTex_HDR);
}

A little explanation:

  • _Tint is just a property defined by the skybox shader
  • If you just add _FrontTex_HDR to your shader, Unity will fill it in with the HDR data:
sampler2D _FrontTex;
half4 _FrontTex_HDR;
  • DecodeHDR and unity_ColorSpaceDouble are defined in UnityCG.cginc (you can download all the built-in shader code from Unity’s download page). Here they are:
#ifdef UNITY_COLORSPACE_GAMMA
#define unity_ColorSpaceGrey fixed4(0.5, 0.5, 0.5, 0.5)
#define unity_ColorSpaceDouble fixed4(2.0, 2.0, 2.0, 2.0)
#define unity_ColorSpaceDielectricSpec half4(0.220916301, 0.220916301, 0.220916301, 1.0 - 0.220916301)
#define unity_ColorSpaceLuminance half4(0.22, 0.707, 0.071, 0.0) // Legacy: alpha is set to 0.0 to specify gamma mode
#else // Linear values
#define unity_ColorSpaceGrey fixed4(0.214041144, 0.214041144, 0.214041144, 0.5)
#define unity_ColorSpaceDouble fixed4(4.59479380, 4.59479380, 4.59479380, 2.0)
#define unity_ColorSpaceDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04) // standard dielectric reflectivity coef at incident angle (= 4%)
#define unity_ColorSpaceLuminance half4(0.0396819152, 0.458021790, 0.00609653955, 1.0) // Legacy: alpha is set to 1.0 to specify linear mode
#endif
// Decodes HDR textures
// handles dLDR, RGBM formats
inline half3 DecodeHDR (half4 data, half4 decodeInstructions)
{
    // Take into account texture alpha if decodeInstructions.w is true(the alpha value affects the RGB channels)
    half alpha = decodeInstructions.w * (data.a - 1.0) + 1.0;

    // If Linear mode is not supported we can skip exponent part
    #if defined(UNITY_COLORSPACE_GAMMA)
        return (decodeInstructions.x * alpha) * data.rgb;
    #else
    #   if defined(UNITY_USE_NATIVE_HDR)
            return decodeInstructions.x * data.rgb; // Multiplier for future HDRI relative to absolute conversion.
    #   else
            return (decodeInstructions.x * pow(alpha, decodeInstructions.y)) * data.rgb;
    #   endif
    #endif
}
5 Likes

I am having this problem now except I am using Shadergraph. How do I sample an HDR texture and return an HDR value? Because the normal sample node is clamping the values to a maximum of 1.0

If you’re not getting an HDR value, the texture you’re sampling isn’t an HDR texture.

How can I make it an HDR texture?

  • URP 2022.2.17f1
  • I have a cube with a high intensity HDR color value material on it. This value is HDR.
  • I have a camera pointing at it that outputs to a render texture. The color format is RGB32SFLOAT so it supports the HDR range of values
  • I have a plane that displays the render texture.
  • I have a global post processing volume with bloom.

The problem is somewhere along that line the color values get clamped to 0-1, the plane doesn’t bloom glow and all that extra data is stripped out. Is there some way I can force or bypass the problem to be sure 100% the HDR values are getting encoded?

Pictured:
9085063--1257790--upload_2023-6-16_8-24-3.png

I would use the Frame Debugger to step through everything and look at what’s happening. That should help you find where the problem is.

Note: there is no “encoding” for HDR render textures. It’s only for baked HDR like those from lightmaps, non-realtime cubemaps, or imported HDR images, that are encoded in any way. For a render texture, the value sampled from the texture should be HDR off the bat.

What is the full path of the shader?