mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-19 18:11:39 -05:00
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.
This commit is contained in:
18
samples/PixelShaders/Broken.hlsl
Normal file
18
samples/PixelShaders/Broken.hlsl
Normal file
@@ -0,0 +1,18 @@
|
||||
// Broken, can be used for explorative testing of pixel shader error handling
|
||||
Texture2D shaderTexture;
|
||||
SamplerState samplerState;
|
||||
|
||||
cbuffer PixelShaderSettings {
|
||||
float Time;
|
||||
float Scale;
|
||||
float2 Resolution;
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// OOPS; vec4 is not a hlsl but a glsl datatype!
|
||||
vec4 color = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
return color;
|
||||
}
|
||||
18
samples/PixelShaders/Error.hlsl
Normal file
18
samples/PixelShaders/Error.hlsl
Normal file
@@ -0,0 +1,18 @@
|
||||
// Shader used to indicate something went wrong during shader loading
|
||||
Texture2D shaderTexture;
|
||||
SamplerState samplerState;
|
||||
|
||||
cbuffer PixelShaderSettings {
|
||||
float Time;
|
||||
float Scale;
|
||||
float2 Resolution;
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
float4 color = shaderTexture.Sample(samplerState, tex);
|
||||
float bars = 0.5+0.5*sin(tex.y*100);
|
||||
color.x += pow(bars, 20.0);
|
||||
return color;
|
||||
}
|
||||
32
samples/PixelShaders/Grayscale.hlsl
Normal file
32
samples/PixelShaders/Grayscale.hlsl
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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);
|
||||
float avg = (color.x + color.y + color.z) / 3.0;
|
||||
// Inverts the rgb values (xyz) but don't touch the alpha (w)
|
||||
color.xyz = avg;
|
||||
|
||||
// Return the final color
|
||||
return color;
|
||||
}
|
||||
32
samples/PixelShaders/Invert.hlsl
Normal file
32
samples/PixelShaders/Invert.hlsl
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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;
|
||||
}
|
||||
17
samples/PixelShaders/Nop.hlsl
Normal file
17
samples/PixelShaders/Nop.hlsl
Normal file
@@ -0,0 +1,17 @@
|
||||
// Does nothing, serves as an example of a minimal pixel shader
|
||||
Texture2D shaderTexture;
|
||||
SamplerState samplerState;
|
||||
|
||||
cbuffer PixelShaderSettings {
|
||||
float Time;
|
||||
float Scale;
|
||||
float2 Resolution;
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
float4 color = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
return color;
|
||||
}
|
||||
136
samples/PixelShaders/README.md
Normal file
136
samples/PixelShaders/README.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# 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:
|
||||
|
||||
```hlsl
|
||||
// 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](https://shadertoy.com/) is a great site that show case what's possible with pixel shaders (albeit in `GLSL`). For example this [menger sponge](https://www.shadertoy.com/view/4scXzn). 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`)
|
||||
|
||||
```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!
|
||||
|
||||
58
samples/PixelShaders/Rasterbars.hlsl
Normal file
58
samples/PixelShaders/Rasterbars.hlsl
Normal file
@@ -0,0 +1,58 @@
|
||||
// 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);
|
||||
}
|
||||
83
samples/PixelShaders/Retro.hlsl
Normal file
83
samples/PixelShaders/Retro.hlsl
Normal file
@@ -0,0 +1,83 @@
|
||||
// The original retro pixel shader
|
||||
Texture2D shaderTexture;
|
||||
SamplerState samplerState;
|
||||
|
||||
cbuffer PixelShaderSettings {
|
||||
float Time;
|
||||
float Scale;
|
||||
float2 Resolution;
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
#define SCANLINE_FACTOR 0.5
|
||||
#define SCALED_SCANLINE_PERIOD Scale
|
||||
#define SCALED_GAUSSIAN_SIGMA (2.0*Scale)
|
||||
|
||||
static const float M_PI = 3.14159265f;
|
||||
|
||||
float Gaussian2D(float x, float y, float sigma)
|
||||
{
|
||||
return 1/(sigma*sqrt(2*M_PI)) * exp(-0.5*(x*x + y*y)/sigma/sigma);
|
||||
}
|
||||
|
||||
float4 Blur(Texture2D input, float2 tex_coord, float sigma)
|
||||
{
|
||||
uint width, height;
|
||||
shaderTexture.GetDimensions(width, height);
|
||||
|
||||
float texelWidth = 1.0f/width;
|
||||
float texelHeight = 1.0f/height;
|
||||
|
||||
float4 color = { 0, 0, 0, 0 };
|
||||
|
||||
int sampleCount = 13;
|
||||
|
||||
for (int x = 0; x < sampleCount; x++)
|
||||
{
|
||||
float2 samplePos = { 0, 0 };
|
||||
|
||||
samplePos.x = tex_coord.x + (x - sampleCount/2) * texelWidth;
|
||||
for (int y = 0; y < sampleCount; y++)
|
||||
{
|
||||
samplePos.y = tex_coord.y + (y - sampleCount/2) * texelHeight;
|
||||
if (samplePos.x <= 0 || samplePos.y <= 0 || samplePos.x >= width || samplePos.y >= height) continue;
|
||||
|
||||
color += input.Sample(samplerState, samplePos) * Gaussian2D((x - sampleCount/2), (y - sampleCount/2), sigma);
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
float SquareWave(float y)
|
||||
{
|
||||
return 1 - (floor(y / SCALED_SCANLINE_PERIOD) % 2) * SCANLINE_FACTOR;
|
||||
}
|
||||
|
||||
float4 Scanline(float4 color, float4 pos)
|
||||
{
|
||||
float wave = SquareWave(pos.y);
|
||||
|
||||
// TODO:GH#3929 make this configurable.
|
||||
// Remove the && false to draw scanlines everywhere.
|
||||
if (length(color.rgb) < 0.2 && false)
|
||||
{
|
||||
return color + wave*0.1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return color * wave;
|
||||
}
|
||||
}
|
||||
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
Texture2D input = shaderTexture;
|
||||
|
||||
// TODO:GH#3930 Make these configurable in some way.
|
||||
float4 color = input.Sample(samplerState, tex);
|
||||
color += Blur(input, tex, SCALED_GAUSSIAN_SIGMA)*0.3;
|
||||
color = Scanline(color, pos);
|
||||
|
||||
return color;
|
||||
}
|
||||
Reference in New Issue
Block a user