Cubes

The first version of the code for the rotating cubes follows immediately after the demo. We need to draw twelve triangles to cover the six faces of each cube, and repeat much data so that the pointers can move sequentially through the buffers. It is more efficient to store the coordinate and colour of each vertex only once, then store the order in which they must be accessed in a separate array. The second version of the code uses this more efficient strategy. If you see no display at school, the security system might have blocked it. You can try instead this direct link to the program running on its own page.

Rotating Cubes

Code of Cubes

unit Form1;

interface

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

type
  TForm1 = class(TW3form)
  protected
    FCanvas : TW3GraphicContext;
    gl : JWebGLRenderingContext;
    rc : TGLRenderingContext;
    FCubeBuffer, FColorBuffer: TGLArrayBuffer;
    FFragmentShader: TGLFragmentShader;
    FVertexShader: TGLVertexShader;
    FShaderProgram: TGLShaderProgram;
    FVertexPosAttrib, FVertexColorAttrib: 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, 500);
  FCanvas.Handle.height := Min(Height, 500);
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
  gl.enable(gl.DEPTH_TEST);           // Enable depth testing
  gl.depthFunc(gl.LEQUAL);            // Near things obscure far things

  FColorBuffer := TGLArrayBuffer.Create(rc);
  //http://chimera.labs.oreilly.com/books/1234000000802/ch02.html
  FColorBuffer.SetData([
     //front face: 0, 1, 2, 0, 2, 3
     1.0, 0.0, 0.0, //r 0
     0.0, 1.0, 0.0, //g 1
     0.0, 0.0, 1.0, //b 2
     1.0, 0.0, 0.0, //r 0
     0.0, 0.0, 1.0, //b 2
     1.0, 1.0, 1.0, //w 3
     //back face: 4, 5, 6, 4, 6, 7
      0.0, 0.0, 0.0, //black 4
      1.0, 1.0, 0.0, //yellow 5
      0.0, 1.0, 1.0, //purple 6
      0.0, 0.0, 0.0, //black 4
      0.0, 1.0, 1.0, //purple 6
      1.0, 0.0, 1.0, //magenta 7
      //top face: 5, 3, 2, 5, 2, 6
      1.0, 1.0, 0.0, //yellow 5
      1.0, 1.0, 1.0, //w 3
      0.0, 0.0, 1.0, //b 2
      1.0, 1.0, 0.0, //yellow 5
      0.0, 0.0, 1.0, //b 2
      0.0, 1.0, 1.0, // purple 6
      //bottom face: 4, 7, 1, 4, 1, 0
      0.0, 0.0, 0.0, //black 4
      1.0, 0.0, 1.0, //magenta 7
      0.0, 1.0, 0.0, //g 1
      0.0, 0.0, 0.0, //black 4
      0.0, 1.0, 0.0, //g 1
      1.0, 0.0, 0.0, //r 0
      //right face: 7, 6, 2, 7, 2, 1
      1.0, 0.0, 1.0, // magenta 7
      0.0, 1.0, 1.0, // purple 6
      0.0, 0.0, 1.0, //b 2
      1.0, 0.0, 1.0, //magenta 7
      0.0, 0.0, 1.0, //b 2
      0.0, 1.0, 0.0, //g 1
      //left face: 4, 0, 3, 4, 3, 5
      0.0, 0.0, 0.0, //black 4
      1.0, 0.0, 0.0, //r 0
      1.0, 1.0, 1.0, //w 3
      0.0, 0.0, 0.0, //black 4
      1.0, 1.0, 1.0, //w 3
      1.0, 1.0, 0.0  //yellow 5
    ], abuStatic);

    FCubeBuffer := TGLArrayBuffer.Create(rc);

    FCubeBuffer.SetData([ //Vertices of  cube
           //front face: 0, 1, 2, 0, 2, 3
           -1.0, -1.0,  1.0, //0
            1.0, -1.0,  1.0, //1
            1.0,  1.0,  1.0, //2
           -1.0, -1.0,  1.0, //0
            1.0, 1.0,  1.0,  //2
           -1.0,  1.0,  1.0, //3
           //back face: 4, 5, 6,  4, 6, 7
           -1.0, -1.0, -1.0, //4
           -1.0,  1.0, -1.0, //5
            1.0,  1.0, -1.0, //6
           -1.0, -1.0, -1.0, //4
            1.0,  1.0, -1.0, //6
            1.0, -1.0, -1.0, //7
            //top face: 8, 9, 10,  8, 10, 11
           -1.0,  1.0, -1.0, //8 same as 5
           -1.0,  1.0,  1.0, //9 same as 3
            1.0,  1.0,  1.0, //10 same as 2
           -1.0,  1.0, -1.0, //8 same as 5
            1.0,  1.0,  1.0, //10 same as 2
            1.0,  1.0, -1.0, //11 same as 6
           //bottom face: 12, 13, 14, 12, 14, 15
           -1.0, -1.0, -1.0, //12 same as 4
            1.0, -1.0, -1.0, //13 same as 7
            1.0, -1.0,  1.0, //14 same as 1
           -1.0, -1.0, -1.0, //12 same as 4
            1.0, -1.0,  1.0, //14 same as 1
           -1.0, -1.0,  1.0, //15 same as 0
           //right face: 16, 17, 18, 16, 18, 19
            1.0, -1.0, -1.0, //16 same as 7
            1.0,  1.0, -1.0, //17 same as 6
            1.0,  1.0,  1.0, //18 same as 2
            1.0, -1.0, -1.0, //16 same as 7
            1.0,  1.0,  1.0, //18 same as 2
            1.0, -1.0,  1.0, //19 same as 1
           //left face: 20, 21, 22, 20, 22, 23
           -1.0, -1.0, -1.0, //20 same as 4
           -1.0, -1.0,  1.0, //21 same as 0
           -1.0,  1.0,  1.0, //22 same as 3
           -1.0, -1.0, -1.0, //20 same as 4
           -1.0,  1.0,  1.0, //22 same as 3
           -1.0,  1.0, -1.0  //23 same as 5
         ], abuStatic);
  // create vertex shader
  FVertexShader := TGLVertexShader.Create(rc);
  if not FVertexShader.Compile(#"
    attribute vec3 aVertexPosition;
    attribute vec3 aVertexColor;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying vec4 vColor;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
      vColor = vec4(aVertexColor, 1.0);
    }") then
    raise Exception.Create(FVertexShader.InfoLog);

  // create fragment shader
  FFragmentShader := TGLFragmentShader.Create(rc);
  FFragmentShader.Compile(#"
    precision mediump float;
    varying vec4 vColor;
    void main(void) {
       gl_FragColor = vColor;
    }");

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

  FVertexPosAttrib := FShaderProgram.AttribLocation("aVertexPosition");
  FVertexColorAttrib := FShaderProgram.AttribLocation("aVertexColor");
  asm
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       ||
              window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame    ||
              window.oRequestAnimationFrame      ||
              window.msRequestAnimationFrame     ||
              function( callback ){
                window.setTimeout(callback, 1000 / 60);
              };
    })();
  end;
