Using Shaders

For a clear introduction to vertex shaders and fragment (pixel) shaders, including what each type of shader can and cannot do, see Lighthouse3D.com. The shader language is similar to C, so refer to our tutorial C/C++ after Pascal as necessary.

The supplied PasSFML demonstration Shader is most impressive and a great starting point for those that can understand all of the code. We provide much simpler examples here. The vertex shader tri.vert lowers the top vertex of the triangle. The fragment shader tri.frag subtracts 0.2 from the red component of each pixel colour in the left half of the triangle and adds 0.2 to the red component of each pixel colour on the right half of the triangle. If the code in either shader file fails to compile, neither is used.

Output

Output

program ShaderTriangleDemo;
{$Apptype Gui}
uses
  SysUtils, SfmlGraphics, SfmlSystem, SfmlWindow;
const
  WINDOW_WIDTH = 200;
  WINDOW_HEIGHT = 200;
var
  Window: TSfmlRenderWindow;
  OutputImage: PsfmlImage;
  Triangle: TSfmlVertexArray;
  TriangleShader: TSfmlShader;
  States: TSfmlRenderStates;
begin
  States.BlendMode := SfmlBlendNone;
  States.Transform := SfmlTransformIdentity;
  // Create the main Window
  Window := TSfmlRenderWindow.Create(SfmlVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), 'Shader',
    [sfTitleBar, sfClose]);
  Window.setVerticalSyncEnabled(true);

  Triangle := TSfmlVertexArray.Create;
  Triangle.PrimitiveType := sfTriangles;
  Triangle.Append(SfmlVertex(SfmlVector2f(WINDOW_WIDTH / 2, 0.0), sfmlRed));
  Triangle.Append(SfmlVertex(SfmlVector2f(0.0, WINDOW_HEIGHT), sfmlBlue));
  Triangle.Append(SfmlVertex(SfmlVector2f(WINDOW_WIDTH, WINDOW_HEIGHT), sfmlGreen));

  TriangleShader := TSfmlShader.CreateFromFile('tri.vert', 'tri.frag');
  States.Shader := TriangleShader.Handle;
  Window.Clear(SfmlBlack);
  // Draw the triangle
  Window.Draw(Triangle, @States);

  OutputImage := Window.Capture;
  SfmlImageSaveToFile(OutputImage, 'ShaderTriangleOutputImage.png');

  Window.Display;
  sfmlsleep(sfmlSeconds(10));
end.
    

Code of tri.vert:

void main()
{
  vec4 vertex = gl_Vertex;
  if (vertex.y == 0)
    vertex.y = 50;
  gl_Position = gl_ModelViewProjectionMatrix * vertex;
  gl_FrontColor = gl_Color;
}
    

Code of tri.frag:

void main()
{
  vec4 pixel = gl_Color;
  if (gl_FragCoord.x > 100)
    pixel.r += 0.2;
  else
    pixel.r -= 0.2;
  gl_FragColor = pixel;
}
    

Setting a Parameter

In order to set a parameter with the above program, insert the line
TriangleShader.SetParameter('effect_x', 120);     
immediately after the line that creates the shader and use this second version of tri.frag:
uniform float effect_x;

void main()
{
  vec4 pixel = gl_Color;
  if (gl_FragCoord.x > effect_x)
    pixel.r += 0.2;
  else
    pixel.r -= 0.2;
  gl_FragColor = pixel;
}    

The output is shown in this captured image:

Output after setting effect_x to 120

Output after setting effect_x to 120

The following example changes the boundary of the redness effect as you move the mouse pointer across the triangle. Paste this code to replace the code of the supplied Shader example (after saving a backup of the original code). Put the tri.vert and the second version of tri.frag into the Resources folder.

program Shader;

uses
  SysUtils,
  SfmlGraphics in '..\..\Source\SfmlGraphics.pas',
  SfmlSystem in '..\..\Source\SfmlSystem.pas',
  SfmlWindow in '..\..\Source\SfmlWindow.pas',
  Effect in 'Effect.pas';
const
  WINDOW_WIDTH = 200;
  WINDOW_HEIGHT = 200;

type
  TTriangle = class(TEffect)
  private
    FTriangle: TSfmlVertexArray;
    FShader: TSfmlShader;
  public
    constructor Create;
    function OnLoad: Boolean; override;
    procedure OnUpdate(Time, X, Y: Single); override;
    procedure OnDraw(Target: TSfmlRenderTarget; States: PSfmlRenderStates); override;
  end;

constructor TTriangle.Create;
begin
  inherited Create('Triangle');
  FTriangle := TSfmlVertexArray.Create;
end;

procedure TTriangle.OnDraw(Target: TSfmlRenderTarget;
  States: PSfmlRenderStates);
begin
  States.Shader := FShader.Handle;
  Target.Draw(FTriangle, States);
end;

