├── .gitignore ├── LICENSE.md ├── README.md ├── analysis_options.yaml ├── pubspec.yaml └── web ├── Teapot.json ├── crate.gif ├── cube.dart ├── earth-specular.gif ├── earth.jpg ├── galvanizedTexture.jpg ├── gl_program.dart ├── glass.gif ├── index.html ├── json_object.dart ├── learn_gl.css ├── learn_gl.dart ├── lesson1.dart ├── lesson10.dart ├── lesson11.dart ├── lesson12.dart ├── lesson13.dart ├── lesson14.dart ├── lesson15.dart ├── lesson16.dart ├── lesson2.dart ├── lesson3.dart ├── lesson4.dart ├── lesson5.dart ├── lesson6.dart ├── lesson7.dart ├── lesson8.dart ├── lesson9.dart ├── macbook.json ├── matrix4.dart ├── mcdole.gif ├── moon.bmp ├── nehe.gif ├── pyramid.dart ├── rectangle.dart ├── renderable.dart ├── sphere.dart ├── star.dart ├── star.gif └── world.json /.gitignore: -------------------------------------------------------------------------------- 1 | packages/ 2 | **/packages 3 | .pub/ 4 | pubspec.lock 5 | .packages 6 | .dart_tool 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, John Thomas McDole. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dart-webgl 2 | ========== 3 | Learn WebGL with Dart! 4 | ========== 5 | 6 | The is my re-write of learningwebgl.com 16 lessons to the Dart programing 7 | language. It is by no means perfect, however I try to leverage the 8 | language and its features to introduce structure earlier in the lessons. 9 | My hope is this will get more people interested in WebGL programming that 10 | might not have been before, while also showing off some of what Dart can do. 11 | 12 | Each lesson is fully loaded when compiled. The user need only selected 13 | which lesson to run from a drop down box. Optional URL parameters 14 | include: 15 | 16 | lsn=(1-16) - The default lesson number to show. 17 | width=x - The width of the canvas element. 18 | height=x - The height of the canvas element. 19 | fps - Display a running average frame rate. 20 | 21 | Future rewrites of the original articles covering these changes are 22 | to coming! 23 | 24 | -John Thomas McDole 25 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | 3 | linter: 4 | rules: 5 | - prefer_final_locals -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: learn_gl 2 | description: A sample web application 3 | dependencies: 4 | 5 | environment: 6 | sdk: ">=2.12.0-0 <3.0.0" 7 | 8 | dev_dependencies: 9 | build_runner: ^1.10.0 10 | build_web_compilers: ^2.11.0 11 | pedantic: ^1.10.0 12 | -------------------------------------------------------------------------------- /web/crate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtmcdole/dart-webgl/5b0542b0c6ba54c62a459ff6096e28aa348747cd/web/crate.gif -------------------------------------------------------------------------------- /web/cube.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | class Cube implements Renderable { 18 | late Buffer positionBuffer, normalBuffer, textureCoordBuffer, indexBuffer; 19 | 20 | Cube() { 21 | positionBuffer = gl.createBuffer(); 22 | gl.bindBuffer(WebGL.ARRAY_BUFFER, positionBuffer); 23 | final vertices = [ 24 | // Front face 25 | -1.0, -1.0, 1.0, 26 | 1.0, -1.0, 1.0, 27 | 1.0, 1.0, 1.0, 28 | -1.0, 1.0, 1.0, 29 | 30 | // Back face 31 | -1.0, -1.0, -1.0, 32 | -1.0, 1.0, -1.0, 33 | 1.0, 1.0, -1.0, 34 | 1.0, -1.0, -1.0, 35 | 36 | // Top face 37 | -1.0, 1.0, -1.0, 38 | -1.0, 1.0, 1.0, 39 | 1.0, 1.0, 1.0, 40 | 1.0, 1.0, -1.0, 41 | 42 | // Bottom face 43 | -1.0, -1.0, -1.0, 44 | 1.0, -1.0, -1.0, 45 | 1.0, -1.0, 1.0, 46 | -1.0, -1.0, 1.0, 47 | 48 | // Right face 49 | 1.0, -1.0, -1.0, 50 | 1.0, 1.0, -1.0, 51 | 1.0, 1.0, 1.0, 52 | 1.0, -1.0, 1.0, 53 | 54 | // Left face 55 | -1.0, -1.0, -1.0, 56 | -1.0, -1.0, 1.0, 57 | -1.0, 1.0, 1.0, 58 | -1.0, 1.0, -1.0 59 | ]; 60 | gl.bufferData( 61 | WebGL.ARRAY_BUFFER, 62 | Float32List.fromList(vertices), 63 | WebGL.STATIC_DRAW, 64 | ); 65 | 66 | normalBuffer = gl.createBuffer(); 67 | gl.bindBuffer(WebGL.ARRAY_BUFFER, normalBuffer); 68 | final vertexNormals = [ 69 | // Front face 70 | 0.0, 0.0, 1.0, 71 | 0.0, 0.0, 1.0, 72 | 0.0, 0.0, 1.0, 73 | 0.0, 0.0, 1.0, 74 | 75 | // Back face 76 | 0.0, 0.0, -1.0, 77 | 0.0, 0.0, -1.0, 78 | 0.0, 0.0, -1.0, 79 | 0.0, 0.0, -1.0, 80 | 81 | // Top face 82 | 0.0, 1.0, 0.0, 83 | 0.0, 1.0, 0.0, 84 | 0.0, 1.0, 0.0, 85 | 0.0, 1.0, 0.0, 86 | 87 | // Bottom face 88 | 0.0, -1.0, 0.0, 89 | 0.0, -1.0, 0.0, 90 | 0.0, -1.0, 0.0, 91 | 0.0, -1.0, 0.0, 92 | 93 | // Right face 94 | 1.0, 0.0, 0.0, 95 | 1.0, 0.0, 0.0, 96 | 1.0, 0.0, 0.0, 97 | 1.0, 0.0, 0.0, 98 | 99 | // Left face 100 | -1.0, 0.0, 0.0, 101 | -1.0, 0.0, 0.0, 102 | -1.0, 0.0, 0.0, 103 | -1.0, 0.0, 0.0, 104 | ]; 105 | gl.bufferData( 106 | WebGL.ARRAY_BUFFER, 107 | Float32List.fromList(vertexNormals), 108 | WebGL.STATIC_DRAW, 109 | ); 110 | 111 | textureCoordBuffer = gl.createBuffer(); 112 | gl.bindBuffer(WebGL.ARRAY_BUFFER, textureCoordBuffer); 113 | final textureCoords = [ 114 | // Front face 115 | 0.0, 0.0, 116 | 1.0, 0.0, 117 | 1.0, 1.0, 118 | 0.0, 1.0, 119 | 120 | // Back face 121 | 1.0, 0.0, 122 | 1.0, 1.0, 123 | 0.0, 1.0, 124 | 0.0, 0.0, 125 | 126 | // Top face 127 | 0.0, 1.0, 128 | 0.0, 0.0, 129 | 1.0, 0.0, 130 | 1.0, 1.0, 131 | 132 | // Bottom face 133 | 1.0, 1.0, 134 | 0.0, 1.0, 135 | 0.0, 0.0, 136 | 1.0, 0.0, 137 | 138 | // Right face 139 | 1.0, 0.0, 140 | 1.0, 1.0, 141 | 0.0, 1.0, 142 | 0.0, 0.0, 143 | 144 | // Left face 145 | 0.0, 0.0, 146 | 1.0, 0.0, 147 | 1.0, 1.0, 148 | 0.0, 1.0, 149 | ]; 150 | gl.bufferData( 151 | WebGL.ARRAY_BUFFER, 152 | Float32List.fromList(textureCoords), 153 | WebGL.STATIC_DRAW, 154 | ); 155 | 156 | indexBuffer = gl.createBuffer(); 157 | gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, indexBuffer); 158 | gl.bufferData( 159 | WebGL.ELEMENT_ARRAY_BUFFER, 160 | Uint16List.fromList([ 161 | 0, 1, 2, 0, 2, 3, // Front face 162 | 4, 5, 6, 4, 6, 7, // Back face 163 | 8, 9, 10, 8, 10, 11, // Top face 164 | 12, 13, 14, 12, 14, 15, // Bottom face 165 | 16, 17, 18, 16, 18, 19, // Right face 166 | 20, 21, 22, 20, 22, 23 // Left face 167 | ]), 168 | WebGL.STATIC_DRAW); 169 | } 170 | 171 | @override 172 | void draw({int? vertex, int? normal, int? coord, int? color, Function()? setUniforms}) { 173 | if (vertex != null) { 174 | gl.bindBuffer(WebGL.ARRAY_BUFFER, positionBuffer); 175 | gl.vertexAttribPointer(vertex, 3, WebGL.FLOAT, false, 0, 0); 176 | } 177 | 178 | if (normal != null) { 179 | gl.bindBuffer(WebGL.ARRAY_BUFFER, normalBuffer); 180 | gl.vertexAttribPointer(normal, 3, WebGL.FLOAT, false, 0, 0); 181 | } 182 | 183 | if (coord != null) { 184 | gl.bindBuffer(WebGL.ARRAY_BUFFER, textureCoordBuffer); 185 | gl.vertexAttribPointer(coord, 2, WebGL.FLOAT, false, 0, 0); 186 | } 187 | 188 | if (color != null) { 189 | gl.bindBuffer(WebGL.ARRAY_BUFFER, this.color.colorBuffer); 190 | gl.vertexAttribPointer(color, 4, WebGL.FLOAT, false, 0, 0); 191 | } 192 | 193 | if (setUniforms != null) setUniforms(); 194 | gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, indexBuffer); 195 | gl.drawElements(WebGL.TRIANGLES, 36, WebGL.UNSIGNED_SHORT, 0); 196 | } 197 | 198 | late CubeColor color; 199 | void addColor(CubeColor color) { 200 | this.color = color; 201 | } 202 | } 203 | 204 | /// Holds a color [late Buffer] for our cube's element array 205 | class CubeColor { 206 | late Buffer colorBuffer; 207 | 208 | CubeColor() { 209 | colorBuffer = gl.createBuffer(); 210 | gl.bindBuffer(WebGL.ARRAY_BUFFER, colorBuffer); 211 | 212 | /// HARD CODED :'( 213 | final colors = >[ 214 | [1.0, 0.0, 0.0, 1.0], // Front face 215 | [1.0, 1.0, 0.0, 1.0], // Back face 216 | [0.0, 1.0, 0.0, 1.0], // Top face 217 | [1.0, 0.5, 0.5, 1.0], // Bottom face 218 | [1.0, 0.0, 1.0, 1.0], // Right face 219 | [0.0, 0.0, 1.0, 1.0] // Left face 220 | ]; 221 | final unpackedColors = []; 222 | for (var i in colors) { 223 | for (var j = 0; j < 4; j++) { 224 | unpackedColors.addAll(i); 225 | } 226 | } 227 | gl.bufferData( 228 | WebGL.ARRAY_BUFFER, 229 | Float32List.fromList(unpackedColors), 230 | WebGL.STATIC_DRAW, 231 | ); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /web/earth-specular.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtmcdole/dart-webgl/5b0542b0c6ba54c62a459ff6096e28aa348747cd/web/earth-specular.gif -------------------------------------------------------------------------------- /web/earth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtmcdole/dart-webgl/5b0542b0c6ba54c62a459ff6096e28aa348747cd/web/earth.jpg -------------------------------------------------------------------------------- /web/galvanizedTexture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtmcdole/dart-webgl/5b0542b0c6ba54c62a459ff6096e28aa348747cd/web/galvanizedTexture.jpg -------------------------------------------------------------------------------- /web/gl_program.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Create a WebGL [Program], compiling [Shader]s from passed in sources and 18 | /// cache [UniformLocation]s and AttribLocations. 19 | class GlProgram { 20 | Map attributes = {}; 21 | Map uniforms = {}; 22 | late Program program; 23 | 24 | late Shader fragShader, vertShader; 25 | 26 | GlProgram(String fragSrc, String vertSrc, List attributeNames, List uniformNames) { 27 | fragShader = gl.createShader(WebGL.FRAGMENT_SHADER); 28 | gl.shaderSource(fragShader, fragSrc); 29 | gl.compileShader(fragShader); 30 | if (gl.getShaderParameter(fragShader, WebGL.COMPILE_STATUS) == 0) { 31 | print('Could not compile fragment shaders'); 32 | } 33 | vertShader = gl.createShader(WebGL.VERTEX_SHADER); 34 | gl.shaderSource(vertShader, vertSrc); 35 | gl.compileShader(vertShader); 36 | if (gl.getShaderParameter(vertShader, WebGL.COMPILE_STATUS) == 0) { 37 | print('Could not compile vertex shaders'); 38 | } 39 | 40 | program = gl.createProgram(); 41 | gl.attachShader(program, vertShader); 42 | gl.attachShader(program, fragShader); 43 | gl.linkProgram(program); 44 | 45 | if (gl.getProgramParameter(program, WebGL.LINK_STATUS) == 0) { 46 | print('Could not initialise shaders'); 47 | } 48 | 49 | for (var attrib in attributeNames) { 50 | final attributeLocation = gl.getAttribLocation(program, attrib); 51 | gl.enableVertexAttribArray(attributeLocation); 52 | attributes[attrib] = attributeLocation; 53 | } 54 | for (var uniform in uniformNames) { 55 | final uniformLocation = gl.getUniformLocation(program, uniform); 56 | uniforms[uniform] = uniformLocation; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /web/glass.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtmcdole/dart-webgl/5b0542b0c6ba54c62a459ff6096e28aa348747cd/web/glass.gif -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Learn gl 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /web/json_object.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Load a JSON object that follows: 18 | /// { 19 | /// 'vertexNormals': [], 20 | /// 'vertexTextureCoords': [], 21 | /// 'vertexPositions': [], 22 | /// 'indices': [], 23 | /// } 24 | /// Use [fromUrl] to load and return the object in the future! 25 | /// If 'indicies' are absent - data is interpreted as a triangle strip. 26 | class JsonObject implements Renderable { 27 | Buffer? vertexNormalBuffer, textureCoordBuffer, vertexPositionBuffer, indexBuffer; 28 | int _itemSize = 0; 29 | 30 | bool strip = false; 31 | 32 | JsonObject(String fromJson) { 33 | final Map data = json.decode(fromJson); 34 | 35 | List? numArray = data['vertexNormals']; 36 | if (numArray != null) { 37 | final normals = List.from(numArray.map((index) => index.toDouble())); 38 | 39 | vertexNormalBuffer = gl.createBuffer(); 40 | gl.bindBuffer(WebGL.ARRAY_BUFFER, vertexNormalBuffer); 41 | gl.bufferData( 42 | WebGL.ARRAY_BUFFER, 43 | Float32List.fromList(normals), 44 | WebGL.STATIC_DRAW, 45 | ); 46 | } 47 | 48 | numArray = data['vertexTextureCoords']; 49 | if (numArray != null) { 50 | final coords = List.from(numArray.map((index) => index.toDouble())); 51 | 52 | textureCoordBuffer = gl.createBuffer(); 53 | gl.bindBuffer(WebGL.ARRAY_BUFFER, textureCoordBuffer); 54 | gl.bufferData( 55 | WebGL.ARRAY_BUFFER, 56 | Float32List.fromList(coords), 57 | WebGL.STATIC_DRAW, 58 | ); 59 | } 60 | 61 | numArray = data['vertexPositions']; 62 | final positions = List.from(numArray!.map((index) => index.toDouble())); 63 | 64 | vertexPositionBuffer = gl.createBuffer(); 65 | gl.bindBuffer(WebGL.ARRAY_BUFFER, vertexPositionBuffer); 66 | gl.bufferData( 67 | WebGL.ARRAY_BUFFER, 68 | Float32List.fromList(positions), 69 | WebGL.STATIC_DRAW, 70 | ); 71 | 72 | numArray = data['indices']; 73 | if (numArray != null) { 74 | final indices = List.from(numArray.map((index) => index.toInt())); 75 | indexBuffer = gl.createBuffer(); 76 | gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, indexBuffer); 77 | gl.bufferData( 78 | WebGL.ELEMENT_ARRAY_BUFFER, 79 | Uint16List.fromList(indices), 80 | WebGL.STATIC_DRAW, 81 | ); 82 | _itemSize = indices.length; 83 | } else { 84 | _itemSize = positions.length ~/ 3; 85 | } 86 | } 87 | 88 | /// Return a future [JsonObject] by fetching the JSON data from [url]. 89 | static Future fromUrl(String url) { 90 | final complete = Completer(); 91 | HttpRequest.getString(url).then((json) { 92 | final obj = JsonObject(json); 93 | print('json object from $url loaded as $obj'); 94 | complete.complete(obj); 95 | }); 96 | return complete.future; 97 | } 98 | 99 | @override 100 | void draw({int? vertex, int? normal, int? coord, Function()? setUniforms}) { 101 | if (vertex != null) { 102 | gl.bindBuffer(WebGL.ARRAY_BUFFER, vertexPositionBuffer); 103 | gl.vertexAttribPointer(vertex, 3, WebGL.FLOAT, false, 0, 0); 104 | } 105 | 106 | if (normal != null && vertexNormalBuffer != null) { 107 | gl.bindBuffer(WebGL.ARRAY_BUFFER, vertexNormalBuffer); 108 | gl.vertexAttribPointer(normal, 3, WebGL.FLOAT, false, 0, 0); 109 | } 110 | 111 | if (coord != null && textureCoordBuffer != null) { 112 | gl.bindBuffer(WebGL.ARRAY_BUFFER, textureCoordBuffer); 113 | gl.vertexAttribPointer(coord, 2, WebGL.FLOAT, false, 0, 0); 114 | } 115 | 116 | if (setUniforms != null) setUniforms(); 117 | 118 | if (indexBuffer != null) { 119 | gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, indexBuffer); 120 | gl.drawElements(WebGL.TRIANGLES, _itemSize, WebGL.UNSIGNED_SHORT, 0); 121 | } else if (strip) { 122 | gl.drawArrays(WebGL.TRIANGLE_STRIP, 0, _itemSize); 123 | } else { 124 | gl.drawArrays(WebGL.TRIANGLES, 0, _itemSize); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /web/learn_gl.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #F8F8F8; 4 | font-family: 'Open Sans', sans-serif; 5 | font-size: 14px; 6 | font-weight: normal; 7 | line-height: 1.2em; 8 | margin: 0; 9 | } 10 | 11 | h1, p { 12 | color: #333; 13 | } 14 | 15 | #sample_container_id { 16 | width: 100%; 17 | height: 400px; 18 | position: relative; 19 | border: 1px solid #ccc; 20 | background-color: #fff; 21 | } 22 | 23 | #sample_text_id { 24 | font-size: 24pt; 25 | text-align: center; 26 | margin-top: 140px; 27 | } 28 | -------------------------------------------------------------------------------- /web/learn_gl.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | library learn_gl; 16 | 17 | import 'dart:async'; 18 | import 'dart:convert'; 19 | import 'dart:html'; 20 | import 'dart:math'; 21 | import 'dart:typed_data'; 22 | import 'dart:web_gl'; 23 | 24 | part 'cube.dart'; 25 | part 'gl_program.dart'; 26 | part 'json_object.dart'; 27 | // All the lessons 28 | part 'lesson1.dart'; 29 | part 'lesson10.dart'; 30 | part 'lesson11.dart'; 31 | part 'lesson12.dart'; 32 | part 'lesson13.dart'; 33 | part 'lesson14.dart'; 34 | part 'lesson15.dart'; 35 | part 'lesson16.dart'; 36 | part 'lesson2.dart'; 37 | part 'lesson3.dart'; 38 | part 'lesson4.dart'; 39 | part 'lesson5.dart'; 40 | part 'lesson6.dart'; 41 | part 'lesson7.dart'; 42 | part 'lesson8.dart'; 43 | part 'lesson9.dart'; 44 | // Math 45 | part 'matrix4.dart'; 46 | part 'pyramid.dart'; 47 | part 'rectangle.dart'; 48 | // Some of our objects that we're going to support 49 | part 'renderable.dart'; 50 | part 'sphere.dart'; 51 | part 'star.dart'; 52 | 53 | final canvas = querySelector('#lesson01-canvas') as CanvasElement; 54 | late RenderingContext2 gl; 55 | late Lesson lesson; 56 | 57 | void main() { 58 | mvMatrix = Matrix4()..identity(); 59 | // Nab the context we'll be drawing to. 60 | // gl = canvas.getContext3d(); 61 | gl = canvas.getContext('webgl2') as RenderingContext2; 62 | 63 | // Allow some URL customization of the program. 64 | parseQueryString(); 65 | 66 | trackFrameRate = urlParameters.containsKey('fps'); 67 | if (!trackFrameRate) { 68 | querySelector('#fps')!.remove(); 69 | } 70 | if (urlParameters.containsKey('width')) { 71 | final String width = urlParameters['width']; 72 | canvas.width = int.tryParse(width) ?? 500; 73 | } 74 | 75 | if (urlParameters.containsKey('height')) { 76 | final String height = urlParameters['height']; 77 | canvas.height = int.tryParse(height) ?? 500; 78 | } 79 | 80 | if (urlParameters.containsKey('overflow')) { 81 | document.body!.style.overflow = 'hidden'; 82 | } 83 | 84 | var defaultLesson = 1; 85 | if (urlParameters.containsKey('lsn')) { 86 | defaultLesson = int.parse(urlParameters['lsn']); 87 | } 88 | 89 | final lessonSelect = querySelector('#lessonNumber') as SelectElement; 90 | for (var i = 1; i < 17; i++) { 91 | lessonSelect.children.add(OptionElement( 92 | data: 'Lesson $i', 93 | value: '$i', 94 | selected: defaultLesson == i, 95 | )); 96 | } 97 | lessonSelect.onChange.listen((event) { 98 | lesson = selectLesson(lessonSelect.selectedIndex! + 1)!..initHtml(lessonHook); 99 | }); 100 | lesson = selectLesson(lessonSelect.selectedIndex! + 1)!..initHtml(lessonHook); 101 | 102 | // Set the fill color to black 103 | gl.clearColor(0, 0, 0, 1.0); 104 | 105 | // Hook into the window's onKeyDown and onKeyUp streams to track key states 106 | window.onKeyDown.listen((KeyboardEvent event) { 107 | currentlyPressedKeys.add(event.keyCode); 108 | }); 109 | 110 | window.onKeyUp.listen((event) { 111 | currentlyPressedKeys.remove(event.keyCode); 112 | }); 113 | 114 | // Start off the infinite animation loop 115 | tick(0); 116 | } 117 | 118 | /// This is the infinite animation loop; we request that the web browser 119 | /// call us back every time its ready for a frame to be rendered. The [time] 120 | /// parameter is an increasing value based on when the animation loop started. 121 | void tick(time) { 122 | window.animationFrame.then(tick); 123 | if (trackFrameRate) frameCount(time); 124 | lesson.handleKeys(); 125 | lesson.animate(time); 126 | lesson.drawScene(canvas.width!, canvas.height!, canvas.width! / canvas.height!); 127 | } 128 | 129 | /// The global key-state map. 130 | Set currentlyPressedKeys = {}; 131 | 132 | /// Test if the given [KeyCode] is active. 133 | bool isActive(int code) => currentlyPressedKeys.contains(code); 134 | 135 | /// Test if any of the given [KeyCode]s are active, returning true. 136 | bool anyActive(List codes) { 137 | return codes.firstWhere((code) => currentlyPressedKeys.contains(code), orElse: () => -1) != -1; 138 | } 139 | 140 | /// Parse and store the URL parameters for start up. 141 | void parseQueryString() { 142 | var search = window.location.search!; 143 | if (search.startsWith('?')) { 144 | search = search.substring(1); 145 | } 146 | final params = search.split('&'); 147 | for (var param in params) { 148 | final pair = param.split('='); 149 | if (pair.length == 1) { 150 | urlParameters[pair[0]] = ''; 151 | } else { 152 | urlParameters[pair[0]] = pair[1]; 153 | } 154 | } 155 | } 156 | 157 | Map urlParameters = {}; 158 | 159 | /// Perspective matrix 160 | late Matrix4 pMatrix; 161 | 162 | /// Model-View matrix. 163 | late Matrix4 mvMatrix; 164 | 165 | List mvStack = []; 166 | 167 | /// Add a copy of the current Model-View matrix to the the stack for future 168 | /// restoration. 169 | void mvPushMatrix() => mvStack.add(Matrix4.fromMatrix(mvMatrix)); 170 | 171 | /// Pop the last matrix off the stack and set the Model View matrix. 172 | void mvPopMatrix() => mvMatrix = mvStack.removeLast(); 173 | 174 | /// Handle common keys through callbacks, making lessons a little easier to code 175 | void handleDirection({Function()? up, Function()? down, Function()? left, Function()? right}) { 176 | if (left != null && anyActive([KeyCode.A, KeyCode.LEFT])) { 177 | left(); 178 | } 179 | if (right != null && anyActive([KeyCode.D, KeyCode.RIGHT])) { 180 | right(); 181 | } 182 | if (down != null && anyActive([KeyCode.S, KeyCode.DOWN])) { 183 | down(); 184 | } 185 | if (up != null && anyActive([KeyCode.W, KeyCode.UP])) { 186 | up(); 187 | } 188 | } 189 | 190 | /// FPS meter - activated when the url parameter "fps" is included. 191 | const double ALPHA_DECAY = 0.1; 192 | const double INVERSE_ALPHA_DECAY = 1 - ALPHA_DECAY; 193 | const SAMPLE_RATE_MS = 500; 194 | const SAMPLE_FACTOR = 1000 ~/ SAMPLE_RATE_MS; 195 | int frames = 0; 196 | double lastSample = 0; 197 | double averageFps = 1; 198 | DivElement fps = querySelector('#fps') as DivElement; 199 | 200 | void frameCount(double now) { 201 | frames++; 202 | if ((now - lastSample) < SAMPLE_RATE_MS) return; 203 | averageFps = averageFps * ALPHA_DECAY + frames * INVERSE_ALPHA_DECAY * SAMPLE_FACTOR; 204 | fps.text = averageFps.toStringAsFixed(2); 205 | frames = 0; 206 | lastSample = now; 207 | } 208 | 209 | /// The base for all Learn WebGL lessons. 210 | abstract class Lesson { 211 | /// Render the scene to the [viewWidth], [viewHeight], and [aspect] ratio. 212 | void drawScene(int viewWidth, int viewHeight, double aspect); 213 | 214 | /// Animate the scene any way you like. [now] is provided as a clock reference 215 | /// since the scene rendering started. 216 | void animate(double now) {} 217 | 218 | /// Handle any keyboard events. 219 | void handleKeys() {} 220 | 221 | /// Fill in any lesson related instructions and input elements. 222 | /// This is provided by default. 223 | void initHtml(DivElement hook) { 224 | hook.innerHtml = "If you see this, don't worry, the lesson doesn't have " 225 | 'any parameters for you to change! Generally up/down/left/right or ' 226 | 'WASD work.'; 227 | } 228 | 229 | /// Added for your convenience to track time between [animate] callbacks. 230 | double lastTime = 0; 231 | } 232 | 233 | /// Load the given image at [url] and call [handle] to execute some GL code. 234 | /// Return a [Future] to asynchronously notify when the texture is complete. 235 | Future loadTexture(String url, Function(Texture tex, ImageElement ele) handle) { 236 | final completer = Completer(); 237 | final texture = gl.createTexture(); 238 | final element = ImageElement(); 239 | element.onLoad.listen((e) { 240 | handle(texture, element); 241 | completer.complete(texture); 242 | }); 243 | element.src = url; 244 | return completer.future; 245 | } 246 | 247 | /// This is a common handler for [loadTexture]. It will be explained in future 248 | /// lessons that require textures. 249 | void handleMipMapTexture(Texture texture, ImageElement image) { 250 | gl.pixelStorei(WebGL.UNPACK_FLIP_Y_WEBGL, 1); 251 | gl.bindTexture(WebGL.TEXTURE_2D, texture); 252 | gl.texImage2D( 253 | WebGL.TEXTURE_2D, 254 | 0, 255 | WebGL.RGBA, 256 | WebGL.RGBA, 257 | WebGL.UNSIGNED_BYTE, 258 | image, 259 | ); 260 | gl.texParameteri( 261 | WebGL.TEXTURE_2D, 262 | WebGL.TEXTURE_MAG_FILTER, 263 | WebGL.LINEAR, 264 | ); 265 | gl.texParameteri( 266 | WebGL.TEXTURE_2D, 267 | WebGL.TEXTURE_MIN_FILTER, 268 | WebGL.LINEAR_MIPMAP_NEAREST, 269 | ); 270 | gl.generateMipmap(WebGL.TEXTURE_2D); 271 | gl.bindTexture(WebGL.TEXTURE_2D, null); 272 | } 273 | 274 | DivElement lessonHook = querySelector('#lesson_html') as DivElement; 275 | bool trackFrameRate = false; 276 | 277 | Lesson? selectLesson(int number) { 278 | switch (number) { 279 | case 1: 280 | return Lesson1(); 281 | case 2: 282 | return Lesson2(); 283 | case 3: 284 | return Lesson3(); 285 | case 4: 286 | return Lesson4(); 287 | case 5: 288 | return Lesson5(); 289 | case 6: 290 | return Lesson6(); 291 | case 7: 292 | return Lesson7(); 293 | case 8: 294 | return Lesson8(); 295 | case 9: 296 | return Lesson9(); 297 | case 10: 298 | return Lesson10(); 299 | case 11: 300 | return Lesson11(); 301 | case 12: 302 | return Lesson12(); 303 | case 13: 304 | return Lesson13(); 305 | case 14: 306 | return Lesson14(); 307 | case 15: 308 | return Lesson15(); 309 | case 16: 310 | return Lesson16(); 311 | } 312 | return null; 313 | } 314 | 315 | /// Work around for setInnerHtml() 316 | class NullTreeSanitizer implements NodeTreeSanitizer { 317 | static NullTreeSanitizer? instance; 318 | factory NullTreeSanitizer() { 319 | instance ??= NullTreeSanitizer._(); 320 | return instance!; 321 | } 322 | 323 | NullTreeSanitizer._(); 324 | @override 325 | void sanitizeTree(Node node) {} 326 | } 327 | -------------------------------------------------------------------------------- /web/lesson1.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Staticly draw a triangle and a square! 18 | class Lesson1 extends Lesson { 19 | late GlProgram program; 20 | 21 | late Buffer triangleVertexPositionBuffer, squareVertexPositionBuffer; 22 | 23 | Lesson1() { 24 | program = GlProgram( 25 | ''' 26 | precision mediump float; 27 | 28 | void main(void) { 29 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); 30 | } 31 | ''', 32 | ''' 33 | attribute vec3 aVertexPosition; 34 | 35 | uniform mat4 uMVMatrix; 36 | uniform mat4 uPMatrix; 37 | 38 | void main(void) { 39 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 40 | } 41 | ''', 42 | ['aVertexPosition'], 43 | ['uMVMatrix', 'uPMatrix'], 44 | ); 45 | gl.useProgram(program.program); 46 | 47 | // Allocate and build the two buffers we need to draw a triangle and box. 48 | // createBuffer() asks the WebGL system to allocate some data for us 49 | triangleVertexPositionBuffer = gl.createBuffer(); 50 | 51 | // bindBuffer() tells the WebGL system the target of future calls 52 | gl.bindBuffer(WebGL.ARRAY_BUFFER, triangleVertexPositionBuffer); 53 | gl.bufferData( 54 | WebGL.ARRAY_BUFFER, Float32List.fromList([0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0]), WebGL.STATIC_DRAW); 55 | 56 | squareVertexPositionBuffer = gl.createBuffer(); 57 | gl.bindBuffer(WebGL.ARRAY_BUFFER, squareVertexPositionBuffer); 58 | gl.bufferData( 59 | WebGL.ARRAY_BUFFER, 60 | Float32List.fromList([ 61 | 1.0, 62 | 1.0, 63 | 0.0, 64 | -1.0, 65 | 1.0, 66 | 0.0, 67 | 1.0, 68 | -1.0, 69 | 0.0, 70 | -1.0, 71 | -1.0, 72 | 0.0, 73 | ]), 74 | WebGL.STATIC_DRAW); 75 | 76 | // Specify the color to clear with (black with 100% alpha) and then enable 77 | // depth testing. 78 | gl.clearColor(0.0, 0.0, 0.0, 1.0); 79 | } 80 | 81 | @override 82 | void drawScene(int viewWidth, int viewHeight, double aspect) { 83 | // Basic viewport setup and clearing of the screen 84 | gl.viewport(0, 0, viewWidth, viewHeight); 85 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 86 | gl.enable(WebGL.DEPTH_TEST); 87 | gl.disable(WebGL.BLEND); 88 | 89 | // Setup the perspective - you might be wondering why we do this every 90 | // time, and that will become clear in much later lessons. Just know, you 91 | // are not crazy for thinking of caching this. 92 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 93 | 94 | // First stash the current model view matrix before we start moving around. 95 | mvPushMatrix(); 96 | 97 | mvMatrix.translate([-1.5, 0.0, -7.0]); 98 | 99 | // Here's that bindBuffer() again, as seen in the constructor 100 | gl.bindBuffer(WebGL.ARRAY_BUFFER, triangleVertexPositionBuffer); 101 | // Set the vertex attribute to the size of each individual element (x,y,z) 102 | gl.vertexAttribPointer(program.attributes['aVertexPosition']!, 3, WebGL.FLOAT, false, 0, 0); 103 | setMatrixUniforms(); 104 | // Now draw 3 vertices 105 | gl.drawArrays(WebGL.TRIANGLES, 0, 3); 106 | 107 | // Move 3 units to the right 108 | mvMatrix.translate([3.0, 0.0, 0.0]); 109 | 110 | // And get ready to draw the square just like we did the triangle... 111 | gl.bindBuffer(WebGL.ARRAY_BUFFER, squareVertexPositionBuffer); 112 | gl.vertexAttribPointer(program.attributes['aVertexPosition']!, 3, WebGL.FLOAT, false, 0, 0); 113 | setMatrixUniforms(); 114 | // Except now draw 2 triangles, re-using the vertices found in the buffer. 115 | gl.drawArrays(WebGL.TRIANGLE_STRIP, 0, 4); 116 | 117 | // Finally, reset the matrix back to what it was before we moved around. 118 | mvPopMatrix(); 119 | } 120 | 121 | /// Write the matrix uniforms (model view matrix and perspective matrix) so 122 | /// WebGL knows what to do with them. 123 | void setMatrixUniforms() { 124 | gl.uniformMatrix4fv(program.uniforms['uPMatrix'], false, pMatrix.buf); 125 | gl.uniformMatrix4fv(program.uniforms['uMVMatrix'], false, mvMatrix.buf); 126 | } 127 | 128 | @override 129 | void animate(double now) { 130 | // We're not animating the scene, but if you want to experiment, here's 131 | // where you get to play around. 132 | } 133 | 134 | @override 135 | void handleKeys() { 136 | // We're not handling keys right now, but if you want to experiment, here's 137 | // where you'd get to play around. 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /web/lesson10.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Load a world! 18 | class Lesson10 extends Lesson { 19 | late GlProgram program; 20 | Texture? texture; 21 | JsonObject? world; 22 | 23 | bool get isLoaded => world != null && texture != null; 24 | 25 | Lesson10() { 26 | JsonObject.fromUrl('world.json').then((object) { 27 | world = object; 28 | print('world loaded with ${world?._itemSize}'); 29 | }); 30 | 31 | loadTexture('mcdole.gif', (Texture texture, ImageElement ele) { 32 | gl.pixelStorei(WebGL.UNPACK_FLIP_Y_WEBGL, 1); 33 | gl.bindTexture(WebGL.TEXTURE_2D, texture); 34 | gl.texImage2D( 35 | WebGL.TEXTURE_2D, 36 | 0, 37 | WebGL.RGBA, 38 | WebGL.RGBA, 39 | WebGL.UNSIGNED_BYTE, 40 | ele, 41 | ); 42 | gl.texParameteri( 43 | WebGL.TEXTURE_2D, 44 | WebGL.TEXTURE_MAG_FILTER, 45 | WebGL.LINEAR, 46 | ); 47 | gl.texParameteri( 48 | WebGL.TEXTURE_2D, 49 | WebGL.TEXTURE_MIN_FILTER, 50 | WebGL.LINEAR, 51 | ); 52 | this.texture = texture; 53 | print('texture loaded'); 54 | }); 55 | 56 | final attributes = ['aVertexPosition', 'aTextureCoord']; 57 | final uniforms = ['uMVMatrix', 'uPMatrix', 'uSampler']; 58 | program = GlProgram( 59 | ''' 60 | precision mediump float; 61 | 62 | varying vec2 vTextureCoord; 63 | 64 | uniform sampler2D uSampler; 65 | 66 | void main(void) { 67 | gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 68 | } 69 | ''', 70 | ''' 71 | attribute vec3 aVertexPosition; 72 | attribute vec2 aTextureCoord; 73 | 74 | uniform mat4 uMVMatrix; 75 | uniform mat4 uPMatrix; 76 | 77 | varying vec2 vTextureCoord; 78 | 79 | void main(void) { 80 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 81 | vTextureCoord = aTextureCoord; 82 | } 83 | ''', 84 | attributes, 85 | uniforms, 86 | ); 87 | gl.useProgram(program.program); 88 | } 89 | 90 | int? get aTextureCoord => program.attributes['aTextureCoord']; 91 | int? get aVertexPosition => program.attributes['aVertexPosition']; 92 | 93 | UniformLocation? get uSampler => program.uniforms['uSampler']; 94 | UniformLocation? get uPMatrix => program.uniforms['uPMatrix']; 95 | UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix']; 96 | 97 | @override 98 | void drawScene(int viewWidth, int viewHeight, double aspect) { 99 | if (!isLoaded) return; 100 | 101 | gl.disable(WebGL.BLEND); 102 | gl.enable(WebGL.DEPTH_TEST); 103 | 104 | // Basic viewport setup and clearing of the screen 105 | gl.viewport(0, 0, viewWidth, viewHeight); 106 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 107 | 108 | // Setup the perspective - you might be wondering why we do this every 109 | // time, and that will become clear in much later lessons. Just know, you 110 | // are not crazy for thinking of caching this. 111 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 112 | 113 | mvPushMatrix(); 114 | 115 | mvMatrix 116 | ..rotateX(radians(-pitch)) 117 | ..rotateY(radians(-yaw)) 118 | ..translate([-xPos, -yPos, -zPos]); 119 | 120 | gl.activeTexture(WebGL.TEXTURE0); 121 | gl.bindTexture(WebGL.TEXTURE_2D, texture); 122 | gl.uniform1i(uSampler, 0); 123 | 124 | world?.draw( 125 | vertex: aVertexPosition, 126 | coord: aTextureCoord, 127 | setUniforms: () { 128 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 129 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 130 | }); 131 | 132 | mvPopMatrix(); 133 | } 134 | 135 | double pitch = 0.0, yaw = 0.0; 136 | double pitchRate = 0.0, yawRate = 0.0; 137 | double xPos = 0.0, yPos = 0.4, zPos = 0.0; 138 | double speed = 0.0; 139 | 140 | @override 141 | void animate(double now) { 142 | if (lastTime != 0) { 143 | final elapsed = now - lastTime; 144 | 145 | if (speed != 0) { 146 | xPos -= sin(radians(yaw)) * speed * elapsed; 147 | zPos -= cos(radians(yaw)) * speed * elapsed; 148 | } 149 | yaw += yawRate * elapsed; 150 | pitch += pitchRate * elapsed; 151 | } 152 | lastTime = now; 153 | } 154 | 155 | @override 156 | void handleKeys() { 157 | if (anyActive([KeyCode.UP, KeyCode.W])) { 158 | speed = 0.003; 159 | } else if (anyActive([KeyCode.DOWN, KeyCode.S])) { 160 | speed = -0.003; 161 | } else { 162 | speed = 0.0; 163 | } 164 | if (anyActive([KeyCode.LEFT, KeyCode.A])) { 165 | yawRate = 0.1; 166 | } else if (anyActive([KeyCode.RIGHT, KeyCode.D])) { 167 | yawRate = -0.1; 168 | } else { 169 | yawRate = 0.0; 170 | } 171 | if (anyActive([KeyCode.PAGE_UP, KeyCode.NINE])) { 172 | pitchRate = 0.1; 173 | } else if (anyActive([KeyCode.PAGE_DOWN, KeyCode.THREE])) { 174 | pitchRate = -0.1; 175 | } else { 176 | pitchRate = 0.0; 177 | } 178 | } 179 | 180 | @override 181 | void initHtml(DivElement hook) { 182 | hook.setInnerHtml( 183 | ''' 184 | Use the cursor keys or WASD to run around, and Page Up/Page Down to 185 | look up and down. 186 | ''', 187 | treeSanitizer: NullTreeSanitizer(), 188 | ); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /web/lesson11.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Spheres, rotations matricies, and mouse events 18 | class Lesson11 extends Lesson { 19 | late GlProgram program; 20 | late Sphere moon; 21 | Texture? moonTexture; 22 | 23 | Matrix4 _rotation = Matrix4()..identity(); 24 | bool _mouseDown = false; 25 | var _lastMouseX, _lastMouseY; 26 | 27 | bool get isLoaded => moonTexture != null; 28 | 29 | Lesson11() { 30 | moon = Sphere(lats: 30, lons: 30, radius: 2); 31 | 32 | final attributes = ['aVertexPosition', 'aVertexNormal', 'aTextureCoord']; 33 | final uniforms = [ 34 | 'uSampler', 35 | 'uMVMatrix', 36 | 'uPMatrix', 37 | 'uNMatrix', 38 | 'uAmbientColor', 39 | 'uLightingDirection', 40 | 'uDirectionalColor', 41 | 'uUseLighting' 42 | ]; 43 | program = GlProgram( 44 | ''' 45 | precision mediump float; 46 | 47 | varying vec2 vTextureCoord; 48 | varying vec3 vLightWeighting; 49 | 50 | uniform sampler2D uSampler; 51 | 52 | void main(void) { 53 | vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 54 | gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a); 55 | } 56 | ''', 57 | ''' 58 | attribute vec3 aVertexPosition; 59 | attribute vec3 aVertexNormal; 60 | attribute vec2 aTextureCoord; 61 | 62 | uniform mat4 uMVMatrix; 63 | uniform mat4 uPMatrix; 64 | uniform mat3 uNMatrix; 65 | 66 | uniform vec3 uAmbientColor; 67 | 68 | uniform vec3 uLightingDirection; 69 | uniform vec3 uDirectionalColor; 70 | 71 | uniform bool uUseLighting; 72 | 73 | varying vec2 vTextureCoord; 74 | varying vec3 vLightWeighting; 75 | 76 | void main(void) { 77 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 78 | vTextureCoord = aTextureCoord; 79 | 80 | if (!uUseLighting) { 81 | vLightWeighting = vec3(1.0, 1.0, 1.0); 82 | } else { 83 | vec3 transformedNormal = uNMatrix * aVertexNormal; 84 | float directionalLightWeighting = max(dot(transformedNormal, uLightingDirection), 0.0); 85 | vLightWeighting = uAmbientColor + uDirectionalColor * directionalLightWeighting; 86 | } 87 | } 88 | ''', 89 | attributes, 90 | uniforms, 91 | ); 92 | 93 | loadTexture('moon.bmp', handleMipMapTexture).then((t) => moonTexture = t); 94 | 95 | gl.useProgram(program.program); 96 | 97 | // Mouse handling (extra listeners) 98 | canvas.onMouseDown.listen((MouseEvent event) { 99 | _mouseDown = true; 100 | _lastMouseX = event.client.x; 101 | _lastMouseY = event.client.y; 102 | }); 103 | 104 | document.onMouseUp.listen((MouseEvent event) { 105 | _mouseDown = false; 106 | }); 107 | 108 | document.onMouseMove.listen((MouseEvent event) { 109 | if (!_mouseDown) return; 110 | final newX = event.client.x; 111 | final newY = event.client.y; 112 | final deltaX = newX - _lastMouseX; 113 | final newRot = Matrix4() 114 | ..identity() 115 | ..rotateY(radians(deltaX / 10)); 116 | final deltaY = newY - _lastMouseY; 117 | newRot.rotateX(radians(deltaY / 10)); 118 | _rotation = newRot * _rotation; // C = A * B, first operand = newRot. 119 | _lastMouseX = newX; 120 | _lastMouseY = newY; 121 | }); 122 | } 123 | 124 | int? get aVertexPosition => program.attributes['aVertexPosition']; 125 | int? get aVertexNormal => program.attributes['aVertexNormal']; 126 | int? get aTextureCoord => program.attributes['aTextureCoord']; 127 | 128 | UniformLocation? get uSampler => program.uniforms['uSampler']; 129 | UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix']; 130 | UniformLocation? get uPMatrix => program.uniforms['uPMatrix']; 131 | UniformLocation? get uNMatrix => program.uniforms['uNMatrix']; 132 | UniformLocation? get uAmbientColor => program.uniforms['uAmbientColor']; 133 | UniformLocation? get uLightingDirection => program.uniforms['uLightingDirection']; 134 | UniformLocation? get uDirectionalColor => program.uniforms['uDirectionalColor']; 135 | UniformLocation? get uUseLighting => program.uniforms['uUseLighting']; 136 | 137 | @override 138 | void drawScene(int viewWidth, int viewHeight, double aspect) { 139 | if (!isLoaded) return; 140 | 141 | gl.viewport(0, 0, viewWidth, viewHeight); 142 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 143 | gl.enable(WebGL.DEPTH_TEST); 144 | gl.disable(WebGL.BLEND); 145 | 146 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 147 | 148 | // One: setup lighting information 149 | final lighting = _lighting.checked!; 150 | gl.uniform1i(uUseLighting, lighting ? 1 : 0); 151 | if (lighting) { 152 | gl.uniform3f(uAmbientColor, double.parse(_aR.value!), double.parse(_aG.value!), double.parse(_aB.value!)); 153 | 154 | // Take the lighting point and normalize / reverse it. 155 | var direction = Vector3(double.parse(_ldX.value!), double.parse(_ldY.value!), double.parse(_ldZ.value!)); 156 | direction = direction.normalize().scale(-1.0); 157 | gl.uniform3fv(uLightingDirection, direction.buf); 158 | 159 | gl.uniform3f(uDirectionalColor, double.parse(_dR.value!), double.parse(_dG.value!), double.parse(_dB.value!)); 160 | } 161 | 162 | mvPushMatrix(); 163 | // Setup the scene -20.0 away. 164 | mvMatrix = mvMatrix..translate([0.0, 0.0, -7.0]); 165 | mvMatrix = mvMatrix * _rotation; 166 | 167 | gl.activeTexture(WebGL.TEXTURE0); 168 | gl.bindTexture(WebGL.TEXTURE_2D, moonTexture); 169 | gl.uniform1i(uSampler, 0); 170 | moon.draw(vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms); 171 | mvPopMatrix(); 172 | } 173 | 174 | var mouseDown = false; 175 | var lastMouseX; 176 | var lastMouseY; 177 | void setMatrixUniforms() { 178 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 179 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 180 | final normalMatrix = mvMatrix.toInverseMat3(); 181 | normalMatrix!.transposeSelf(); 182 | gl.uniformMatrix3fv(uNMatrix, false, normalMatrix.buf); 183 | } 184 | 185 | @override 186 | void animate(double now) {} 187 | 188 | @override 189 | void handleKeys() {} 190 | 191 | // Lighting enabled / Ambient color 192 | late InputElement _lighting, _aR, _aG, _aB; 193 | 194 | // Light position 195 | late InputElement _ldX, _ldY, _ldZ; 196 | 197 | // Point color 198 | late InputElement _dR, _dG, _dB; 199 | 200 | @override 201 | void initHtml(DivElement hook) { 202 | hook.setInnerHtml( 203 | '''" 204 | Use lighting
205 | Spin the moon by dragging it with the mouse. 206 |
207 | 208 |

Directional light:

209 | 210 | 211 | 212 | 217 | 218 | 223 |
Direction: 213 | X: 214 | Y: 215 | Z: 216 |
Colour: 219 | R: 220 | G: 221 | B: 222 |
224 | 225 | 226 |

Ambient light:

227 | 228 | 229 | 230 | 235 |
Colour: 231 | R: 232 | G: 233 | B: 234 |
236 |
237 | 238 | Moon texture courtesy of the Jet Propulsion Laboratory. 239 | ''', 240 | treeSanitizer: NullTreeSanitizer(), 241 | ); 242 | 243 | // Re-look up our dom elements 244 | _lighting = querySelector('#lighting') as InputElement; 245 | _aR = querySelector('#ambientR') as InputElement; 246 | _aG = querySelector('#ambientG') as InputElement; 247 | _aB = querySelector('#ambientB') as InputElement; 248 | 249 | _dR = querySelector('#directionalR') as InputElement; 250 | _dG = querySelector('#directionalG') as InputElement; 251 | _dB = querySelector('#directionalB') as InputElement; 252 | 253 | _ldX = querySelector('#lightDirectionX') as InputElement; 254 | _ldY = querySelector('#lightDirectionY') as InputElement; 255 | _ldZ = querySelector('#lightDirectionZ') as InputElement; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /web/lesson12.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Handle custome shaders, animation, etc for Lesson 12 - Point Lighting. 18 | /// In this lesson, we spin a sphere and a cube around a central axis with at its core is a 19 | /// point of directional lighting. 20 | /// 21 | /// In the original lesson, the moon and box are tidal locked (always showing the same face), 22 | /// lets play around with that. 23 | class Lesson12 extends Lesson { 24 | late GlProgram program; 25 | late Cube cube; 26 | late Sphere moon; 27 | Texture? moonTexture, cubeTexture; 28 | 29 | bool get isLoaded => moonTexture != null && cubeTexture != null; 30 | 31 | Lesson12() { 32 | moon = Sphere(lats: 30, lons: 30, radius: 2); 33 | cube = Cube(); 34 | 35 | final attributes = ['aVertexPosition', 'aVertexNormal', 'aTextureCoord']; 36 | final uniforms = [ 37 | 'uSampler', 38 | 'uMVMatrix', 39 | 'uPMatrix', 40 | 'uNMatrix', 41 | 'uAmbientColor', 42 | 'uPointLightingLocation', 43 | 'uPointLightingColor', 44 | 'uUseLighting' 45 | ]; 46 | program = GlProgram( 47 | ''' 48 | precision mediump float; 49 | 50 | varying vec2 vTextureCoord; 51 | varying vec3 vLightWeighting; 52 | 53 | uniform sampler2D uSampler; 54 | 55 | void main(void) { 56 | vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 57 | gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a); 58 | } 59 | ''', 60 | ''' 61 | attribute vec3 aVertexPosition; 62 | attribute vec3 aVertexNormal; 63 | attribute vec2 aTextureCoord; 64 | 65 | uniform mat4 uMVMatrix; 66 | uniform mat4 uPMatrix; 67 | uniform mat3 uNMatrix; 68 | 69 | uniform vec3 uAmbientColor; 70 | 71 | uniform vec3 uPointLightingLocation; 72 | uniform vec3 uPointLightingColor; 73 | 74 | uniform bool uUseLighting; 75 | 76 | varying vec2 vTextureCoord; 77 | varying vec3 vLightWeighting; 78 | 79 | void main(void) { 80 | vec4 mvPosition = uMVMatrix * vec4(aVertexPosition, 1.0); 81 | gl_Position = uPMatrix * mvPosition; 82 | vTextureCoord = aTextureCoord; 83 | 84 | if (!uUseLighting) { 85 | vLightWeighting = vec3(1.0, 1.0, 1.0); 86 | } else { 87 | vec3 lightDirection = normalize(uPointLightingLocation - mvPosition.xyz); 88 | 89 | vec3 transformedNormal = uNMatrix * aVertexNormal; 90 | float directionalLightWeighting = max(dot(transformedNormal, lightDirection), 0.0); 91 | vLightWeighting = uAmbientColor + uPointLightingColor * directionalLightWeighting; 92 | } 93 | } 94 | ''', 95 | attributes, 96 | uniforms, 97 | ); 98 | 99 | loadTexture('moon.bmp', handleMipMapTexture).then((t) => moonTexture = t); 100 | loadTexture('crate.gif', handleMipMapTexture).then((t) => cubeTexture = t); 101 | 102 | gl.useProgram(program.program); 103 | gl.enable(WebGL.DEPTH_TEST); 104 | } 105 | 106 | int? get aVertexPosition => program.attributes['aVertexPosition']; 107 | int? get aVertexNormal => program.attributes['aVertexNormal']; 108 | int? get aTextureCoord => program.attributes['aTextureCoord']; 109 | 110 | UniformLocation? get uSampler => program.uniforms['uSampler']; 111 | UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix']; 112 | UniformLocation? get uPMatrix => program.uniforms['uPMatrix']; 113 | UniformLocation? get uNMatrix => program.uniforms['uNMatrix']; 114 | UniformLocation? get uAmbientColor => program.uniforms['uAmbientColor']; 115 | UniformLocation? get uPointLightingLocation => program.uniforms['uPointLightingLocation']; 116 | UniformLocation? get uPointLightingColor => program.uniforms['uPointLightingColor']; 117 | UniformLocation? get uUseLighting => program.uniforms['uUseLighting']; 118 | 119 | @override 120 | void drawScene(int viewWidth, int viewHeight, double aspect) { 121 | if (!isLoaded) return; 122 | 123 | gl.enable(WebGL.DEPTH_TEST); 124 | gl.disable(WebGL.BLEND); 125 | 126 | gl.viewport(0, 0, viewWidth, viewHeight); 127 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 128 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 129 | 130 | gl.useProgram(program.program); 131 | 132 | // One: setup lighting information 133 | final lighting = _lighting.checked!; 134 | gl.uniform1i(uUseLighting, lighting ? 1 : 0); 135 | if (lighting) { 136 | gl.uniform3f(uAmbientColor, double.parse(_aR.value!), double.parse(_aG.value!), double.parse(_aB.value!)); 137 | 138 | gl.uniform3f( 139 | uPointLightingLocation, double.parse(_lpX.value!), double.parse(_lpY.value!), double.parse(_lpZ.value!)); 140 | 141 | gl.uniform3f(uPointLightingColor, double.parse(_pR.value!), double.parse(_pG.value!), double.parse(_pB.value!)); 142 | } 143 | 144 | mvPushMatrix(); 145 | 146 | // Setup the scene -20.0 away. 147 | mvMatrix 148 | ..translate([0.0, 0.0, -20.0]) 149 | ..rotateX(radians(tilt)); 150 | 151 | mvPushMatrix(); 152 | // Rotate and move away from the scene 153 | mvMatrix 154 | ..rotateY(radians(moonAngle)) 155 | ..translate([5.0, 0.0, 0.0]); 156 | gl.activeTexture(WebGL.TEXTURE0); 157 | gl.bindTexture(WebGL.TEXTURE_2D, moonTexture); 158 | gl.uniform1i(uSampler, 0); 159 | moon.draw(vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms); 160 | mvPopMatrix(); 161 | 162 | mvMatrix 163 | ..rotateY(radians(cubeAngle)) 164 | ..translate([5.0, 0.0, 0.0]); 165 | gl.activeTexture(WebGL.TEXTURE0); 166 | gl.bindTexture(WebGL.TEXTURE_2D, cubeTexture); 167 | gl.uniform1i(uSampler, 0); 168 | cube.draw(vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms); 169 | mvPopMatrix(); 170 | } 171 | 172 | double moonAngle = 180.0; 173 | double cubeAngle = 0.0; 174 | double tilt = 0.0; 175 | 176 | void setMatrixUniforms() { 177 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 178 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 179 | final normalMatrix = mvMatrix.toInverseMat3(); 180 | normalMatrix!.transposeSelf(); 181 | gl.uniformMatrix3fv(uNMatrix, false, normalMatrix.buf); 182 | } 183 | 184 | @override 185 | void animate(double now) { 186 | if (lastTime != 0) { 187 | final elapsed = now - lastTime; 188 | moonAngle += 0.05 * elapsed; 189 | cubeAngle += 0.05 * elapsed; 190 | } 191 | lastTime = now; 192 | } 193 | 194 | @override 195 | void handleKeys() { 196 | handleDirection( 197 | up: () => tilt -= 1.0, 198 | down: () => tilt += 1.0, 199 | left: () { 200 | moonAngle -= 1.0; 201 | cubeAngle -= 1.0; 202 | }, 203 | right: () { 204 | moonAngle += 1.0; 205 | cubeAngle += 1.0; 206 | }); 207 | } 208 | 209 | // Lighting enabled / Ambient color 210 | late InputElement _lighting, _aR, _aG, _aB; 211 | 212 | // Light position 213 | late InputElement _lpX, _lpY, _lpZ; 214 | 215 | // Point color 216 | late InputElement _pR, _pG, _pB; 217 | 218 | @override 219 | void initHtml(DivElement hook) { 220 | hook.setInnerHtml( 221 | ''' 222 | Use lighting
223 |
224 | 225 |

Point light:

226 | 227 | 228 | 229 | 234 | 235 | 240 |
Location: 230 | X: 231 | Y: 232 | Z: 233 |
Colour: 236 | R: 237 | G: 238 | B: 239 |
241 | 242 |

Ambient light:

243 | 244 | 245 | 246 | 251 |
Colour: 247 | R: 248 | G: 249 | B: 250 |
252 |
253 | 254 | Moon texture courtesy of the Jet Propulsion Laboratory. 255 | ''', 256 | treeSanitizer: NullTreeSanitizer(), 257 | ); 258 | 259 | // Re-look up our dom elements 260 | _lighting = querySelector('#lighting') as InputElement; 261 | _aR = querySelector('#ambientR') as InputElement; 262 | _aG = querySelector('#ambientG') as InputElement; 263 | _aB = querySelector('#ambientB') as InputElement; 264 | 265 | _pR = querySelector('#pointR') as InputElement; 266 | _pG = querySelector('#pointG') as InputElement; 267 | _pB = querySelector('#pointB') as InputElement; 268 | 269 | _lpX = querySelector('#lightPositionX') as InputElement; 270 | _lpY = querySelector('#lightPositionY') as InputElement; 271 | _lpZ = querySelector('#lightPositionZ') as InputElement; 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /web/lesson13.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Handle custom shaders, animation, etc for Lesson 12 - Point Lighting. 18 | /// In this lesson, we spin a sphere and a cube around a central axis with at its core is a 19 | /// point of directional lighting. 20 | /// 21 | /// In the original lesson, the moon and box are tidal locked (always showing the same face), 22 | /// lets play around with that. 23 | class Lesson13 extends Lesson { 24 | late Cube cube; 25 | late Sphere moon; 26 | 27 | late GlProgram perVertexProgram; 28 | late GlProgram perFragmentProgram; 29 | 30 | late GlProgram currentProgram; 31 | 32 | Texture? moonTexture, cubeTexture; 33 | bool get isLoaded => moonTexture != null && cubeTexture != null; 34 | 35 | Lesson13() { 36 | moon = Sphere(lats: 30, lons: 30, radius: 1); 37 | cube = Cube(); 38 | 39 | final attributes = ['aVertexPosition', 'aVertexNormal', 'aTextureCoord']; 40 | final uniforms = [ 41 | 'uPMatrix', 42 | 'uMVMatrix', 43 | 'uNMatrix', 44 | 'uSampler', 45 | 'uUseTextures', 46 | 'uUseLighting', 47 | 'uAmbientColor', 48 | 'uPointLightingLocation', 49 | 'uPointLightingColor' 50 | ]; 51 | 52 | perVertexProgram = GlProgram( 53 | ''' 54 | precision mediump float; 55 | 56 | varying vec2 vTextureCoord; 57 | varying vec3 vLightWeighting; 58 | 59 | uniform bool uUseTextures; 60 | 61 | uniform sampler2D uSampler; 62 | 63 | void main(void) { 64 | vec4 fragmentColor; 65 | if (uUseTextures) { 66 | fragmentColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 67 | } else { 68 | fragmentColor = vec4(1.0, 1.0, 1.0, 1.0); 69 | } 70 | gl_FragColor = vec4(fragmentColor.rgb * vLightWeighting, fragmentColor.a); 71 | } 72 | ''', 73 | ''' 74 | attribute vec3 aVertexPosition; 75 | attribute vec3 aVertexNormal; 76 | attribute vec2 aTextureCoord; 77 | 78 | uniform mat4 uMVMatrix; 79 | uniform mat4 uPMatrix; 80 | uniform mat3 uNMatrix; 81 | 82 | uniform vec3 uAmbientColor; 83 | 84 | uniform vec3 uPointLightingLocation; 85 | uniform vec3 uPointLightingColor; 86 | 87 | uniform bool uUseLighting; 88 | 89 | varying vec2 vTextureCoord; 90 | varying vec3 vLightWeighting; 91 | 92 | void main(void) { 93 | vec4 mvPosition = uMVMatrix * vec4(aVertexPosition, 1.0); 94 | gl_Position = uPMatrix * mvPosition; 95 | vTextureCoord = aTextureCoord; 96 | 97 | if (!uUseLighting) { 98 | vLightWeighting = vec3(1.0, 1.0, 1.0); 99 | } else { 100 | vec3 lightDirection = normalize(uPointLightingLocation - mvPosition.xyz); 101 | 102 | vec3 transformedNormal = uNMatrix * aVertexNormal; 103 | float directionalLightWeighting = max(dot(transformedNormal, lightDirection), 0.0); 104 | vLightWeighting = uAmbientColor + uPointLightingColor * directionalLightWeighting; 105 | } 106 | } 107 | ''', 108 | attributes, 109 | uniforms, 110 | ); 111 | 112 | currentProgram = perFragmentProgram = GlProgram( 113 | ''' 114 | precision mediump float; 115 | 116 | varying vec2 vTextureCoord; 117 | varying vec3 vTransformedNormal; 118 | varying vec4 vPosition; 119 | 120 | uniform bool uUseLighting; 121 | uniform bool uUseTextures; 122 | 123 | uniform vec3 uAmbientColor; 124 | 125 | uniform vec3 uPointLightingLocation; 126 | uniform vec3 uPointLightingColor; 127 | 128 | uniform sampler2D uSampler; 129 | 130 | 131 | void main(void) { 132 | vec3 lightWeighting; 133 | if (!uUseLighting) { 134 | lightWeighting = vec3(1.0, 1.0, 1.0); 135 | } else { 136 | vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz); 137 | 138 | float directionalLightWeighting = max(dot(normalize(vTransformedNormal), lightDirection), 0.0); 139 | lightWeighting = uAmbientColor + uPointLightingColor * directionalLightWeighting; 140 | } 141 | 142 | vec4 fragmentColor; 143 | if (uUseTextures) { 144 | fragmentColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 145 | } else { 146 | fragmentColor = vec4(1.0, 1.0, 1.0, 1.0); 147 | } 148 | gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a); 149 | } 150 | ''', 151 | ''' 152 | attribute vec3 aVertexPosition; 153 | attribute vec3 aVertexNormal; 154 | attribute vec2 aTextureCoord; 155 | 156 | uniform mat4 uMVMatrix; 157 | uniform mat4 uPMatrix; 158 | uniform mat3 uNMatrix; 159 | 160 | varying vec2 vTextureCoord; 161 | varying vec3 vTransformedNormal; 162 | varying vec4 vPosition; 163 | 164 | 165 | void main(void) { 166 | vPosition = uMVMatrix * vec4(aVertexPosition, 1.0); 167 | gl_Position = uPMatrix * vPosition; 168 | vTextureCoord = aTextureCoord; 169 | vTransformedNormal = uNMatrix * aVertexNormal; 170 | } 171 | ''', 172 | attributes, 173 | uniforms, 174 | ); 175 | 176 | // Handle textures 177 | loadTexture('moon.bmp', handleMipMapTexture).then((t) => moonTexture = t); 178 | loadTexture('crate.gif', handleMipMapTexture).then((t) => cubeTexture = t); 179 | 180 | gl.enable(WebGL.DEPTH_TEST); 181 | } 182 | 183 | int? get aVertexPosition => currentProgram.attributes['aVertexPosition']; 184 | int? get aVertexNormal => currentProgram.attributes['aVertexNormal']; 185 | int? get aTextureCoord => currentProgram.attributes['aTextureCoord']; 186 | 187 | UniformLocation? get uPMatrix => currentProgram.uniforms['uPMatrix']; 188 | UniformLocation? get uMVMatrix => currentProgram.uniforms['uMVMatrix']; 189 | UniformLocation? get uNMatrix => currentProgram.uniforms['uNMatrix']; 190 | UniformLocation? get uSampler => currentProgram.uniforms['uSampler']; 191 | UniformLocation? get uUseTextures => currentProgram.uniforms['uUseTextures']; 192 | UniformLocation? get uUseLighting => currentProgram.uniforms['uUseLighting']; 193 | UniformLocation? get uAmbientColor => currentProgram.uniforms['uAmbientColor']; 194 | UniformLocation? get uPointLightingLocation => currentProgram.uniforms['uPointLightingLocation']; 195 | UniformLocation? get uPointLightingColor => currentProgram.uniforms['uPointLightingColor']; 196 | 197 | @override 198 | void drawScene(int viewWidth, int viewHeight, double aspect) { 199 | if (!isLoaded) return; 200 | 201 | gl.enable(WebGL.DEPTH_TEST); 202 | gl.disable(WebGL.BLEND); 203 | 204 | gl.viewport(0, 0, viewWidth, viewHeight); 205 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 206 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 207 | 208 | final perFragmentLighting = _perFragment.checked!; 209 | if (perFragmentLighting) { 210 | currentProgram = perFragmentProgram; 211 | } else { 212 | currentProgram = perVertexProgram; 213 | } 214 | gl.useProgram(currentProgram.program); 215 | 216 | // One: setup lighting information 217 | final lighting = _lighting.checked!; 218 | gl.uniform1i(uUseLighting, lighting ? 1 : 0); 219 | if (lighting) { 220 | gl.uniform3f(uAmbientColor, double.parse(_aR.value!), double.parse(_aG.value!), double.parse(_aB.value!)); 221 | 222 | gl.uniform3f( 223 | uPointLightingLocation, double.parse(_lpX.value!), double.parse(_lpY.value!), double.parse(_lpZ.value!)); 224 | 225 | gl.uniform3f(uPointLightingColor, double.parse(_pR.value!), double.parse(_pG.value!), double.parse(_pB.value!)); 226 | } 227 | gl.uniform1i(uUseTextures, _textures.checked! ? 1 : 0); 228 | 229 | mvPushMatrix(); 230 | 231 | // Setup the scene -5.0 away and pitch up by 30 degrees 232 | mvMatrix 233 | ..translate([0.0, 0.0, -5.0]) 234 | ..rotateX(radians(tilt)); 235 | 236 | mvPushMatrix(); 237 | // Rotate and move away from the scene 238 | mvMatrix 239 | ..rotateY(radians(moonAngle)) 240 | ..translate([2.0, 0.0, 0.0]); 241 | gl.activeTexture(WebGL.TEXTURE0); 242 | gl.bindTexture(WebGL.TEXTURE_2D, moonTexture); 243 | gl.uniform1i(uSampler, 0); 244 | moon.draw(vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms); 245 | mvPopMatrix(); 246 | 247 | mvMatrix 248 | ..rotateY(radians(cubeAngle)) 249 | ..translate([1.25, 0.0, 0.0]); 250 | gl.activeTexture(WebGL.TEXTURE0); 251 | gl.bindTexture(WebGL.TEXTURE_2D, cubeTexture); 252 | gl.uniform1i(uSampler, 0); 253 | cube.draw(vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms); 254 | mvPopMatrix(); 255 | } 256 | 257 | double moonAngle = 180.0; 258 | double cubeAngle = 0.0; 259 | double tilt = 30.0; 260 | 261 | void setMatrixUniforms() { 262 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 263 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 264 | final normalMatrix = mvMatrix.toInverseMat3(); 265 | normalMatrix!.transposeSelf(); 266 | gl.uniformMatrix3fv(uNMatrix, false, normalMatrix.buf); 267 | } 268 | 269 | @override 270 | void animate(double now) { 271 | if (lastTime != 0) { 272 | final elapsed = now - lastTime; 273 | moonAngle += 0.05 * elapsed; 274 | cubeAngle += 0.05 * elapsed; 275 | } 276 | lastTime = now; 277 | } 278 | 279 | @override 280 | void handleKeys() { 281 | handleDirection( 282 | up: () => tilt -= 1.0, 283 | down: () => tilt += 1.0, 284 | left: () { 285 | moonAngle -= 1.0; 286 | cubeAngle -= 1.0; 287 | }, 288 | right: () { 289 | moonAngle += 1.0; 290 | cubeAngle += 1.0; 291 | }); 292 | } 293 | 294 | late InputElement _perFragment; 295 | late InputElement _textures; 296 | 297 | // Lighting enabled / Ambient color 298 | late InputElement _lighting, _aR, _aG, _aB; 299 | 300 | // Light position 301 | late InputElement _lpX, _lpY, _lpZ; 302 | 303 | // Point color 304 | late InputElement _pR, _pG, _pB; 305 | 306 | @override 307 | void initHtml(DivElement hook) { 308 | hook.setInnerHtml( 309 | ''' 310 | Use lighting
311 | Per-fragment lighting
312 | Use textures
313 |
314 | 315 |

