Using Textures

A process known as texture mapping transfers the data from a bitmap to a surface. The bitmap used to represent the surface information is a texture map (or texture, for short).

This demonstration simply displays a single image in a rectangle, but you will be able to put images on the faces of your tetrahedra, cubes and more complex shapes as you progress. Add the image to Resources (in the left panel of the IDE) to copy it to the res folder.

In order to test your texture code before uploading it to a webserver, the Firefox browser allows you to access images locally. With other browsers such as Chrome, you may need a local webserver to see your images. See WebGL guidance from the Mozilla Developer Network (MDN) for details of access restrictions and recommended dimensions of textures.

Note that you need to make sure that the image is fully loaded before processing it. We assign to the OnLoad property of FImage the procedure ProcessImage. This procedure must have the correct signature for an event (with an Object as parameter).

Instead of an array of colours we have an array of texture coordinates (TexCoords). These are declared as being "varying" so that values will be interpolated by the GPU. Texture coordinates are in the range 0.0 to 1.0 irrespective of the dimensions of the texture. Intermediate values will cause the corresponding portion of the image to be rendered. The y coordinate of an image (unlike that of an openGL y coordinate) becomes more positive down the screen.

This code runs in Version 3.0 of Smart Mobile Studio. The image shows when run in the internal browser and also on selecting the "Open in browser" tab (with url http://192.168.1.5:8090/Texture.html in Chrome, Opera, Firefox and Edge). The resultant HTML file saved on this PC works in Edge but not in Firefox (CORS error reported on pressing F12 and selecting the console tab), Chrome or Opera.

The following display of a brick wall is from an HTML file output from Smart Mobile Studio 3.02. 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.

Texture

Smart Pascal Code of Form

unit Form1;

interface

uses
  System.Types, W3C.TypedArray, SmartCL.System, SmartCL.Graphics,
  SmartCL.Controls, SmartCL.Components, SmartCL.Forms, SmartCL.Fonts,
  SmartCL.Borders, SmartCL.Application, Khronos.WebGl, GLS.Base, W3C.Canvas2DContext;

type
  TForm1 = class(TW3form)
  protected
    FCanvas: TW3GraphicContext;
    gl: JWebGLRenderingContext;
    rc: TGLRenderingContext;
    FRectBuffer, FTexCoordBuffer: TGLArrayBuffer;
    FFragmentShader: TGLFragmentShader;
    FVertexShader: TGLVertexShader;
    FShaderProgram: TGLShaderProgram;
    FVertexPosAttrib, FTexCoordAttrib: Integer;
    resolutionLocation: JWebGLUniformLocation;
    FImage: TW3Image;
    data: JImageData;
    texture: JWebGLTexture;
    procedure ProcessImage(Sender: TObject);
    procedure InitializeObject; override;
    procedure SetupScene;
    procedure InitBuffers;
  end;

implementation

procedure TForm1.InitializeObject;
begin
  inherited;
  FImage := TW3Image.Create(nil);
  FImage.LoadFromURL('res/Bricks0.png');
  FCanvas := TW3GraphicContext.Create(Self.Handle);
  gl := JWebGLRenderingContext(FCanvas.Handle.getContext('experimental-webgl'));
  rc := TGLRenderingContext.Create;
  rc.GL := gl;
  gl.canvas.width := 350;
  gl.canvas.height := 350;
  FImage.OnLoad := ProcessImage;
  InitBuffers;
end;

procedure TForm1.ProcessImage(Sender : TObject);
begin
  if FImage.Handle and FImage.Ready then
    begin
      data := JImageData(FImage.Handle);
      SetupScene;
    end
   else
     ShowMessage('Error: image unready');
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.ViewportSet(0, 0, FCanvas.Handle.width, FCanvas.Handle.height);
  gl.clear(gl.COLOR_BUFFER_BIT);  // clears background
  // Create the vertex shader.
  FVertexShader := TGLVertexShader.Create(rc);
  FVertexShader.Compile(#"
    attribute vec2 a_position;
    attribute vec2 a_texCoord;
    uniform vec2 u_resolution;
    varying vec2 v_texCoord;
    void main() {
    vec2 zeroToOne = a_position / u_resolution;
    vec2 zeroToTwo = zeroToOne * 2.0;
    vec2 clipSpace = zeroToTwo - 1.0;
    gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
    v_texCoord = a_texCoord;
          }");

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

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

  // Create the shader program and link the shaders.
  FShaderProgram := TGLShaderProgram.Create(rc);
  FShaderProgram.Link(FVertexShader, FFragmentShader);
  FShaderProgram.Use;
  // Obtain the locations of  a_position and a_texCoord.
  FVertexPosAttrib := FShaderProgram.AttribLocation("a_position");
  FTexCoordAttrib := FShaderProgram.AttribLocation("a_texCoord");
  // Enable access to FTexCoordBuffer.
  gl.enableVertexAttribArray(FTexCoordAttrib);
  FTexCoordBuffer.VertexAttribPointer(FTexCoordAttrib, 2, false, 0, 0);
  // Create the texture, then activate and bind it.
  texture := gl.createTexture;
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, texture);
  // 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);
  // Find the location of  u_resolution and set its value.
  resolutionLocation := FShaderProgram.UniformLocation("u_resolution");
  gl.uniform2f(resolutionLocation, FCanvas.Handle.width, FCanvas.Handle.height);
  // Enable access to FRectBuffer.
  gl.enableVertexAttribArray(FVertexPosAttrib);
  FRectBuffer.VertexAttribPointer(FVertexPosAttrib, 2, false, 0, 0);
  // Draw the image.
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
end;

procedure TForm1.InitBuffers;
begin
  // Put the texture coordinates into FTexCoordBuffer.
  FTexCoordBuffer := TGLArrayBuffer.Create(rc);
  FTexCoordBuffer.SetData([
      0.0,  0.0,
      1.0,  0.0,
      0.0,  1.0,
      1.0,  1.0
    ], abuStatic);

  // Put the x and y pixel coords of vertices into FRectBuffer.
  FRectBuffer := TGLArrayBuffer.Create(rc);
  FRectBuffer.SetData([
    0, 0,
    360, 0,
    0, 360,
    360, 360
    ], abuStatic);
end;
 
initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.    

Using a TW3WebGL Component to display a Texture in Version 3.0 of Smart Mobile Studio

Start a new form-based project in Version 3.0 of Smart Mobile Studio, select the Graphics tab and add a TW3WebGL component then name the component WebGLCanvas. Paste this code into the source view of the form. Add the image to Resources (in the left panel of the IDE) to copy it to the res folder.

unit Form1;

interface

uses
  System.Types, System.Types.Convert, System.Objects, System.Time,
  System.IOUtils, System.Device.Storage,
  SmartCL.System, SmartCL.Time, SmartCL.Graphics, SmartCL.Components,
  SmartCL.FileUtils, SmartCL.Device.Storage, SmartCL.Forms, SmartCL.Fonts,
  SmartCL.Theme, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.WebGL,
  Khronos.WebGl, GLS.Vectors, GLS.Base, W3C.TypedArray, SmartCL.Controls, W3C.Canvas2DContext;

type
  TForm1 = class(TW3Form)
  private
    {$I 'Form1:intf'}
  protected
    rc: TGLRenderingContext;
    FWebGL: JWebGLRenderingContext;
    FRectBuffer, FTexCoordBuffer: TGLArrayBuffer;
     FFragmentShader: TGLFragmentShader;
    FVertexShader: TGLVertexShader;
    FShaderProgram: TGLShaderProgram;
    FVertexPosAttrib, FTexCoordAttrib: Integer;
    resolutionLocation: JWebGLUniformLocation;
    FImage: TW3Image;
    data: JImageData;

    texture: JWebGLTexture;
    procedure InitializeObject; override;
    procedure InitBuffers;
    procedure SetupScene;
    procedure ProcessImage(Sender: TObject);
  end;

implementation

procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}
  FImage := TW3Image.Create(nil);
  FImage.LoadFromURL('res/Bricks0.png');   
  FWebGL := WebGLCanvas.Scene.Handle;
  rc := TGLRenderingContext.Create; 
  rc.GL := FWebGL;
  WebGLCanvas.Width := 360;
  WebGLCanvas.Height := 360;

  asm
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       ||
              window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame    ||
              window.oRequestAnimationFrame      ||
              window.msRequestAnimationFrame     ||
              function( callback ){
                window.setTimeout(callback, 1000 / 60);
              };
    })();
  end;
  FImage.Onload := ProcessImage;
  InitBuffers;