function TTriangle.OnLoad: Boolean;
begin
  // Create the triangles
  FTriangle.PrimitiveType := sfTriangles;
  FTriangle.Append(SfmlVertex(SfmlVector2f(WINDOW_WIDTH / 2, 0.0), sfmlRed));
  FTriangle.Append(SfmlVertex(SfmlVector2f(0.0, WINDOW_HEIGHT), sfmlBlue));
  FTriangle.Append(SfmlVertex(SfmlVector2f(WINDOW_WIDTH, WINDOW_HEIGHT), sfmlGreen));
  // Load the shader
  FShader := TSfmlShader.CreateFromFile('../Resources/tri.vert', '../Resources/tri.frag');
  Result := Assigned(FShader);
end;

procedure TTriangle.OnUpdate(Time, X, Y: Single);
begin
  inherited;
  FShader.SetParameter('effect_x', X);
end;

var
  Window: TSfmlRenderWindow;
  State: TSfmlRenderStates;
  Clock: TSfmlClock;
  TriangleEffect: TEffect;
  Event: TSfmlEvent;
  X, Y: Single;
begin
  State.BlendMode := SfmlBlendAlpha;
  State.Transform := SfmlTransformIdentity;

  // Create the main Window
  Window := TSfmlRenderWindow.Create(SfmlVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), 'Shader',
    [sfTitleBar, sfClose]);
  Window.SetVerticalSyncEnabled(true);
  // Create the effect
  TriangleEffect := TTriangle.Create;
  // Initialize it
  TriangleEffect.Load;
  // Start the game loop
  Clock := TSfmlClock.Create;
  while Window.isOpen do
    begin
      // Process events
      while Window.PollEvent(Event) do
        begin
          // Close Window: exit
          if Event.EventType = sfEvtClosed then
            Window.Close;
          if Event.EventType = sfEvtKeyPressed then
            if Event.Key.Code = sfKeyEscape then
              Window.Close;
        end;
      // Update the current example
      X := Window.MousePosition.X;
      TriangleEffect.Update(Clock.ElapsedTime.AsSeconds, X, Y);
      // Clear the Window
      Window.Clear(SfmlBlack);
      // Draw the triangle
      TriangleEffect.Draw(Window, @State);
      // Finally, display the rendered frame on screen
      Window.Display;
    end;
  // delete the effect
  TriangleEffect.Free;
end.
    

Using a Shader with a Texture

This example is much simpler than the pixelate and edge effects in the supplied Shader example. As you move the mouse pointer away from the top left corner the red, blue and green components of the colour increase to give a whiter image. The program uses, in the project folder, OncaPintada.jpg (copied from the supplied Resources folder) and this little file texture.frag:

uniform sampler2D texture;
uniform float deltaRGB;

void main()
{
  gl_FragColor = texture2D(texture,gl_TexCoord[0].st) + vec4(deltaRGB, deltaRGB, deltaRGB, 0.0);	
}    

Pascal code:

program ShaderTextureDemo;
{$Apptype Gui}
uses
  SysUtils, SfmlGraphics, SfmlSystem, SfmlWindow;
const
  WINDOW_WIDTH = 800;
  WINDOW_HEIGHT = 600;
var
  Window: TSfmlRenderWindow;
  Event: TSfmlEvent;
  MyTexture:  TSfmlTexture;
  MySprite: TSfmlSprite;
  TextureShader: TSfmlShader;
  States: TSfmlRenderStates;
  X, Y: single;
begin
  States.BlendMode := SfmlBlendNone;
  States.Transform := SfmlTransformIdentity;
  // Create the main Window
  Window := TSfmlRenderWindow.Create(SfmlVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), 'Shader',
    [sfTitleBar, sfClose]);
  Window.setVerticalSyncEnabled(true);
  MySprite := TSfmlSprite.Create;
  MyTexture := TSfmlTexture.Create('OncaPintada.jpg');
  MySprite.SetTexture(MyTexture);
  TextureShader := TSfmlShader.CreateFromFile('', 'texture.frag');
  States.Shader := TextureShader.Handle;
  while Window.isOpen do
    begin
      while SfmlRenderWindowPollEvent(Window.Handle, Event) do
        begin
          if Event.EventType = sfEvtClosed then  // Window closed
            SfmlRenderWindowClose(Window.Handle);
          // Escape key pressed
          if (Event.EventType = sfEvtKeyPressed) and (Event.Key.Code = sfKeyEscape) then
            SfmlRenderWindowClose(Window.Handle);
        end;
      X := Window.MousePosition.X / Window.Size.X;
      Y := Window.MousePosition.Y / Window.Size.Y;
      TextureShader.SetParameter('deltaRGB', (X + Y) / 3);
      // Draw the sprite
      Window.Draw(MySprite, @States);
      Window.Display;
    end;
end.

Programming - a skill for life!

How to use the Simple Fast Media Library in Lazarus