Point light:

316 | 317 | 318 | 319 | 324 | 325 | 330 |
Location: 320 | X: 321 | Y: 322 | Z: 323 |
Colour: 326 | R: 327 | G: 328 | B: 329 |
331 | 332 |

Ambient light:

333 | 334 | 335 | 336 | 341 |
Colour: 337 | R: 338 | G: 339 | B: 340 |
342 | 343 | 344 |
345 | 346 | Moon texture courtesy of the Jet Propulsion Laboratory. 347 | ''', 348 | treeSanitizer: NullTreeSanitizer(), 349 | ); 350 | 351 | // Re-look up our dom elements 352 | _lighting = querySelector('#lighting') as InputElement; 353 | _aR = querySelector('#ambientR') as InputElement; 354 | _aG = querySelector('#ambientG') as InputElement; 355 | _aB = querySelector('#ambientB') as InputElement; 356 | 357 | _pR = querySelector('#pointR') as InputElement; 358 | _pG = querySelector('#pointG') as InputElement; 359 | _pB = querySelector('#pointB') as InputElement; 360 | 361 | _lpX = querySelector('#lightPositionX') as InputElement; 362 | _lpY = querySelector('#lightPositionY') as InputElement; 363 | _lpZ = querySelector('#lightPositionZ') as InputElement; 364 | 365 | _perFragment = querySelector('#per-fragment') as InputElement; 366 | _textures = querySelector('#textures') as InputElement; 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /web/lesson14.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | class Lesson14 extends Lesson { 18 | late GlProgram currentProgram; 19 | Texture? earthTexture, galvanizedTexture, moonTexture; 20 | late JsonObject? teapot; 21 | 22 | int texturesLoaded = 0; 23 | bool get isLoaded => teapot != null && texturesLoaded == 3; 24 | 25 | double teapotAngle = 180; 26 | double tilt = 23.4; 27 | 28 | Lesson14() { 29 | JsonObject.fromUrl('Teapot.json').then((JsonObject obj) { 30 | print('Teapot: $obj'); 31 | teapot = obj; 32 | }); 33 | 34 | final attributes = ['aVertexPosition', 'aVertexNormal', 'aTextureCoord']; 35 | final uniforms = [ 36 | 'uPMatrix', 37 | 'uMVMatrix', 38 | 'uNMatrix', 39 | 'uSampler', 40 | 'uUseTextures', 41 | 'uUseLighting', 42 | 'uAmbientColor', 43 | 'uPointLightingLocation', 44 | 'uPointLightingSpecularColor', 45 | 'uPointLightingDiffuseColor', 46 | 'uMaterialShininess', 47 | 'uShowSpecularHighlights' 48 | ]; 49 | 50 | currentProgram = GlProgram( 51 | ''' 52 | precision mediump float; 53 | 54 | varying vec2 vTextureCoord; 55 | varying vec3 vTransformedNormal; 56 | varying vec4 vPosition; 57 | 58 | uniform float uMaterialShininess; 59 | 60 | uniform bool uShowSpecularHighlights; 61 | uniform bool uUseLighting; 62 | uniform bool uUseTextures; 63 | 64 | uniform vec3 uAmbientColor; 65 | 66 | uniform vec3 uPointLightingLocation; 67 | uniform vec3 uPointLightingSpecularColor; 68 | uniform vec3 uPointLightingDiffuseColor; 69 | 70 | uniform sampler2D uSampler; 71 | 72 | 73 | void main(void) { 74 | vec3 lightWeighting; 75 | if (!uUseLighting) { 76 | lightWeighting = vec3(1.0, 1.0, 1.0); 77 | } else { 78 | vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz); 79 | vec3 normal = normalize(vTransformedNormal); 80 | 81 | float specularLightWeighting = 0.0; 82 | if (uShowSpecularHighlights) { 83 | vec3 eyeDirection = normalize(-vPosition.xyz); 84 | vec3 reflectionDirection = reflect(-lightDirection, normal); 85 | 86 | specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uMaterialShininess); 87 | } 88 | 89 | float diffuseLightWeighting = max(dot(normal, lightDirection), 0.0); 90 | lightWeighting = uAmbientColor 91 | + uPointLightingSpecularColor * specularLightWeighting 92 | + uPointLightingDiffuseColor * diffuseLightWeighting; 93 | } 94 | 95 | vec4 fragmentColor; 96 | if (uUseTextures) { 97 | fragmentColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 98 | } else { 99 | fragmentColor = vec4(1.0, 1.0, 1.0, 1.0); 100 | } 101 | gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a); 102 | } 103 | ''', 104 | ''' 105 | attribute vec3 aVertexPosition; 106 | attribute vec3 aVertexNormal; 107 | attribute vec2 aTextureCoord; 108 | 109 | uniform mat4 uMVMatrix; 110 | uniform mat4 uPMatrix; 111 | uniform mat3 uNMatrix; 112 | 113 | varying vec2 vTextureCoord; 114 | varying vec3 vTransformedNormal; 115 | varying vec4 vPosition; 116 | 117 | 118 | void main(void) { 119 | vPosition = uMVMatrix * vec4(aVertexPosition, 1.0); 120 | gl_Position = uPMatrix * vPosition; 121 | vTextureCoord = aTextureCoord; 122 | vTransformedNormal = uNMatrix * aVertexNormal; 123 | } 124 | ''', 125 | attributes, 126 | uniforms, 127 | ); 128 | 129 | gl.useProgram(currentProgram.program); 130 | 131 | // Handle textures 132 | loadTexture('earth.jpg', handleMipMapTexture).then((t) { 133 | earthTexture = t; 134 | texturesLoaded++; 135 | }); 136 | loadTexture('moon.bmp', handleMipMapTexture).then((t) { 137 | moonTexture = t; 138 | texturesLoaded++; 139 | }); 140 | loadTexture('galvanizedTexture.jpg', handleMipMapTexture).then((t) { 141 | galvanizedTexture = t; 142 | texturesLoaded++; 143 | }); 144 | 145 | gl.enable(WebGL.DEPTH_TEST); 146 | } 147 | 148 | void handleTexture(Texture texture, ImageElement image) { 149 | gl.pixelStorei(WebGL.UNPACK_FLIP_Y_WEBGL, 1); 150 | gl.bindTexture(WebGL.TEXTURE_2D, texture); 151 | gl.texImage2D( 152 | WebGL.TEXTURE_2D, 153 | 0, 154 | WebGL.RGBA, 155 | WebGL.RGBA, 156 | WebGL.UNSIGNED_BYTE, 157 | image, 158 | ); 159 | gl.texParameteri( 160 | WebGL.TEXTURE_2D, 161 | WebGL.TEXTURE_MAG_FILTER, 162 | WebGL.LINEAR, 163 | ); 164 | gl.texParameteri( 165 | WebGL.TEXTURE_2D, 166 | WebGL.TEXTURE_MIN_FILTER, 167 | WebGL.LINEAR_MIPMAP_NEAREST, 168 | ); 169 | gl.generateMipmap(WebGL.TEXTURE_2D); 170 | gl.bindTexture(WebGL.TEXTURE_2D, null); 171 | texturesLoaded++; 172 | print('loaded ${image.src}'); 173 | } 174 | 175 | int? get aVertexPosition => currentProgram.attributes['aVertexPosition']; 176 | int? get aVertexNormal => currentProgram.attributes['aVertexNormal']; 177 | int? get aTextureCoord => currentProgram.attributes['aTextureCoord']; 178 | 179 | UniformLocation? get uShowSpecularHighlights => currentProgram.uniforms['uShowSpecularHighlights']; 180 | UniformLocation? get uMaterialShininess => currentProgram.uniforms['uMaterialShininess']; 181 | UniformLocation? get uPMatrix => currentProgram.uniforms['uPMatrix']; 182 | UniformLocation? get uMVMatrix => currentProgram.uniforms['uMVMatrix']; 183 | UniformLocation? get uNMatrix => currentProgram.uniforms['uNMatrix']; 184 | UniformLocation? get uSampler => currentProgram.uniforms['uSampler']; 185 | UniformLocation? get uUseTextures => currentProgram.uniforms['uUseTextures']; 186 | UniformLocation? get uUseLighting => currentProgram.uniforms['uUseLighting']; 187 | UniformLocation? get uAmbientColor => currentProgram.uniforms['uAmbientColor']; 188 | UniformLocation? get uPointLightingLocation => currentProgram.uniforms['uPointLightingLocation']; 189 | UniformLocation? get uPointLightingSpecularColor => currentProgram.uniforms['uPointLightingSpecularColor']; 190 | UniformLocation? get uPointLightingDiffuseColor => currentProgram.uniforms['uPointLightingDiffuseColor']; 191 | 192 | @override 193 | void drawScene(int viewWidth, int viewHeight, double aspect) { 194 | if (!isLoaded) return; 195 | // Setup the viewport, pulling information from the element. 196 | gl.viewport(0, 0, viewWidth, viewHeight); 197 | 198 | // Clear! 199 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 200 | 201 | gl.enable(WebGL.DEPTH_TEST); 202 | gl.disable(WebGL.BLEND); 203 | 204 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 205 | 206 | final specularHighlights = _specular.checked!; 207 | gl.uniform1i(uShowSpecularHighlights, specularHighlights ? 1 : 0); 208 | final lighting = _lighting.checked!; 209 | gl.uniform1i(uUseLighting, lighting ? 1 : 0); 210 | if (lighting) { 211 | gl.uniform3f(uAmbientColor, double.parse(_aR.value!), double.parse(_aG.value!), double.parse(_aB.value!)); 212 | 213 | gl.uniform3f( 214 | uPointLightingLocation, double.parse(_lpX.value!), double.parse(_lpY.value!), double.parse(_lpZ.value!)); 215 | 216 | gl.uniform3f( 217 | uPointLightingSpecularColor, double.parse(_sR.value!), double.parse(_sG.value!), double.parse(_sB.value!)); 218 | 219 | gl.uniform3f( 220 | uPointLightingDiffuseColor, double.parse(_dR.value!), double.parse(_dG.value!), double.parse(_dB.value!)); 221 | } 222 | 223 | final texture = _texture.value; 224 | gl.uniform1i(uUseTextures, texture != 'none' ? 1 : 0); 225 | 226 | mvPushMatrix(); 227 | 228 | mvMatrix 229 | ..translate([0.0, 0.0, -40.0]) 230 | ..rotate(radians(tilt), [1, 0, -1]) 231 | ..rotateY(radians(teapotAngle)); 232 | 233 | gl.activeTexture(WebGL.TEXTURE0); 234 | if (texture == 'earth') { 235 | gl.bindTexture(WebGL.TEXTURE_2D, earthTexture); 236 | } else if (texture == 'galvanized') { 237 | gl.bindTexture(WebGL.TEXTURE_2D, galvanizedTexture); 238 | } else if (texture == 'moon') { 239 | gl.bindTexture(WebGL.TEXTURE_2D, moonTexture); 240 | } 241 | gl.uniform1i(uSampler, 0); 242 | 243 | gl.uniform1f(uMaterialShininess, double.parse(_shininess.value!)); 244 | 245 | teapot?.draw(vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms); 246 | mvPopMatrix(); 247 | } 248 | 249 | void setMatrixUniforms() { 250 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 251 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 252 | final normalMatrix = mvMatrix.toInverseMat3(); 253 | normalMatrix!.transposeSelf(); 254 | gl.uniformMatrix3fv(uNMatrix, false, normalMatrix.buf); 255 | } 256 | 257 | @override 258 | void animate(double now) { 259 | if (lastTime != 0) { 260 | final elapsed = now - lastTime; 261 | teapotAngle += 0.05 * elapsed; 262 | } 263 | lastTime = now; 264 | } 265 | 266 | @override 267 | void handleKeys() { 268 | handleDirection( 269 | up: () => tilt -= 1.0, 270 | down: () => tilt += 1.0, 271 | left: () => teapotAngle -= 1.0, 272 | right: () => teapotAngle += 1.0); 273 | } 274 | 275 | // Lighting enabled / Ambient color 276 | late InputElement _lighting, _aR, _aG, _aB; 277 | 278 | // Light position 279 | late InputElement _lpX, _lpY, _lpZ; 280 | 281 | // Difuse color 282 | late InputElement _dR, _dG, _dB; 283 | 284 | // Specular color 285 | late InputElement _specular; 286 | late InputElement _sR, _sG, _sB; 287 | 288 | late InputElement _shininess; 289 | late SelectElement _texture; 290 | 291 | @override 292 | void initHtml(DivElement hook) { 293 | hook.setInnerHtml( 294 | ''' 295 | Show specular highlight
296 | Use lighting
297 | 298 | Texture: 299 | 305 | 306 |

