crt-rgb-to-crt

This shader combines the current frame, the previous frame, screen mask, and diffusion into the final render.

It is a relatively complex shader, and certainly if there are features that aren't needed for a given use case, the shader could be simplified.

Index

Input Textures/Samplers

g_currentFrameTexture
                g_currentFrameTexture
              
Type
texture (platform-specific)
Description
This is the RGB current frame texture, i.e. the image that will be displayed on the virtual CRT screen.
g_currentFrameSampler
                g_currentFrameSampler
              
Type
sampler (platform-specific, does not exist on some platforms)
Description
The sampler to use to sample g_currentFrameTexture.
g_previousFrameTexture
                g_previousFrameTexture
              
Type
texture (platform-specific)
Description
This is the previous frame's texture (i.e. last frame's g_currentFrameTexture).
g_previousFrameSampler
                g_previousFrameSampler
              
Type
sampler (platform-specific, does not exist on some platforms)
Description
The sampler to use to sample g_previousFrameTexture.
g_screenMaskTexture
                g_screenMaskTexture
              
Type
texture (platform-specific)
Description
This texture is the output of the generate-screen-texture shader, containing the (scaled, tiled, and antialiased) mask texture in the rgb channels and the edge-of-screen mask value in the alpha channel. It is expected to have been generated at our output resolution (i.e. it's 1:1 pixels with our output render target), using the same values of g_viewScale, g_overscanScale, g_overscanOffset, and g_distortion as this shader is given.
g_screenMaskSampler
                g_screenMaskSampler
              
Type
sampler (platform-specific, does not exist on some platforms)
Description
The sampler to use to sample g_screenMaskTexture.
g_diffusionTexture
                g_diffusionTexture
              
Type
texture (platform-specific)
Description
This texture contains a tonemapped/blurred version of the input texture, to emulate the diffusion of the light from the phosphors through the glass on the front of a CRT screen.
g_diffusionSampler
                g_diffusionSampler
              
Type
sampler (platform-specific, does not exist on some platforms)
Description
The sampler to use to sample g_diffusionTexture.

Uniform Buffer Values

g_viewScale
                float2 g_viewScale
              
Type
float2
Description

NOTE: this value is expected to match the equivalent value in generate-screen-texture.

This value describes how to scale the screen to account for different aspect ratios between the output dimensions and the emulated visible CRT dimensions (i.e. excluding any overscan-clipped content).

This shader is intended to render a screen of the correct shape regardless of the output render target shape, effectively letterboxing or pillarboxing as needed (i.e. rendering a 4:3 screen to a 16:9 render target).

In the event the output render target is wider than the intended screen, the screen needs to be scaled down horizontally to pillarbox, usually like:

                  x = (renderTargetWidth / renderTargetHeight) 
                    * (crtScreenHeight / crtScreenWidth)
                  y = 1.0
                

if the output render target is taller than the intended screen, it will end up letterboxed using something like:

                  x = 1.0
                  y = (renderTargetHeight / renderTargetWidth) 
                    * (crtScreenWidth / crtScreenHeight)
                
g_overscanScale
                float2 g_overscanScale
              
Type
float2
Description

NOTE: this value is expected to match the equivalent value in generate-screen-texture.

If overscan emulation is intended (where the edges of the screen cover up some of the picture), then this is the amount of signal texture scaling needed to account for that.

Given an overscan value named overscanAmount that is (where the given values are in texels):

                  overscanAmount.x = overscanLeft + overscanRight
                  overscanAmount.y = overscanTop + overscanBottom
                

the value of g_overscanScale should end up being:

                  (inputImageSize.xy - overscanAmount.xy)
                    * 0.5
                    / inputImageSize.xy
                
g_overscanOffset
                float2 g_overscanOffset
              
Type
float2
Description

NOTE: this value is expected to match the equivalent value in generate-screen-texture.

the texture coordinate offset to adjust for overscan. Because the screen coordinates are [-1..1] instead of [0..1], this is the offset needed to recenter the value.

Given an overscan value named overscanAmount that is (where the given values are in texels):

                  overscanDifference.x = overscanLeft - overscanRight
                  overscanDifference.y = overscanTop - overscanBottom
                

the value of g_overscanScale should end up being:

                  overscanDifference.xy
                    * 0.5
                    / inputImageSize.xy
                
g_distortion
                float2 g_distortion
              
Type
float2
Description

NOTE: this value is expected to match the equivalent value in generate-screen-texture.

The amount along each axis to apply the virtual-curved screen distortion. Usually a value in [0..1], where 0 indicates no curvature (a flat screen) and 1 indicates "quite curved"

g_backgroundColor
                float4 g_backgroundColor
              
Type
float4
Description
The RGBA color of the area around the screen.
g_phosphorPersistence
                float g_phosphorPersistence
              
Type
float
Description

How much of the previous frame's brightness to keep. 0 means "we don't use the previous frame at all" and 1 means "the previous frame is at full brightness".

In many CRTs, the phosphor persistence is short enough that it would be effectively 0 at 50-60fps (As a CRT's phospors could potentially be completely faded out by then). However, for some cases (for instance, interlaced video or for actual NES/SNES/probably other console output) it is generally preferable to turn on a little bit of persistance to lessen temporal flickering on an LCD screen as it can tend to look bad depending on the panel.

[Author's Note: seriously, check out https://www.youtube.com/watch?v=kA8CIY0DeS8 which is what my LCD panel was doing *after* the flickering interlace test truck I was using had been gone for 10 minutes]

g_scanlineCount
                float g_scanlineCount
              
Type
float
Description
How many scanlines there are in this field of the input (where a field is either the even or odd scanlines of an interlaced frame, or the entirety of a progressive-scan frame). In other words, the height of the input texture, in texels.
g_scanlineStrength
                float g_scanlineStrength
              
Type
float
Description
The strength of the separation between scanlines. 0 means "no scanline separation at all" and 1 means "separate the scanlines as much as possible" - on high-enough resolution output render target (at 4k for sure) 1 means "fully black between scanlines", but to reduce aliasing that amount of separation will diminish at lower output resolutions.
g_curEvenOddTexelOffset
                float g_curEvenOddTexelOffset
              
Type
float
Description
This is the scanline-space coordinate offset to use to adjust the input texture coordinate's y value based on whether this is a (1-based) odd frame or an even frame (in the context of interlacing). It will be 0.5 (shifting the texture up half a scanline) for an odd frame and -0.5 (shifting the texture down half a scanline) for an even frame.
g_prevEvenOddTexelOffset
                float g_prevEvenOddTexelOffset
              
Type
float
Description

This value corresponds to the value that g_curEvenOddTexelOffset had on the previous frame.

This should match g_curEvenOddTexelOffset for a progressive-scan signal and should be -g_curEvenOddTexelOffset if interlaced.

g_diffusionStrength
                float g_diffusionStrength
              
Type
float
Description
This is how much diffusion to apply, blending in the diffusion texture which is an emulation of the light from the screen scattering in the glass on the front of the CRT - 0 means "no diffusion" and 1 means "a whole lot of diffusion."
g_maskStrength
                float g_maskStrength
              
Type
float
Description
How much we want to blend in the mask. 0 means "mask is not visible" and 1 means "mask is fully visible."
g_maskDepth
                float g_maskDepth
              
Type
float
Description
The darkness of the darkest part of the mask. 0 means the area between the RGB sections is black, 0.9 means the spaces between are nearly white.