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.

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;
  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 := 360;
  gl.canvas.height := 360;
  FImage.OnLoad := ProcessImage;
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 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 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 shader program and link shaders
  FShaderProgram := TGLShaderProgram.Create(rc);
  FShaderProgram.Link(FVertexShader, FFragmentShader);
  FShaderProgram.Use;
  FVertexPosAttrib := FShaderProgram.AttribLocation("a_position");
  FTexCoordAttrib := FShaderProgram.AttribLocation("a_texCoord");
  FTexCoordBuffer := TGLArrayBuffer.Create(rc);
  FTexCoordBuffer.SetData([
      0.0,  0.0,
      1.0,  0.0,
      0.0,  1.0,
      0.0,  1.0,
      1.0,  0.0,
      1.0,  1.0
    ], abuStatic);

  gl.enableVertexAttribArray(FTexCoordAttrib);
  FTexCoordBuffer.VertexAttribPointer(FTexCoordAttrib, 2, false, 0, 0);

  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);

  resolutionLocation := FShaderProgram.UniformLocation("u_resolution");
  gl.uniform2f(resolutionLocation, FCanvas.Handle.width, FCanvas.Handle.height);

  // Put x and y pixel coords of vertices in buffer
  FRectBuffer := TGLArrayBuffer.Create(rc);

  FRectBuffer.SetData([
    0, 0,
    360, 0,
    0, 360,
    0, 360,
    360, 0,
    360, 360
    ], abuStatic);

  gl.enableVertexAttribArray(FVertexPosAttrib);

  FRectBuffer.VertexAttribPointer(FVertexPosAttrib, 2, false, 0, 0);
  //Draw the image
  gl.drawArrays(gl.TRIANGLES, 0, 6);
end;
 
initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end. 
   
Programming - a skill for life!

Using WebGL for advanced graphics in Smart Mobile Studio