Material:

307 | 308 | 309 | 310 | 313 |
Shininess: 311 | 312 |
314 | 315 |

Point light:

316 | 317 | 318 | 319 | 324 | 325 | 330 | 331 | 336 |
Location: 320 | X: 321 | Y: 322 | Z: 323 |
Specular colour: 326 | R: 327 | G: 328 | B: 329 |
Diffuse colour: 332 | R: 333 | G: 334 | B: 335 |
337 | 338 |

Ambient light:

339 | 340 | 341 | 342 | 347 |
Colour: 343 | R: 344 | G: 345 | B: 346 |
348 | 349 | Earth texture courtesy of the European Space Agency/Envisat.
350 | Galvanized texture courtesy of Arroway Textures.
351 | Moon texture courtesy of the Jet Propulsion Laboratory. 352 | ''', 353 | treeSanitizer: NullTreeSanitizer(), 354 | ); 355 | 356 | // Re-look up our dom elements 357 | _lighting = querySelector('#lighting') as InputElement; 358 | _aR = querySelector('#ambientR') as InputElement; 359 | _aG = querySelector('#ambientG') as InputElement; 360 | _aB = querySelector('#ambientB') as InputElement; 361 | 362 | _lpX = querySelector('#lightPositionX') as InputElement; 363 | _lpY = querySelector('#lightPositionY') as InputElement; 364 | _lpZ = querySelector('#lightPositionZ') as InputElement; 365 | 366 | _dR = querySelector('#diffuseR') as InputElement; 367 | _dG = querySelector('#diffuseG') as InputElement; 368 | _dB = querySelector('#diffuseB') as InputElement; 369 | 370 | _specular = querySelector('#specular') as InputElement; 371 | _sR = querySelector('#specularR') as InputElement; 372 | _sG = querySelector('#specularG') as InputElement; 373 | _sB = querySelector('#specularB') as InputElement; 374 | 375 | _shininess = querySelector('#shininess') as InputElement; 376 | _texture = querySelector('#texture') as SelectElement; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /web/lesson15.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | class Lesson15 extends Lesson { 18 | late GlProgram currentProgram; 19 | Sphere? sphere; 20 | Texture? earthTexture, moonTexture, earthSpecularMapTexture; 21 | 22 | int textureCount = 0; 23 | bool get isLoaded => sphere != null && textureCount == 3; 24 | 25 | double sphereAngle = 180.0; 26 | double tilt = 23.4; 27 | 28 | Lesson15() { 29 | sphere = Sphere(lats: 30, lons: 30, radius: 13); 30 | 31 | final attributes = ['aVertexPosition', 'aVertexNormal', 'aTextureCoord']; 32 | final uniforms = [ 33 | 'uPMatrix', 34 | 'uMVMatrix', 35 | 'uNMatrix', 36 | 'uAmbientColor', 37 | 'uPointLightingLocation', 38 | 'uPointLightingSpecularColor', 39 | 'uPointLightingDiffuseColor', 40 | 'uUseColorMap', 41 | 'uUseSpecularMap', 42 | 'uUseLighting', 43 | 'uColorMapSampler', 44 | 'uSpecularMapSampler' 45 | ]; 46 | 47 | currentProgram = GlProgram( 48 | ''' 49 | precision mediump float; 50 | 51 | varying vec2 vTextureCoord; 52 | varying vec3 vTransformedNormal; 53 | varying vec4 vPosition; 54 | 55 | uniform bool uUseColorMap; 56 | uniform bool uUseSpecularMap; 57 | uniform bool uUseLighting; 58 | 59 | uniform vec3 uAmbientColor; 60 | 61 | uniform vec3 uPointLightingLocation; 62 | uniform vec3 uPointLightingSpecularColor; 63 | uniform vec3 uPointLightingDiffuseColor; 64 | 65 | uniform sampler2D uColorMapSampler; 66 | uniform sampler2D uSpecularMapSampler; 67 | 68 | 69 | void main(void) { 70 | vec3 lightWeighting; 71 | if (!uUseLighting) { 72 | lightWeighting = vec3(1.0, 1.0, 1.0); 73 | } else { 74 | vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz); 75 | vec3 normal = normalize(vTransformedNormal); 76 | 77 | float specularLightWeighting = 0.0; 78 | float shininess = 32.0; 79 | if (uUseSpecularMap) { 80 | shininess = texture2D(uSpecularMapSampler, vec2(vTextureCoord.s, vTextureCoord.t)).r * 255.0; 81 | } 82 | if (shininess < 255.0) { 83 | vec3 eyeDirection = normalize(-vPosition.xyz); 84 | vec3 reflectionDirection = reflect(-lightDirection, normal); 85 | 86 | specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), shininess); 87 | } 88 | 89 | float diffuseLightWeighting = max(dot(normal, lightDirection), 0.0); 90 | lightWeighting = uAmbientColor 91 | + uPointLightingSpecularColor * specularLightWeighting 92 | + uPointLightingDiffuseColor * diffuseLightWeighting; 93 | } 94 | 95 | vec4 fragmentColor; 96 | if (uUseColorMap) { 97 | fragmentColor = texture2D(uColorMapSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 98 | } else { 99 | fragmentColor = vec4(1.0, 1.0, 1.0, 1.0); 100 | } 101 | gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a); 102 | } 103 | ''', 104 | ''' 105 | attribute vec3 aVertexPosition; 106 | attribute vec3 aVertexNormal; 107 | attribute vec2 aTextureCoord; 108 | 109 | uniform mat4 uMVMatrix; 110 | uniform mat4 uPMatrix; 111 | uniform mat3 uNMatrix; 112 | 113 | varying vec2 vTextureCoord; 114 | varying vec3 vTransformedNormal; 115 | varying vec4 vPosition; 116 | 117 | 118 | void main(void) { 119 | vPosition = uMVMatrix * vec4(aVertexPosition, 1.0); 120 | gl_Position = uPMatrix * vPosition; 121 | vTextureCoord = aTextureCoord; 122 | vTransformedNormal = uNMatrix * aVertexNormal; 123 | } 124 | ''', 125 | attributes, 126 | uniforms, 127 | ); 128 | 129 | gl.useProgram(currentProgram.program); 130 | 131 | // Handle textures 132 | loadTexture('earth.jpg', handleMipMapTexture).then((t) { 133 | earthTexture = t; 134 | textureCount++; 135 | }); 136 | loadTexture('moon.bmp', handleMipMapTexture).then((t) { 137 | moonTexture = t; 138 | textureCount++; 139 | }); 140 | loadTexture('earth-specular.gif', handleMipMapTexture).then((t) { 141 | earthSpecularMapTexture = t; 142 | textureCount++; 143 | }); 144 | 145 | gl.enable(WebGL.DEPTH_TEST); 146 | } 147 | 148 | int? get aVertexPosition => currentProgram.attributes['aVertexPosition']; 149 | int? get aVertexNormal => currentProgram.attributes['aVertexNormal']; 150 | int? get aTextureCoord => currentProgram.attributes['aTextureCoord']; 151 | 152 | UniformLocation? get uPMatrix => currentProgram.uniforms['uPMatrix']; 153 | UniformLocation? get uMVMatrix => currentProgram.uniforms['uMVMatrix']; 154 | UniformLocation? get uNMatrix => currentProgram.uniforms['uNMatrix']; 155 | UniformLocation? get uColorMapSampler => currentProgram.uniforms['uColorMapSampler']; 156 | UniformLocation? get uSpecularMapSampler => currentProgram.uniforms['uSpecularMapSampler']; 157 | UniformLocation? get uUseColorMap => currentProgram.uniforms['uUseColorMap']; 158 | UniformLocation? get uUseSpecularMap => currentProgram.uniforms['uUseSpecularMap']; 159 | UniformLocation? get uUseLighting => currentProgram.uniforms['uUseLighting']; 160 | UniformLocation? get uAmbientColor => currentProgram.uniforms['uAmbientColor']; 161 | UniformLocation? get uPointLightingLocation => currentProgram.uniforms['uPointLightingLocation']; 162 | UniformLocation? get uPointLightingSpecularColor => currentProgram.uniforms['uPointLightingSpecularColor']; 163 | UniformLocation? get uPointLightingDiffuseColor => currentProgram.uniforms['uPointLightingDiffuseColor']; 164 | 165 | @override 166 | void drawScene(int viewWidth, int viewHeight, double aspect) { 167 | if (!isLoaded) return; 168 | // Setup the viewport, pulling information from the element. 169 | gl.viewport(0, 0, viewWidth, viewHeight); 170 | 171 | // Clear! 172 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 173 | 174 | gl.enable(WebGL.DEPTH_TEST); 175 | gl.disable(WebGL.BLEND); 176 | 177 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 178 | 179 | gl.uniform1i(uUseColorMap, _colorMap.checked! ? 1 : 0); 180 | gl.uniform1i(uUseSpecularMap, _specularMap.checked! ? 1 : 0); 181 | 182 | gl.uniform1i(uUseLighting, _lighting.checked! ? 1 : 0); 183 | if (_lighting.checked!) { 184 | gl.uniform3f(uAmbientColor, double.parse(_aR.value!), double.parse(_aG.value!), double.parse(_aB.value!)); 185 | 186 | gl.uniform3f( 187 | uPointLightingLocation, double.parse(_lpX.value!), double.parse(_lpY.value!), double.parse(_lpZ.value!)); 188 | 189 | gl.uniform3f( 190 | uPointLightingSpecularColor, double.parse(_sR.value!), double.parse(_sG.value!), double.parse(_sB.value!)); 191 | 192 | gl.uniform3f( 193 | uPointLightingDiffuseColor, double.parse(_dR.value!), double.parse(_dG.value!), double.parse(_dB.value!)); 194 | } 195 | 196 | mvPushMatrix(); 197 | 198 | mvMatrix 199 | ..translate([0.0, 0.0, -40.0]) 200 | ..rotate(radians(tilt), [1, 0, -1]) 201 | ..rotateY(radians(sphereAngle)); 202 | 203 | gl.activeTexture(WebGL.TEXTURE0); 204 | if (_texture.value == 'earth') { 205 | gl.bindTexture(WebGL.TEXTURE_2D, earthTexture); 206 | } else if (_texture.value == 'moon') { 207 | gl.bindTexture(WebGL.TEXTURE_2D, moonTexture); 208 | } 209 | gl.uniform1i(uColorMapSampler, 0); 210 | 211 | gl.activeTexture(WebGL.TEXTURE1); 212 | gl.bindTexture(WebGL.TEXTURE_2D, earthSpecularMapTexture); 213 | gl.uniform1i(uSpecularMapSampler, 1); 214 | 215 | sphere?.draw(vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms); 216 | mvPopMatrix(); 217 | } 218 | 219 | void setMatrixUniforms() { 220 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 221 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 222 | final normalMatrix = mvMatrix.toInverseMat3(); 223 | normalMatrix!.transposeSelf(); 224 | gl.uniformMatrix3fv(uNMatrix, false, normalMatrix.buf); 225 | } 226 | 227 | @override 228 | void animate(double now) { 229 | if (lastTime != 0) { 230 | final elapsed = now - lastTime; 231 | sphereAngle += 0.05 * elapsed; 232 | } 233 | lastTime = now; 234 | } 235 | 236 | @override 237 | void handleKeys() { 238 | handleDirection( 239 | up: () => tilt -= 1.0, 240 | down: () => tilt += 1.0, 241 | left: () => sphereAngle -= 1.0, 242 | right: () => sphereAngle += 1.0); 243 | } 244 | 245 | // Lighting enabled / Ambient color 246 | late InputElement _lighting, _aR, _aG, _aB; 247 | 248 | // Light position 249 | late InputElement _lpX, _lpY, _lpZ; 250 | 251 | // Difuse color 252 | late InputElement _dR, _dG, _dB; 253 | 254 | // Specular color 255 | late InputElement _sR, _sG, _sB; 256 | 257 | // Assorted options 258 | late InputElement _colorMap, _specularMap; 259 | late SelectElement _texture; 260 | 261 | @override 262 | void initHtml(DivElement hook) { 263 | hook.setInnerHtml( 264 | ''' 265 | Use color map
266 | Use specular map
267 | Use lighting
268 | 269 | Texture: 270 | 274 |