end;

procedure TForm1.Render;
const
  cSpeed = 24 * 3600;
var
  ProjectionMatrix, ModelViewMatrix : Matrix4;
  ModelViewStack : array of Matrix4;

  procedure RenderCube(x, y, z : Double);
  begin
    ModelViewStack.Push(ModelViewMatrix); //save the matrix
    ModelViewMatrix := ModelViewMatrix.Translate([x, y, z]);
    ModelViewMatrix := ModelViewMatrix.RotateX(cSpeed * Frac(Now) );
    ModelViewMatrix := ModelViewMatrix.RotateY(cSpeed * Frac(Now) / 2 );
    ModelViewMatrix := ModelViewMatrix.RotateZ(cSpeed * Frac(Now) / 3 );
    FShaderProgram.SetUniform('uModelViewMatrix', ModelViewMatrix);
    FCubeBuffer.VertexAttribPointer(FVertexPosAttrib, 3, False, 0, 0);
    FColorBuffer.VertexAttribPointer(FVertexColorAttrib, 3, False, 0, 0);
    //Draw the 12 triangles starting vertices in strict order.
      gl.drawArrays(gl.TRIANGLES, 0, 36);

    ModelViewMatrix := ModelViewStack.Pop; //restore the matrix
  end;

begin
  // set viewport to bounds of canvas
  gl.ViewportSet(0, 0, FCanvas.Handle.width, FCanvas.Handle.height);
  // clear background
  gl.clear(gl.COLOR_BUFFER_BIT);
  FShaderProgram.Use;
  //Camera field of view of 45 shows the cube just fitting into the canvas
  ProjectionMatrix := Matrix4.CreatePerspective(40, FCanvas.width / FCanvas.height,
                                                0.1, 100);
  ModelViewMatrix := Matrix4.Identity;
  FShaderProgram.SetUniform('uProjectionMatrix', ProjectionMatrix);
  gl.enableVertexAttribArray(FVertexPosAttrib);
  gl.enableVertexAttribArray(FVertexColorAttrib);

  RenderCube(-1.5, 0, -10); //nested procedure
  RenderCube(1.5, 0, -10); //nested procedure
  //RenderCube(0, 0, 0); //to put the camera inside a cube
  var renderCallback := @Render;
  asm
    window.requestAnimFrame(@renderCallback);
  end;
end;

end.

Using an array of indices

In the above example, although the positions and colours of only eight vertices were required, they were repeated several times in the arrays so that they could be read sequentially. This example, just showing one static cube for simplicity, uses an array of indices. The new lines of code to look for in the listing are:
FIndexBuffer: TGLElementArrayBuffer;
FIndexBuffer:= TGLElementArrayBuffer.Create(rc);
FIndexBuffer.SetData([0,1,2,0,2,3,4,5,6,4,6,7,5,3,2,5,2,6,4,7,1,4,1,0,7,6,2,7,2,1,4,0,3,4,3,5],abuStatic);	
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, FIndexBuffer.Handle);
gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0); 
unit Form1;

