Displaying a Rectangle

Our first three demonstrations are based on WebGL Fundamentals by Gregg Tavares. The output of our Smart Mobile Studio code is similar to that of the original JavaScript. The first example renders a green rectangle comprising two triangles. Since we wrote this introduction and demonstration, a visual TW3WebGL control has become available, with different code to support its use. See the next section of this page for updated notes and code for use with Version 2.1 of Smart Mobile Studio

The key to getting started is the line
gl := JWebGLRenderingContext(FCanvas.Handle.getContext('experimental-webgl'));
(or more simply gl := JWebGLRenderingContext(FCanvas.Handle.getContext('webgl')); now that WebGL has become established). Once you have a reference to the context you have access to many inbuilt routines in the w3c.WebGL unit.

The coordinates for the two triangles are given in "clipspace" with values in the range -1 to +1. See the following page for code to convert coordinates in pixels to clipspace.

This first example shows how you need both a vertex shader and a fragment (pixel) shader even to render a couple of triangles to make up a rectangle. The first line of the vertex shader is
attribute vec2 a_position;
According to the useful WebGL Quick Reference Card, the type vec2 denotes a 2-component floating point vector. Conventionally, the prefix "a" represents attribute. Attributes have a one-to-one relationship with vertices.

The output gl_Position has homogeneous coordinates (with x, y, z, and w) components. In this simple case no matrix is applied and we simply need to add a z component of 0 and a w component of 1 to the x and y values that we input.

We need to supply the values of the attribute a_position to the shader. We find its location with
FVertexPosAttrib := FShaderProgram.AttribLocation("a_position");
then supply values from the buffer with
FRectBuffer.VertexAttribPointer(FVertexPosAttrib, 2, False, 0, 0);
The value of the second argument is 2 because the numbers are supplied in pairs (the x and y coordinates). In later examples with 3D shapes its value will be 3.

The trivial fragment shader simply outputs the same value of gl_FragColor corresponding to green for each pixel. You should not find it difficult to output a different colour.

unit Form1;
//Draws a green rectangle made up of two triangles
//Based on http://www.html5rocks.com/en/tutorials/webgl/webgl_fundamentals/
interface

uses
  w3system, w3graphics, w3forms, w3application, w3c.webgl, GLS.Base;

type
  TForm1 = class(TW3form)
  protected
    FCanvas : TW3GraphicContext;
    gl : JWebGLRenderingContext;
    rc : TGLRenderingContext;
    FRectBuffer : TGLArrayBuffer;
    FFragmentShader: TGLFragmentShader;
    FVertexShader: TGLVertexShader;
    FShaderProgram: TGLShaderProgram;
    FVertexPosAttrib : Integer;
    procedure InitializeObject; override;
    procedure Resize; override;
    procedure SetupScene;
    procedure Render;
  end;

implementation

procedure TForm1.InitializeObject;
begin
  inherited;
  FCanvas := TW3GraphicContext.Create(Self.Handle);
  gl := JWebGLRenderingContext(FCanvas.Handle.getContext('experimental-webgl'));
  rc := TGLRenderingContext.Create;
  rc.GL := gl;
  setupScene;
  Render;
end;

procedure TForm1.Resize;
begin
  inherited;
  FCanvas.Handle.width := Min(Width, 300);
  FCanvas.Handle.height := Min(Height, 300);
end;