Point light:

275 | 276 | 277 | 278 | 283 | 284 | 289 | 290 | 295 |
Location: 279 | X: 280 | Y: 281 | Z: 282 |
Specular colour: 285 | R: 286 | G: 287 | B: 288 |
Diffuse colour: 291 | R: 292 | G: 293 | B: 294 |
296 | 297 |

Ambient light:

298 | 299 | 300 | 301 | 306 |
Colour: 302 | R: 303 | G: 304 | B: 305 |
307 | 308 | Earth texture courtesy of the European Space Agency/Envisat.
309 | Galvanized texture courtesy of Arroway Textures.
310 | Moon texture courtesy of the Jet Propulsion Laboratory. 311 | ''', 312 | treeSanitizer: NullTreeSanitizer(), 313 | ); 314 | 315 | // Re-look up our dom elements 316 | _lighting = querySelector('#lighting') as InputElement; 317 | _aR = querySelector('#ambientR') as InputElement; 318 | _aG = querySelector('#ambientG') as InputElement; 319 | _aB = querySelector('#ambientB') as InputElement; 320 | 321 | _lpX = querySelector('#lightPositionX') as InputElement; 322 | _lpY = querySelector('#lightPositionY') as InputElement; 323 | _lpZ = querySelector('#lightPositionZ') as InputElement; 324 | 325 | _dR = querySelector('#diffuseR') as InputElement; 326 | _dG = querySelector('#diffuseG') as InputElement; 327 | _dB = querySelector('#diffuseB') as InputElement; 328 | 329 | _sR = querySelector('#specularR') as InputElement; 330 | _sG = querySelector('#specularG') as InputElement; 331 | _sB = querySelector('#specularB') as InputElement; 332 | 333 | _colorMap = querySelector('#color-map') as InputElement; 334 | _specularMap = querySelector('#specular-map') as InputElement; 335 | _texture = querySelector('#texture') as SelectElement; 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /web/lesson16.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | class Lesson16 extends Lesson { 18 | bool get isLoaded => laptop != null; 19 | 20 | late GlProgram currentProgram; 21 | 22 | JsonObject? laptop; 23 | JsonObject? laptopScreen; 24 | 25 | double laptopAngle = 0.0; 26 | 27 | late Lesson13 lesson13; 28 | 29 | Lesson16() { 30 | lesson13 = Lesson13(); 31 | 32 | // There is no HTML, so we're going to override these options. 33 | lesson13._lighting = InputElement()..checked = true; 34 | lesson13._aR = InputElement()..value = '0.2'; 35 | lesson13._aG = InputElement()..value = '0.2'; 36 | lesson13._aB = InputElement()..value = '0.2'; 37 | lesson13._lpX = InputElement()..value = '0.0'; 38 | lesson13._lpY = InputElement()..value = '0.0'; 39 | lesson13._lpZ = InputElement()..value = '-5.0'; 40 | lesson13._pR = InputElement()..value = '0.8'; 41 | lesson13._pG = InputElement()..value = '0.8'; 42 | lesson13._pB = InputElement()..value = '0.8'; 43 | lesson13._perFragment = InputElement()..checked = true; 44 | lesson13._textures = InputElement()..checked = true; 45 | 46 | JsonObject.fromUrl('macbook.json').then((JsonObject obj) { 47 | print('macbook: $obj'); 48 | laptop = obj; 49 | }); 50 | 51 | final laptopScreenJson = ''' 52 | { 53 | "vertexPositions": [ 54 | 0.580687, 0.659, 0.813106, 55 | -0.580687, 0.659, 0.813107, 56 | 0.580687, 0.472, 0.113121, 57 | -0.580687, 0.472, 0.113121 58 | ], 59 | "vertexTextureCoords": [ 60 | 1.0, 1.0, 61 | 0.0, 1.0, 62 | 1.0, 0.0, 63 | 0.0, 0.0 64 | ], 65 | "vertexNormals": [ 66 | 0.000000, -0.965926, 0.258819, 67 | 0.000000, -0.965926, 0.258819, 68 | 0.000000, -0.965926, 0.258819, 69 | 0.000000, -0.965926, 0.258819 70 | ] 71 | } 72 | '''; 73 | laptopScreen = JsonObject(laptopScreenJson)..strip = true; 74 | 75 | final attributes = ['aVertexPosition', 'aVertexNormal', 'aTextureCoord']; 76 | final uniforms = [ 77 | 'uPMatrix', 78 | 'uMVMatrix', 79 | 'uNMatrix', 80 | 'uUseTextures', 81 | 'uMaterialAmbientColor', 82 | 'uMaterialDiffuseColor', 83 | 'uMaterialSpecularColor', 84 | 'uMaterialShininess', 85 | 'uMaterialEmissiveColor', 86 | 'uPointLightingDiffuseColor', 87 | 'uPointLightingLocation', 88 | 'uPointLightingSpecularColor', 89 | 'uShowSpecularHighlights', 90 | 'uSampler', 91 | 'uAmbientLightingColor' 92 | ]; 93 | 94 | currentProgram = GlProgram( 95 | ''' 96 | precision mediump float; 97 | 98 | varying vec2 vTextureCoord; 99 | varying vec3 vTransformedNormal; 100 | varying vec4 vPosition; 101 | 102 | uniform vec3 uMaterialAmbientColor; 103 | uniform vec3 uMaterialDiffuseColor; 104 | uniform vec3 uMaterialSpecularColor; 105 | uniform float uMaterialShininess; 106 | uniform vec3 uMaterialEmissiveColor; 107 | 108 | uniform bool uShowSpecularHighlights; 109 | uniform bool uUseTextures; 110 | 111 | uniform vec3 uAmbientLightingColor; 112 | 113 | uniform vec3 uPointLightingLocation; 114 | uniform vec3 uPointLightingDiffuseColor; 115 | uniform vec3 uPointLightingSpecularColor; 116 | 117 | uniform sampler2D uSampler; 118 | 119 | 120 | void main(void) { 121 | vec3 ambientLightWeighting = uAmbientLightingColor; 122 | 123 | vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz); 124 | vec3 normal = normalize(vTransformedNormal); 125 | 126 | vec3 specularLightWeighting = vec3(0.0, 0.0, 0.0); 127 | if (uShowSpecularHighlights) { 128 | vec3 eyeDirection = normalize(-vPosition.xyz); 129 | vec3 reflectionDirection = reflect(-lightDirection, normal); 130 | 131 | float specularLightBrightness = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uMaterialShininess); 132 | specularLightWeighting = uPointLightingSpecularColor * specularLightBrightness; 133 | } 134 | 135 | float diffuseLightBrightness = max(dot(normal, lightDirection), 0.0); 136 | vec3 diffuseLightWeighting = uPointLightingDiffuseColor * diffuseLightBrightness; 137 | 138 | vec3 materialAmbientColor = uMaterialAmbientColor; 139 | vec3 materialDiffuseColor = uMaterialDiffuseColor; 140 | vec3 materialSpecularColor = uMaterialSpecularColor; 141 | vec3 materialEmissiveColor = uMaterialEmissiveColor; 142 | float alpha = 1.0; 143 | if (uUseTextures) { 144 | vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 145 | materialAmbientColor = materialAmbientColor * textureColor.rgb; 146 | materialDiffuseColor = materialDiffuseColor * textureColor.rgb; 147 | materialEmissiveColor = materialEmissiveColor * textureColor.rgb; 148 | alpha = textureColor.a; 149 | } 150 | gl_FragColor = vec4( 151 | materialAmbientColor * ambientLightWeighting 152 | + materialDiffuseColor * diffuseLightWeighting 153 | + materialSpecularColor * specularLightWeighting 154 | + materialEmissiveColor, 155 | alpha 156 | ); 157 | } 158 | ''', 159 | ''' 160 | attribute vec3 aVertexPosition; 161 | attribute vec3 aVertexNormal; 162 | attribute vec2 aTextureCoord; 163 | 164 | uniform mat4 uMVMatrix; 165 | uniform mat4 uPMatrix; 166 | uniform mat3 uNMatrix; 167 | 168 | varying vec2 vTextureCoord; 169 | varying vec3 vTransformedNormal; 170 | varying vec4 vPosition; 171 | 172 | void main(void) { 173 | vPosition = uMVMatrix * vec4(aVertexPosition, 1.0); 174 | gl_Position = uPMatrix * vPosition; 175 | vTextureCoord = aTextureCoord; 176 | vTransformedNormal = uNMatrix * aVertexNormal; 177 | } 178 | ''', 179 | attributes, 180 | uniforms, 181 | ); 182 | 183 | gl.useProgram(currentProgram.program); 184 | gl.enable(WebGL.DEPTH_TEST); 185 | 186 | rttFramebuffer = gl.createFramebuffer(); 187 | gl.bindFramebuffer(WebGL.FRAMEBUFFER, rttFramebuffer); 188 | 189 | rttTexture = gl.createTexture(); 190 | gl.bindTexture(WebGL.TEXTURE_2D, rttTexture); 191 | gl.texParameteri( 192 | WebGL.TEXTURE_2D, 193 | WebGL.TEXTURE_MAG_FILTER, 194 | WebGL.LINEAR, 195 | ); 196 | gl.texParameteri( 197 | WebGL.TEXTURE_2D, 198 | WebGL.TEXTURE_MIN_FILTER, 199 | WebGL.LINEAR_MIPMAP_NEAREST, 200 | ); 201 | gl.generateMipmap(WebGL.TEXTURE_2D); 202 | 203 | gl.texImage2D( 204 | WebGL.TEXTURE_2D, 205 | 0, 206 | WebGL.RGBA, 207 | rttWidth, 208 | rttHeight, 209 | 0, 210 | WebGL.RGBA, 211 | WebGL.UNSIGNED_BYTE, 212 | null, 213 | ); 214 | 215 | renderbuffer = gl.createRenderbuffer(); 216 | gl.bindRenderbuffer(WebGL.RENDERBUFFER, renderbuffer); 217 | gl.renderbufferStorage(WebGL.RENDERBUFFER, WebGL.DEPTH_COMPONENT16, rttWidth, rttHeight); 218 | 219 | gl.framebufferTexture2D( 220 | WebGL.FRAMEBUFFER, 221 | WebGL.COLOR_ATTACHMENT0, 222 | WebGL.TEXTURE_2D, 223 | rttTexture, 224 | 0, 225 | ); 226 | gl.framebufferRenderbuffer( 227 | WebGL.FRAMEBUFFER, 228 | WebGL.DEPTH_ATTACHMENT, 229 | WebGL.RENDERBUFFER, 230 | renderbuffer, 231 | ); 232 | 233 | gl.bindTexture(WebGL.TEXTURE_2D, null); 234 | gl.bindRenderbuffer(WebGL.RENDERBUFFER, null); 235 | gl.bindFramebuffer(WebGL.FRAMEBUFFER, null); 236 | } 237 | 238 | late Framebuffer rttFramebuffer; 239 | late Texture rttTexture; 240 | late Renderbuffer renderbuffer; 241 | static const rttWidth = 512; 242 | static const rttHeight = 512; 243 | 244 | int? get aVertexPosition => currentProgram.attributes['aVertexPosition']; 245 | int? get aVertexNormal => currentProgram.attributes['aVertexNormal']; 246 | int? get aTextureCoord => currentProgram.attributes['aTextureCoord']; 247 | 248 | UniformLocation? get uShowSpecularHighlights => currentProgram.uniforms['uShowSpecularHighlights']; 249 | UniformLocation? get uMaterialShininess => currentProgram.uniforms['uMaterialShininess']; 250 | 251 | UniformLocation? get uPMatrix => currentProgram.uniforms['uPMatrix']; 252 | UniformLocation? get uMVMatrix => currentProgram.uniforms['uMVMatrix']; 253 | UniformLocation? get uNMatrix => currentProgram.uniforms['uNMatrix']; 254 | UniformLocation? get uSampler => currentProgram.uniforms['uSampler']; 255 | UniformLocation? get uUseTextures => currentProgram.uniforms['uUseTextures']; 256 | UniformLocation? get uUseLighting => currentProgram.uniforms['uUseLighting']; 257 | UniformLocation? get uAmbientColor => currentProgram.uniforms['uAmbientColor']; 258 | UniformLocation? get uPointLightingLocation => currentProgram.uniforms['uPointLightingLocation']; 259 | UniformLocation? get uPointLightingSpecularColor => currentProgram.uniforms['uPointLightingSpecularColor']; 260 | UniformLocation? get uPointLightingDiffuseColor => currentProgram.uniforms['uPointLightingDiffuseColor']; 261 | 262 | UniformLocation? get uAmbientLightingColor => currentProgram.uniforms['uAmbientLightingColor']; 263 | UniformLocation? get uMaterialAmbientColor => currentProgram.uniforms['uMaterialAmbientColor']; 264 | UniformLocation? get uMaterialDiffuseColor => currentProgram.uniforms['uMaterialDiffuseColor']; 265 | UniformLocation? get uMaterialSpecularColor => currentProgram.uniforms['uMaterialSpecularColor']; 266 | UniformLocation? get uMaterialEmissiveColor => currentProgram.uniforms['uMaterialEmissiveColor']; 267 | 268 | @override 269 | void drawScene(int viewWidth, int viewHeight, double aspect) { 270 | if (!isLoaded) return; 271 | 272 | // First: render lesson 13 to the render buffer! 273 | gl.bindFramebuffer(WebGL.FRAMEBUFFER, rttFramebuffer); 274 | lesson13.drawScene(rttWidth, rttHeight, 1.66); 275 | 276 | // What ever was the output of that, copy it to our texture 277 | gl.bindTexture(WebGL.TEXTURE_2D, rttTexture); 278 | gl.generateMipmap(WebGL.TEXTURE_2D); 279 | gl.bindTexture(WebGL.TEXTURE_2D, null); // reset to default 280 | 281 | // Back to normal framebuffer rendering 282 | gl.bindFramebuffer(WebGL.FRAMEBUFFER, null); 283 | 284 | // And use our current program. 285 | gl.useProgram(currentProgram.program); 286 | 287 | // Setup the viewport, pulling information from the element. 288 | gl.viewport(0, 0, viewWidth, viewHeight); 289 | 290 | // Clear! 291 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 292 | 293 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 294 | 295 | mvPushMatrix(); 296 | 297 | mvMatrix 298 | ..translate([0.0, -0.4, -2.2]) 299 | ..rotateY(radians(laptopAngle)) 300 | ..rotateX(radians(-90.0)); 301 | 302 | /* 303 | * Draw the laptop first with the following parameters 304 | * -TODO(jtmcdole): better description :) 305 | */ 306 | gl.uniform1i(uShowSpecularHighlights, 1); 307 | gl.uniform3f(uPointLightingLocation, -1.0, 2.0, -1.0); 308 | 309 | gl.uniform3f(uAmbientLightingColor, 0.2, 0.2, 0.2); 310 | gl.uniform3f(uPointLightingDiffuseColor, 0.8, 0.8, 0.8); 311 | gl.uniform3f(uPointLightingSpecularColor, 0.8, 0.8, 0.8); 312 | 313 | // The laptop body is quite shiny and has no texture. 314 | // It reflects lots of specular light 315 | gl.uniform3f(uMaterialAmbientColor, 1.0, 1.0, 1.0); 316 | gl.uniform3f(uMaterialDiffuseColor, 1.0, 1.0, 1.0); 317 | gl.uniform3f(uMaterialSpecularColor, 1.5, 1.5, 1.5); 318 | gl.uniform1f(uMaterialShininess, 5.0); 319 | gl.uniform3f(uMaterialEmissiveColor, 0.0, 0.0, 0.0); 320 | gl.uniform1i(uUseTextures, 0); 321 | 322 | laptop?.draw(vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms); 323 | 324 | /* 325 | * Now draw the laptop screen with different lighting parameters. 326 | */ 327 | gl.uniform3f(uMaterialAmbientColor, 0.0, 0.0, 0.0); 328 | gl.uniform3f(uMaterialDiffuseColor, 0.0, 0.0, 0.0); 329 | gl.uniform3f(uMaterialSpecularColor, 0.5, 0.5, 0.5); 330 | gl.uniform1f(uMaterialShininess, 20.0); 331 | gl.uniform3f(uMaterialEmissiveColor, 1.5, 1.5, 1.5); 332 | gl.uniform1i(uUseTextures, 1); 333 | 334 | // Now re-use the rttTexture for the screen - bam! 335 | gl.activeTexture(WebGL.TEXTURE0); 336 | gl.bindTexture(WebGL.TEXTURE_2D, rttTexture); 337 | gl.uniform1i(uSampler, 0); 338 | laptopScreen?.draw( 339 | vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms); 340 | 341 | mvPopMatrix(); 342 | } 343 | 344 | void setMatrixUniforms() { 345 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 346 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 347 | final normalMatrix = mvMatrix.toInverseMat3(); 348 | normalMatrix!.transposeSelf(); 349 | gl.uniformMatrix3fv(uNMatrix, false, normalMatrix.buf); 350 | } 351 | 352 | @override 353 | void animate(double now) { 354 | if (lastTime != 0) { 355 | final elapsed = now - lastTime; 356 | laptopAngle -= 0.005 * elapsed; 357 | } 358 | lastTime = now; 359 | lesson13.animate(now); 360 | } 361 | 362 | @override 363 | void handleKeys() { 364 | if (isActive(KeyCode.A)) { 365 | laptopAngle -= 1.0; 366 | } 367 | if (isActive(KeyCode.D)) { 368 | laptopAngle += 1.0; 369 | } 370 | lesson13.handleKeys(); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /web/lesson2.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Staticly draw a triangle and a square - With Color! 18 | class Lesson2 extends Lesson { 19 | late GlProgram program; 20 | 21 | late Buffer triangleVertexPositionBuffer, squareVertexPositionBuffer; 22 | late Buffer triangleVertexColorBuffer, squareVertexColorBuffer; 23 | 24 | Lesson2() { 25 | program = GlProgram( 26 | ''' 27 | precision mediump float; 28 | 29 | varying vec4 vColor; 30 | 31 | void main(void) { 32 | gl_FragColor = vColor; 33 | } 34 | ''', 35 | ''' 36 | attribute vec3 aVertexPosition; 37 | attribute vec4 aVertexColor; 38 | 39 | uniform mat4 uMVMatrix; 40 | uniform mat4 uPMatrix; 41 | 42 | varying vec4 vColor; 43 | 44 | void main(void) { 45 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 46 | vColor = aVertexColor; 47 | } 48 | ''', 49 | ['aVertexPosition', 'aVertexColor'], 50 | ['uMVMatrix', 'uPMatrix'], 51 | ); 52 | gl.useProgram(program.program); 53 | 54 | // Allocate and build the two buffers we need to draw a triangle and box. 55 | // createBuffer() asks the WebGL system to allocate some data for us 56 | triangleVertexPositionBuffer = gl.createBuffer(); 57 | 58 | // bindBuffer() tells the WebGL system the target of future calls 59 | gl.bindBuffer(WebGL.ARRAY_BUFFER, triangleVertexPositionBuffer); 60 | gl.bufferData( 61 | WebGL.ARRAY_BUFFER, Float32List.fromList([0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0]), WebGL.STATIC_DRAW); 62 | 63 | triangleVertexColorBuffer = gl.createBuffer(); 64 | gl.bindBuffer(WebGL.ARRAY_BUFFER, triangleVertexColorBuffer); 65 | var colors = [1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0]; 66 | gl.bufferData( 67 | WebGL.ARRAY_BUFFER, 68 | Float32List.fromList(colors), 69 | WebGL.STATIC_DRAW, 70 | ); 71 | 72 | squareVertexPositionBuffer = gl.createBuffer(); 73 | gl.bindBuffer(WebGL.ARRAY_BUFFER, squareVertexPositionBuffer); 74 | gl.bufferData(WebGL.ARRAY_BUFFER, 75 | Float32List.fromList([1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0]), WebGL.STATIC_DRAW); 76 | 77 | squareVertexColorBuffer = gl.createBuffer(); 78 | gl.bindBuffer(WebGL.ARRAY_BUFFER, squareVertexColorBuffer); 79 | colors = [0.5, 0.5, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0]; 80 | gl.bufferData( 81 | WebGL.ARRAY_BUFFER, 82 | Float32List.fromList(colors), 83 | WebGL.STATIC_DRAW, 84 | ); 85 | 86 | // Specify the color to clear with (black with 100% alpha) and then enable 87 | // depth testing. 88 | gl.clearColor(0.0, 0.0, 0.0, 1.0); 89 | } 90 | 91 | @override 92 | void drawScene(int viewWidth, int viewHeight, double aspect) { 93 | // Basic viewport setup and clearing of the screen 94 | gl.viewport(0, 0, viewWidth, viewHeight); 95 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 96 | gl.enable(WebGL.DEPTH_TEST); 97 | gl.disable(WebGL.BLEND); 98 | 99 | // Setup the perspective - you might be wondering why we do this every 100 | // time, and that will become clear in much later lessons. Just know, you 101 | // are not crazy for thinking of caching this. 102 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 103 | 104 | // First stash the current model view matrix before we start moving around. 105 | mvPushMatrix(); 106 | 107 | mvMatrix.translate([-1.5, 0.0, -7.0]); 108 | 109 | // Here's that bindBuffer() again, as seen in the constructor 110 | gl.bindBuffer(WebGL.ARRAY_BUFFER, triangleVertexPositionBuffer); 111 | // Set the vertex attribute to the size of each individual element (x,y,z) 112 | gl.vertexAttribPointer(program.attributes['aVertexPosition']!, 3, WebGL.FLOAT, false, 0, 0); 113 | 114 | gl.bindBuffer(WebGL.ARRAY_BUFFER, triangleVertexColorBuffer); 115 | gl.vertexAttribPointer(program.attributes['aVertexColor']!, 4, WebGL.FLOAT, false, 0, 0); 116 | 117 | setMatrixUniforms(); 118 | // Now draw 3 vertices 119 | gl.drawArrays(WebGL.TRIANGLES, 0, 3); 120 | 121 | // Move 3 units to the right 122 | mvMatrix.translate([3.0, 0.0, 0.0]); 123 | 124 | // And get ready to draw the square just like we did the triangle... 125 | gl.bindBuffer(WebGL.ARRAY_BUFFER, squareVertexPositionBuffer); 126 | gl.vertexAttribPointer(program.attributes['aVertexPosition']!, 3, WebGL.FLOAT, false, 0, 0); 127 | 128 | gl.bindBuffer(WebGL.ARRAY_BUFFER, squareVertexColorBuffer); 129 | gl.vertexAttribPointer(program.attributes['aVertexColor']!, 4, WebGL.FLOAT, false, 0, 0); 130 | 131 | setMatrixUniforms(); 132 | // Except now draw 2 triangles, re-using the vertices found in the buffer. 133 | gl.drawArrays(WebGL.TRIANGLE_STRIP, 0, 4); 134 | 135 | // Finally, reset the matrix back to what it was before we moved around. 136 | mvPopMatrix(); 137 | } 138 | 139 | /// Write the matrix uniforms (model view matrix and perspective matrix) so 140 | /// WebGL knows what to do with them. 141 | void setMatrixUniforms() { 142 | gl.uniformMatrix4fv(program.uniforms['uPMatrix'], false, pMatrix.buf); 143 | gl.uniformMatrix4fv(program.uniforms['uMVMatrix'], false, mvMatrix.buf); 144 | } 145 | 146 | @override 147 | void animate(double now) { 148 | // We're not animating the scene, but if you want to experiment, here's 149 | // where you get to play around. 150 | } 151 | 152 | @override 153 | void handleKeys() { 154 | // We're not handling keys right now, but if you want to experiment, here's 155 | // where you'd get to play around. 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /web/lesson3.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Draw a colored triangle and a square, and have them rotate on axis. 18 | /// This lesson is nearly identical to Lesson 2, and we could clean it up... 19 | /// however that's a future lesson. 20 | class Lesson3 extends Lesson { 21 | late GlProgram program; 22 | 23 | late Buffer triangleVertexPositionBuffer, squareVertexPositionBuffer; 24 | late Buffer triangleVertexColorBuffer, squareVertexColorBuffer; 25 | 26 | double rTriangle = 0.0, rSquare = 0.0; 27 | 28 | Lesson3() { 29 | program = GlProgram( 30 | ''' 31 | precision mediump float; 32 | 33 | varying vec4 vColor; 34 | 35 | void main(void) { 36 | gl_FragColor = vColor; 37 | } 38 | ''', 39 | ''' 40 | attribute vec3 aVertexPosition; 41 | attribute vec4 aVertexColor; 42 | 43 | uniform mat4 uMVMatrix; 44 | uniform mat4 uPMatrix; 45 | 46 | varying vec4 vColor; 47 | 48 | void main(void) { 49 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 50 | vColor = aVertexColor; 51 | } 52 | ''', 53 | ['aVertexPosition', 'aVertexColor'], 54 | ['uMVMatrix', 'uPMatrix'], 55 | ); 56 | gl.useProgram(program.program); 57 | 58 | // Allocate and build the two buffers we need to draw a triangle and box. 59 | // createBuffer() asks the WebGL system to allocate some data for us 60 | triangleVertexPositionBuffer = gl.createBuffer(); 61 | 62 | // bindBuffer() tells the WebGL system the target of future calls 63 | gl.bindBuffer(WebGL.ARRAY_BUFFER, triangleVertexPositionBuffer); 64 | gl.bufferData( 65 | WebGL.ARRAY_BUFFER, Float32List.fromList([0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0]), WebGL.STATIC_DRAW); 66 | 67 | triangleVertexColorBuffer = gl.createBuffer(); 68 | gl.bindBuffer(WebGL.ARRAY_BUFFER, triangleVertexColorBuffer); 69 | var colors = [1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0]; 70 | gl.bufferData( 71 | WebGL.ARRAY_BUFFER, 72 | Float32List.fromList(colors), 73 | WebGL.STATIC_DRAW, 74 | ); 75 | 76 | squareVertexPositionBuffer = gl.createBuffer(); 77 | gl.bindBuffer(WebGL.ARRAY_BUFFER, squareVertexPositionBuffer); 78 | gl.bufferData(WebGL.ARRAY_BUFFER, 79 | Float32List.fromList([1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0]), WebGL.STATIC_DRAW); 80 | 81 | squareVertexColorBuffer = gl.createBuffer(); 82 | gl.bindBuffer(WebGL.ARRAY_BUFFER, squareVertexColorBuffer); 83 | colors = [0.5, 0.5, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0]; 84 | gl.bufferData( 85 | WebGL.ARRAY_BUFFER, 86 | Float32List.fromList(colors), 87 | WebGL.STATIC_DRAW, 88 | ); 89 | 90 | // Specify the color to clear with (black with 100% alpha) and then enable 91 | // depth testing. 92 | gl.clearColor(0.0, 0.0, 0.0, 1.0); 93 | } 94 | 95 | @override 96 | void drawScene(int viewWidth, int viewHeight, double aspect) { 97 | // Basic viewport setup and clearing of the screen 98 | gl.viewport(0, 0, viewWidth, viewHeight); 99 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 100 | gl.enable(WebGL.DEPTH_TEST); 101 | gl.disable(WebGL.BLEND); 102 | 103 | // Setup the perspective - you might be wondering why we do this every 104 | // time, and that will become clear in much later lessons. Just know, you 105 | // are not crazy for thinking of caching this. 106 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 107 | 108 | // First stash the current model view matrix before we start moving around. 109 | mvPushMatrix(); 110 | 111 | mvMatrix.translate([-1.5, 0.0, -7.0]); 112 | 113 | mvPushMatrix(); 114 | mvMatrix.rotateY(radians(rTriangle)); 115 | 116 | // Here's that bindBuffer() again, as seen in the constructor 117 | gl.bindBuffer(WebGL.ARRAY_BUFFER, triangleVertexPositionBuffer); 118 | // Set the vertex attribute to the size of each individual element (x,y,z) 119 | gl.vertexAttribPointer(program.attributes['aVertexPosition']!, 3, WebGL.FLOAT, false, 0, 0); 120 | 121 | gl.bindBuffer(WebGL.ARRAY_BUFFER, triangleVertexColorBuffer); 122 | gl.vertexAttribPointer(program.attributes['aVertexColor']!, 4, WebGL.FLOAT, false, 0, 0); 123 | 124 | setMatrixUniforms(); 125 | // Now draw 3 vertices 126 | gl.drawArrays(WebGL.TRIANGLES, 0, 3); 127 | 128 | mvPopMatrix(); 129 | 130 | // Move 3 units to the right 131 | mvMatrix.translate([3.0, 0.0, 0.0]); 132 | mvMatrix.rotateX(radians(rSquare)); 133 | 134 | // And get ready to draw the square just like we did the triangle... 135 | gl.bindBuffer(WebGL.ARRAY_BUFFER, squareVertexPositionBuffer); 136 | gl.vertexAttribPointer(program.attributes['aVertexPosition']!, 3, WebGL.FLOAT, false, 0, 0); 137 | 138 | gl.bindBuffer(WebGL.ARRAY_BUFFER, squareVertexColorBuffer); 139 | gl.vertexAttribPointer(program.attributes['aVertexColor']!, 4, WebGL.FLOAT, false, 0, 0); 140 | 141 | setMatrixUniforms(); 142 | // Except now draw 2 triangles, re-using the vertices found in the buffer. 143 | gl.drawArrays(WebGL.TRIANGLE_STRIP, 0, 4); 144 | 145 | // Finally, reset the matrix back to what it was before we moved around. 146 | mvPopMatrix(); 147 | } 148 | 149 | /// Write the matrix uniforms (model view matrix and perspective matrix) so 150 | /// WebGL knows what to do with them. 151 | void setMatrixUniforms() { 152 | gl.uniformMatrix4fv(program.uniforms['uPMatrix'], false, pMatrix.buf); 153 | gl.uniformMatrix4fv(program.uniforms['uMVMatrix'], false, mvMatrix.buf); 154 | } 155 | 156 | /// Every time the browser tells us to draw the scene, animate is called. 157 | /// If there's something being movied, this is where that movement i 158 | /// calculated. 159 | @override 160 | void animate(double now) { 161 | if (lastTime != 0) { 162 | var elapsed = now - lastTime; 163 | rTriangle += (90 * elapsed) / 1000.0; 164 | rSquare += (75 * elapsed) / 1000.0; 165 | } 166 | lastTime = now; 167 | } 168 | 169 | @override 170 | void handleKeys() { 171 | // We're not handling keys right now, but if you want to experiment, here's 172 | // where you'd get to play around. 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /web/lesson4.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Draw a colored triangle and a square, and have them rotate on axis. 18 | /// This lesson is nearly identical to Lesson 2, and we could clean it up... 19 | /// however that's a future lesson. 20 | class Lesson4 extends Lesson { 21 | late GlProgram program; 22 | 23 | Pyramid pyramid = Pyramid(); 24 | Cube cube = Cube(); 25 | 26 | double rPyramid = 0.0, rCube = 0.0; 27 | 28 | Lesson4() { 29 | program = GlProgram( 30 | ''' 31 | precision mediump float; 32 | 33 | varying vec4 vColor; 34 | 35 | void main(void) { 36 | gl_FragColor = vColor; 37 | } 38 | ''', 39 | ''' 40 | attribute vec3 aVertexPosition; 41 | attribute vec4 aVertexColor; 42 | 43 | uniform mat4 uMVMatrix; 44 | uniform mat4 uPMatrix; 45 | 46 | varying vec4 vColor; 47 | 48 | void main(void) { 49 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 50 | vColor = aVertexColor; 51 | } 52 | ''', 53 | ['aVertexPosition', 'aVertexColor'], 54 | ['uMVMatrix', 'uPMatrix'], 55 | ); 56 | gl.useProgram(program.program); 57 | 58 | // Currently this is hardcoded, because well... everything else is textures 59 | // from here out. 60 | cube.addColor(CubeColor()); 61 | 62 | // Specify the color to clear with (black with 100% alpha) and then enable 63 | // depth testing. 64 | gl.clearColor(0.0, 0.0, 0.0, 1.0); 65 | } 66 | 67 | @override 68 | void drawScene(int viewWidth, int viewHeight, double aspect) { 69 | // Basic viewport setup and clearing of the screen 70 | gl.viewport(0, 0, viewWidth, viewHeight); 71 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 72 | 73 | gl.enable(WebGL.DEPTH_TEST); 74 | gl.disable(WebGL.BLEND); 75 | 76 | // Setup the perspective - you might be wondering why we do this every 77 | // time, and that will become clear in much later lessons. Just know, you 78 | // are not crazy for thinking of caching this. 79 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 80 | 81 | // First stash the current model view matrix before we start moving around. 82 | mvPushMatrix(); 83 | 84 | mvMatrix.translate([-1.5, 0.0, -8.0]); 85 | 86 | // Let the user play around with some directional changes. 87 | mvMatrix.rotateX(radians(x)).rotateY(radians(y)); 88 | 89 | mvPushMatrix(); 90 | mvMatrix.rotate(radians(rPyramid), [0, 1, 0]); 91 | pyramid.draw( 92 | setUniforms: setMatrixUniforms, 93 | vertex: program.attributes['aVertexPosition'], 94 | color: program.attributes['aVertexColor']); 95 | mvPopMatrix(); 96 | 97 | // Move 3 units to the right 98 | mvMatrix.translate([3.0, 0.0, 0.0]); 99 | mvMatrix.rotate(radians(rCube), [1, 1, 1]); 100 | cube.draw( 101 | setUniforms: setMatrixUniforms, 102 | vertex: program.attributes['aVertexPosition'], 103 | color: program.attributes['aVertexColor']); 104 | 105 | // Finally, reset the matrix back to what it was before we moved around. 106 | mvPopMatrix(); 107 | } 108 | 109 | /// Write the matrix uniforms (model view matrix and perspective matrix) so 110 | /// WebGL knows what to do with them. 111 | void setMatrixUniforms() { 112 | gl.uniformMatrix4fv(program.uniforms['uPMatrix'], false, pMatrix.buf); 113 | gl.uniformMatrix4fv(program.uniforms['uMVMatrix'], false, mvMatrix.buf); 114 | } 115 | 116 | /// Every time the browser tells us to draw the scene, animate is called. 117 | /// If there's something being movied, this is where that movement i 118 | /// calculated. 119 | @override 120 | void animate(double now) { 121 | if (lastTime != 0) { 122 | var elapsed = now - lastTime; 123 | rPyramid += (90 * elapsed) / 1000.0; 124 | rCube -= (75 * elapsed) / 1000.0; 125 | } 126 | lastTime = now; 127 | } 128 | 129 | double x = 0.0, y = 0.0, z = 0.0; 130 | @override 131 | void handleKeys() { 132 | handleDirection(up: () => y -= 0.5, down: () => y += 0.5, left: () => x -= 0.5, right: () => x += 0.5); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /web/lesson5.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Introducing Textures! 18 | class Lesson5 extends Lesson { 19 | late GlProgram program; 20 | Texture? neheTexture; 21 | late Cube cube; 22 | 23 | bool get isLoaded => neheTexture != null; 24 | 25 | Lesson5() { 26 | cube = Cube(); 27 | loadTexture('nehe.gif', (Texture texture, ImageElement element) { 28 | gl.bindTexture(WebGL.TEXTURE_2D, texture); 29 | gl.pixelStorei(WebGL.UNPACK_FLIP_Y_WEBGL, 1); 30 | gl.texImage2D( 31 | WebGL.TEXTURE_2D, 32 | 0, 33 | WebGL.RGBA, 34 | WebGL.RGBA, 35 | WebGL.UNSIGNED_BYTE, 36 | element, 37 | ); 38 | gl.texParameteri( 39 | WebGL.TEXTURE_2D, 40 | WebGL.TEXTURE_MAG_FILTER, 41 | WebGL.NEAREST, 42 | ); 43 | gl.texParameteri( 44 | WebGL.TEXTURE_2D, 45 | WebGL.TEXTURE_MIN_FILTER, 46 | WebGL.NEAREST, 47 | ); 48 | gl.bindTexture(WebGL.TEXTURE_2D, null); 49 | neheTexture = texture; 50 | }); 51 | 52 | final attributes = ['aVertexPosition', 'aTextureCoord']; 53 | final uniforms = ['uPMatrix', 'uMVMatrix', 'uSampler']; 54 | 55 | program = GlProgram( 56 | ''' 57 | precision mediump float; 58 | 59 | varying vec2 vTextureCoord; 60 | 61 | uniform sampler2D uSampler; 62 | 63 | void main(void) { 64 | gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 65 | } 66 | ''', 67 | ''' 68 | attribute vec3 aVertexPosition; 69 | attribute vec2 aTextureCoord; 70 | 71 | uniform mat4 uMVMatrix; 72 | uniform mat4 uPMatrix; 73 | 74 | varying vec2 vTextureCoord; 75 | 76 | void main(void) { 77 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 78 | vTextureCoord = aTextureCoord; 79 | } 80 | ''', 81 | attributes, 82 | uniforms, 83 | ); 84 | 85 | gl.useProgram(program.program); 86 | } 87 | 88 | @override 89 | void drawScene(int viewWidth, int viewHeight, double aspect) { 90 | if (!isLoaded) return; 91 | 92 | // Basic viewport setup and clearing of the screen 93 | gl.viewport(0, 0, viewWidth, viewHeight); 94 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 95 | gl.enable(WebGL.DEPTH_TEST); 96 | gl.disable(WebGL.BLEND); 97 | 98 | // Setup the perspective - you might be wondering why we do this every 99 | // time, and that will become clear in much later lessons. Just know, you 100 | // are not crazy for thinking of caching this. 101 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 102 | 103 | // First stash the current model view matrix before we start moving around. 104 | mvPushMatrix(); 105 | 106 | mvMatrix 107 | ..translate([0.0, 0.0, -5.0]) 108 | ..rotateX(radians(xRot)) 109 | ..rotateY(radians(yRot)) 110 | ..rotateZ(radians(zRot)); 111 | 112 | gl.activeTexture(WebGL.TEXTURE0); 113 | gl.bindTexture(WebGL.TEXTURE_2D, neheTexture); 114 | gl.uniform1i(uSampler, 0); 115 | cube.draw( 116 | setUniforms: setMatrixUniforms, 117 | vertex: program.attributes['aVertexPosition'], 118 | coord: program.attributes['aTextureCoord']); 119 | 120 | // Finally, reset the matrix back to what it was before we moved around. 121 | mvPopMatrix(); 122 | } 123 | 124 | UniformLocation? get uPMatrix => program.uniforms['uPMatrix']; 125 | UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix']; 126 | UniformLocation? get uSampler => program.uniforms['uSampler']; 127 | 128 | void setMatrixUniforms() { 129 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 130 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 131 | } 132 | 133 | double xRot = 0.0, yRot = 0.0, zRot = 0.0; 134 | 135 | @override 136 | void animate(double now) { 137 | if (lastTime != 0) { 138 | final elapsed = now - lastTime; 139 | 140 | xRot += (90 * elapsed) / 1000.0; 141 | yRot += (90 * elapsed) / 1000.0; 142 | zRot += (90 * elapsed) / 1000.0; 143 | } 144 | lastTime = now; 145 | } 146 | 147 | @override 148 | void handleKeys() { 149 | handleDirection(up: () => yRot -= 0.5, down: () => yRot += 0.5, left: () => xRot -= 0.5, right: () => xRot += 0.5); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /web/lesson6.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Textures part two: filtering. 18 | class Lesson6 extends Lesson { 19 | late GlProgram program; 20 | List textures = []; 21 | late Cube cube; 22 | 23 | bool get isLoaded => textures.length == 3; 24 | 25 | Lesson6() { 26 | cube = Cube(); 27 | 28 | final attributes = ['aVertexPosition', 'aTextureCoord']; 29 | final uniforms = ['uPMatrix', 'uMVMatrix', 'uSampler']; 30 | 31 | program = GlProgram( 32 | ''' 33 | precision mediump float; 34 | 35 | varying vec2 vTextureCoord; 36 | 37 | uniform sampler2D uSampler; 38 | 39 | void main(void) { 40 | gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 41 | } 42 | ''', 43 | ''' 44 | attribute vec3 aVertexPosition; 45 | attribute vec2 aTextureCoord; 46 | 47 | uniform mat4 uMVMatrix; 48 | uniform mat4 uPMatrix; 49 | 50 | varying vec2 vTextureCoord; 51 | 52 | void main(void) { 53 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 54 | vTextureCoord = aTextureCoord; 55 | } 56 | ''', 57 | attributes, 58 | uniforms, 59 | ); 60 | 61 | gl.useProgram(program.program); 62 | 63 | // Do some extra texture filters after loading the create texture 64 | loadTexture('crate.gif', (Texture text, ImageElement ele) { 65 | gl.pixelStorei(WebGL.UNPACK_FLIP_Y_WEBGL, 1); 66 | textures.add(text); 67 | 68 | gl.bindTexture(WebGL.TEXTURE_2D, textures[0]); 69 | gl.texImage2D( 70 | WebGL.TEXTURE_2D, 71 | 0, 72 | WebGL.RGBA, 73 | WebGL.RGBA, 74 | WebGL.UNSIGNED_BYTE, 75 | ele, 76 | ); 77 | gl.texParameteri( 78 | WebGL.TEXTURE_2D, 79 | WebGL.TEXTURE_MAG_FILTER, 80 | WebGL.NEAREST, 81 | ); 82 | gl.texParameteri( 83 | WebGL.TEXTURE_2D, 84 | WebGL.TEXTURE_MIN_FILTER, 85 | WebGL.NEAREST, 86 | ); 87 | 88 | textures.add(gl.createTexture()); 89 | gl.bindTexture(WebGL.TEXTURE_2D, textures[1]); 90 | gl.texImage2D( 91 | WebGL.TEXTURE_2D, 92 | 0, 93 | WebGL.RGBA, 94 | WebGL.RGBA, 95 | WebGL.UNSIGNED_BYTE, 96 | ele, 97 | ); 98 | gl.texParameteri( 99 | WebGL.TEXTURE_2D, 100 | WebGL.TEXTURE_MAG_FILTER, 101 | WebGL.LINEAR, 102 | ); 103 | gl.texParameteri( 104 | WebGL.TEXTURE_2D, 105 | WebGL.TEXTURE_MIN_FILTER, 106 | WebGL.LINEAR, 107 | ); 108 | 109 | textures.add(gl.createTexture()); 110 | gl.bindTexture(WebGL.TEXTURE_2D, textures[2]); 111 | gl.texImage2D( 112 | WebGL.TEXTURE_2D, 113 | 0, 114 | WebGL.RGBA, 115 | WebGL.RGBA, 116 | WebGL.UNSIGNED_BYTE, 117 | ele, 118 | ); 119 | gl.texParameteri( 120 | WebGL.TEXTURE_2D, 121 | WebGL.TEXTURE_MAG_FILTER, 122 | WebGL.LINEAR, 123 | ); 124 | gl.texParameteri( 125 | WebGL.TEXTURE_2D, 126 | WebGL.TEXTURE_MIN_FILTER, 127 | WebGL.LINEAR_MIPMAP_NEAREST, 128 | ); 129 | gl.generateMipmap(WebGL.TEXTURE_2D); 130 | 131 | // reset the texture 2d 132 | gl.bindTexture(WebGL.TEXTURE_2D, null); 133 | }); 134 | 135 | // We want to trigger on the unique key-down event for switching filters; 136 | // trying to handle this in handleKeys would lead to rapid changing. 137 | window.onKeyDown.listen((event) { 138 | if (event.keyCode == KeyCode.F) { 139 | activeFilter = (activeFilter + 1) % 3; 140 | } 141 | }); 142 | } 143 | 144 | int activeFilter = 0; 145 | 146 | @override 147 | void drawScene(int viewWidth, int viewHeight, double aspect) { 148 | if (!isLoaded) return; 149 | // Basic viewport setup and clearing of the screen 150 | gl.viewport(0, 0, viewWidth, viewHeight); 151 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 152 | gl.enable(WebGL.DEPTH_TEST); 153 | gl.disable(WebGL.BLEND); 154 | 155 | // Setup the perspective - you might be wondering why we do this every 156 | // time, and that will become clear in much later lessons. Just know, you 157 | // are not crazy for thinking of caching this. 158 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 159 | 160 | // First stash the current model view matrix before we start moving around. 161 | mvPushMatrix(); 162 | 163 | mvMatrix 164 | ..translate([0.0, 0.0, z]) 165 | ..rotateX(radians(xRot)) 166 | ..rotateY(radians(yRot)); 167 | 168 | gl.activeTexture(WebGL.TEXTURE0); 169 | gl.bindTexture(WebGL.TEXTURE_2D, textures[activeFilter]); 170 | gl.uniform1i(uSampler, 0); 171 | cube.draw( 172 | setUniforms: setMatrixUniforms, 173 | vertex: program.attributes['aVertexPosition'], 174 | coord: program.attributes['aTextureCoord']); 175 | 176 | // Finally, reset the matrix back to what it was before we moved around. 177 | mvPopMatrix(); 178 | } 179 | 180 | UniformLocation? get uPMatrix => program.uniforms['uPMatrix']; 181 | UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix']; 182 | UniformLocation? get uSampler => program.uniforms['uSampler']; 183 | 184 | void setMatrixUniforms() { 185 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 186 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 187 | } 188 | 189 | double xSpeed = 0.0, ySpeed = 0.0; 190 | double xRot = 0.0, yRot = 0.0; 191 | double z = -5.0; 192 | 193 | @override 194 | void animate(double now) { 195 | if (lastTime != 0) { 196 | final elapsed = now - lastTime; 197 | 198 | xRot += (xSpeed * elapsed) / 1000.0; 199 | yRot += (ySpeed * elapsed) / 1000.0; 200 | } 201 | lastTime = now; 202 | } 203 | 204 | @override 205 | void handleKeys() { 206 | handleDirection( 207 | up: () => ySpeed -= 1.0, down: () => ySpeed += 1.0, left: () => xSpeed -= 1.0, right: () => xSpeed += 1.0); 208 | if (isActive(KeyCode.PAGE_UP)) { 209 | z -= 0.05; 210 | } 211 | if (isActive(KeyCode.PAGE_DOWN)) { 212 | z += 0.05; 213 | } 214 | } 215 | 216 | @override 217 | void initHtml(DivElement hook) { 218 | hook.setInnerHtml( 219 | ''' 220 |