interface

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

type
  TForm1 = class(TW3form)
  protected
    FCanvas : TW3GraphicContext;
    gl : JWebGLRenderingContext;
    rc : TGLRenderingContext;
    FIndexBuffer: TGLElementArrayBuffer;
    FCubeBuffer, FColorBuffer: TGLArrayBuffer;
    FFragmentShader: TGLFragmentShader;
    FVertexShader: TGLVertexShader;
    FShaderProgram: TGLShaderProgram;
    FVertexPosAttrib, FVertexColorAttrib: 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, 500);
  FCanvas.Handle.height := Min(Height, 500);
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
  gl.enable(gl.DEPTH_TEST);           // Enable depth testing
  gl.depthFunc(gl.LEQUAL);            // Near things obscure far things

  FIndexBuffer:= TGLElementArrayBuffer.Create(rc);
  FIndexBuffer.SetData([0,1,2,0,2,3,4,5,6,4,6,7,5,3,2,5,2,6,4,7,1,4,1,0,7,6,2,7,2,1,4,0,3,4,3,5],abuStatic);

  FColorBuffer := TGLArrayBuffer.Create(rc);
  //http://chimera.labs.oreilly.com/books/1234000000802/ch02.html
  FColorBuffer.SetData([
     1.0, 0.0, 0.0, //r 0
     0.0, 1.0, 0.0, //g 1
     0.0, 0.0, 1.0, //b 2
     1.0, 1.0, 1.0, //w 3
     0.0, 0.0, 0.0, //black 4
     1.0, 1.0, 0.0, //yellow 5
     0.0, 1.0, 1.0, //cyan 6
     1.0, 0.0, 1.0  //magenta 7
    ], abuStatic);

    FCubeBuffer := TGLArrayBuffer.Create(rc);

    FCubeBuffer.SetData([ //Vertices of  cube
           -1.0, -1.0,  1.0, //0
            1.0, -1.0,  1.0, //1
            1.0,  1.0,  1.0, //2
           -1.0,  1.0,  1.0, //3
           -1.0, -1.0, -1.0, //4
           -1.0,  1.0, -1.0, //5
            1.0,  1.0, -1.0, //6
            1.0, -1.0, -1.0  //7
         ], abuStatic);
  // create vertex shader
  FVertexShader := TGLVertexShader.Create(rc);
  if not FVertexShader.Compile(#"
    attribute vec3 aVertexPosition;
    attribute vec3 aVertexColor;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying vec4 vColor;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
      vColor = vec4(aVertexColor, 1.0);
    }") then
    raise Exception.Create(FVertexShader.InfoLog);

  // create fragment shader
  FFragmentShader := TGLFragmentShader.Create(rc);
  FFragmentShader.Compile(#"
    precision mediump float;
    varying vec4 vColor;
    void main(void) {
       gl_FragColor = vColor;
    }");

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

  FVertexPosAttrib := FShaderProgram.AttribLocation("aVertexPosition");
  FVertexColorAttrib := FShaderProgram.AttribLocation("aVertexColor");
end;

procedure TForm1.Render;

var
  ProjectionMatrix, ModelViewMatrix : Matrix4;
  ModelViewStack : array of Matrix4;

  procedure RenderCube(x, y, z : Double);
  begin
    ModelViewStack.Push(ModelViewMatrix); //save the matrix
    ModelViewMatrix := ModelViewMatrix.Translate([x, y, z]);
    ModelViewMatrix := ModelViewMatrix.RotateX(0.5);
    ModelViewMatrix := ModelViewMatrix.RotateY(0.5);
    ModelViewMatrix := ModelViewMatrix.RotateZ(0.2);
    FShaderProgram.SetUniform('uModelViewMatrix', ModelViewMatrix);

    FCubeBuffer.VertexAttribPointer(FVertexPosAttrib, 3, False, 0, 0);
    FColorBuffer.VertexAttribPointer(FVertexColorAttrib, 3, False, 0, 0);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, FIndexBuffer.Handle);
    //Draw the 12 triangles that make up the cube
    gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);

    ModelViewMatrix := ModelViewStack.Pop; //restore the matrix
  end;

begin
  // set viewport to bounds of canvas
  gl.ViewportSet(0, 0, FCanvas.Handle.width, FCanvas.Handle.height);
  // clear background
  gl.clear(gl.COLOR_BUFFER_BIT);
  FShaderProgram.Use;
  //Camera field of view of 45 shows the cube just fitting into the canvas
  ProjectionMatrix := Matrix4.CreatePerspective(40, FCanvas.width / FCanvas.height,
                                                0.1, 100);
  ModelViewMatrix := Matrix4.Identity;
  FShaderProgram.SetUniform('uProjectionMatrix', ProjectionMatrix);
  gl.enableVertexAttribArray(FVertexPosAttrib);
  gl.enableVertexAttribArray(FVertexColorAttrib);
  RenderCube(0, 0, -10);
end;
Programming - a skill for life!

Using WebGL for advanced graphics in Smart Mobile Studio