Using Two Shader Programs
This demonstration uses one shader program to colour four faces of a cube and another to display textures near a top corner of two of the faces. The idea behind the project is to be able to view from different angles photographs of two paintings near to each other on walls of different colours in order to make decisions about the positioning of the actual oil paintings to best effect. We are grateful to Gwen Adair for permission to use photographs of her oil paintings. She holds the copyright of both of the photographs used by this program. We wait until both the images have loaded before processing them in a similar way to those on the previous page. You need to remember to add each image that you use to Resources (in the left panel of the Smart Mobile Studio IDE) so that they will be copied to the res folder. We use the WinPhone8.css theme.
The Smart Pascal and XML code of the form (developed using Version 2.2 Beta 5 of Smart Mobile Studio) follow the demonstration. 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. We have tested it to our satisfaction using the browsers Chrome, Opera and Firefox. Edge did not render the scene until we clicked on a list box.
An easy challenge is to change the colours of the floor, ceiling and both walls. Can you add another texture to a wall?
Smart Pascal Code of Form
unit Form1; { BOTH IMAGES COPYRIGHT GWEN ADAIR https://www.facebook.com/gwenadairpainter/ and https://gwenadair.wixsite.com/painter} 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, System.Colors; type TForm1 = class(TW3form) procedure lbDistancesClick(Sender: TObject); procedure lbAnglesClick(Sender: TObject); private {$I 'Form1:intf'} protected FFilepaths: array[0..1] of string = ['res/Lee.jpg', 'res/RiverNithAtDumfries.jpg']; gl: JWebGLRenderingContext; rc: TGLRenderingContext; FCubeBuffer, FColorBuffer, FPlacementBuffer, FTexCoordBuffer: TGLArrayBuffer; FTexFragShader, FColFragShader: TGLFragmentShader; FTexVertexShader, FColVertShader: TGLVertexShader; FTexShaderProg, FColShaderProg: TGLShaderProgram; FVertexPosAttrib, FTexCoordAttrib, FColorAttrib, FPlacementAttrib, FNumTextures, FLastImageNum: integer; FImages: array of TW3Image; data: array of JImageData; textures: array of JWebGLTexture; procedure InitializeObject; override; procedure Resize; override; procedure SetupScene; procedure Render(YRot, DistFromCentre: Float); procedure ProcessImage(Sender : TObject); procedure Adjust; end; implementation procedure TForm1.Adjust; begin render(lbAngles.SelectedIndex * 0.27 , (lbDistances.SelectedIndex + 4) / 2 ); end; procedure TForm1.lbAnglesClick(Sender: TObject); begin Adjust; end; procedure TForm1.lbDistancesClick(Sender: TObject); begin Adjust; end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} FNumTextures := length(FFilepaths); FLastImageNum := FNumTextures - 1; for var i := 0 to FLastImageNum do begin FImages[i] := TW3Image.Create(nil); FImages[i].LoadFromURL(FFilepaths[i]); FImages[i].OnLoad := ProcessImage; end; gl := WebGLCanvas.Scene.Handle; rc := TGLRenderingContext.Create; rc.gl := gl; // Set up the list boxes for angles and distances. for var i := 0 to 6 do begin lbAngles.Add; lbAngles.Items[i].InnerText := inttostr(i * 15); lbDistances.Add; lbDistances.Items[i].InnerText := floattostr( (i + 4) / 2 ); end; lbAngles.Styles.SelectedColor := clLightBlue; lbAngles.SelectedIndex := 3; lbAngles.EnableAnimation := false; lbDistances.Styles.SelectedColor := clLightGreen; lbDistances.SelectedIndex := 3; lbDistances.EnableAnimation := false; end; procedure TForm1.ProcessImage(Sender : TObject); begin dec (FNumTextures); if FNumTextures = 0 then begin for var i := 0 to FLastImageNum do if FImages[i].Handle and FImages[i].Ready then data[i] := JImageData(FImages[i].Handle) else ShowMessage('Error: image unready'); SetupScene; Adjust; end; end; procedure TForm1.Resize; begin inherited; WebGLCanvas.width := Min(Width, 500); WebGLCanvas.height := Min(Height, 500); end; procedure TForm1.SetupScene; begin gl.clearColor(0.9, 0.9, 0.9, 1.0); // Set to light grey, 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 // Set viewport to the bounds of the canvas. gl.ViewportSet(0, 0, WebGLCanvas.Width, WebGLCanvas.Height); // Create and populate buffer for texture coordinates. FTexCoordBuffer := TGLArrayBuffer.Create(rc); FTexCoordBuffer.SetData([ // first texture 0.0, 0.0, // top left 1.0, 0.0, // top right 0.0, 1.0, // bottom left 1.0, 1.0, // bottom right // second texture as above 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0 ], abuStatic); // Create and populate buffer for colouring faces of cube. FCubeBuffer := TGLArrayBuffer.Create(rc); FCubeBuffer.SetData([ // Vertices of back and right faces of cube // Axis Y increases up screen and Z increases out of front of screen // back face -1.0, 1.0, -1.0, // top left 1.0, 1.0, -1.0, // top right -1.0, -1.0, -1.0, // bottom left 1.0, -1.0, -1.0, // bottom right // right face 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, // bottom face -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, // top face -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0 ], abuStatic); // Create and populate buffer for colours of faces of cube. FColorBuffer := TGLArrayBuffer.Create(rc); FColorBuffer.SetData([ // Colours of vertices of faces of cube // back face 0.891, 0.852, 0.758, 0.891, 0.852, 0.758, 0.891, 0.852, 0.758, 0.891, 0.852, 0.758, // right face 0.953, 0.93, 0.879, 0.953, 0.93, 0.879, 0.953, 0.93, 0.879, 0.953, 0.93, 0.879, // bottom grey 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, // top white 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ], abuStatic); // Create and populate buffer for positioning textures on faces of the cube. FPlacementBuffer := TGLArrayBuffer.Create(rc); FPlacementBuffer.SetData([ // Axis y increases up screen and z increases out of front of screen // back face 0.0, 0.8, -0.95, // half way along face, near top 0.8, 0.8, -0.95, // near top right corner of face 0.0, 0.0, -0.95, // near bottom left, half way down face 0.8, 0.0, -0.95, // near right edge, half way down face // right face 0.95, 0.8, -0.8, 0.95, 0.8, 0.0, 0.95, 0.0, -0.8, 0.95, 0.0, 0.0 ], abuStatic); // create vertex shader for colouring faces FColVertShader := TGLVertexShader.Create(rc); FColVertShader.Compile(#" attribute vec3 a_position; attribute vec3 aVertexColor; uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; varying vec4 vColor; void main() { vColor = vec4(aVertexColor, 1.0); gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(a_position, 1.0); }"); // Create fragment shader for colouring faces. FColFragShader := TGLFragmentShader.Create(rc); FColFragShader.Compile(#" precision mediump float; varying vec4 vColor; void main() { gl_FragColor = vColor; }"); // Create the shader program for colour and link shaders. FColShaderProg := TGLShaderProgram.Create(rc); FColShaderProg.Link(FColVertShader, FColFragShader); // Create the vertex shader for textures. FTexVertexShader := TGLVertexShader.Create(rc); FTexVertexShader.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; }"); // Create the fragment shader for textures. FTexFragShader := TGLFragmentShader.Create(rc); FTexFragShader.Compile(#" precision mediump float; uniform sampler2D u_image; varying vec2 v_texCoord; void main(void) { gl_FragColor = texture2D(u_image, v_texCoord); }"); // Create the shader program and link the shaders for textures. FTexShaderProg := TGLShaderProgram.Create(rc); FTexShaderProg.Link(FTexVertexShader, FTexFragShader); FTexCoordAttrib := FTexShaderProg.AttribLocation("a_texCoord"); for var i := 0 to FLastImageNum do begin textures[i] := gl.createTexture; gl.activeTexture(gl.TEXTURE0 + i); gl.bindTexture(gl.TEXTURE_2D, textures[i]); // 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[i]); end; end; procedure TForm1.Render(YRot, DistFromCentre: Float); var ProjectionMatrix, ModelViewMatrix : Matrix4; ModelViewStack : array of Matrix4; procedure RenderCube(z : Float); begin ModelViewStack.Push(ModelViewMatrix); // Saves the matrix ModelViewMatrix := ModelViewMatrix.Translate([0, 0, z]); ModelViewMatrix := ModelViewMatrix.RotateY(YRot); // Colour four faces. FColShaderProg.Use; FColShaderProg.SetUniform('uProjectionMatrix', ProjectionMatrix); FColShaderProg.SetUniform('uModelViewMatrix', ModelViewMatrix); FVertexPosAttrib := FColShaderProg.AttribLocation("a_position"); FColorAttrib := FColShaderProg.AttribLocation("aVertexColor"); // Enable arrays and set pointers to them. gl.enableVertexAttribArray(FVertexPosAttrib); gl.enableVertexAttribArray(FColorAttrib); FCubeBuffer.VertexAttribPointer(FVertexPosAttrib, 3, False, 0, 0); FColorBuffer.VertexAttribPointer(FColorAttrib, 3, False, 0, 0); // Draw for var i := 0 to 12 step 4 do gl.drawArrays(gl.TRIANGLE_STRIP, i, 4); // Display the textures. FTexShaderProg.Use; FTexShaderProg.SetUniform('uProjectionMatrix', ProjectionMatrix); FTexShaderProg.SetUniform('uModelViewMatrix', ModelViewMatrix); FPlacementAttrib := FTexShaderProg.AttribLocation("aVertexPosition"); // Enable arrays and set pointers to them. gl.enableVertexAttribArray(FPlacementAttrib); gl.enableVertexAttribArray(FTexCoordAttrib); FPlacementBuffer.VertexAttribPointer(FPlacementAttrib, 3, False, 0, 0); FTexCoordBuffer.VertexAttribPointer(FTexCoordAttrib, 2, False, 0, 0); // Bind each texture then display it. gl.activeTexture(gl.TEXTURE0); for var i := 0 to FLastImageNum do begin gl.bindTexture(gl.TEXTURE_2D, textures[i]); gl.drawArrays(gl.TRIANGLE_STRIP, i * 4, 4); end; ModelViewMatrix := ModelViewStack.Pop; // Restores the matrix end; begin // Clear the 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, WebGLCanvas.Width / WebGLCanvas.Height, 0.1, 100); ModelViewMatrix := Matrix4.Identity; RenderCube(-DistFromCentre); end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end.
XML Code of Form
<SMART> <Form version="2" subversion="2"> <Created>2019-09-17T10:03:34.254</Created> <Modified>2019-10-03T11:58:10.832</Modified> <object type="TW3Form"> <Caption>W3Form</Caption> <Name>Form1</Name> <object type="TW3WebGL"> <Width>504</Width> <Height>496</Height> <Name>WebGLCanvas</Name> </object> <object type="TW3ListBox"> <Width>48</Width> <Top>80</Top> <Left>512</Left> <Height>240</Height> <Name>lbAngles</Name> <OnClick>lbAnglesClick</OnClick> </object> <object type="TW3ListBox"> <Width>57</Width> <Top>80</Top> <Left>587</Left> <Height>240</Height> <Name>lbDistances</Name> <OnClick>lbDistancesClick</OnClick> </object> <object type="TW3Label"> <Caption>Viewing<br>angle<br>(degrees)</Caption> <Width>96</Width> <Top>8</Top> <Left>512</Left> <Height>64</Height> <Name>lblAngle</Name> </object> <object type="TW3Label"> <Caption>Distance from<br>back wall<br>(metres)</Caption> <Width>96</Width> <Top>8</Top> <Left>584</Left> <Height>62</Height> <Name>lblDistance</Name> </object> </object> </Form> </SMART>
Using Version 3.0 of Smart Mobile Studio
Smart Mobile Studio 3.0, when attempting to compile the project (.sproj) file containing the code above, reported a syntax error: 'There is no accessible member with name "InnerText"'. We changed the list box code to the following to enable it to compile.// Set up the list boxes for angles and distances. for var i := 0 to 6 do begin lbAngles.AddItem(inttostr(i * 15)); lbDistances.AddItem(floattostr( (i + 4) / 2 )); end; lbAngles.SelectedIndex := 3; lbDistances.SelectedIndex := 3;
- Put the slider directly below the edit box for the angle.
- Make the slider display update if the angle is changed using the edit box.
- Add text to explain what the controls do (as in the top demo).
procedure TForm1.Adjust; begin Render(strToInt(W3EditBox1.Text) * 0.1, strToInt(W3EditBox2.Text) * 0.5); end; procedure TForm1.W3EditBox1Click(Sender: TObject); begin Adjust; end; procedure TForm1.W3EditBox2Click(Sender: TObject); begin Adjust; end; procedure TForm1.W3Slider1Change(Sender: TObject); begin W3EditBox1.Text := IntToStr(Round(W3Slider1.Value)); Adjust; end;.These lines of code are at the end of the InitializeObject procedure.
W3EditBox1.InputType := itNumber; W3EditBox1.MinValue := 0; W3EditBox1.MaxValue := 16; W3EditBox1.Text := '8'; W3EditBox2.InputType := itNumber; W3EditBox2.MaxValue := 8; W3EditBox2.MinValue := 4; W3EditBox2.Text := '6';
See Using an EditBox as a SpinEdit Control for code variations for earlier versions of Smart Mobile Studio.