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.
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.