Controls:

221 | 222 |
    223 |
  • Page Up/Page Down to zoom out/in 224 |
  • Cursor keys: make the cube rotate (the longer you hold down a cursor key, the more it accelerates) 225 |
  • F to toggle through three different kinds of texture filters 226 |
227 | ''', 228 | treeSanitizer: NullTreeSanitizer(), 229 | ); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /web/lesson7.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Basic directional and abient lighting 18 | class Lesson7 extends Lesson { 19 | late Cube cube; 20 | late GlProgram program; 21 | Texture? texture; 22 | 23 | bool get isLoaded => texture != null; 24 | 25 | Lesson7() { 26 | cube = Cube(); 27 | loadTexture('crate.gif', handleMipMapTexture).then((t) => texture = t); 28 | 29 | final attributes = ['aVertexPosition', 'aVertexNormal', 'aTextureCoord']; 30 | final uniforms = [ 31 | 'uPMatrix', 32 | 'uMVMatrix', 33 | 'uNMatrix', 34 | 'uSampler', 35 | 'uAmbientColor', 36 | 'uLightingDirection', 37 | 'uDirectionalColor', 38 | 'uUseLighting' 39 | ]; 40 | 41 | program = GlProgram( 42 | ''' 43 | precision mediump float; 44 | 45 | varying vec2 vTextureCoord; 46 | varying vec3 vLightWeighting; 47 | 48 | uniform sampler2D uSampler; 49 | 50 | void main(void) { 51 | vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 52 | gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a); 53 | } 54 | ''', 55 | ''' 56 | attribute vec3 aVertexPosition; 57 | attribute vec3 aVertexNormal; 58 | attribute vec2 aTextureCoord; 59 | 60 | uniform mat4 uMVMatrix; 61 | uniform mat4 uPMatrix; 62 | uniform mat3 uNMatrix; 63 | 64 | uniform vec3 uAmbientColor; 65 | 66 | uniform vec3 uLightingDirection; 67 | uniform vec3 uDirectionalColor; 68 | 69 | uniform bool uUseLighting; 70 | 71 | varying vec2 vTextureCoord; 72 | varying vec3 vLightWeighting; 73 | 74 | void main(void) { 75 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 76 | vTextureCoord = aTextureCoord; 77 | 78 | if (!uUseLighting) { 79 | vLightWeighting = vec3(1.0, 1.0, 1.0); 80 | } else { 81 | vec3 transformedNormal = uNMatrix * aVertexNormal; 82 | float directionalLightWeighting = max(dot(transformedNormal, uLightingDirection), 0.0); 83 | vLightWeighting = uAmbientColor + uDirectionalColor * directionalLightWeighting; 84 | } 85 | } 86 | ''', 87 | attributes, 88 | uniforms, 89 | ); 90 | 91 | gl.useProgram(program.program); 92 | } 93 | 94 | UniformLocation? get uPMatrix => program.uniforms['uPMatrix']; 95 | UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix']; 96 | UniformLocation? get uNMatrix => program.uniforms['uNMatrix']; 97 | UniformLocation? get uSampler => program.uniforms['uSampler']; 98 | UniformLocation? get uAmbientColor => program.uniforms['uAmbientColor']; 99 | UniformLocation? get uLightingDirection => program.uniforms['uLightingDirection']; 100 | UniformLocation? get uDirectionalColor => program.uniforms['uDirectionalColor']; 101 | UniformLocation? get uUseLighting => program.uniforms['uUseLighting']; 102 | 103 | void setMatrixUniforms() { 104 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 105 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 106 | final normalMatrix = mvMatrix.toInverseMat3(); 107 | normalMatrix!.transposeSelf(); 108 | gl.uniformMatrix3fv(uNMatrix, false, normalMatrix.buf); 109 | } 110 | 111 | @override 112 | void drawScene(int viewWidth, int viewHeight, double aspect) { 113 | if (!isLoaded) return; 114 | // Basic viewport setup and clearing of the screen 115 | gl.viewport(0, 0, viewWidth, viewHeight); 116 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 117 | gl.enable(WebGL.DEPTH_TEST); 118 | gl.disable(WebGL.BLEND); 119 | 120 | // Setup the perspective - you might be wondering why we do this every 121 | // time, and that will become clear in much later lessons. Just know, you 122 | // are not crazy for thinking of caching this. 123 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 124 | 125 | // First stash the current model view matrix before we start moving around. 126 | mvPushMatrix(); 127 | 128 | mvMatrix 129 | ..translate([0.0, 0.0, z]) 130 | ..rotateX(radians(xRot)) 131 | ..rotateY(radians(yRot)); 132 | 133 | gl.uniform1i(uUseLighting, _lighting.checked! ? 1 : 0); 134 | if (_lighting.checked!) { 135 | gl.uniform3f(uAmbientColor, double.parse(_aR.value!), double.parse(_aG.value!), double.parse(_aB.value!)); 136 | 137 | // Take the lighting point and normalize / reverse it. 138 | var direction = Vector3(double.parse(_ldX.value!), double.parse(_ldY.value!), double.parse(_ldZ.value!)); 139 | direction = direction.normalize().scale(-1.0); 140 | gl.uniform3fv(uLightingDirection, direction.buf); 141 | 142 | gl.uniform3f(uDirectionalColor, double.parse(_dR.value!), double.parse(_dG.value!), double.parse(_dB.value!)); 143 | } 144 | 145 | gl.activeTexture(WebGL.TEXTURE0); 146 | gl.bindTexture(WebGL.TEXTURE_2D, texture); 147 | gl.uniform1i(uSampler, 0); 148 | 149 | cube.draw( 150 | setUniforms: setMatrixUniforms, 151 | vertex: program.attributes['aVertexPosition'], 152 | coord: program.attributes['aTextureCoord'], 153 | normal: program.attributes['aVertexNormal']); 154 | 155 | mvPopMatrix(); 156 | } 157 | 158 | var xSpeed = 3.0, ySpeed = -3.0; 159 | var xRot = 0.0, yRot = 0.0; 160 | var z = -5.0; 161 | 162 | @override 163 | void animate(double now) { 164 | if (lastTime != 0) { 165 | final elapsed = now - lastTime; 166 | 167 | xRot += (xSpeed * elapsed) / 1000.0; 168 | yRot += (ySpeed * elapsed) / 1000.0; 169 | } 170 | lastTime = now; 171 | } 172 | 173 | @override 174 | void handleKeys() { 175 | handleDirection( 176 | up: () => ySpeed -= 1.0, down: () => ySpeed += 1.0, left: () => xSpeed -= 1.0, right: () => xSpeed += 1.0); 177 | if (isActive(KeyCode.PAGE_UP)) { 178 | z -= 0.05; 179 | } 180 | if (isActive(KeyCode.PAGE_DOWN)) { 181 | z += 0.05; 182 | } 183 | } 184 | 185 | // Lighting enabled / Ambient color 186 | late InputElement _lighting, _aR, _aG, _aB; 187 | 188 | // Light position 189 | late InputElement _ldX, _ldY, _ldZ; 190 | 191 | // Directional light color 192 | late InputElement _dR, _dG, _dB; 193 | 194 | @override 195 | void initHtml(DivElement hook) { 196 | hook.setInnerHtml( 197 | ''' 198 | Use lighting
199 | (Use cursor keys to spin the box and Page Up/Page Down to zoom out/in) 200 | 201 |
202 |

