- DelphiTools - https://www.delphitools.info -

Hello WebGL source code

[1]With SmartMS v1.0.1 [2] out you can now use WebGL!

Download HelloWebGL.zip [3] (51 kB), it contains the first demo (in .opp form and pre-compiled), as well as the initial WebGLScene units which you’ll have to copy to your “Libraries” folder (the WebGL import units should have been delivered in v1.0.1).

 

Quick tour of the demo (or how to setup and use WebGL)

In TForm1.InitializeObject, the WebGL graphic & rendering contexts are created, this will be simplified and wrapped more neatly by a component in the future, currently it just piggybacks a TW3GraphicContext, but it stays rather simple:

canvas := TW3GraphicContext.Create(Self.Handle);

gl := JWebGLRenderingContext( canvas.Handle.getContext('experimental-webgl') );

rc := TGLRenderingContext.Create;
rc.GL := gl;

The TGLRenderingContext is a container class for the JWebGLRenderingContext that the other helper refer, it’s currently quite bare-bones.

In SetupScene, the OpenGL scene is initialized, it begins with classic OpenGL initialization code, which the above mentioned future component should take care off one day:

gl.clearColor(0.0, 0.0, 0.25, 1.0); // Set clear color to black, 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

Note that since gl is exposed by JWebGLRenderingContext as an external class, the function names are case-insensitive as usual in Pascal, you can follow the JavaScript case, but you don’t have to.

Then comes the raw geometry buffer, that uses a TGLArrayBuffer helper, f.i. for the triangle you’ve got

triangleBuffer := TGLArrayBuffer.Create(rc);
triangleBuffer.SetData([
    0.0,  1.0,  0.0,
   -1.0, -1.0,  0.0,
    1.0, -1.0,  0.0
   ], abuStatic);

abuStatic is to indicate a static buffer (can be abuStream or abuDynamic as well).
Next: Setting up the Shaders [4]

Previous: Quick tour of the demo. [5]

Setting up the Shaders

This is followed by the shader setup, which uses GLScene helpers too, f.i. the fragment shader is created and compiled with

fragmentShader := TGLFragmentShader.Create(rc);
if not fragmentShader.Compile(#"
   precision mediump float;
   varying vec4 vColor;
   void main(void) {
      gl_FragColor = vColor;
   }") then
   raise Exception.Create(fragmentShader.InfoLog);

The vertex shader is created & compiled similarly, then both are linked into a shader program:

shaderProgram := TGLShaderProgram.Create(rc);
if not shaderProgram.Link(vertexShader, fragmentShader) then
   raise Exception.Create(shaderProgram.InfoLog);

Note that the shader program automatically builds a cache of uniforms and locations, and you can also use the AttribInfo[] and UniformInfo[] properties to enumerate attributes and uniforms.

Finally the Render loop is where you can see the vector & matrix helpers at work.
Since OpenGL ES 2.0 doesn’t include matrix stacks, you have to do them on your side, but this is easily achieved with a dynamic array’s Push() & Pop() pseudo-methods.

const
   cSpeed = 24*3600;
var
   projMat, mvMat : Matrix4;
   mvStack : array of Matrix4;
begin
   gl.ViewportSet(0, 0, canvas.Handle.width, canvas.Handle.height);

   gl.clear(gl.COLOR_BUFFER_BIT);

   shaderProgram.Use;

   projMat := Matrix4.CreatePerspective(45, canvas.width/canvas.height, 0.1, 100);
   mvMat := Matrix4.Identity;

   shaderProgram.SetUniform('uPMatrix', projMat);

   gl.enableVertexAttribArray(vertexPosAttrib);

   mvMat := mvMat.Translate([-1.5, 0, -7]);
   mvStack.Push(mvMat);

   mvMat := mvMat.RotateY(Frac(Now)*cSpeed);
   shaderProgram.SetUniform('uMVMatrix', mvMat);

   triangleBuffer.VertexAttribPointer(vertexPosAttrib, 3, false, 0, 0);
   gl.drawArrays(gl.TRIANGLES, 0, 3);

   mvMat := mvStack.Pop;

   mvMat := mvMat.Translate([3.0, 0, 0]).RotateX(Frac(Now)*cSpeed);
   shaderProgram.SetUniform('uMVMatrix', mvMat);

   squareBuffer.VertexAttribPointer(vertexPosAttrib, 3, false, 0, 0);
   gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

The last bit I didn’t mention is the requestAnimationFrame [6] shim, which is used to keep things rotating/animating, and which should at some point become part of the VJL or RTL.

So that’s about it for the walk-through!