Rendering a Texture on Faces of a Cube

See our page on applying a texture to a single rectangle before studying this example. Find the image Bricks0.png used in this demonstration in the folder Smart Mobile Projects\Contest Demos\Afternoon Walk\bin.

In this example we change the code of the static cube on the previous page so that it now displays Bricks0.png on the front, back, left and right faces. In each case the image is as viewed from the outside of the cube. In order to specify the texture coordinates for each triangle in the correct order, we first drew a diagram of the cube and labelled the vertices (knowing the three faces that contain each vertex). The y coordinate of an image (unlike that of an openGL y coordinate) becomes more positive down the screen. For the first triangle, we need to supply the bottom left, bottom right and top right coordinates of our image. (The GPU will interpolate to calculate the colour of each pixel in the triangle). The other triangle of the front face (vertices 0, 2 then 3 of the cube) needs the bottom left, top right and then top left coordinates of the image. If you supplied a coordinate of 0.5 instead of 1.0, you would use only half the image in that direction. In this way you can change the dimensions of the bricks in this example. You can also load a composite image and use only the portion you require for each rendered triangle.

Screenshot of Four Walls

Screenshot of Four Walls

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
    FFilepath: string = 'res/Bricks0.png';
    FCanvas: TW3GraphicContext;
    gl: JWebGLRenderingContext;
    rc: TGLRenderingContext;
    FCubeBuffer, FTexCoordBuffer: TGLArrayBuffer;
    FFragmentShader: TGLFragmentShader;
    FVertexShader: TGLVertexShader;
    FShaderProgram: TGLShaderProgram;
    FVertexPosAttrib, FTexCoordAttrib: integer;
    FImage: TW3Image;
    data: JImageData;
    texture: JWebGLTexture;
    procedure InitializeObject; override;
    procedure Resize; override;
    procedure SetupScene;
    procedure Render;
    procedure ProcessImage(Sender : TObject);
  end;

implementation

procedure TForm1.InitializeObject;
begin
  inherited;
  FImage := TW3Image.Create(nil);
  FImage.LoadFromURL(FFilepath);
  FImage.OnLoad := ProcessImage;

  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
  if FImage.Handle and FImage.Ready then
    data := JImageData(FImage.Handle)
  else
    ShowMessage('Error: image unready');
  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

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

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);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.drawArrays(gl.TRIANGLES, 0, 24);
    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