Directional light:

203 | 204 | 205 | 206 | 211 | 212 | 217 |
Direction: 207 | X: 208 | Y: 209 | Z: 210 |
Colour: 213 | R: 214 | G: 215 | B: 216 |
218 | 219 |

Ambient light:

220 | 221 | 222 | 227 |
Colour: 223 | R: 224 | G: 225 | B: 226 |
228 | ''', 229 | treeSanitizer: NullTreeSanitizer(), 230 | ); 231 | 232 | // Re-look up our dom elements 233 | _lighting = querySelector('#lighting') as InputElement; 234 | _aR = querySelector('#ambientR') as InputElement; 235 | _aG = querySelector('#ambientG') as InputElement; 236 | _aB = querySelector('#ambientB') as InputElement; 237 | 238 | _dR = querySelector('#directionalR') as InputElement; 239 | _dG = querySelector('#directionalG') as InputElement; 240 | _dB = querySelector('#directionalB') as InputElement; 241 | 242 | _ldX = querySelector('#lightDirectionX') as InputElement; 243 | _ldY = querySelector('#lightDirectionY') as InputElement; 244 | _ldZ = querySelector('#lightDirectionZ') as InputElement; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /web/lesson8.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Depth late Buffer, Transparency and Blending 18 | class Lesson8 extends Lesson { 19 | late Cube cube; 20 | late GlProgram program; 21 | Texture? texture; 22 | 23 | bool get isLoaded => texture != null; 24 | 25 | Lesson8() { 26 | cube = Cube(); 27 | loadTexture('glass.gif', handleMipMapTexture).then((t) => texture = t); 28 | 29 | final attributes = ['aVertexPosition', 'aVertexNormal', 'aTextureCoord']; 30 | final uniforms = [ 31 | 'uPMatrix', 32 | 'uMVMatrix', 33 | 'uNMatrix', 34 | 'uSampler', 35 | 'uAmbientColor', 36 | 'uLightingDirection', 37 | 'uDirectionalColor', 38 | 'uUseLighting', 39 | 'uAlpha' 40 | ]; 41 | 42 | program = GlProgram( 43 | ''' 44 | precision mediump float; 45 | 46 | varying vec2 vTextureCoord; 47 | varying vec3 vLightWeighting; 48 | 49 | uniform float uAlpha; 50 | 51 | uniform sampler2D uSampler; 52 | 53 | void main(void) { 54 | vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 55 | gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a * uAlpha); 56 | } 57 | ''', 58 | ''' 59 | attribute vec3 aVertexPosition; 60 | attribute vec3 aVertexNormal; 61 | attribute vec2 aTextureCoord; 62 | 63 | uniform mat4 uMVMatrix; 64 | uniform mat4 uPMatrix; 65 | uniform mat3 uNMatrix; 66 | 67 | uniform vec3 uAmbientColor; 68 | 69 | uniform vec3 uLightingDirection; 70 | uniform vec3 uDirectionalColor; 71 | 72 | uniform bool uUseLighting; 73 | 74 | varying vec2 vTextureCoord; 75 | varying vec3 vLightWeighting; 76 | 77 | void main(void) { 78 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 79 | vTextureCoord = aTextureCoord; 80 | 81 | if (!uUseLighting) { 82 | vLightWeighting = vec3(1.0, 1.0, 1.0); 83 | } else { 84 | vec3 transformedNormal = uNMatrix * aVertexNormal; 85 | float directionalLightWeighting = max(dot(transformedNormal, uLightingDirection), 0.0); 86 | vLightWeighting = uAmbientColor + uDirectionalColor * directionalLightWeighting; 87 | } 88 | } 89 | ''', 90 | attributes, 91 | uniforms, 92 | ); 93 | 94 | gl.useProgram(program.program); 95 | } 96 | 97 | UniformLocation? get uPMatrix => program.uniforms['uPMatrix']; 98 | UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix']; 99 | UniformLocation? get uNMatrix => program.uniforms['uNMatrix']; 100 | UniformLocation? get uSampler => program.uniforms['uSampler']; 101 | UniformLocation? get uAmbientColor => program.uniforms['uAmbientColor']; 102 | UniformLocation? get uLightingDirection => program.uniforms['uLightingDirection']; 103 | UniformLocation? get uDirectionalColor => program.uniforms['uDirectionalColor']; 104 | UniformLocation? get uUseLighting => program.uniforms['uUseLighting']; 105 | UniformLocation? get uAlpha => program.uniforms['uAlpha']; 106 | 107 | void setMatrixUniforms() { 108 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 109 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 110 | final normalMatrix = mvMatrix.toInverseMat3(); 111 | normalMatrix!.transposeSelf(); 112 | gl.uniformMatrix3fv(uNMatrix, false, normalMatrix.buf); 113 | } 114 | 115 | @override 116 | void drawScene(int viewWidth, int viewHeight, double aspect) { 117 | if (!isLoaded) return; 118 | // Basic viewport setup and clearing of the screen 119 | gl.viewport(0, 0, viewWidth, viewHeight); 120 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 121 | 122 | // Setup the perspective - you might be wondering why we do this every 123 | // time, and that will become clear in much later lessons. Just know, you 124 | // are not crazy for thinking of caching this. 125 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 126 | 127 | // First stash the current model view matrix before we start moving around. 128 | mvPushMatrix(); 129 | 130 | mvMatrix 131 | ..translate([0.0, 0.0, z]) 132 | ..rotateX(radians(xRot)) 133 | ..rotateY(radians(yRot)); 134 | 135 | if (_blending.checked!) { 136 | gl.blendFunc(WebGL.SRC_ALPHA, WebGL.ONE); 137 | gl.enable(WebGL.BLEND); 138 | gl.disable(WebGL.DEPTH_TEST); 139 | gl.uniform1f(uAlpha, double.tryParse(_alpha.value!) ?? 1.0); 140 | } else { 141 | gl.disable(WebGL.BLEND); 142 | gl.enable(WebGL.DEPTH_TEST); 143 | } 144 | 145 | gl.uniform1i(uUseLighting, _lighting.checked! ? 1 : 0); 146 | if (_lighting.checked!) { 147 | gl.uniform3f(uAmbientColor, double.parse(_aR.value!), double.parse(_aG.value!), double.parse(_aB.value!)); 148 | 149 | // Take the lighting point and normalize / reverse it. 150 | var direction = Vector3(double.parse(_ldX.value!), double.parse(_ldY.value!), double.parse(_ldZ.value!)); 151 | direction = direction.normalize().scale(-1.0); 152 | gl.uniform3fv(uLightingDirection, direction.buf); 153 | 154 | gl.uniform3f(uDirectionalColor, double.parse(_dR.value!), double.parse(_dG.value!), double.parse(_dB.value!)); 155 | } 156 | 157 | gl.activeTexture(WebGL.TEXTURE0); 158 | gl.bindTexture(WebGL.TEXTURE_2D, texture); 159 | gl.uniform1i(uSampler, 0); 160 | 161 | cube.draw( 162 | setUniforms: setMatrixUniforms, 163 | vertex: program.attributes['aVertexPosition'], 164 | coord: program.attributes['aTextureCoord'], 165 | normal: program.attributes['aVertexNormal']); 166 | 167 | mvPopMatrix(); 168 | } 169 | 170 | double xSpeed = 3.0, ySpeed = -3.0; 171 | double xRot = 0.0, yRot = 0.0; 172 | double z = -5.0; 173 | 174 | @override 175 | void animate(double now) { 176 | if (lastTime != 0) { 177 | final elapsed = now - lastTime; 178 | 179 | xRot += (xSpeed * elapsed) / 1000.0; 180 | yRot += (ySpeed * elapsed) / 1000.0; 181 | } 182 | lastTime = now; 183 | } 184 | 185 | @override 186 | void handleKeys() { 187 | handleDirection( 188 | up: () => ySpeed -= 1.0, down: () => ySpeed += 1.0, left: () => xSpeed -= 1.0, right: () => xSpeed += 1.0); 189 | if (isActive(KeyCode.PAGE_UP)) { 190 | z -= 0.05; 191 | } 192 | if (isActive(KeyCode.PAGE_DOWN)) { 193 | z += 0.05; 194 | } 195 | } 196 | 197 | // Lighting enabled / Ambient color 198 | late InputElement _lighting, _aR, _aG, _aB; 199 | 200 | // Light position 201 | late InputElement _ldX, _ldY, _ldZ; 202 | 203 | // Directional light color 204 | late InputElement _dR, _dG, _dB; 205 | 206 | late InputElement _blending, _alpha; 207 | 208 | @override 209 | void initHtml(DivElement hook) { 210 | hook.setInnerHtml( 211 | ''' 212 | Use blending
213 | Alpha level
214 | 215 | Use lighting
216 | (Use cursor keys to spin the box and Page Up/Page Down to zoom out/in) 217 | 218 |
219 |

Directional light:

220 | 221 | 222 | 223 | 228 | 229 | 234 |
Direction: 224 | X: 225 | Y: 226 | Z: 227 |
Colour: 230 | R: 231 | G: 232 | B: 233 |
235 | 236 |

Ambient light:

