This is a quick little note exploring the current state of High-Dynamic Range (HDR) and Wide-Color Gamuts (WCG) in Bevy.
Display-referred colors are defined by a display standard (typically sRGB, BT.709, BT.2020, BT.2100 or Display P3). sRGB and BT.709 are SDR, Display P3 and BT.2020 are WCG, BT.2100 is HDR + WCG. All display-referred colors have bounds on chromaticity and intensity, and usually have coefficients between 0 and 1.
Scene-referred colors are defined by quantities of light. They typically use the primaries from a display standard and are expressed in luminous units, such as lux or candela per square meter. They have bounded chromaticity but unbounded luminosity (meaning the light can be arbitrarily intense intense), which translates to allowing their coefficients to go above 1.
WebGPU allows us to create canvases in either the sRGB or Display P3 color spaces. Additionally, when the attachment format is set to rgba16float
, WebGPU converts the output color values to the color space of the actual screen and then clamps them to the [0, 1]
interval in that color space.
Canvas color values outside of the [0, 1]
interval may be used to display colors outside of the gamut defined by the canvas' color space’s primaries, when permitted by the configured format and the user’s display capabilities. E.g, the color value (1.09,-0.23,-0.15)
in an 'srgb' canvas will produce the same color as the color value (1,0,0)
in 'display-p3' canvas.
Currently, bevy's PBR shaders are built using the BT.709 primaries; they expect display-referred BT.709 (or sRGB) assets and output scene-referred BT.709 frames. When HDR is enabled, we output those frames directly as rgba16float
and the OS compositor takes care of sending them to the HDR display in the correct format. Otherwise we tonemap the frames back to display-referred BT.709 and output them as rgba8-unorm-srgb
which WebGPU then maps into sRGB for us.
All "color assets" (textures, UI swatches, whatever) are display referred and the vast majority are sRGB. Assets take one of two paths to the display: they are either (a) rendered directly without pbr shaders or (b) used in physically-based shading to produce scene-referred output data.
None of this needs to effect bevy_color
much. It should do it's best to abstract away all this complex stuff. It's more about the render stack consumes the bevy_color
api.
Our support for HDR requires some work to look similar to the SDR path. We still need to:
We currently have no support for WCG. To add support, we would need to: