Using Multiple Textures

This demonstration uses four textures. We wait until all the images have loaded before processing them in a similar way to the single texture on the previous page. For successive textures we need to set the active texture to the currently processed texture and also to bind that texture. When rendering, it is easy to switch between textures by binding and to select the appropriate couple of triangles to display.

You need to remember to add each image that you use to Resources (in the left panel of the Smart Mobile Studio IDE) so that they will be copied to the res folder. Images in which the number of pixels in both the x and y directions is a power of two are better for scaling.

The code follows a screenshot of the program in action. The images on the walls of the box are screenshots of some popular student programs.

Multiple Textures

Multiple Textures

This code compiles with Versions 2.2 and 3.0 of Smart Mobile Studio to give an effective HTML file on a server as shown above. Some browsers do not show the images when opening the HTML file directly from a local folder on a PC.

unit Form1;

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, GLS.Base, W3C.Canvas2DContext;

type
  TForm1 = class(TW3form)
  protected
    FFilepaths: array[0..3] of string = ['res/crazypaint.jpg', 'res/destination.png', 'res/Screenshot-5.jpg', 'res/adventure.png'];
    FCanvas: TW3GraphicContext;
    gl: JWebGLRenderingContext;
    rc: TGLRenderingContext;
    FCubeBuffer, FTexCoordBuffer: TGLArrayBuffer;
    FFragmentShader: TGLFragmentShader;
    FVertexShader: TGLVertexShader;
    FShaderProgram: TGLShaderProgram;
    FVertexPosAttrib, FTexCoordAttrib, FNumTextures, FLastImageNum: integer;
    FImages: array of TW3Image;
    data: array of JImageData;
    textures: array of JWebGLTexture;
    procedure InitializeObject; override;
    procedure Resize; override;
    procedure SetupScene;
    procedure Render;
    procedure ProcessImage(Sender : TObject);
  end;

implementation

procedure TForm1.InitializeObject;
begin
  inherited;
  FNumTextures := length(FFilepaths);
  FLastImageNum := FNumTextures - 1;
  for var i := 0 to FLastImageNum do
    begin
      FImages[i] := TW3Image.Create(nil);
      FImages[i].LoadFromURL(FFilepaths[i]);
      FImages[i].OnLoad := ProcessImage;
    end;
  FCanvas := TW3GraphicContext.Create(Self.Handle);
  gl := JWebGLRenderingContext(FCanvas.Handle.getContext('experimental-webgl'));
  rc := TGLRenderingContext.Create;
  rc.gl := gl;
end;

procedure TForm1.ProcessImage(Sender : TObject);
begin
  dec (FNumTextures);
  if FNumTextures = 0 then
    begin
      for  var i := 0 to FLastImageNum do
        if FImages[i].Handle and FImages[i].Ready then
          data[i] := JImageData(FImages[i].Handle)
        else
          ShowMessage('Error: image unready');
      SetupScene;
      Render;
  end;
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.4, 0.4, 0.8, 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

  FTexCoordBuffer := TGLArrayBuffer.Create(rc);

  FTexCoordBuffer.SetData([
    0.0, 1.0,
    1.0, 1.0,
    1.0, 0.0,
    0.0, 1.0,
    1.0, 0.0,
    0.0, 0.0,
    1.0, 1.0,
    1.0, 0.0,
    0.0, 0.0,
    1.0, 1.0,
    0.0, 0.0,
    0.0, 1.0,
    1.0, 1.0,
    1.0, 0.0,
    0.0, 0.0,
    1.0, 1.0,
    0.0, 0.0,
    0.0, 1.0,
    0.0, 1.0,
    1.0, 1.0,
    1.0, 0.0,
    0.0, 1.0,
    1.0, 0.0,
    0.0, 0.0
  ], 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
   //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 vec2 a_texCoord;
    varying vec2 v_texCoord;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

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

  // create fragment shader
  FFragmentShader := TGLFragmentShader.Create(rc);
  FFragmentShader.Compile(#"
    precision mediump float;
    uniform sampler2D u_image;
    varying vec2 v_texCoord;

    void main(void) {
      gl_FragColor = texture2D(u_image, v_texCoord);
    }");

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

  FVertexPosAttrib := FShaderProgram.AttribLocation("aVertexPosition");
  FTexCoordAttrib := FShaderProgram.AttribLocation("a_texCoord");
  for var i := 0 to FLastImageNum do
    begin
      textures[i] := gl.createTexture;
      gl.activeTexture(gl.TEXTURE0 + i);
      gl.bindTexture(gl.TEXTURE_2D, textures[i]);
      // Set the parameters so we can render any size image.
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

      // Upload the image into the texture.
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data[i]);
    end;
end;

procedure TForm1.Render;
var
  ProjectionMatrix, ModelViewMatrix : Matrix4;
  ModelViewStack : array of Matrix4;

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

    FCubeBuffer.VertexAttribPointer(FVertexPosAttrib, 3, False, 0, 0);
    FTexCoordBuffer.VertexAttribPointer(FTexCoordAttrib, 2, False, 0, 0);

    gl.activeTexture(gl.TEXTURE0);
    for var i := 0 to FLastImageNum do
      begin
        gl.bindTexture(gl.TEXTURE_2D, textures[i]);
        gl.drawArrays(gl.TRIANGLES, i * 6, 6);
      end;
    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(FTexCoordAttrib);
  RenderCube(0, 0, -5);
end;

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

Using WebGL for Advanced Graphics in Smart Mobile Studio