procedure TForm1.SetupScene;
begin
  gl.clearColor(0.0, 0.0, 0.25, 1.0); // Set clear color to blue, fully opaque
  gl.clearDepth(1.0);                 // Clear everything
  //Put x and y coords of vertices in buffer
  FRectBuffer := TGLArrayBuffer.Create(rc);
  FRectBuffer.SetData([
        -1.0, -1.0,
         1.0, -1.0,
        -1.0,  1.0,
        -1.0,  1.0,
         1.0, -1.0,
         1.0,  1.0
    ], abuStatic);

  // create vertex shader
  FVertexShader := TGLVertexShader.Create(rc);
  FVertexShader.Compile(#"
   attribute vec2 a_position;

   void main() {
     gl_Position = vec4(a_position, 0, 1);
          }");

  // create fragment shader
  FFragmentShader := TGLFragmentShader.Create(rc);
  FFragmentShader.Compile(#"
    void main() {
      gl_FragColor = vec4(0, 1, 0, 1);  // green
    }");

  // create shader program and link shaders
  FShaderProgram := TGLShaderProgram.Create(rc);
  FShaderProgram.Link(FVertexShader, FFragmentShader);

  FVertexPosAttrib := FShaderProgram.AttribLocation("a_position");
end;

procedure TForm1.Render;
begin
  gl.ViewportSet(0, 0, FCanvas.Handle.width, FCanvas.Handle.height);
  gl.clear(gl.COLOR_BUFFER_BIT);  // clears background
  FShaderProgram.Use;
  gl.enableVertexAttribArray(FVertexPosAttrib);
  FRectBuffer.VertexAttribPointer(FVertexPosAttrib, 2, False, 0, 0);
  gl.drawArrays(gl.TRIANGLES, 0, 6);
end;

end.

Using a TW3WebGL Component in Version 2.1 of Smart Mobile Studio

This demonstration is based on both WebGL Fundamentals by Gregg Tavares and on the featured demo Lesson 1. (Lesson 1 in the Demos\Featured Demos\API\WebGL folder did not work for us without the callback code).

Once you have written the code FWebGL := WebGLCanvas.Scene.Handle;, you have access to many inbuilt routines. Type FWebGL. to see the possibilities in code insight.

The coordinates for the two triangles are given in "clipspace" with values in the range -1 to +1. See the following page for code to convert coordinates in pixels to clipspace.

This first example shows how you need both a vertex shader and a fragment (pixel) shader even to render a couple of triangles to make up a rectangle. The first line of the vertex shader is
attribute vec2 a_position;
According to the useful WebGL Quick Reference Card, the type vec2 denotes a 2-component floating point vector. Conventionally, the prefix "a" represents attribute. Attributes have a one-to-one relationship with vertices.

The output gl_Position has homogeneous coordinates (with x, y, z, and w) components. In this simple case no matrix is applied and we simply need to add a z component of 0 and a w component of 1 to the x and y values that we input.

We need to supply the values of the attribute a_position to the shader. We find its location with
FVertexPosAttrib := FWebGL.getAttribLocation(FShaderProgram, "a_position");
then supply values from the buffer with
FWebGL.vertexAttribPointer(FVertexPosAttrib, 2, FWebGL.FLOAT, False, 0, 0);
The value of the second argument is 2 because the numbers are supplied in pairs (the x and y coordinates). In later examples with 3D shapes its value will be 3.

The trivial fragment shader simply outputs the same value of gl_FragColor corresponding to green for each pixel. You should not find it difficult to output a different colour.

See the comment at the start of this code for instructions on how to use it in a new project.

unit Form1;
{ Start a new form-based project, select the Graphics tab and add a TW3WebGL
  component then name the component WebGLCanvas. Paste this code into the source
  view of the form. Change Line 121 to FWebGL.drawArrays(FWebGL.TRIANGLES, 0, 3);
  to see one of the two green triangles.}

interface

uses
  W3C.TypedArray, SmartCL.System, SmartCL.Graphics,
  SmartCL.Controls, SmartCL.Components, SmartCL.Forms, SmartCL.Fonts,
  SmartCL.Borders, SmartCL.Application, SmartCL.Controls.WebGL,
  Khronos.WebGl, GLS.Vectors;

type
  TForm1 = class(TW3Form)
  private
    {$I 'Form1:intf'}
  protected
    FWebGL: JWebGLRenderingContext;
    FBuffer: JWebGLBuffer;
    FFragmentShader, FVertexShader: JWebGLShader;
    FShaderProgram: JWebGLProgram;
    FVertexPosAttrib: GLint;

    procedure InitializeObject; override;
    procedure InitShaders;
    procedure InitBuffers;
    procedure DrawScene;
  end;

implementation

procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}
  FWebGL := WebGLCanvas.Scene.Handle;

  InitShaders;
  InitBuffers;

  FWebGL.clearColor(0.0, 0.0, 1.0, 1.0);
  FWebGL.enable(FWebGL.DEPTH_TEST);
  asm
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       ||
              window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame    ||
              window.oRequestAnimationFrame      ||
              window.msRequestAnimationFrame     ||
              function( callback ){
                window.setTimeout(callback, 1000 / 60);
              };
    })();
  end;
  DrawScene;
end;

procedure TForm1.InitBuffers;
var
  Vertices: array of Float;
begin
  FBuffer := FWebGL.createBuffer;
  FWebGL.bindBuffer(FWebGL.ARRAY_BUFFER, FBuffer);
  Vertices :=  [
        -1.0, -1.0,
         1.0, -1.0,
        -1.0,  1.0,
        -1.0,  1.0,
         1.0, -1.0,
         1.0,  1.0
    ];

  FWebGL.bufferData(FWebGL.ARRAY_BUFFER, JFloat32Array.Create(Vertices),
    FWebGL.STATIC_DRAW);
end;

procedure TForm1.InitShaders;
begin
  // create vertex shader
  FVertexShader := FWebGL.createShader(FWebGL.VERTEX_SHADER);
  FWebGL.shaderSource(FVertexShader, #"
    attribute vec2 a_position;

    void main(void) {
        gl_Position = vec4(a_position, 0.0, 1.0);
    }");

  // compile vertex shader
  FWebGL.compileShader(FVertexShader);
  // create fragment shader
  FFragmentShader := FWebGL.createShader(FWebGL.FRAGMENT_SHADER);
  FWebGL.shaderSource(FFragmentShader, #"
     void main(void) {
        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    }");

  // compile fragment shader
  FWebGL.compileShader(FFragmentShader);

  // create shader program and attach shaders, then link shader program
  FShaderProgram := FWebGL.createProgram();
  FWebGL.attachShader(FShaderProgram, FVertexShader);
  FWebGL.attachShader(FShaderProgram, FFragmentShader);
  FWebGL.linkProgram(FShaderProgram);
  FWebGL.useProgram(FShaderProgram);

  FVertexPosAttrib := FWebGL.getAttribLocation(FShaderProgram, "a_position");
  FWebGL.enableVertexAttribArray(FVertexPosAttrib);
end;

procedure TForm1.DrawScene;
begin
  // Set viewport to bounds of WebGL canvas
  FWebGL.ViewportSet(0, 0, WebGLCanvas.Width, WebGLCanvas.Height);
  // clear background
  FWebGL.clear(FWebGL.COLOR_BUFFER_BIT);

  FWebGL.vertexAttribPointer(FVertexPosAttrib, 2, FWebGL.FLOAT, False, 0, 0);
  FWebGL.drawArrays(FWebGL.TRIANGLES, 0, 6);

  var renderCallback := @DrawScene;
  asm
    window.requestAnimFrame(@renderCallback);
  end;
end;

initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.
Programming - a skill for life!

Using WebGL for advanced graphics in Smart Mobile Studio