├── madoka.png ├── checkerboard.png ├── README.md ├── shader.js ├── LICENSE ├── index.html ├── model.js ├── vec4.js ├── mat4.js └── main.js /madoka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delphifirst/js-rasterizer/HEAD/madoka.png -------------------------------------------------------------------------------- /checkerboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delphifirst/js-rasterizer/HEAD/checkerboard.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-rasterizer 2 | A software 3D rasterizer written in JavaScript. You can click https://delphifirst.github.io/js-rasterizer/ to try it in your browser directly. 3 | 4 | Notice that if you download the source code and open it in your browser locally, you may encounter cross-origin problem. Sadly there is no perfect solution to this problem. You can search on Google for more information. 5 | -------------------------------------------------------------------------------- /shader.js: -------------------------------------------------------------------------------- 1 | function vertexShader(attributes) 2 | { 3 | var position = attributes[0]; 4 | 5 | position[3] = 1; 6 | position = mat4.transform(this.worldMatrix, position); 7 | position = mat4.transform(this.viewMatrix, position); 8 | position = mat4.transform(this.projectionMatrix, position); 9 | position = mat4.transform(this.viewportMatrix, position); 10 | 11 | attributes[0] = position; 12 | 13 | return attributes; 14 | } 15 | 16 | function texture2D(textureData, u, v) 17 | { 18 | var i = Math.floor(textureData.height * v) % textureData.height; 19 | var j = Math.floor(textureData.width * u) % textureData.width; 20 | var pixelIndex = i * textureData.width * 4 + j * 4; 21 | return vec4.fromValues(textureData.data[pixelIndex], textureData.data[pixelIndex + 1], textureData.data[pixelIndex + 2], 1); 22 | } 23 | 24 | function pixelShader(varyings) 25 | { 26 | var uv = varyings[2]; 27 | var diffuse = texture2D(this.textureData, uv[0], uv[1]); 28 | return diffuse; 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Yang Cao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |15 | A software 3D rasterizer written in JavaScript. 16 | You can browse the source code at source code. 17 |
18 | 21 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /model.js: -------------------------------------------------------------------------------- 1 | /* The first element in each vertex should always be position */ 2 | 3 | var modelTriangle = { 4 | format: [3, 3, 2], // position, color, uv 5 | vertices: [ 6 | // One line for one vertex 7 | 0, 0, 0, 1, 0, 0, 0, 1, 8 | 1, 0, 0, 0, 1, 0, 1, 1, 9 | 0, 1, 0, 0, 0, 1, 0, 0, 10 | ], 11 | }; 12 | 13 | var modelCube = { 14 | format: [3, 3, 2], // position, color, uv 15 | vertices: [ 16 | // One line for one vertex 17 | -1.0, 1.0, 1.0, 1, 0, 0, 0, 1, 18 | 1.0, 1.0, 1.0, 0, 1, 0, 1, 1, 19 | 1.0, 1.0, -1.0, 0, 0, 1, 1, 0, 20 | -1.0, 1.0, 1.0, 1, 0, 0, 0, 1, 21 | 1.0, 1.0, -1.0, 0, 0, 1, 1, 0, 22 | -1.0, 1.0, -1.0, 1, 1, 0, 0, 0, 23 | 24 | -1.0, -1.0, 1.0, 0, 0, 1, 0, 0, 25 | -1.0, -1.0, -1.0, 0, 1, 0, 0, 1, 26 | 1.0, -1.0, -1.0, 1, 0, 0, 1, 1, 27 | -1.0, -1.0, 1.0, 0, 0, 1, 0, 0, 28 | 1.0, -1.0, -1.0, 1, 0, 0, 1, 1, 29 | 1.0, -1.0, 1.0, 1, 0, 1, 1, 0, 30 | 31 | -1.0, -1.0, -1.0, 0, 1, 0, 0, 1, 32 | -1.0, -1.0, 1.0, 1, 0, 0, 1, 1, 33 | -1.0, 1.0, 1.0, 0, 0, 1, 1, 0, 34 | -1.0, -1.0, -1.0, 0, 1, 0, 0, 1, 35 | -1.0, 1.0, 1.0, 0, 0, 1, 1, 0, 36 | -1.0, 1.0, -1.0, 1, 1, 1, 0, 0, 37 | 38 | 1.0, -1.0, -1.0, 1, 0, 1, 1, 1, 39 | 1.0, 1.0, -1.0, 0, 1, 0, 1, 0, 40 | 1.0, 1.0, 1.0, 1, 1, 1, 0, 0, 41 | 1.0, -1.0, -1.0, 1, 0, 1, 1, 1, 42 | 1.0, 1.0, 1.0, 1, 1, 1, 0, 0, 43 | 1.0, -1.0, 1.0, 0, 0, 1, 0, 1, 44 | 45 | -1.0, -1.0, -1.0, 1, 0, 0, 1, 1, 46 | -1.0, 1.0, -1.0, 0, 0, 1, 1, 0, 47 | 1.0, 1.0, -1.0, 1, 0, 1, 0, 0, 48 | -1.0, -1.0, -1.0, 1, 0, 0, 1, 1, 49 | 1.0, 1.0, -1.0, 1, 0, 1, 0, 0, 50 | 1.0, -1.0, -1.0, 0, 1, 0, 0, 1, 51 | 52 | -1.0, -1.0, 1.0, 0, 1, 0, 0, 1, 53 | 1.0, -1.0, 1.0, 1, 0, 1, 1, 1, 54 | 1.0, 1.0, 1.0, 0, 1, 1, 1, 0, 55 | -1.0, -1.0, 1.0, 0, 1, 0, 0, 1, 56 | 1.0, 1.0, 1.0, 0, 1, 1, 1, 0, 57 | -1.0, 1.0, 1.0, 1, 0, 0, 0, 0, 58 | ], 59 | }; 60 | -------------------------------------------------------------------------------- /vec4.js: -------------------------------------------------------------------------------- 1 | var vec4 = {}; 2 | 3 | vec4.create = function() 4 | { 5 | return new Float32Array([0, 0, 0, 0]); 6 | } 7 | 8 | vec4.fromValues = function(x, y, z, w) 9 | { 10 | return new Float32Array([x, y, z, w]); 11 | } 12 | 13 | vec4.str = function(v) 14 | { 15 | return "vec4(" + v[0] + ", " + v[1] + ", " + v[2] + ", " + v[3] + ")"; 16 | } 17 | 18 | vec4.len = function(v) 19 | { 20 | return Math.sqrt(vec4.sqrLen(v)); 21 | } 22 | 23 | vec4.sqrLen = function(v) 24 | { 25 | return vec4.dot(v, v); 26 | } 27 | 28 | vec4.neg = function(v) 29 | { 30 | return[-v[0], -v[1], -v[2], -v[3]]; 31 | } 32 | 33 | vec4.normalized = function(v) 34 | { 35 | len = vec4.len(v); 36 | if(len == 0) 37 | return vec4.create(); 38 | else 39 | return vec4.scale(1 / len, v); 40 | } 41 | 42 | vec4.add = function(v1, v2) 43 | { 44 | return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2], v1[3] + v2[3]]; 45 | } 46 | 47 | vec4.sub = function(v1, v2) 48 | { 49 | return vec4.add(v1, vec4.neg(v2)); 50 | } 51 | 52 | vec4.mul = function(v1, v2) 53 | { 54 | return [v1[0] * v2[0], v1[1] * v2[1], v1[2] * v2[2], v1[3] * v2[3]]; 55 | } 56 | 57 | vec4.scale = function(a, v) 58 | { 59 | return [a * v[0], a * v[1], a * v[2], a * v[3]]; 60 | } 61 | 62 | vec4.dot = function(v1, v2) 63 | { 64 | return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2] + v1[3] * v2[3]; 65 | } 66 | 67 | vec4.cross = function(v1, v2) 68 | { 69 | return [v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2], v1[0] * v2[1] - v1[1] * v2[0], 0]; 70 | } 71 | 72 | vec4.exactEquals = function(v1, v2) 73 | { 74 | return v1[0] === v2[0] && v1[1] === v2[1] && v1[2] === v2[2] && v1[3] === v2[3]; 75 | } 76 | 77 | vec4.equals = function(v1, v2) 78 | { 79 | return vec4.len(vec4.sub(v1, v2)) < 0.000001; 80 | } 81 | 82 | vec4.unitTest = function() 83 | { 84 | var a = vec4.create(); 85 | a[0] = 1; 86 | a[3] = 2; 87 | var b = vec4.fromValues(1, 2, 3, 4); 88 | console.log("vec4.len: " + vec4.str(b) + " -> " + vec4.len(b)); 89 | console.log("vec4.sqrLen: " + vec4.str(b) + " -> " + vec4.sqrLen(b)); 90 | console.log("vec4.neg: " + vec4.str(b) + " -> " + vec4.str(vec4.neg(b))); 91 | console.log("vec4.normalized: " + vec4.str(b) + " -> " + vec4.str(vec4.normalized(b))); 92 | console.log("vec4.add: " + vec4.str(a) + ", " + vec4.str(b) + " -> " + vec4.str(vec4.add(a, b))); 93 | console.log("vec4.sub: " + vec4.str(a) + ", " + vec4.str(b) + " -> " + vec4.str(vec4.sub(a, b))); 94 | console.log("vec4.mul: " + vec4.str(a) + ", " + vec4.str(b) + " -> " + vec4.str(vec4.mul(a, b))); 95 | console.log("vec4.scale: 2, " + vec4.str(b) + " -> " + vec4.str(vec4.scale(2, b))); 96 | console.log("vec4.dot: " + vec4.str(a) + ", " + vec4.str(b) + " -> " + vec4.dot(a, b)); 97 | console.log("vec4.cross: " + vec4.str(a) + ", " + vec4.str(b) + " -> " + vec4.str(vec4.cross(a, b))); 98 | console.log("vec4.exactEquals: " + vec4.str(a) + ", " + vec4.str(b) + " -> " + vec4.exactEquals(a, b)); 99 | console.log("vec4.equals: " + vec4.str(a) + ", " + vec4.str(b) + " -> " + vec4.equals(a, b)); 100 | } 101 | -------------------------------------------------------------------------------- /mat4.js: -------------------------------------------------------------------------------- 1 | mat4 = {} 2 | 3 | mat4.create = function() 4 | { 5 | return new Float32Array([0, 0, 0, 0, 6 | 0, 0, 0, 0, 7 | 0, 0, 0, 0, 8 | 0, 0, 0, 0]); 9 | } 10 | 11 | mat4.fromCols = function(v1, v2, v3, v4) 12 | { 13 | return new Float32Array([v1[0], v2[0], v3[0], v4[0], 14 | v1[1], v2[1], v3[1], v4[1], 15 | v1[2], v2[2], v3[2], v4[2], 16 | v1[3], v2[3], v3[3], v4[3]]); 17 | } 18 | 19 | mat4.fromRows = function(v1, v2, v3, v4) 20 | { 21 | return mat4.transposed(mat4.fromCols(v1, v2, v3, v4)); 22 | } 23 | 24 | mat4.fromScale = function(scale) 25 | { 26 | return new Float32Array([scale, 0, 0, 0, 27 | 0, scale, 0, 0, 28 | 0, 0, scale, 0, 29 | 0, 0, 0, 1]); 30 | } 31 | 32 | mat4.fromTranslation = function(v) 33 | { 34 | return new Float32Array([1, 0, 0, v[0], 35 | 0, 1, 0, v[1], 36 | 0, 0, 1, v[2], 37 | 0, 0, 0, 1]); 38 | } 39 | 40 | mat4.fromRotationX = function(angle) 41 | { 42 | return new Float32Array([1, 0, 0, 0, 43 | 0, Math.cos(angle), -Math.sin(angle), 0, 44 | 0, Math.sin(angle), Math.cos(angle), 0, 45 | 0, 0, 0, 1]); 46 | } 47 | 48 | mat4.fromRotationY = function(angle) 49 | { 50 | return new Float32Array([Math.cos(angle), 0, Math.sin(angle), 0, 51 | 0, 1, 0, 0, 52 | -Math.sin(angle), 0, Math.cos(angle), 0, 53 | 0, 0, 0, 1]); 54 | } 55 | 56 | mat4.fromRotationZ = function(angle) 57 | { 58 | return new Float32Array([Math.cos(angle), -Math.sin(angle), 0, 0, 59 | Math.sin(angle), Math.cos(angle), 0, 0, 60 | 0, 0, 1, 0, 61 | 0, 0, 0, 1]); 62 | } 63 | 64 | mat4.lookAt = function(eye, center, up) 65 | { 66 | var w = vec4.neg(vec4.normalized(vec4.sub(center, eye))); 67 | var u = vec4.normalized(vec4.cross(up, w)); 68 | var v = vec4.cross(w, u); 69 | return mat4.invert(mat4.fromCols(u, v, w, eye)); 70 | } 71 | 72 | mat4.identity = function() 73 | { 74 | return new Float32Array([1, 0, 0, 0, 75 | 0, 1, 0, 0, 76 | 0, 0, 1, 0, 77 | 0, 0, 0, 1]); 78 | } 79 | 80 | mat4.viewport = function(nx, ny, near, far) 81 | { 82 | return new Float32Array([nx / 2, 0, 0, (nx - 1) / 2, 83 | 0, ny / 2, 0, (ny - 1) / 2, 84 | 0, 0, (far - near) / 2, (far + near) / 2, 85 | 0, 0, 0, 1]); 86 | } 87 | 88 | mat4.perspective = function(fovy, aspect, near, far) 89 | { 90 | var f = 1.0 / Math.tan(fovy / 2), nf = 1 / (near - far); 91 | return new Float32Array([f / aspect, 0, 0, 0, 92 | 0, f, 0, 0, 93 | 0, 0, (far + near) * nf, (2 * far * near) * nf, 94 | 0, 0, -1, 0]); 95 | } 96 | 97 | mat4.str = function(m) 98 | { 99 | var result = "mat4(\n"; 100 | for(var i = 0; i < 4; ++i) 101 | { 102 | for(var j = 0; j < 4; ++j) 103 | { 104 | result += mat4.get(m, i, j) + ","; 105 | if(j != 3) 106 | result += "\t"; 107 | } 108 | if(i != 3) 109 | result += "\n"; 110 | } 111 | result += ")"; 112 | return result; 113 | } 114 | 115 | mat4.get = function(m, row, col) 116 | { 117 | return m[4 * row + col]; 118 | } 119 | 120 | mat4.getCol = function(m, col) 121 | { 122 | return vec4.fromValues(m[col], m[col + 4], m[col + 8], m[col + 12]); 123 | } 124 | 125 | mat4.getRow = function(m, row) 126 | { 127 | return vec4.fromValues(m[4 * row], m[4 * row + 1], m[4 * row + 2], m[4 * row + 3]); 128 | } 129 | 130 | mat4.set = function(m, row, col, val) 131 | { 132 | m[4 * row + col] = val; 133 | } 134 | 135 | mat4.transposed = function(m) 136 | { 137 | return new Float32Array([m[0], m[4], m[8], m[12], 138 | m[1], m[5], m[9], m[13], 139 | m[2], m[6], m[10], m[14], 140 | m[3], m[7], m[11], m[15]]); 141 | } 142 | 143 | mat4.neg = function(m) 144 | { 145 | var result = mat4.create(); 146 | for(var i = 0; i < 16; ++i) 147 | result[i] = -m[i]; 148 | return result; 149 | } 150 | 151 | mat4.add = function(m1, m2) 152 | { 153 | var result = mat4.create(); 154 | for(var i = 0; i < 16; ++i) 155 | result[i] = m1[i] + m2[i]; 156 | return result; 157 | } 158 | 159 | mat4.sub = function(m1, m2) 160 | { 161 | return mat4.add(m1, mat4.neg(m2)); 162 | } 163 | 164 | mat4.elemMul = function(m1, m2) 165 | { 166 | var result = mat4.create(); 167 | for(var i = 0; i < 16; ++i) 168 | result[i] = m1[i] * m2[i]; 169 | return result; 170 | } 171 | 172 | mat4.scale = function(a, m) 173 | { 174 | var result = mat4.create(); 175 | for(var i = 0; i < 16; ++i) 176 | result[i] = a * m[i]; 177 | return result; 178 | } 179 | 180 | mat4.mul = function(m1, m2) 181 | { 182 | var result = mat4.create(); 183 | for(var i = 0; i < 4; ++i) 184 | for(var j = 0; j < 4; ++j) 185 | mat4.set(result, i, j, vec4.dot(mat4.getRow(m1, i), mat4.getCol(m2, j))); 186 | return result; 187 | } 188 | 189 | mat4.cofactor = function(m, row, col) 190 | { 191 | var sign = (row + col) % 2 === 0 ? 1 : -1; 192 | var remainMat = new Array(9); 193 | for(var i = 0, srcRow = 0; i < 3; ++srcRow) 194 | { 195 | if(srcRow === row) 196 | continue; 197 | for(var j = 0, srcCol = 0; j < 3; ++srcCol) 198 | { 199 | if(srcCol === col) 200 | continue; 201 | remainMat[3 * i + j] = mat4.get(m, srcRow, srcCol); 202 | ++j; 203 | } 204 | ++i; 205 | } 206 | var remainMatDet = remainMat[0] * remainMat[4] * remainMat[8] 207 | + remainMat[2] * remainMat[3] * remainMat[7] 208 | + remainMat[6] * remainMat[1] * remainMat[5] 209 | - remainMat[2] * remainMat[4] * remainMat[6] 210 | - remainMat[0] * remainMat[5] * remainMat[7] 211 | - remainMat[8] * remainMat[3] * remainMat[1]; 212 | return sign * remainMatDet; 213 | } 214 | 215 | mat4.det = function(m) 216 | { 217 | var result = 0; 218 | for(var i = 0; i < 4; ++i) 219 | result += mat4.get(m, i, 0) * mat4.cofactor(m, i, 0); 220 | return result; 221 | } 222 | 223 | mat4.invert = function(m) 224 | { 225 | var cofactorMatrix = mat4.create(); 226 | for(var i = 0; i < 4; ++i) 227 | for(var j = 0; j < 4; ++j) 228 | mat4.set(cofactorMatrix, i, j, mat4.cofactor(m, i, j)); 229 | cofactorMatrix = mat4.transposed(cofactorMatrix); 230 | return mat4.scale(1 / mat4.det(m), cofactorMatrix); 231 | } 232 | 233 | mat4.transform = function(m, v) 234 | { 235 | var result = vec4.create(); 236 | for(var i = 0; i < 4; ++i) 237 | result[i] = vec4.dot(mat4.getRow(m, i), v); 238 | return result; 239 | } 240 | 241 | mat4.unitTest = function() 242 | { 243 | var m = mat4.fromRows(vec4.fromValues(1, 2, 0, 4), 244 | vec4.fromValues(5, 1, 2, 8), 245 | vec4.fromValues(1, 3, 4, 2), 246 | vec4.fromValues(3, 1, 5, 9)); 247 | console.log("mat4.getRow: " + mat4.str(m) + ", 2 -> " + vec4.str(mat4.getRow(m, 2))) 248 | console.log("mat4.getCol: " + mat4.str(m) + ", 2 -> " + vec4.str(mat4.getCol(m, 2))) 249 | console.log("mat4.transposed: " + mat4.str(m) + " -> " + mat4.str(mat4.transposed(m))); 250 | console.log("mat4.neg: " + mat4.str(m) + " -> " + mat4.str(mat4.neg(m))); 251 | console.log("mat4.add: " + mat4.str(m) + ", " + mat4.str(m) + " -> " + mat4.str(mat4.add(m, m))); 252 | console.log("mat4.sub: " + mat4.str(m) + ", " + mat4.str(m) + " -> " + mat4.str(mat4.sub(m, m))); 253 | console.log("mat4.elemMul: " + mat4.str(m) + ", " + mat4.str(m) + " -> " + mat4.str(mat4.elemMul(m, m))); 254 | console.log("mat4.mul: " + mat4.str(m) + ", " + mat4.str(m) + " -> " + mat4.str(mat4.mul(m, m))); 255 | console.log("mat4.scale: 2, " + mat4.str(m) + " -> " + mat4.str(mat4.scale(2, m))); 256 | console.log("mat4.cofactor: " + mat4.str(m) + ", 1, 2 -> " + mat4.cofactor(m, 1, 2)); 257 | console.log("mat4.det: " + mat4.str(m) + " -> " + mat4.det(m)); 258 | console.log("mat4.invert: " + mat4.str(m) + " -> " + mat4.str(mat4.invert(m))); 259 | } 260 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var debug = false; 2 | var debugLine = true; 3 | var debugPixelSize = 20; 4 | 5 | var width, height; 6 | 7 | var canvas; 8 | var canvasWidth, canvasHeight; 9 | var canvasContext; 10 | var canvasImageData; 11 | 12 | var totalTime = 0; 13 | 14 | var renderState = { 15 | framebuffer: undefined, 16 | depthbuffer: undefined, 17 | vertexShader: undefined, 18 | pixelShader: undefined, 19 | textureData: { 20 | width: 0, 21 | height: 0, 22 | data: null, 23 | }, 24 | viewportMatrix: mat4.identity(), 25 | projectionMatrix: mat4.identity(), 26 | viewMatrix: mat4.identity(), 27 | worldMatrix: mat4.identity(), 28 | }; 29 | 30 | function clearFramebuffer() 31 | { 32 | for(var i = 0; i < height * width * 4; ++i) 33 | renderState.framebuffer[i] = 0; 34 | } 35 | 36 | function clearDepthbuffer() 37 | { 38 | for(var i = 0; i < height * width; ++i) 39 | renderState.depthbuffer[i] = 1; 40 | } 41 | 42 | function drawPixel(x, y, z, r, g, b) 43 | { 44 | var i = Math.floor(height - y - 0.5); 45 | if(i >= 0 && i < height) 46 | { 47 | var j = Math.floor(x + 0.5); 48 | if(j >= 0 && j < width) 49 | { 50 | var depthPixelIndex = i * width + j; 51 | if(z < renderState.depthbuffer[depthPixelIndex]) 52 | { 53 | // Pass depth test 54 | renderState.depthbuffer[depthPixelIndex] = z; 55 | var pixelStartIndex = i * width * 4 + j * 4; 56 | renderState.framebuffer[pixelStartIndex] = r; 57 | renderState.framebuffer[pixelStartIndex + 1] = g; 58 | renderState.framebuffer[pixelStartIndex + 2] = b; 59 | } 60 | } 61 | } 62 | } 63 | 64 | function isBackFace(v1, v2, v3) 65 | { 66 | var p = vec4.sub(v2, v1); 67 | var q = vec4.sub(v3, v1); 68 | return vec4.cross(p, q)[2] < 0; 69 | } 70 | 71 | function drawTriangle(vertex1, vertex2, vertex3) 72 | { 73 | // The first element in each vertex is always position 74 | // Here the vertex position is homogenized 75 | var w1 = vertex1[0][3], w2 = vertex2[0][3], w3 = vertex3[0][3]; 76 | 77 | var v1h = vec4.scale(1 / w1, vertex1[0]); 78 | var v2h = vec4.scale(1 / w2, vertex2[0]); 79 | var v3h = vec4.scale(1 / w3, vertex3[0]); 80 | 81 | if(isBackFace(v1h, v2h, v3h)) 82 | return; 83 | 84 | var minX = Math.min(v1h[0], v2h[0], v3h[0]); 85 | var maxX = Math.max(v1h[0], v2h[0], v3h[0]); 86 | var minY = Math.min(v1h[1], v2h[1], v3h[1]); 87 | var maxY = Math.max(v1h[1], v2h[1], v3h[1]); 88 | 89 | var varyingCount = vertex1.length; 90 | 91 | function f12(x, y) 92 | { 93 | return (v1h[1] - v2h[1]) * x 94 | + (v2h[0] - v1h[0]) * y 95 | + v1h[0] * v2h[1] - v2h[0] * v1h[1]; 96 | } 97 | 98 | function f23(x, y) 99 | { 100 | return (v2h[1] - v3h[1]) * x 101 | + (v3h[0] - v2h[0]) * y 102 | + v2h[0] * v3h[1] - v3h[0] * v2h[1]; 103 | } 104 | 105 | function f31(x, y) 106 | { 107 | return (v3h[1] - v1h[1]) * x 108 | + (v1h[0] - v3h[0]) * y 109 | + v3h[0] * v1h[1] - v1h[0] * v3h[1]; 110 | } 111 | 112 | var startY = Math.floor(minY), startX = Math.floor(minX); 113 | var endY = Math.ceil(maxY), endX = Math.ceil(maxX); 114 | for(var y = startY; y <= endY; ++y) 115 | for(var x = startX; x <= endX; ++x) 116 | { 117 | var alpha = f23(x, y) / f23(v1h[0], v1h[1]); 118 | var beta = f31(x, y) / f31(v2h[0], v2h[1]); 119 | var gamma = f12(x, y) / f12(v3h[0], v3h[1]); 120 | 121 | if(alpha > 0 && beta > 0 && gamma > 0) 122 | { 123 | // Interpolate attributes 124 | var oneOverW = alpha / w1 + beta / w2 + gamma / w3; 125 | var varyings = new Array(varyingCount); 126 | for(var varyingIndex = 0; varyingIndex < varyingCount; ++varyingIndex) 127 | { 128 | varyings[varyingIndex] = vec4.scale(1 / oneOverW, vec4.add( 129 | vec4.scale(alpha / w1, vertex1[varyingIndex]), 130 | vec4.add(vec4.scale(beta / w2, vertex2[varyingIndex]), 131 | vec4.scale(gamma / w3, vertex3[varyingIndex])) 132 | )); 133 | } 134 | 135 | var color = renderState.pixelShader(varyings); 136 | drawPixel(x, y, varyings[0][2] / varyings[0][3], color[0], color[1], color[2]); 137 | } 138 | } 139 | } 140 | 141 | function draw(format, vertexBuffer) 142 | { 143 | var vertexSize = format.reduce(function(previousValue, currentValue){return previousValue + currentValue;}); 144 | var vertexCount = vertexBuffer.length / vertexSize; 145 | var processedVertexBuffer = new Array(vertexCount); 146 | for(var vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) 147 | { 148 | // Process vertex one by one 149 | var attributes = new Array(format.length); 150 | for(var attributeIndex = 0, bufferIndex = 0; attributeIndex < attributes.length; ++attributeIndex) 151 | { 152 | var attribute = vec4.create(); 153 | for(var i = 0; i < format[attributeIndex]; ++i) 154 | { 155 | attribute[i] = vertexBuffer[vertexIndex * vertexSize + bufferIndex]; 156 | ++bufferIndex; 157 | } 158 | attributes[attributeIndex] = attribute; 159 | } 160 | processedVertexBuffer[vertexIndex] = renderState.vertexShader(attributes); 161 | } 162 | 163 | var triangleCount = vertexCount / 3; 164 | 165 | for(var triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex) 166 | { 167 | var v1 = processedVertexBuffer[3 * triangleIndex]; 168 | var v2 = processedVertexBuffer[3 * triangleIndex + 1]; 169 | var v3 = processedVertexBuffer[3 * triangleIndex + 2]; 170 | 171 | drawTriangle(v1, v2, v3); 172 | } 173 | } 174 | 175 | function drawScene(deltaTime) 176 | { 177 | clearFramebuffer(); 178 | clearDepthbuffer(); 179 | renderState.vertexShader = vertexShader; 180 | renderState.pixelShader = pixelShader; 181 | renderState.viewportMatrix = mat4.viewport(width, height, 0, 1); 182 | var rotationMatrixX = mat4.fromRotationX(0.2 * totalTime); 183 | var rotationMatrixY = mat4.fromRotationY(0.3 * totalTime); 184 | var rotationMatrixZ = mat4.fromRotationZ(0.1 * totalTime); 185 | renderState.worldMatrix = mat4.mul(rotationMatrixX, mat4.mul(rotationMatrixY, rotationMatrixZ)); 186 | renderState.viewMatrix = mat4.lookAt(vec4.fromValues(2, 1.5, 2, 1), vec4.fromValues(0, 0, 0, 1), vec4.fromValues(0, 1, 0, 0)); 187 | renderState.projectionMatrix = mat4.perspective(Math.PI / 2, width / height, 1, 100); 188 | draw(modelCube.format, modelCube.vertices); 189 | } 190 | 191 | function render(deltaTime) 192 | { 193 | drawScene(deltaTime); 194 | 195 | if(debug) 196 | { 197 | for(var i = 0; i < height; ++i) 198 | for(var j = 0; j < width; ++j) 199 | { 200 | var pixelStartIndex = i * width * 4 + j * 4; 201 | r = Math.floor(255 * renderState.framebuffer[pixelStartIndex]); 202 | g = Math.floor(255 * renderState.framebuffer[pixelStartIndex + 1]); 203 | b = Math.floor(255 * renderState.framebuffer[pixelStartIndex + 2]); 204 | canvasContext.fillStyle = "rgb(" + r + "," + g + "," + b + ")"; 205 | canvasContext.fillRect(j * debugPixelSize, i * debugPixelSize, debugPixelSize, debugPixelSize); 206 | } 207 | 208 | if(debugLine) 209 | { 210 | canvasContext.beginPath(); 211 | canvasContext.lineWidth = 2; 212 | canvasContext.strokeStyle = "rgb(0, 128, 255)"; 213 | for(var i = 0; i <= height; ++i) 214 | { 215 | canvasContext.moveTo(0, i * debugPixelSize); 216 | canvasContext.lineTo(width * debugPixelSize, i * debugPixelSize); 217 | } 218 | for(var i = 0; i <= width; ++i) 219 | { 220 | canvasContext.moveTo(i * debugPixelSize, 0); 221 | canvasContext.lineTo(i * debugPixelSize, height * debugPixelSize); 222 | } 223 | canvasContext.stroke(); 224 | } 225 | } 226 | else 227 | { 228 | var data = canvasImageData.data; 229 | 230 | for(var pixelIndex = 0; pixelIndex < data.length;) 231 | { 232 | data[pixelIndex] = 255 * renderState.framebuffer[pixelIndex]; 233 | ++pixelIndex; 234 | data[pixelIndex] = 255 * renderState.framebuffer[pixelIndex]; 235 | ++pixelIndex; 236 | data[pixelIndex] = 255 * renderState.framebuffer[pixelIndex]; 237 | ++pixelIndex; 238 | data[pixelIndex] = 255; 239 | ++pixelIndex; 240 | } 241 | canvasContext.putImageData(canvasImageData, 0, 0); 242 | } 243 | } 244 | 245 | function initFramebufferDepthbuffer() 246 | { 247 | if(debug) 248 | { 249 | width = Math.floor(canvasWidth / debugPixelSize); 250 | height = Math.floor(canvasHeight / debugPixelSize); 251 | } 252 | else 253 | { 254 | width = canvasWidth; 255 | height = canvasHeight; 256 | } 257 | renderState.framebuffer = new Float32Array(width * height * 4); 258 | for(var i = 0; i < renderState.framebuffer.length; ++i) 259 | renderState.framebuffer[i] = 0; 260 | renderState.depthbuffer = new Float32Array(width * height); 261 | for(var i = 0; i < renderState.depthbuffer.length; ++i) 262 | renderState.depthbuffer[i] = 0; 263 | } 264 | 265 | function changeTexture(textureName) 266 | { 267 | var img = document.getElementById(textureName); 268 | var textureCanvas = document.getElementById("texture-canvas"); 269 | var context = textureCanvas.getContext("2d"); 270 | context.drawImage(img, 0, 0); 271 | var textureImageData = context.getImageData(0, 0, 272 | textureCanvas.attributes.width.value, textureCanvas.attributes.height.value); 273 | renderState.textureData.width = textureImageData.width; 274 | renderState.textureData.height = textureImageData.height; 275 | renderState.textureData.data = new Float32Array(textureImageData.data.length); 276 | for(var i = 0; i < textureImageData.data.length; ++i) 277 | renderState.textureData.data[i] = textureImageData.data[i] / 255; 278 | } 279 | 280 | function init() 281 | { 282 | canvas = document.getElementById("raster-canvas"); 283 | canvasWidth = canvas.attributes.width.value; 284 | canvasHeight = canvas.attributes.height.value; 285 | canvasContext = canvas.getContext("2d"); 286 | canvasImageData = canvasContext.createImageData(canvasWidth, canvasHeight); 287 | 288 | initFramebufferDepthbuffer(); 289 | changeTexture("texture-madoka"); 290 | var checkboxDebug = document.getElementById("checkbox-debug"); 291 | checkboxDebug.checked = debug; 292 | var checkboxDebugLine = document.getElementById("checkbox-debug-line"); 293 | checkboxDebugLine.checked = debugLine; 294 | var lastUpdateTime = (new Date).getTime(); 295 | var lastUpdateFpsTime = lastUpdateTime; 296 | var frameCount = 0; 297 | setInterval(function(){ 298 | var currentTime = (new Date).getTime(); 299 | var deltaTime = (currentTime - lastUpdateTime) / 1000; 300 | lastUpdateTime = currentTime; 301 | if(currentTime - lastUpdateFpsTime > 1000) 302 | { 303 | var labelFps = document.getElementById("label-fps"); 304 | labelFps.innerHTML = "FPS: " + frameCount; 305 | lastUpdateFpsTime = currentTime; 306 | frameCount = 0; 307 | } 308 | totalTime += deltaTime; 309 | render(deltaTime); 310 | ++frameCount; 311 | }, 1000 / 60); 312 | } 313 | 314 | function toggleDebug() 315 | { 316 | var checkboxDebug = document.getElementById("checkbox-debug"); 317 | debug = checkboxDebug.checked; 318 | canvasContext.fillStyle = "rgb(255,255,255)"; 319 | canvasContext.fillRect(0, 0, canvasWidth, canvasHeight); 320 | initFramebufferDepthbuffer(); 321 | } 322 | 323 | function toggleDebugLine() 324 | { 325 | var checkboxDebugLine = document.getElementById("checkbox-debug-line"); 326 | debugLine = checkboxDebugLine.checked; 327 | } 328 | 329 | function start() 330 | { 331 | init(); 332 | } 333 | --------------------------------------------------------------------------------