237 | 238 | 239 | 244 |
Colour: 240 | R: 241 | G: 242 | B: 243 |
245 | ''', 246 | treeSanitizer: NullTreeSanitizer(), 247 | ); 248 | 249 | // Re-look up our dom elements 250 | _lighting = querySelector('#lighting') as InputElement; 251 | _aR = querySelector('#ambientR') as InputElement; 252 | _aG = querySelector('#ambientG') as InputElement; 253 | _aB = querySelector('#ambientB') as InputElement; 254 | 255 | _dR = querySelector('#directionalR') as InputElement; 256 | _dG = querySelector('#directionalG') as InputElement; 257 | _dB = querySelector('#directionalB') as InputElement; 258 | 259 | _ldX = querySelector('#lightDirectionX') as InputElement; 260 | _ldY = querySelector('#lightDirectionY') as InputElement; 261 | _ldZ = querySelector('#lightDirectionZ') as InputElement; 262 | 263 | _blending = querySelector('#blending') as InputElement; 264 | _alpha = querySelector('#alpha') as InputElement; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /web/lesson9.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Twinkle, twinkle little star... 18 | class Lesson9 extends Lesson { 19 | late GlProgram program; 20 | Texture? texture; 21 | List stars = []; 22 | 23 | bool get isLoaded => texture != null; 24 | 25 | Lesson9() { 26 | for (var i = 0; i < 50; i++) { 27 | stars.add(Star((i / 50) * 5.0, i / 50)); 28 | } 29 | loadTexture('star.gif', (Texture texture, ImageElement ele) { 30 | gl.pixelStorei(WebGL.UNPACK_FLIP_Y_WEBGL, 1); 31 | gl.bindTexture(WebGL.TEXTURE_2D, texture); 32 | gl.texImage2D( 33 | WebGL.TEXTURE_2D, 34 | 0, 35 | WebGL.RGBA, 36 | WebGL.RGBA, 37 | WebGL.UNSIGNED_BYTE, 38 | ele, 39 | ); 40 | gl.texParameteri( 41 | WebGL.TEXTURE_2D, 42 | WebGL.TEXTURE_MAG_FILTER, 43 | WebGL.LINEAR, 44 | ); 45 | gl.texParameteri( 46 | WebGL.TEXTURE_2D, 47 | WebGL.TEXTURE_MIN_FILTER, 48 | WebGL.LINEAR, 49 | ); 50 | 51 | gl.bindTexture(WebGL.TEXTURE_2D, null); 52 | this.texture = texture; 53 | }); 54 | 55 | final attributes = ['aVertexPosition', 'aTextureCoord']; 56 | final uniforms = ['uMVMatrix', 'uPMatrix', 'uColor', 'uSampler']; 57 | program = GlProgram( 58 | ''' 59 | precision mediump float; 60 | 61 | varying vec2 vTextureCoord; 62 | 63 | uniform sampler2D uSampler; 64 | 65 | uniform vec3 uColor; 66 | 67 | void main(void) { 68 | vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 69 | gl_FragColor = textureColor * vec4(uColor, 1.0); 70 | } 71 | ''', 72 | ''' 73 | attribute vec3 aVertexPosition; 74 | attribute vec2 aTextureCoord; 75 | 76 | uniform mat4 uMVMatrix; 77 | uniform mat4 uPMatrix; 78 | 79 | varying vec2 vTextureCoord; 80 | 81 | void main(void) { 82 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 83 | vTextureCoord = aTextureCoord; 84 | } 85 | ''', 86 | attributes, 87 | uniforms, 88 | ); 89 | gl.useProgram(program.program); 90 | } 91 | 92 | @override 93 | void drawScene(int viewWidth, int viewHeight, double aspect) { 94 | if (!isLoaded) return; 95 | // Basic viewport setup and clearing of the screen 96 | gl.viewport(0, 0, viewWidth, viewHeight); 97 | gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT); 98 | 99 | // Setup the perspective - you might be wondering why we do this every 100 | // time, and that will become clear in much later lessons. Just know, you 101 | // are not crazy for thinking of caching this. 102 | pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0); 103 | 104 | // First stash the current model view matrix before we start moving around. 105 | mvPushMatrix(); 106 | gl.blendFunc(WebGL.SRC_ALPHA, WebGL.ONE); 107 | gl.disable(WebGL.DEPTH_TEST); 108 | gl.enable(WebGL.BLEND); 109 | 110 | mvMatrix 111 | ..translate([0.0, 0.0, zoom]) 112 | ..rotateX(radians(tilt)); 113 | 114 | gl.activeTexture(WebGL.TEXTURE0); 115 | gl.bindTexture(WebGL.TEXTURE_2D, texture); 116 | gl.uniform1i(program.uniforms['uSampler'], 0); 117 | 118 | for (var star in stars) { 119 | star.draw( 120 | vertex: program.attributes['aVertexPosition']!, 121 | coord: program.attributes['aTextureCoord']!, 122 | color: program.uniforms['uColor'], 123 | twinkle: _twinkle.checked!, 124 | tilt: tilt, 125 | spin: spin, 126 | setUniforms: setMatrixUniforms); 127 | } 128 | mvPopMatrix(); 129 | } 130 | 131 | UniformLocation? get uPMatrix => program.uniforms['uPMatrix']; 132 | UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix']; 133 | 134 | void setMatrixUniforms() { 135 | gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf); 136 | gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf); 137 | } 138 | 139 | double tilt = 90.0; 140 | double spin = 0.0; 141 | double zoom = -15.0; 142 | 143 | @override 144 | void animate(double now) { 145 | if (lastTime != 0) { 146 | final elapsed = now - lastTime; 147 | for (var star in stars) { 148 | star.animate(elapsed); 149 | } 150 | } 151 | lastTime = now; 152 | } 153 | 154 | @override 155 | void handleKeys() { 156 | handleDirection(up: () => tilt += 2.0, down: () => tilt -= 2.0); 157 | if (isActive(KeyCode.PAGE_UP)) { 158 | zoom -= 0.1; 159 | } 160 | if (isActive(KeyCode.PAGE_DOWN)) { 161 | zoom += 0.1; 162 | } 163 | } 164 | 165 | late InputElement _twinkle; 166 | @override 167 | void initHtml(DivElement hook) { 168 | hook.setInnerHtml( 169 | ''' 170 | Twinkle
171 | (Use up/down cursor keys to rotate, and Page Up/Page Down to zoom out/in) 172 | ''', 173 | treeSanitizer: NullTreeSanitizer(), 174 | ); 175 | 176 | _twinkle = querySelector('#twinkle') as InputElement; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /web/matrix4.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unnecessary_this 2 | // Trimmed down matrix code - its best you use a well known library 3 | // instead of this code. 4 | part of learn_gl; 5 | 6 | /// Thrown if you attempt to normalize a zero length vector. 7 | class ZeroLengthVectorException implements Exception { 8 | ZeroLengthVectorException(); 9 | } 10 | 11 | /// Thrown if you attempt to invert a singular matrix. (A 12 | /// singular matrix has no inverse.) 13 | class SingularMatrixException implements Exception { 14 | SingularMatrixException(); 15 | } 16 | 17 | /// 3 dimensional vector. 18 | class Vector3 { 19 | Float32List buf; 20 | 21 | Vector3(double x, double y, double z) : buf = Float32List(3) { 22 | this.x = x; 23 | this.y = y; 24 | this.z = z; 25 | } 26 | 27 | Vector3.fromList(List list) : buf = Float32List.fromList(list); 28 | 29 | double get x => buf[0]; 30 | double get y => buf[1]; 31 | double get z => buf[2]; 32 | set x(double v) => buf[0] = v; 33 | set y(double v) => buf[1] = v; 34 | set z(double v) => buf[2] = v; 35 | 36 | double magnitude() => sqrt(x * x + y * y + z * z); 37 | 38 | Vector3 normalize() { 39 | final len = magnitude(); 40 | if (len == 0.0) { 41 | throw ZeroLengthVectorException(); 42 | } 43 | return Vector3(x / len, y / len, z / len); 44 | } 45 | 46 | Vector3 operator -() { 47 | return Vector3(-x, -y, -z); 48 | } 49 | 50 | Vector3 operator -(Vector3 other) { 51 | return Vector3(x - other.x, y - other.y, z - other.z); 52 | } 53 | 54 | Vector3 cross(Vector3 other) { 55 | final xResult = y * other.z - z * other.y; 56 | final yResult = z * other.x - x * other.z; 57 | final zResult = x * other.y - y * other.x; 58 | return Vector3(xResult, yResult, zResult); 59 | } 60 | 61 | Vector3 scale(double by) { 62 | x *= by; 63 | y *= by; 64 | z *= by; 65 | return this; 66 | } 67 | 68 | @override 69 | String toString() { 70 | return 'Vector3($x,$y,$z)'; 71 | } 72 | } 73 | 74 | const double degrees2radians = pi / 180.0; 75 | 76 | /// Convert [degrees] to radians. 77 | double radians(double degrees) { 78 | return degrees * degrees2radians; 79 | } 80 | 81 | /// A 4x4 transformation matrix (for use with webgl) 82 | /// 83 | /// We label the elements of the matrix as follows: 84 | /// 85 | /// c+ 1 2 3 4 + buff offset + Happy mRowCol 86 | /// r+_________|_____________|________________ 87 | /// 1| 1 0 0 0 | 0 4 8 12 | m00 m01 m02 m03 88 | /// 2| 0 1 0 0 | 1 5 9 13 | m10 m11 m12 m13 89 | /// 3| 0 0 1 0 | 2 6 10 14 | m20 m21 m22 m23 90 | /// 4| 0 0 0 1 | 3 7 11 15 | m30 m31 m32 m33 91 | /// 92 | /// These are stored in a 16 element [Float32List], in column major 93 | /// order, so they are ordered like this: 94 | /// 95 | /// [ m00,m10,m20,m30, m01,m11,m21,m31, m02,m12,m22,m32, m03,m13,m23,m33 ] 96 | /// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 97 | /// 98 | /// We use column major order because that is what WebGL APIs expect. 99 | /// 100 | class Matrix4 { 101 | Float32List buf; 102 | 103 | /// Constructs a Matrix4 with all entries initialized 104 | /// to zero. 105 | Matrix4() : buf = Float32List(16); 106 | 107 | /// Make a copy of another matrix. 108 | Matrix4.fromMatrix(Matrix4 other) : buf = Float32List.fromList(other.buf); 109 | 110 | Matrix4.fromBuffer(this.buf); 111 | 112 | /// returns the index into [buf] for a given 113 | /// row and column. 114 | static int rc(int row, int col) => row + col * 4; 115 | 116 | double get m00 => buf[rc(0, 0)]; 117 | double get m01 => buf[rc(0, 1)]; 118 | double get m02 => buf[rc(0, 2)]; 119 | double get m03 => buf[rc(0, 3)]; 120 | double get m10 => buf[rc(1, 0)]; 121 | double get m11 => buf[rc(1, 1)]; 122 | double get m12 => buf[rc(1, 2)]; 123 | double get m13 => buf[rc(1, 3)]; 124 | double get m20 => buf[rc(2, 0)]; 125 | double get m21 => buf[rc(2, 1)]; 126 | double get m22 => buf[rc(2, 2)]; 127 | double get m23 => buf[rc(2, 3)]; 128 | double get m30 => buf[rc(3, 0)]; 129 | double get m31 => buf[rc(3, 1)]; 130 | double get m32 => buf[rc(3, 2)]; 131 | double get m33 => buf[rc(3, 3)]; 132 | 133 | set m00(double m) { 134 | buf[rc(0, 0)] = m; 135 | } 136 | 137 | set m01(double m) { 138 | buf[rc(0, 1)] = m; 139 | } 140 | 141 | set m02(double m) { 142 | buf[rc(0, 2)] = m; 143 | } 144 | 145 | set m03(double m) { 146 | buf[rc(0, 3)] = m; 147 | } 148 | 149 | set m10(double m) { 150 | buf[rc(1, 0)] = m; 151 | } 152 | 153 | set m11(double m) { 154 | buf[rc(1, 1)] = m; 155 | } 156 | 157 | set m12(double m) { 158 | buf[rc(1, 2)] = m; 159 | } 160 | 161 | set m13(double m) { 162 | buf[rc(1, 3)] = m; 163 | } 164 | 165 | set m20(double m) { 166 | buf[rc(2, 0)] = m; 167 | } 168 | 169 | set m21(double m) { 170 | buf[rc(2, 1)] = m; 171 | } 172 | 173 | set m22(double m) { 174 | buf[rc(2, 2)] = m; 175 | } 176 | 177 | set m23(double m) { 178 | buf[rc(2, 3)] = m; 179 | } 180 | 181 | set m30(double m) { 182 | buf[rc(3, 0)] = m; 183 | } 184 | 185 | set m31(double m) { 186 | buf[rc(3, 1)] = m; 187 | } 188 | 189 | set m32(double m) { 190 | buf[rc(3, 2)] = m; 191 | } 192 | 193 | set m33(double m) { 194 | buf[rc(3, 3)] = m; 195 | } 196 | 197 | @override 198 | String toString() { 199 | final rows = []; 200 | for (var row = 0; row < 4; row++) { 201 | final items = []; 202 | for (var col = 0; col < 4; col++) { 203 | var v = buf[rc(row, col)]; 204 | if (v.abs() < 1e-16) { 205 | v = 0.0; 206 | } 207 | String display; 208 | try { 209 | display = v.toStringAsPrecision(4); 210 | } catch (e) { 211 | // TODO - remove this once toStringAsPrecision is implemented in vm 212 | display = v.toString(); 213 | } 214 | items.add(display); 215 | } 216 | rows.add("| ${items.join(", ")} |"); 217 | } 218 | return "Matrix4:\n${rows.join('\n')}"; 219 | } 220 | 221 | /// Cosntructs a Matrix4 that represents the identity transformation 222 | /// (all the diagonal entries are 1, and everything else is zero). 223 | void identity() { 224 | for (var i = 0; i < 16; i++) { 225 | buf[i] = 0.0; 226 | } 227 | m00 = 1.0; 228 | m11 = 1.0; 229 | m22 = 1.0; 230 | m33 = 1.0; 231 | } 232 | 233 | /// Constructs a Matrix4 that represents a rotation around an axis. 234 | /// 235 | /// [radians] to rotate 236 | /// [axis] direction of axis of rotation (must not be zero length) 237 | static Matrix4 rotation(double radians, Vector3 axis) { 238 | axis = axis.normalize(); 239 | 240 | final x = axis.x; 241 | final y = axis.y; 242 | final z = axis.z; 243 | final s = sin(radians); 244 | final c = cos(radians); 245 | final t = 1 - c; 246 | 247 | final m = Matrix4(); 248 | m.m00 = x * x * t + c; 249 | m.m10 = x * y * t + z * s; 250 | m.m20 = x * z * t - y * s; 251 | 252 | m.m01 = x * y * t - z * s; 253 | m.m11 = y * y * t + c; 254 | m.m21 = y * z * t + x * s; 255 | 256 | m.m02 = x * z * t + y * s; 257 | m.m12 = y * z * t - x * s; 258 | m.m22 = z * z * t + c; 259 | 260 | m.m33 = 1.0; 261 | return m; 262 | } 263 | 264 | /// Rotate this [radians] around X 265 | Matrix4 rotateX(double radians) { 266 | final c = cos(radians); 267 | final s = sin(radians); 268 | final t1 = buf[4] * c + buf[8] * s; 269 | final t2 = buf[5] * c + buf[9] * s; 270 | final t3 = buf[6] * c + buf[10] * s; 271 | final t4 = buf[7] * c + buf[11] * s; 272 | final t5 = buf[4] * -s + buf[8] * c; 273 | final t6 = buf[5] * -s + buf[9] * c; 274 | final t7 = buf[6] * -s + buf[10] * c; 275 | final t8 = buf[7] * -s + buf[11] * c; 276 | buf[4] = t1; 277 | buf[5] = t2; 278 | buf[6] = t3; 279 | buf[7] = t4; 280 | buf[8] = t5; 281 | buf[9] = t6; 282 | buf[10] = t7; 283 | buf[11] = t8; 284 | return this; 285 | } 286 | 287 | /// Rotate this matrix [radians] around Y 288 | Matrix4 rotateY(double radians) { 289 | final c = cos(radians); 290 | final s = sin(radians); 291 | final t1 = buf[0] * c + buf[8] * -s; 292 | final t2 = buf[1] * c + buf[9] * -s; 293 | final t3 = buf[2] * c + buf[10] * -s; 294 | final t4 = buf[3] * c + buf[11] * -s; 295 | final t5 = buf[0] * s + buf[8] * c; 296 | final t6 = buf[1] * s + buf[9] * c; 297 | final t7 = buf[2] * s + buf[10] * c; 298 | final t8 = buf[3] * s + buf[11] * c; 299 | buf[0] = t1; 300 | buf[1] = t2; 301 | buf[2] = t3; 302 | buf[3] = t4; 303 | buf[8] = t5; 304 | buf[9] = t6; 305 | buf[10] = t7; 306 | buf[11] = t8; 307 | return this; 308 | } 309 | 310 | /// Rotate this matrix [radians] around Z 311 | Matrix4 rotateZ(double radians) { 312 | final c = cos(radians); 313 | final s = sin(radians); 314 | final t1 = buf[0] * c + buf[4] * s; 315 | final t2 = buf[1] * c + buf[5] * s; 316 | final t3 = buf[2] * c + buf[6] * s; 317 | final t4 = buf[3] * c + buf[7] * s; 318 | final t5 = buf[0] * -s + buf[4] * c; 319 | final t6 = buf[1] * -s + buf[5] * c; 320 | final t7 = buf[2] * -s + buf[6] * c; 321 | final t8 = buf[3] * -s + buf[7] * c; 322 | buf[0] = t1; 323 | buf[1] = t2; 324 | buf[2] = t3; 325 | buf[3] = t4; 326 | buf[4] = t5; 327 | buf[5] = t6; 328 | buf[6] = t7; 329 | buf[7] = t8; 330 | return this; 331 | } 332 | 333 | /// Translates a matrix by the given vector 334 | /// 335 | /// [v] vector representing which direction to move and how much to move 336 | Matrix4 translate(List v) { 337 | final tx = v[0]; 338 | final ty = v[1]; 339 | final tz = v[2]; 340 | final tw = v.length == 4 ? v[3] : 1.0; 341 | 342 | buf[12] = buf[0] * tx + buf[4] * ty + buf[8] * tz + buf[12] * tw; 343 | buf[13] = buf[1] * tx + buf[5] * ty + buf[9] * tz + buf[13] * tw; 344 | buf[14] = buf[2] * tx + buf[6] * ty + buf[10] * tz + buf[14] * tw; 345 | buf[15] = buf[3] * tx + buf[7] * ty + buf[11] * tz + buf[15] * tw; 346 | return this; 347 | } 348 | 349 | /// returns the transpose of this matrix 350 | Matrix4 transpose() { 351 | final m = Matrix4(); 352 | for (var row = 0; row < 4; row++) { 353 | for (var col = 0; col < 4; col++) { 354 | m.buf[rc(col, row)] = this.buf[rc(row, col)]; 355 | } 356 | } 357 | return m; 358 | } 359 | 360 | /// Returns result of multiplication of this matrix 361 | /// by another matrix. 362 | /// 363 | /// In this equation: 364 | /// 365 | /// C = A * B 366 | /// 367 | /// C is the result of multiplying A * B. 368 | /// A is this matrix 369 | /// B is another matrix 370 | /// 371 | Matrix4 operator *(Matrix4 matrixB) { 372 | final matrixC = Matrix4(); 373 | final bufA = this.buf; 374 | final bufB = matrixB.buf; 375 | final bufC = matrixC.buf; 376 | for (var row = 0; row < 4; row++) { 377 | for (var col = 0; col < 4; col++) { 378 | for (var i = 0; i < 4; i++) { 379 | bufC[rc(row, col)] += bufA[rc(row, i)] * bufB[rc(i, col)]; 380 | } 381 | } 382 | } 383 | return matrixC; 384 | } 385 | 386 | /// Makse a 4x4 matrix perspective projection matrix given a field of view and 387 | /// aspect ratio. 388 | /// 389 | /// [fovyDegrees] field of view (in degrees) of the y-axis 390 | /// [aspectRatio] width to height aspect ratio. 391 | /// [zNear] distance to the near clipping plane. 392 | /// [zFar] distance to the far clipping plane. 393 | /// 394 | static Matrix4 perspective(double fovyDegrees, double aspectRatio, double zNear, double zFar) { 395 | final height = tan(radians(fovyDegrees) * 0.5) * zNear.toDouble(); 396 | final width = height * aspectRatio.toDouble(); 397 | return frustum(-width, width, -height, height, zNear, zFar); 398 | } 399 | 400 | static Matrix4 frustum(left, right, bottom, top, near, far) { 401 | final dest = Matrix4(); 402 | left = left.toDouble(); 403 | right = right.toDouble(); 404 | bottom = bottom.toDouble(); 405 | top = top.toDouble(); 406 | near = near.toDouble(); 407 | far = far.toDouble(); 408 | final two_near = 2.0 * near; 409 | final double right_minus_left = right - left; 410 | final double top_minus_bottom = top - bottom; 411 | final double far_minus_near = far - near; 412 | dest.m00 = two_near / right_minus_left; 413 | dest.m11 = two_near / top_minus_bottom; 414 | dest.m02 = (right + left) / right_minus_left; 415 | dest.m12 = (top + bottom) / top_minus_bottom; 416 | dest.m22 = -(far + near) / far_minus_near; 417 | dest.m32 = -1.0; 418 | dest.m23 = -(two_near * far) / far_minus_near; 419 | return dest; 420 | } 421 | 422 | /// Generates a orthogonal projection matrix with the given bounds 423 | /// 424 | /// @param {mat4} out mat4 frustum matrix will be written into 425 | /// @param {number} left Left bound of the frustum 426 | /// @param {number} right Right bound of the frustum 427 | /// @param {number} bottom Bottom bound of the frustum 428 | /// @param {number} top Top bound of the frustum 429 | /// @param {number} near Near bound of the frustum 430 | /// @param {number} far Far bound of the frustum 431 | /// @returns {mat4} out 432 | /// 433 | static Matrix4 ortho(left, right, bottom, top, near, far) { 434 | final out = Float32List(16); 435 | final lr = 1 / (left - right), bt = 1 / (bottom - top), nf = 1 / (near - far); 436 | out[0] = -2 * lr; 437 | out[1] = 0.0; 438 | out[2] = 0.0; 439 | out[3] = 0.0; 440 | out[4] = 0.0; 441 | out[5] = -2 * bt; 442 | out[6] = 0.0; 443 | out[7] = 0.0; 444 | out[8] = 0.0; 445 | out[9] = 0.0; 446 | out[10] = 2 * nf; 447 | out[11] = 0.0; 448 | out[12] = (left + right) * lr; 449 | out[13] = (top + bottom) * bt; 450 | out[14] = (far + near) * nf; 451 | out[15] = 1.0; 452 | return Matrix4.fromBuffer(out); 453 | } 454 | 455 | /// Returns the inverse of this matrix. 456 | Matrix4 inverse() { 457 | final a0 = m00 * m11 - m10 * m01; 458 | final a1 = m00 * m21 - m20 * m01; 459 | final a2 = m00 * m31 - m30 * m01; 460 | final a3 = m10 * m21 - m20 * m11; 461 | final a4 = m10 * m31 - m30 * m11; 462 | final a5 = m20 * m31 - m30 * m21; 463 | 464 | final b0 = m02 * m13 - m12 * m03; 465 | final b1 = m02 * m23 - m22 * m03; 466 | final b2 = m02 * m33 - m32 * m03; 467 | final b3 = m12 * m23 - m22 * m13; 468 | final b4 = m12 * m33 - m32 * m13; 469 | final b5 = m22 * m33 - m32 * m23; 470 | 471 | // compute determinant 472 | final det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0; 473 | if (det == 0) { 474 | throw SingularMatrixException(); 475 | } 476 | 477 | final m = Matrix4(); 478 | m.m00 = (m11 * b5 - m21 * b4 + m31 * b3) / det; 479 | m.m10 = (-m10 * b5 + m20 * b4 - m30 * b3) / det; 480 | m.m20 = (m13 * a5 - m23 * a4 + m33 * a3) / det; 481 | m.m30 = (-m12 * a5 + m22 * a4 - m32 * a3) / det; 482 | 483 | m.m01 = (-m01 * b5 + m21 * b2 - m31 * b1) / det; 484 | m.m11 = (m00 * b5 - m20 * b2 + m30 * b1) / det; 485 | m.m21 = (-m03 * a5 + m23 * a2 - m33 * a1) / det; 486 | m.m31 = (m02 * a5 - m22 * a2 + m32 * a1) / det; 487 | 488 | m.m02 = (m01 * b4 - m11 * b2 + m31 * b0) / det; 489 | m.m12 = (-m00 * b4 + m10 * b2 - m30 * b0) / det; 490 | m.m22 = (m03 * a4 - m13 * a2 + m33 * a0) / det; 491 | m.m32 = (-m02 * a4 + m12 * a2 - m32 * a0) / det; 492 | 493 | m.m03 = (-m01 * b3 + m11 * b1 - m21 * b0) / det; 494 | m.m13 = (m00 * b3 - m10 * b1 + m20 * b0) / det; 495 | m.m23 = (-m03 * a3 + m13 * a1 - m23 * a0) / det; 496 | m.m33 = (m02 * a3 - m12 * a1 + m22 * a0) / det; 497 | 498 | return m; 499 | } 500 | 501 | /// mat4.toInverseMat3 502 | /// Calculates the inverse of the upper 3x3 elements of a mat4 and copies the 503 | /// result into a [Matrix3]. The resulting matrix is useful for calculating 504 | /// transformed normals. 505 | /// 506 | /// Returns: 507 | /// A [Matrix3] 508 | /// 509 | Matrix3? toInverseMat3() { 510 | // Cache the matrix values (makes for huge speed increases!) 511 | final a00 = m00, a01 = m10, a02 = m20; 512 | final a10 = m01, a11 = m11, a12 = m21; 513 | final a20 = m02, a21 = m12, a22 = m22; 514 | 515 | final b01 = a22 * a11 - a12 * a21; 516 | final b11 = -a22 * a10 + a12 * a20; 517 | final b21 = a21 * a10 - a11 * a20; 518 | 519 | final d = a00 * b01 + a01 * b11 + a02 * b21; 520 | if (d == 0) { 521 | return null; 522 | } 523 | final id = 1 / d; 524 | 525 | final dest = Matrix3(); 526 | 527 | dest.m00 = b01 * id; 528 | dest.m10 = (-a22 * a01 + a02 * a21) * id; 529 | dest.m20 = (a12 * a01 - a02 * a11) * id; 530 | dest.m01 = b11 * id; 531 | dest.m11 = (a22 * a00 - a02 * a20) * id; 532 | dest.m21 = (-a12 * a00 + a02 * a10) * id; 533 | dest.m02 = b21 * id; 534 | dest.m12 = (-a21 * a00 + a01 * a20) * id; 535 | dest.m22 = (a11 * a00 - a01 * a10) * id; 536 | 537 | return dest; 538 | } 539 | 540 | static const double GLMAT_EPSILON = 0.000001; 541 | Matrix4 rotate(double rad, List axis) { 542 | // ignore: omit_local_variable_types 543 | double x = axis[0], y = axis[1], z = axis[2]; 544 | var len = sqrt(x * x + y * y + z * z); 545 | if (len.abs() < GLMAT_EPSILON) throw 'length of normal vector <~ $GLMAT_EPSILON'; 546 | if (len != 1) { 547 | len = 1 / len; 548 | x *= len; 549 | y *= len; 550 | z *= len; 551 | } 552 | final c = cos(rad); 553 | final s = sin(rad); 554 | final C = 1.0 - c; 555 | final m11 = x * x * C + c; 556 | final m12 = x * y * C - z * s; 557 | final m13 = x * z * C + y * s; 558 | final m21 = y * x * C + z * s; 559 | final m22 = y * y * C + c; 560 | final m23 = y * z * C - x * s; 561 | final m31 = z * x * C - y * s; 562 | final m32 = z * y * C + x * s; 563 | final m33 = z * z * C + c; 564 | final t1 = buf[0] * m11 + buf[4] * m21 + buf[8] * m31; 565 | final t2 = buf[1] * m11 + buf[5] * m21 + buf[9] * m31; 566 | final t3 = buf[2] * m11 + buf[6] * m21 + buf[10] * m31; 567 | final t4 = buf[3] * m11 + buf[7] * m21 + buf[11] * m31; 568 | final t5 = buf[0] * m12 + buf[4] * m22 + buf[8] * m32; 569 | final t6 = buf[1] * m12 + buf[5] * m22 + buf[9] * m32; 570 | final t7 = buf[2] * m12 + buf[6] * m22 + buf[10] * m32; 571 | final t8 = buf[3] * m12 + buf[7] * m22 + buf[11] * m32; 572 | final t9 = buf[0] * m13 + buf[4] * m23 + buf[8] * m33; 573 | final t10 = buf[1] * m13 + buf[5] * m23 + buf[9] * m33; 574 | final t11 = buf[2] * m13 + buf[6] * m23 + buf[10] * m33; 575 | final t12 = buf[3] * m13 + buf[7] * m23 + buf[11] * m33; 576 | buf[0] = t1; 577 | buf[1] = t2; 578 | buf[2] = t3; 579 | buf[3] = t4; 580 | buf[4] = t5; 581 | buf[5] = t6; 582 | buf[6] = t7; 583 | buf[7] = t8; 584 | buf[8] = t9; 585 | buf[9] = t10; 586 | buf[10] = t11; 587 | buf[11] = t12; 588 | return this; 589 | } 590 | } 591 | 592 | /// A 3x3 transformation matrix (for use with webgl) 593 | /// 594 | /// We label the elements of the matrix as follows: 595 | /// 596 | /// c+ 1 2 3 + buff off + Happy mRowCol 597 | /// r+_______|__________|_______________ 598 | /// 1| 1 0 0 | 0 3 6 | m00 m01 m02 599 | /// 2| 0 1 0 | 1 4 7 | m10 m11 m12 600 | /// 3| 0 0 1 | 2 5 8 | m20 m21 m22 601 | /// 602 | /// These are stored in a 16 element [Float32List], in column major 603 | /// order, so they are ordered like this: 604 | /// 605 | /// [ m00,m10,m20, m01,m11,m21, m02,m12,m22] 606 | /// 0 1 2 3 4 5 6 7 8 607 | /// 608 | /// We use column major order because that is what WebGL APIs expect. 609 | /// 610 | class Matrix3 { 611 | Float32List buf; 612 | 613 | Matrix3() : buf = Float32List(9); 614 | Matrix3.fromMatrix(Matrix3 other) : buf = Float32List.fromList(other.buf); 615 | 616 | /// returns the index into [buf] for a given 617 | /// row and column. 618 | static int rc(int row, int col) => row + col * 3; 619 | 620 | double get m00 => buf[rc(0, 0)]; 621 | double get m01 => buf[rc(0, 1)]; 622 | double get m02 => buf[rc(0, 2)]; 623 | double get m10 => buf[rc(1, 0)]; 624 | double get m11 => buf[rc(1, 1)]; 625 | double get m12 => buf[rc(1, 2)]; 626 | double get m20 => buf[rc(2, 0)]; 627 | double get m21 => buf[rc(2, 1)]; 628 | double get m22 => buf[rc(2, 2)]; 629 | 630 | set m00(double m) { 631 | buf[rc(0, 0)] = m; 632 | } 633 | 634 | set m01(double m) { 635 | buf[rc(0, 1)] = m; 636 | } 637 | 638 | set m02(double m) { 639 | buf[rc(0, 2)] = m; 640 | } 641 | 642 | set m10(double m) { 643 | buf[rc(1, 0)] = m; 644 | } 645 | 646 | set m11(double m) { 647 | buf[rc(1, 1)] = m; 648 | } 649 | 650 | set m12(double m) { 651 | buf[rc(1, 2)] = m; 652 | } 653 | 654 | set m20(double m) { 655 | buf[rc(2, 0)] = m; 656 | } 657 | 658 | set m21(double m) { 659 | buf[rc(2, 1)] = m; 660 | } 661 | 662 | set m22(double m) { 663 | buf[rc(2, 2)] = m; 664 | } 665 | 666 | @override 667 | String toString() { 668 | final rows = []; 669 | for (var row = 0; row < 3; row++) { 670 | final items = []; 671 | for (var col = 0; col < 3; col++) { 672 | var v = buf[rc(row, col)]; 673 | if (v.abs() < 1e-16) { 674 | v = 0.0; 675 | } 676 | String display; 677 | try { 678 | display = v.toStringAsPrecision(4); 679 | } catch (e) { 680 | // TODO - remove this once toStringAsPrecision is implemented in vm 681 | display = v.toString(); 682 | } 683 | items.add(display); 684 | } 685 | rows.add("| ${items.join(", ")} |"); 686 | } 687 | return "Matrix3:\n${rows.join('\n')}"; 688 | } 689 | 690 | /// Transposes a [Matrix3] (flips the values over the diagonal) 691 | Matrix3 transpose() { 692 | final dest = Matrix3(); 693 | for (var row = 0; row < 3; row++) { 694 | for (var col = 0; col < 3; col++) { 695 | dest.buf[rc(col, row)] = this.buf[rc(row, col)]; 696 | } 697 | } 698 | return dest; 699 | } 700 | 701 | /// Transpose ourselves 702 | /// m00 m01 m02 m00 m10 m20 703 | /// m10 m11 m12 => m01 m11 m21 704 | /// m20 m21 m22 m02 m12 m22 705 | void transposeSelf() { 706 | final a01 = m01, a02 = m02, a12 = m12; 707 | m01 = m10; 708 | m02 = m20; 709 | m10 = a01; 710 | m12 = m21; 711 | m20 = a02; 712 | m21 = a12; 713 | } 714 | } 715 | -------------------------------------------------------------------------------- /web/mcdole.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtmcdole/dart-webgl/5b0542b0c6ba54c62a459ff6096e28aa348747cd/web/mcdole.gif -------------------------------------------------------------------------------- /web/moon.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtmcdole/dart-webgl/5b0542b0c6ba54c62a459ff6096e28aa348747cd/web/moon.bmp -------------------------------------------------------------------------------- /web/nehe.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtmcdole/dart-webgl/5b0542b0c6ba54c62a459ff6096e28aa348747cd/web/nehe.gif -------------------------------------------------------------------------------- /web/pyramid.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | class Pyramid implements Renderable { 18 | late Buffer positionBuffer, normalBuffer, textureCoordBuffer; 19 | late Buffer colorBuffer; 20 | 21 | Pyramid() { 22 | positionBuffer = gl.createBuffer(); 23 | normalBuffer = gl.createBuffer(); 24 | textureCoordBuffer = gl.createBuffer(); 25 | 26 | gl.bindBuffer(WebGL.ARRAY_BUFFER, positionBuffer); 27 | final vertices = [ 28 | // Front face 29 | 0.0, 1.0, 0.0, 30 | -1.0, -1.0, 1.0, 31 | 1.0, -1.0, 1.0, 32 | 33 | // Right face 34 | 0.0, 1.0, 0.0, 35 | 1.0, -1.0, 1.0, 36 | 1.0, -1.0, -1.0, 37 | 38 | // Back face 39 | 0.0, 1.0, 0.0, 40 | 1.0, -1.0, -1.0, 41 | -1.0, -1.0, -1.0, 42 | 43 | // Left face 44 | 0.0, 1.0, 0.0, 45 | -1.0, -1.0, -1.0, 46 | -1.0, -1.0, 1.0, 47 | 48 | // NOTE: Missing the bottom triangles :) 49 | -1.0, -1.0, -1.0, 50 | 1.0, -1.0, -1.0, 51 | 1.0, -1.0, 1.0, 52 | -1.0, -1.0, -1.0, 53 | 1.0, -1.0, 1.0, 54 | -1.0, -1.0, 1.0, 55 | ]; 56 | gl.bufferData( 57 | WebGL.ARRAY_BUFFER, 58 | Float32List.fromList(vertices), 59 | WebGL.STATIC_DRAW, 60 | ); 61 | 62 | normalBuffer = gl.createBuffer(); 63 | gl.bindBuffer(WebGL.ARRAY_BUFFER, normalBuffer); 64 | final vertexNormals = [ 65 | // Front face 66 | 0.0, 0.4472135901451111, 0.8944271802902222, 67 | 0.0, 0.4472135901451111, 0.8944271802902222, 68 | 0.0, 0.4472135901451111, 0.8944271802902222, 69 | 70 | // Right face 71 | 0.8944271802902222, 0.4472135901451111, 0.0, 72 | 0.8944271802902222, 0.4472135901451111, 0.0, 73 | 0.8944271802902222, 0.4472135901451111, 0.0, 74 | 75 | // Back face 76 | 0.0, 0.4472135901451111, -0.8944271802902222, 77 | 0.0, 0.4472135901451111, -0.8944271802902222, 78 | 0.0, 0.4472135901451111, -0.8944271802902222, 79 | 80 | // Left face 81 | -0.8944271802902222, 0.4472135901451111, 0.0, 82 | -0.8944271802902222, 0.4472135901451111, 0.0, 83 | -0.8944271802902222, 0.4472135901451111, 0.0, 84 | 85 | // Bottom face - non-triangle strip 86 | 0.0, -1.0, 0.0, 87 | 0.0, -1.0, 0.0, 88 | 0.0, -1.0, 0.0, 89 | 0.0, -1.0, 0.0, 90 | 0.0, -1.0, 0.0, 91 | 0.0, -1.0, 0.0 92 | ]; 93 | gl.bufferData( 94 | WebGL.ARRAY_BUFFER, 95 | Float32List.fromList(vertexNormals), 96 | WebGL.STATIC_DRAW, 97 | ); 98 | 99 | // TODO: Come up with a better way to store color buffer vs texture buffer :) 100 | colorBuffer = gl.createBuffer(); 101 | gl.bindBuffer(WebGL.ARRAY_BUFFER, colorBuffer); 102 | final colors = [ 103 | // Front face 104 | 1.0, 0.0, 0.0, 1.0, 105 | 0.0, 1.0, 0.0, 1.0, 106 | 0.0, 0.0, 1.0, 1.0, 107 | 108 | // Right face 109 | 1.0, 0.0, 0.0, 1.0, 110 | 0.0, 0.0, 1.0, 1.0, 111 | 0.0, 1.0, 0.0, 1.0, 112 | 113 | // Back face 114 | 1.0, 0.0, 0.0, 1.0, 115 | 0.0, 1.0, 0.0, 1.0, 116 | 0.0, 0.0, 1.0, 1.0, 117 | 118 | // Left face 119 | 1.0, 0.0, 0.0, 1.0, 120 | 0.0, 0.0, 1.0, 1.0, 121 | 0.0, 1.0, 0.0, 1.0, 122 | 123 | // Bottom face 124 | 0.0, 1.0, 0.0, 1.0, 125 | 0.0, 1.0, 0.0, 1.0, 126 | 0.0, 1.0, 0.0, 1.0, 127 | 0.0, 1.0, 0.0, 1.0, 128 | 0.0, 1.0, 0.0, 1.0, 129 | 0.0, 1.0, 0.0, 1.0 130 | ]; 131 | gl.bufferData( 132 | WebGL.ARRAY_BUFFER, 133 | Float32List.fromList(colors), 134 | WebGL.STATIC_DRAW, 135 | ); 136 | 137 | // Normal discovery from a list triangles 138 | // for (int i = 0; i < vertices.length; i += 9 ) { 139 | // Vector3 p0 = Vector3(vertices[i], vertices[i+1], vertices[i+2]), 140 | // p1 = Vector3(vertices[i+3], vertices[i+4], vertices[i+5]), 141 | // p2 = Vector3(vertices[i+6], vertices[i+7], vertices[i+8]); 142 | // 143 | // Vector3 v0 = p1 - p0, v1 = p2 - p0; 144 | // Vector3 normal = v0.cross(v1).normalize(); 145 | // print("normal = $normal"); 146 | // } 147 | } 148 | 149 | @override 150 | void draw({int? vertex, int? normal, int? coord, int? color, Function()? setUniforms}) { 151 | if (vertex != null) { 152 | gl.bindBuffer(WebGL.ARRAY_BUFFER, positionBuffer); 153 | gl.vertexAttribPointer(vertex, 3, WebGL.FLOAT, false, 0, 0); 154 | } 155 | 156 | if (normal != null) { 157 | gl.bindBuffer(WebGL.ARRAY_BUFFER, normalBuffer); 158 | gl.vertexAttribPointer(normal, 3, WebGL.FLOAT, false, 0, 0); 159 | } 160 | 161 | if (coord != null) { 162 | gl.bindBuffer(WebGL.ARRAY_BUFFER, textureCoordBuffer); 163 | gl.vertexAttribPointer(coord, 2, WebGL.FLOAT, false, 0, 0); 164 | } 165 | 166 | if (color != null) { 167 | gl.bindBuffer(WebGL.ARRAY_BUFFER, colorBuffer); 168 | gl.vertexAttribPointer(color, 4, WebGL.FLOAT, false, 0, 0); 169 | } 170 | 171 | if (setUniforms != null) setUniforms(); 172 | gl.drawArrays(WebGL.TRIANGLES, 0, 18); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /web/rectangle.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | class Rectangle implements Renderable { 18 | late Buffer? positionBuffer, normalBuffer, textureCoordBuffer, colorBuffer, indexBuffer; 19 | 20 | static const List WHITE_COLOR = [ 21 | 1.0, 1.0, 1.0, 1.0, // bottom left 22 | 1.0, 1.0, 1.0, 1.0, // bottom right 23 | 1.0, 1.0, 1.0, 1.0, // top right 24 | 1.0, 1.0, 1.0, 1.0, // top left 25 | ]; 26 | 27 | Rectangle(double width, double height, {double left = 0.0, double bottom = 0.0, required Float32List vertexColors}) { 28 | positionBuffer = gl.createBuffer(); 29 | normalBuffer = gl.createBuffer(); 30 | textureCoordBuffer = gl.createBuffer(); 31 | colorBuffer = gl.createBuffer(); 32 | 33 | gl.bindBuffer(WebGL.ARRAY_BUFFER, positionBuffer); 34 | final vertices = [ 35 | left, bottom, 0.0, // bottom left 36 | left + width, bottom, 0.0, // bottom right 37 | left + width, bottom + height, 0.0, // top right 38 | left, bottom + height, 0.0, // top left 39 | ]; 40 | gl.bufferData( 41 | WebGL.ARRAY_BUFFER, 42 | Float32List.fromList(vertices), 43 | WebGL.STATIC_DRAW, 44 | ); 45 | 46 | gl.bindBuffer(WebGL.ARRAY_BUFFER, normalBuffer); 47 | final vertexNormals = [ 48 | // Front face 49 | 0.0, 0.0, 1.0, 50 | 0.0, 0.0, 1.0, 51 | 0.0, 0.0, 1.0, 52 | 0.0, 0.0, 1.0, 53 | ]; 54 | gl.bufferData( 55 | WebGL.ARRAY_BUFFER, 56 | Float32List.fromList(vertexNormals), 57 | WebGL.STATIC_DRAW, 58 | ); 59 | 60 | gl.bindBuffer(WebGL.ARRAY_BUFFER, textureCoordBuffer); 61 | final coords = [ 62 | // Front face 63 | 0.0, 0.0, 64 | 1.0, 0.0, 65 | 1.0, 1.0, 66 | 0.0, 1.0, 67 | ]; 68 | gl.bufferData( 69 | WebGL.ARRAY_BUFFER, 70 | Float32List.fromList(coords), 71 | WebGL.STATIC_DRAW, 72 | ); 73 | 74 | // TODO: Come up with a better way to store color buffer vs texture buffer :) 75 | gl.bindBuffer(WebGL.ARRAY_BUFFER, colorBuffer); 76 | var colors = WHITE_COLOR; 77 | colors = []; 78 | if (vertexColors.length == 4) { 79 | colors.addAll(vertexColors); 80 | colors.addAll(vertexColors); 81 | colors.addAll(vertexColors); 82 | colors.addAll(vertexColors); 83 | } else if (vertexColors.length == 8) { 84 | colors.addAll(vertexColors.sublist(0, 4)); 85 | colors.addAll(vertexColors.sublist(0, 4)); 86 | colors.addAll(vertexColors.sublist(4, 8)); 87 | colors.addAll(vertexColors.sublist(4, 8)); 88 | } 89 | // var colors = [ 90 | // // Front face 91 | // 1.0, 0.0, 0.0, 1.0, // bottom left 92 | // 0.0, 1.0, 0.0, 1.0, // bottom right 93 | // 0.0, 0.0, 1.0, 1.0, // top right 94 | // 1.0, 1.0, 1.0, 1.0, // top left 95 | // ]; 96 | gl.bufferData( 97 | WebGL.ARRAY_BUFFER, 98 | Float32List.fromList(colors), 99 | WebGL.STATIC_DRAW, 100 | ); 101 | 102 | indexBuffer = gl.createBuffer(); 103 | gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, indexBuffer); 104 | gl.bufferData( 105 | WebGL.ELEMENT_ARRAY_BUFFER, 106 | Uint16List.fromList([ 107 | 0, 1, 2, 0, 2, 3, // Front face 108 | ]), 109 | WebGL.STATIC_DRAW); 110 | } 111 | 112 | @override 113 | void draw({int? vertex, int? normal, int? coord, int? color, Function()? setUniforms}) { 114 | if (vertex != null) { 115 | gl.bindBuffer(WebGL.ARRAY_BUFFER, positionBuffer); 116 | gl.vertexAttribPointer(vertex, 3, WebGL.FLOAT, false, 0, 0); 117 | } 118 | 119 | if (normal != null) { 120 | gl.bindBuffer(WebGL.ARRAY_BUFFER, normalBuffer); 121 | gl.vertexAttribPointer(normal, 3, WebGL.FLOAT, false, 0, 0); 122 | } 123 | 124 | if (coord != null) { 125 | gl.bindBuffer(WebGL.ARRAY_BUFFER, textureCoordBuffer); 126 | gl.vertexAttribPointer(coord, 2, WebGL.FLOAT, false, 0, 0); 127 | } 128 | 129 | if (color != null) { 130 | gl.bindBuffer(WebGL.ARRAY_BUFFER, colorBuffer); 131 | gl.vertexAttribPointer(color, 4, WebGL.FLOAT, false, 0, 0); 132 | } 133 | 134 | if (setUniforms != null) setUniforms(); 135 | gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, indexBuffer); 136 | gl.drawElements(WebGL.TRIANGLES, 6, WebGL.UNSIGNED_SHORT, 0); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /web/renderable.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | abstract class Renderable { 18 | void draw({required int vertex, required int normal, required int coord, Function()? setUniforms}); 19 | } 20 | -------------------------------------------------------------------------------- /web/sphere.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | class Sphere implements Renderable { 18 | final int lats; 19 | final int lons; 20 | final double radius; 21 | 22 | late Buffer _positionBuffer; 23 | late Buffer _normalBuffer; 24 | late Buffer _textureCoordBuffer; 25 | late Buffer _indexBuffer; 26 | int _indexBufferSize = 0; 27 | 28 | Sphere({this.lats = 30, this.lons = 30, this.radius = 2}) { 29 | final vertexPositions = []; 30 | final normals = []; 31 | final textureCoords = []; 32 | final indexData = []; 33 | 34 | // Step 1: Generate normals, texture coordinates and vertex positions 35 | for (var lat = 0; lat <= lats; lat++) { 36 | final theta = lat * pi / lats; 37 | final sinTheta = sin(theta); 38 | final cosTheta = cos(theta); 39 | 40 | for (var lon = 0; lon <= lons; lon++) { 41 | final phi = lon * 2 * pi / lons; 42 | final sinPhi = sin(phi); 43 | final cosPhi = cos(phi); 44 | 45 | final x = cosPhi * sinTheta; 46 | final y = cosTheta; 47 | final z = sinPhi * sinTheta; 48 | final u = 1 - (lon / lons); 49 | final v = 1 - (lat / lats); 50 | 51 | normals.addAll([x, y, z]); 52 | textureCoords.addAll([u, v]); 53 | vertexPositions.addAll([radius * x, radius * y, radius * z]); 54 | } 55 | } 56 | 57 | // Step 2: Stich vertex positions together as a series of triangles. 58 | for (var lat = 0; lat < lats; lat++) { 59 | for (var lon = 0; lon < lons; lon++) { 60 | final first = (lat * (lons + 1)) + lon; 61 | final second = first + lons + 1; 62 | indexData.addAll([first, second, first + 1, second, second + 1, first + 1]); 63 | } 64 | } 65 | _indexBufferSize = indexData.length; 66 | 67 | _normalBuffer = gl.createBuffer(); 68 | gl.bindBuffer(WebGL.ARRAY_BUFFER, _normalBuffer); 69 | gl.bufferData( 70 | WebGL.ARRAY_BUFFER, 71 | Float32List.fromList(normals), 72 | WebGL.STATIC_DRAW, 73 | ); 74 | 75 | _textureCoordBuffer = gl.createBuffer(); 76 | gl.bindBuffer(WebGL.ARRAY_BUFFER, _textureCoordBuffer); 77 | gl.bufferData( 78 | WebGL.ARRAY_BUFFER, 79 | Float32List.fromList(textureCoords), 80 | WebGL.STATIC_DRAW, 81 | ); 82 | 83 | _positionBuffer = gl.createBuffer(); 84 | gl.bindBuffer(WebGL.ARRAY_BUFFER, _positionBuffer); 85 | gl.bufferData( 86 | WebGL.ARRAY_BUFFER, 87 | Float32List.fromList(vertexPositions), 88 | WebGL.STATIC_DRAW, 89 | ); 90 | 91 | _indexBuffer = gl.createBuffer(); 92 | gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, _indexBuffer); 93 | gl.bufferData( 94 | WebGL.ELEMENT_ARRAY_BUFFER, 95 | Uint16List.fromList(indexData), 96 | WebGL.STATIC_DRAW, 97 | ); 98 | } 99 | 100 | @override 101 | void draw({int? vertex, int? normal, int? coord, Function()? setUniforms}) { 102 | if (vertex != null) { 103 | gl.bindBuffer(WebGL.ARRAY_BUFFER, _positionBuffer); 104 | gl.vertexAttribPointer(vertex, 3, WebGL.FLOAT, false, 0, 0); 105 | } 106 | 107 | if (normal != null) { 108 | gl.bindBuffer(WebGL.ARRAY_BUFFER, _normalBuffer); 109 | gl.vertexAttribPointer(normal, 3, WebGL.FLOAT, false, 0, 0); 110 | } 111 | 112 | if (coord != null) { 113 | gl.bindBuffer(WebGL.ARRAY_BUFFER, _textureCoordBuffer); 114 | gl.vertexAttribPointer(coord, 2, WebGL.FLOAT, false, 0, 0); 115 | } 116 | 117 | if (setUniforms != null) setUniforms(); 118 | 119 | gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, _indexBuffer); 120 | gl.drawElements(WebGL.TRIANGLES, _indexBufferSize, WebGL.UNSIGNED_SHORT, 0); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /web/star.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, John Thomas McDole. 2 | /* 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | part of learn_gl; 16 | 17 | /// Note; not happy about this, it just a texturized rectangle that's conflated 18 | /// with particles. Needs clean up. 19 | class Star implements Renderable { 20 | static Random rand = Random(42); 21 | static bool loaded = false; 22 | static int starCount = 0; 23 | final int id = starCount++; 24 | 25 | double dist = 0; 26 | double rotationSpeed = 0; 27 | double angle = 0.0; 28 | 29 | /// Normal color 30 | double r = 0, g = 0, b = 0; 31 | 32 | /// Twinkle color 33 | double rT = 0, gT = 0, bT = 0; 34 | 35 | Star(this.dist, this.rotationSpeed) { 36 | randomizeColors(); 37 | starVertexPositionBuffer = gl.createBuffer(); 38 | gl.bindBuffer(WebGL.ARRAY_BUFFER, starVertexPositionBuffer); 39 | final vertices = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 0.0]; 40 | gl.bufferData( 41 | WebGL.ARRAY_BUFFER, 42 | Float32List.fromList(vertices), 43 | WebGL.STATIC_DRAW, 44 | ); 45 | 46 | starVertexTextureCoordBuffer = gl.createBuffer(); 47 | gl.bindBuffer(WebGL.ARRAY_BUFFER, starVertexTextureCoordBuffer); 48 | final textureCoords = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]; 49 | gl.bufferData( 50 | WebGL.ARRAY_BUFFER, 51 | Float32List.fromList(textureCoords), 52 | WebGL.STATIC_DRAW, 53 | ); 54 | } 55 | 56 | @override 57 | void draw( 58 | {required int vertex, 59 | int? normal, 60 | required int coord, 61 | UniformLocation? color, 62 | bool twinkle = false, 63 | double tilt = 0, 64 | double spin = 0, 65 | Function()? setUniforms}) { 66 | mvPushMatrix(); 67 | 68 | // Move to the star's position 69 | mvMatrix 70 | ..rotateY(radians(angle)) 71 | ..translate([dist, 0.0, 0.0]); 72 | 73 | // Rotate back so that the star is facing the viewer 74 | mvMatrix 75 | ..rotateY(radians(-angle)) 76 | ..rotateX(radians(-tilt)); 77 | 78 | if (twinkle) { 79 | // Draw a non-rotating star in the alternate "twinkling" color 80 | gl.uniform3f(color, rT, gT, bT); 81 | drawStar(vertex, normal, coord, setUniforms!); 82 | } 83 | 84 | mvMatrix.rotateZ(radians(spin)); 85 | 86 | // Draw the star in its main color 87 | gl.uniform3f(color, r, g, b); 88 | drawStar(vertex, normal, coord, setUniforms!); 89 | 90 | mvPopMatrix(); 91 | } 92 | 93 | static const double effectiveFPMS = 60 / 1000; 94 | void animate(double time) { 95 | angle += rotationSpeed * effectiveFPMS * time; 96 | 97 | // Decrease the distance, resetting the star to the outside of 98 | // the spiral if it's at the center. 99 | dist -= 0.01 * effectiveFPMS * time; 100 | if (dist < 0.0) { 101 | dist += 5.0; 102 | randomizeColors(); 103 | } 104 | } 105 | 106 | void randomizeColors() { 107 | r = rand.nextDouble(); 108 | g = rand.nextDouble(); 109 | b = rand.nextDouble(); 110 | rT = rand.nextDouble(); 111 | gT = rand.nextDouble(); 112 | bT = rand.nextDouble(); 113 | } 114 | 115 | late Buffer starVertexPositionBuffer; 116 | late Buffer starVertexTextureCoordBuffer; 117 | 118 | void drawStar(int vertex, int? normal, int coord, Function() setUniforms) { 119 | gl.bindBuffer(WebGL.ARRAY_BUFFER, starVertexTextureCoordBuffer); 120 | gl.vertexAttribPointer(coord, 2, WebGL.FLOAT, false, 0, 0); 121 | 122 | gl.bindBuffer(WebGL.ARRAY_BUFFER, starVertexPositionBuffer); 123 | gl.vertexAttribPointer(vertex, 3, WebGL.FLOAT, false, 0, 0); 124 | 125 | setUniforms(); 126 | gl.drawArrays(WebGL.TRIANGLE_STRIP, 0, 4); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /web/star.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtmcdole/dart-webgl/5b0542b0c6ba54c62a459ff6096e28aa348747cd/web/star.gif -------------------------------------------------------------------------------- /web/world.json: -------------------------------------------------------------------------------- 1 | { 2 | "vertexPositions": [-3.0, 0.0, -3.0, -3.0, 0.0, 3.0, 3.0, 0.0, 3.0, -3.0, 0.0, -3.0, 3.0, 0.0, -3.0, 3.0, 0.0, 3.0, -3.0, 1.0, -3.0, -3.0, 1.0, 3.0, 3.0, 1.0, 3.0, -3.0, 1.0, -3.0, 3.0, 1.0, -3.0, 3.0, 1.0, 3.0, -2.0, 1.0, -2.0, -2.0, 0.0, -2.0, -0.5, 0.0, -2.0, -2.0, 1.0, -2.0, -0.5, 1.0, -2.0, -0.5, 0.0, -2.0, 2.0, 1.0, -2.0, 2.0, 0.0, -2.0, 0.5, 0.0, -2.0, 2.0, 1.0, -2.0, 0.5, 1.0, -2.0, 0.5, 0.0, -2.0, -2.0, 1.0, 2.0, -2.0, 0.0, 2.0, -0.5, 0.0, 2.0, -2.0, 1.0, 2.0, -0.5, 1.0, 2.0, -0.5, 0.0, 2.0, 2.0, 1.0, 2.0, 2.0, 0.0, 2.0, 0.5, 0.0, 2.0, 2.0, 1.0, 2.0, 0.5, 1.0, 2.0, 0.5, 0.0, 2.0, -2.0, 1.0, -2.0, -2.0, 0.0, -2.0, -2.0, 0.0, -0.5, -2.0, 1.0, -2.0, -2.0, 1.0, -0.5, -2.0, 0.0, -0.5, -2.0, 1.0, 2.0, -2.0, 0.0, 2.0, -2.0, 0.0, 0.5, -2.0, 1.0, 2.0, -2.0, 1.0, 0.5, -2.0, 0.0, 0.5, 2.0, 1.0, -2.0, 2.0, 0.0, -2.0, 2.0, 0.0, -0.5, 2.0, 1.0, -2.0, 2.0, 1.0, -0.5, 2.0, 0.0, -0.5, 2.0, 1.0, 2.0, 2.0, 0.0, 2.0, 2.0, 0.0, 0.5, 2.0, 1.0, 2.0, 2.0, 1.0, 0.5, 2.0, 0.0, 0.5, -0.5, 1.0, -3.0, -0.5, 0.0, -3.0, -0.5, 0.0, -2.0, -0.5, 1.0, -3.0, -0.5, 1.0, -2.0, -0.5, 0.0, -2.0, 0.5, 1.0, -3.0, 0.5, 0.0, -3.0, 0.5, 0.0, -2.0, 0.5, 1.0, -3.0, 0.5, 1.0, -2.0, 0.5, 0.0, -2.0, -0.5, 1.0, 3.0, -0.5, 0.0, 3.0, -0.5, 0.0, 2.0, -0.5, 1.0, 3.0, -0.5, 1.0, 2.0, -0.5, 0.0, 2.0, 0.5, 1.0, 3.0, 0.5, 0.0, 3.0, 0.5, 0.0, 2.0, 0.5, 1.0, 3.0, 0.5, 1.0, 2.0, 0.5, 0.0, 2.0, -3.0, 1.0, 0.5, -3.0, 0.0, 0.5, -2.0, 0.0, 0.5, -3.0, 1.0, 0.5, -2.0, 1.0, 0.5, -2.0, 0.0, 0.5, -3.0, 1.0, -0.5, -3.0, 0.0, -0.5, -2.0, 0.0, -0.5, -3.0, 1.0, -0.5, -2.0, 1.0, -0.5, -2.0, 0.0, -0.5, 3.0, 1.0, 0.5, 3.0, 0.0, 0.5, 2.0, 0.0, 0.5, 3.0, 1.0, 0.5, 2.0, 1.0, 0.5, 2.0, 0.0, 0.5, 3.0, 1.0, -0.5, 3.0, 0.0, -0.5, 2.0, 0.0, -0.5, 3.0, 1.0, -0.5, 2.0, 1.0, -0.5, 2.0, 0.0, -0.5], 3 | "vertexTextureCoords": [0.0, 6.0, 0.0, 0.0, 6.0, 0.0, 0.0, 6.0, 6.0, 6.0, 6.0, 0.0, 0.0, 6.0, 0.0, 0.0, 6.0, 0.0, 0.0, 6.0, 6.0, 6.0, 6.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.5, 0.0, 0.0, 1.0, 1.5, 1.0, 1.5, 0.0, 2.0, 1.0, 2.0, 0.0, 0.5, 0.0, 2.0, 1.0, 0.5, 1.0, 0.5, 0.0, 2.0, 1.0, 2.0, 0.0, 0.5, 0.0, 2.0, 1.0, 0.5, 1.0, 0.5, 0.0, 2.0, 1.0, 2.0, 0.0, 0.5, 0.0, 2.0, 1.0, 0.5, 1.0, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 1.5, 0.0, 0.0, 1.0, 1.5, 1.0, 1.5, 0.0, 2.0, 1.0, 2.0, 0.0, 0.5, 0.0, 2.0, 1.0, 0.5, 1.0, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 1.5, 0.0, 0.0, 1.0, 1.5, 1.0, 1.5, 0.0, 2.0, 1.0, 2.0, 0.0, 0.5, 0.0, 2.0, 1.0, 0.5, 1.0, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0] 4 | } 5 | --------------------------------------------------------------------------------