Ambient Lighting

The lighting is based on Learning WebGL Lesson 7 and omits the code for directional lighting; ambient lighting is much easier to implement than the other forms of lighting described in Lesson 7. We will provide demonstrations of directional lighting and point lighting if and when we can make them work perfectly in Smart Mobile Studio.

Here we reuse code for displaying the cube with bricks on four walls from Rendering a Texture on the Faces of a Cube. The same rgb components are added to each pixel to implement the ambient lighting. The bricks are red by default, but you can enter your own RGB values in the edit boxes. RGB values of 1.0, 0.5 and 0.2 colour the bricks orange. 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.

Ambient Lighting

Code of Ambient Lighting Demonstration

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

type
  TForm1 = class(TW3form)
    procedure edtBlueChanged(Sender: TObject);
    procedure edtGreenChanged(Sender: TObject);
    procedure edtRedChanged(Sender: TObject);
  private
    {$I 'Form1:intf'}

  protected
    FCanvas: TW3GraphicContext;
    gl: JWebGLRenderingContext;
    rc: TGLRenderingContext;
    FCubeBuffer, FTexCoordBuffer, FNormalBuffer: TGLArrayBuffer;
    FFragmentShader: TGLFragmentShader;
    FVertexShader: TGLVertexShader;
    FShaderProgram: TGLShaderProgram;
    FVertexPosAttrib, FTexCoordAttrib: Integer;
    FImage: TW3Image;
    data: JImageData;
    texture: JWebGLTexture;
    adjustedLD : Vector3;
    ambientColorLocation: JWebGLUniformLocation;
    yAngle: real = 0.2;
    dirRed : real = 1.0;
    procedure InitializeObject; override;
    procedure Resize; override;
    procedure SetupScene;
    procedure Render;
    procedure ProcessImage(Sender : TObject);
    procedure Rotate(sender : TObject);
    procedure UpdateAmbient;
  end;

implementation

procedure TForm1.edtRedChanged(Sender: TObject);
begin
  UpdateAmbient;
end;

procedure TForm1.edtGreenChanged(Sender: TObject);
begin
  UpdateAmbient;
end;

procedure TForm1.edtBlueChanged(Sender: TObject);
begin
  UpdateAmbient;
end;

procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}
  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;
  FImage.OnLoad := ProcessImage;
  OnClick := Rotate;
  asm
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       ||
              window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame    ||
              window.oRequestAnimationFrame      ||
              window.msRequestAnimationFrame     ||
              function( callback ){
                window.setTimeout(callback, 1000 / 500);
              };
    })();
  end;
end;

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

procedure TForm1.Resize;
begin
  inherited;
  FCanvas.Handle.width := Min(Width, 500);
  FCanvas.Handle.height := Min(Height, 500);
end;

procedure TForm1.UpdateAmbient;
begin
  gl.uniform3fv(ambientColorLocation, [strToFloat(edtRed.text),
                                       strToFloat(edtGreen.text),
                                       strToFloat(edtBlue.text)]);
end;

procedure TForm1.Rotate(sender : TObject);
begin
 UpdateAmbient;
 yAngle +=  0.1;
end;

procedure TForm1.SetupScene;
begin
  gl.clearColor(0.5, 0.5, 0.9, 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;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying vec2 v_texCoord;

    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;
    uniform vec3 uAmbientColor;

    void main(void) {
      vec4 textureColor = texture2D(u_image, v_texCoord);
      gl_FragColor = vec4(textureColor.rgb * uAmbientColor, textureColor.a);
    }");

  // create shader program and link shaders
  FShaderProgram := TGLShaderProgram.Create(rc);
  FShaderProgram.Link(FVertexShader, FFragmentShader);
  FShaderProgram.Use;
  FVertexPosAttrib := FShaderProgram.AttribLocation("aVertexPosition");
  FTexCoordAttrib := FShaderProgram.AttribLocation("a_texCoord");
  ambientColorLocation := FShaderProgram.UniformLocation("uAmbientColor");

  updateAmbient;
  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, yAngle : Double);
  begin
    ModelViewStack.Push(ModelViewMatrix); //save the matrix
    ModelViewMatrix := ModelViewMatrix.Translate([x, y, z]);
    ModelViewMatrix := ModelViewMatrix.RotateX(0.5);
    ModelViewMatrix := ModelViewMatrix.RotateY(yAngle);
    ModelViewMatrix := ModelViewMatrix.RotateZ(0.2 );
    FShaderProgram.SetUniform('uModelViewMatrix', ModelViewMatrix);

    FCubeBuffer.VertexAttribPointer(FVertexPosAttrib, 3, False, 0, 0);
    FTexCoordBuffer.VertexAttribPointer(FTexCoordAttrib, 2, False, 0, 0);
    gl.drawArrays(gl.TRIANGLES, 0, 24);
    ModelViewMatrix := ModelViewStack.Pop; //restore the matrix

    var renderCallback := @Render;
    asm
      window.requestAnimFrame(@renderCallback);
    end;
  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);
  //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, yAngle);
end;
 
initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);

end.
    

XML View of Form of Ambient Lighting Demonstration

<SMART>
  <Form version="2" subversion="1">
    <Created>2015-06-05T17:24:41.363</Created>
    <Modified>2015-06-05T17:24:41.367</Modified>
    <object type="TW3Form">
      <Caption>W3Form</Caption>
      <Name>Form1</Name>
      <object type="TW3EditBox">
        <Value></Value>
        <Text>1.0</Text>
        <Range></Range>
        <Width>32</Width>
        <Top>24</Top>
        <Left>8</Left>
        <Height>32</Height>
        <Name>edtRed</Name>
        <OnChanged>edtRedChanged</OnChanged>
      </object>
      <object type="TW3EditBox">
        <Value></Value>
        <Text>0.5</Text>
        <Range></Range>
        <Width>32</Width>
        <Top>24</Top>
        <Left>40</Left>
        <Height>32</Height>
        <Name>edtGreen</Name>
        <OnChanged>edtGreenChanged</OnChanged>
      </object>
      <object type="TW3EditBox">
        <Value></Value>
        <Text>0.5</Text>
        <Range></Range>
        <Width>32</Width>
        <Top>24</Top>
        <Left>72</Left>
        <Height>32</Height>
        <Name>edtBlue</Name>
        <OnChanged>edtBlueChanged</OnChanged>
      </object>
      <object type="TW3Label">
        <Caption>R</Caption>
        <Width>32</Width>
        <Left>8</Left>
        <Height>24</Height>
        <Color>16711680</Color>
        <Name>W3Label1</Name>
      </object>
      <object type="TW3Label">
        <Caption>G</Caption>
        <Width>32</Width>
        <Left>40</Left>
        <Height>24</Height>
        <Color>32768</Color>
        <Name>W3Label2</Name>
      </object>
      <object type="TW3Label">
        <Caption>B</Caption>
        <Width>32</Width>
        <Left>72</Left>
        <Height>24</Height>
        <Color>255</Color>
        <Name>W3Label3</Name>
      </object>
    </object>
  </Form>
</SMART>

Programming - a skill for life!

Using WebGL for advanced graphics in Smart Mobile Studio