end;

procedure TForm1.InitBuffers;
begin
  // Put the texture coordinates into FTexCoordBuffer.
  FTexCoordBuffer := TGLArrayBuffer.Create(rc);
  FTexCoordBuffer.SetData([
      0.0,  0.0,
      1.0,  0.0,
      0.0,  1.0,
      1.0,  1.0
    ], abuStatic);

  // Put the x and y pixel coords of vertices into FRectBuffer.
  FRectBuffer := TGLArrayBuffer.Create(rc);
  FRectBuffer.SetData([
    0, 0,
    360, 0,
    0, 360,
    360, 360
    ], abuStatic);
end;

procedure TForm1.SetupScene;
begin
  FWebGL.clearColor(0.0, 0.0, 1.0, 1.0);  // blue
  FWebGL.enable(FWebGL.DEPTH_TEST);
  // Set viewport to bounds of WebGL canvas.
  FWebGL.ViewportSet(0, 0, WebGLCanvas.Width, WebGLCanvas.Height);
  // Clear background to blue.
  FWebGL.clear(FWebGL.COLOR_BUFFER_BIT);
  // Create the vertex shader.
  
  FVertexShader := TGLVertexShader.Create(rc);
  FVertexShader.Compile(#"
    attribute vec2 a_position;
    attribute vec2 a_texCoord;
    uniform vec2 u_resolution;
    varying vec2 v_texCoord;
    void main() {
    vec2 zeroToOne = a_position / u_resolution;
    vec2 zeroToTwo = zeroToOne * 2.0;
    vec2 clipSpace = zeroToTwo - 1.0;
    gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
    v_texCoord = a_texCoord;
          }");

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

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

  // Create the shader program and link the shaders.
  FShaderProgram := TGLShaderProgram.Create(rc);
  FShaderProgram.Link(FVertexShader, FFragmentShader);
  FShaderProgram.Use;
   // Obtain the locations of  a_position and a_texCoord.
  FVertexPosAttrib := FShaderProgram.AttribLocation("a_position");
  FTexCoordAttrib := FShaderProgram.AttribLocation("a_texCoord");  
  // Enable access to FTexCoordBuffer
  FWebGL.enableVertexAttribArray(FTexCoordAttrib);  
  FTexCoordBuffer.VertexAttribPointer(FTexCoordAttrib, 2, false, 0, 0);
  // Create the texture, then activate and bind it.
  texture := FWebGL.createTexture;
  FWebGL.activeTexture(FWebGL.TEXTURE0);
  FWebGL.bindTexture(FWebGL.TEXTURE_2D, texture);
  // Set the parameters so we can render any size image. 
  FWebGL.texParameteri(FWebGL.TEXTURE_2D, FWebGL.TEXTURE_WRAP_S, FWebGL.CLAMP_TO_EDGE);
  FWebGL.texParameteri(FWebGL.TEXTURE_2D, FWebGL.TEXTURE_WRAP_T, FWebGL.CLAMP_TO_EDGE);
  FWebGL.texParameteri(FWebGL.TEXTURE_2D, FWebGL.TEXTURE_MIN_FILTER, FWebGL.NEAREST);
  FWebGL.texParameteri(FWebGL.TEXTURE_2D, FWebGL.TEXTURE_MAG_FILTER, FWebGL.NEAREST);
  // Upload the image into the texture.
  FWebGL.texImage2D(FWebGL.TEXTURE_2D, 0, FWebGL.RGBA, FWebGL.RGBA, FWebGL.UNSIGNED_BYTE, data);  
  // Find the location of  u_resolution and set its value.
  resolutionLocation := FShaderProgram.UniformLocation("u_resolution");  
  FWebGL.uniform2f(resolutionLocation, WebGLCanvas.Width, WebGLCanvas.Height);
  // Enable access to FRectBuffer.
  FWebGL.enableVertexAttribArray(FVertexPosAttrib);
  FRectBuffer.VertexAttribPointer(FVertexPosAttrib, 2, false, 0, 0);
  // Draw
  FWebGL.drawArrays(FWebGL.TRIANGLE_STRIP, 0, 4);

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

procedure TForm1.ProcessImage(Sender: TObject);
begin
  if FImage.Handle and FImage.Ready then
    begin
      data := JImageData(FImage.Handle);
      SetupScene;
    end
   else
     ShowMessage('Error: image unready');
end;

initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.    

The following display of a brick wall is from an HTML file output from the command line compiler of Smart Mobile Studio 3.02 using a project (.sproj) file containing the code immediately above. 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.

Texture
Programming - a skill for life!

Using WebGL for Advanced Graphics in Smart Mobile Studio