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.