terminal/samples/PixelShaders/README.md
Mike Griese b140299e50
Implement user-specified pixel shaders, redux (#8565)
Co-authored-by: mrange <marten_range@hotmail.com>

I loved the pixel shaders in #7058, but that PR needed a bit of polish
to be ready for ingestion. This PR is almost _exactly_ that PR, with
some small changes.

* It adds a new pre-profile setting `"experimental.pixelShaderPath"`,
  which lets the user set a pixel shader to use with the Terminal.
    - CHANGED FROM #7058: It does _not_ add any built-in shaders.
    - CHANGED FROM #7058: it will _override_
      `experimental.retroTerminalEffect`
* It adds a bunch of sample shaders in `samples/shaders`. Included: 
    - A NOP shader as a base to build from.
    - An "invert" shader that inverts the colors, as a simple example
    - An "grayscale" shader that converts all colors to grayscale, as a
      simple example
    - An "raster bars" shader that draws some colored bars on the screen
      with a drop shadow, as a more involved example
    - The original retro terminal effects, as a more involved example
    - It also includes a broken shader, as an example of what heppens
      when the shader fails to compile
    - CHANGED FROM #7058: It does _not_ add the "retroII" shader we were
      all worried about.
* When a shader fails to be found or fails to compile, we'll display an
  error dialog to the user with a relevant error message.
    - CHANGED FROM #7058: Originally, #7058 would display "error bars"
      on the screen. I've removed that, and had the Terminal disable the
      shader entirely then.
* Renames the `toggleRetroEffect` action to `toggleShaderEffect`.
  (`toggleRetroEffect` is now an alias to `toggleShaderEffect`). This
  action will turn the shader OR the retro effects on/off. 

`toggleShaderEffect` works the way you'd expect it to, but the mental
math on _how_ is a little weird. The logic is basically:

```
useShader = shaderEffectsEnabled ? 
                (pixelShaderProvided ? 
                    pixelShader : 
                    (retroEffectEnabled ? 
                        retroEffect : null
                    )
                ) : 
                null
```

and `toggleShaderEffect` toggles `shaderEffectsEnabled`.

* If you've got both a shader and retro enabled, `toggleShaderEffect`
  will toggle between the shader on/off.
* If you've got a shader and retro disabled, `toggleShaderEffect` will
  toggle between the shader on/off.

References #6191
References #7058

Closes #7013

Closes #3930 "Add setting to retro terminal shader to control blur
radius, color" 
Closes #3929 "Add setting to retro terminal shader to enable drawing
scanlines" 
     - At this point, just roll your own version of the shader.
2020-12-15 20:40:22 +00:00

5.4 KiB

Pixel Shaders in Windows Terminal

Due to the sheer amount of computing power in GPUs, one can do awesome things with pixel shaders such as real-time fractal zoom, ray tracers and image processing.

Windows Terminal allows user to provide a pixel shader which will be applied to the terminal. To try it out, add the following setting to one of your profiles:

"experimental.pixelShaderPath": "<path to a .hlsl pixel shader>"

Note

: if you specify a shader with experimental.pixelShaderPath, the Terminal will use that instead of the experimental.retroTerminalEffect.

To get started using pixel shaders in the Terminal, start with the following sample shader. This is Invert.hlsl in this directory:

// A minimal pixel shader that inverts the colors

// The terminal graphics as a texture
Texture2D shaderTexture;
SamplerState samplerState;

// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
  // Time since pixel shader was enabled
  float  Time;
  // UI Scale
  float  Scale;
  // Resolution of the shaderTexture
  float2 Resolution;
  // Background color as rgba
  float4 Background;
};

// A pixel shader is a program that given a texture coordinate (tex) produces a color
//  Just ignore the pos parameter
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
    // Read the color value at the current texture coordinate (tex)
    //  float4 is tuple of 4 floats, rgba
    float4 color = shaderTexture.Sample(samplerState, tex);

    // Inverts the rgb values (xyz) but don't touch the alpha (w)
    color.xyz = 1.0 - color.xyz;

    // Return the final color
    return color;
}

Save this file as C:\temp\invert.hlsl, then update a profile with the setting:

"experimental.pixelShaderEffect": "C:\\temp\\invert.hlsl"

Once the settings file is saved, open a terminal with the changed profile. It should now invert the colors of the screen!

If your shader fails to compile, the Terminal will display a warning dialog and ignore it temporarily. After fixing your shader, touch the settings.json file again, or open a new tab, and the Terminal will try loading the shader again.

HLSL

The language we use to write pixel shaders is called HLSL. It a C-like language, with some restrictions.You can't allocate memory, use pointers or recursion. What you get access to is computing power in the teraflop range on decently recent GPUs. This means writing real-time raytracers or other cool effects are in the realm of possibility.

shadertoy is a great site that show case what's possible with pixel shaders (albeit in GLSL). For example this menger sponge. Converting from GLSL to HLSL isn't overly hard once you gotten the hang of it.

Adding some retro raster bars

Let's try a more complicated example. Raster bars was cool in the 80's, so let's add that. Start by modifying shader like so: (This is Rasterbars.hlsl)

// A minimal pixel shader that shows some raster bars

// The terminal graphics as a texture
Texture2D shaderTexture;
SamplerState samplerState;

// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
  // Time since pixel shader was enabled
  float  Time;
  // UI Scale
  float  Scale;
  // Resolution of the shaderTexture
  float2 Resolution;
  // Background color as rgba
  float4 Background;
};

// A pixel shader is a program that given a texture coordinate (tex) produces a color
//  Just ignore the pos parameter
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
    // Read the color value at the current texture coordinate (tex)
    //  float4 is tuple of 4 floats, rgba
    float4 color = shaderTexture.Sample(samplerState, tex);

    // Read the color value at some offset, will be used as shadow
    float4 ocolor = shaderTexture.Sample(samplerState, tex+2.0*Scale*float2(-1.0, -1.0)/Resolution.y);

    // Thickness of raster
    const float thickness = 0.1;

    float ny = floor(tex.y/thickness);
    float my = tex.y%thickness;
    const float pi = 3.141592654;


    // ny is used to compute the rasterbar base color
    float cola = ny*2.0*pi;
    float3 col = 0.75+0.25*float3(sin(cola*0.111), sin(cola*0.222), sin(cola*0.333));

    // my is used to compute the rasterbar brightness
    //  smoothstep is a great little function: https://en.wikipedia.org/wiki/Smoothstep
    float brightness = 1.0-smoothstep(0.0, thickness*0.5, abs(my - 0.5*thickness));

    float3 rasterColor = col*brightness;

    // lerp(x, y, a) is another very useful function: https://en.wikipedia.org/wiki/Linear_interpolation
    float3 final = rasterColor;
    // Create the drop shadow of the terminal graphics
    //  .w is the alpha channel, 0 is fully transparent and 1 is fully opaque
    final = lerp(final, float(0.0), ocolor.w);
    // Draw the terminal graphics
    final = lerp(final, color.xyz, color.w);

    // Return the final color, set alpha to 1 (ie opaque)
    return float4(final, 1.0);
}

Once reloaded, it should show some retro raster bars in the background, with a drop shadow to make the text more readable.

Retro Terminal Effect

As a more complicated example, the Terminal's built-in experimental.retroTerminalEffect is included as the Retro.hlsl file in this directory. Feel free to modify and experiment!