├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── README_EN.md ├── assets ├── box-textured │ ├── BoxTextured.gltf │ ├── BoxTextured0.bin │ └── CesiumLogoFlat.png ├── cornell-box.gltf ├── cube │ ├── cube.bin │ └── cube.gltf ├── monkey │ └── monkey.gltf ├── sphere │ ├── sphere.bin │ └── sphere.gltf └── suzanne │ ├── Suzanne.bin │ ├── Suzanne.gltf │ ├── Suzanne_BaseColor.png │ └── Suzanne_MetallicRoughness.png ├── examples ├── bresenham_line.rs └── rendering.rs ├── screenshots ├── blinn_phong_color.png ├── blinn_phong_texture.png ├── bresenham_line.png ├── texture_mapping.png ├── vertex_color_interpolation.png └── wireframe_rendering.png └── src ├── camera.rs ├── color.rs ├── lib.rs ├── light.rs ├── loader.rs ├── material.rs ├── math.rs ├── mesh.rs ├── renderer.rs ├── shader.rs ├── texture.rs ├── transform.rs └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "base64" 19 | version = "0.13.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "1.3.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "2.3.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" 34 | 35 | [[package]] 36 | name = "bytemuck" 37 | version = "1.13.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" 40 | 41 | [[package]] 42 | name = "byteorder" 43 | version = "1.4.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 46 | 47 | [[package]] 48 | name = "cc" 49 | version = "1.0.79" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 52 | 53 | [[package]] 54 | name = "cfg-if" 55 | version = "1.0.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 58 | 59 | [[package]] 60 | name = "cmake" 61 | version = "0.1.50" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" 64 | dependencies = [ 65 | "cc", 66 | ] 67 | 68 | [[package]] 69 | name = "color_quant" 70 | version = "1.1.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 73 | 74 | [[package]] 75 | name = "crc32fast" 76 | version = "1.3.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 79 | dependencies = [ 80 | "cfg-if", 81 | ] 82 | 83 | [[package]] 84 | name = "crossbeam-channel" 85 | version = "0.5.8" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 88 | dependencies = [ 89 | "cfg-if", 90 | "crossbeam-utils", 91 | ] 92 | 93 | [[package]] 94 | name = "crossbeam-utils" 95 | version = "0.8.15" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" 98 | dependencies = [ 99 | "cfg-if", 100 | ] 101 | 102 | [[package]] 103 | name = "fdeflate" 104 | version = "0.3.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" 107 | dependencies = [ 108 | "simd-adler32", 109 | ] 110 | 111 | [[package]] 112 | name = "flate2" 113 | version = "1.0.26" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" 116 | dependencies = [ 117 | "crc32fast", 118 | "miniz_oxide", 119 | ] 120 | 121 | [[package]] 122 | name = "fltk" 123 | version = "1.4.4" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "b45b2b62bcda7954d1843e51b1f823d12449ec6832e7e4ade870769676ed0541" 126 | dependencies = [ 127 | "bitflags 2.3.1", 128 | "crossbeam-channel", 129 | "fltk-sys", 130 | "paste", 131 | "ttf-parser", 132 | ] 133 | 134 | [[package]] 135 | name = "fltk-sys" 136 | version = "1.4.4" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "91941fe955506c9a4582018c68ecec7aad685731611ef7aa901e00d7a29cddd5" 139 | dependencies = [ 140 | "cmake", 141 | ] 142 | 143 | [[package]] 144 | name = "getrandom" 145 | version = "0.2.9" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" 148 | dependencies = [ 149 | "cfg-if", 150 | "libc", 151 | "wasi", 152 | ] 153 | 154 | [[package]] 155 | name = "gltf" 156 | version = "1.1.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "1fd7703af6975def3b32573c60aaa5ebfebfab5d879da1e1315d87155ba57bcd" 159 | dependencies = [ 160 | "base64", 161 | "byteorder", 162 | "gltf-json", 163 | "image", 164 | "lazy_static", 165 | "urlencoding", 166 | ] 167 | 168 | [[package]] 169 | name = "gltf-derive" 170 | version = "1.1.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "67b33dbe598480111e3b2e5a1e9a7e52ad5df0f836e04b8c80fc96f52a9c9f2e" 173 | dependencies = [ 174 | "inflections", 175 | "proc-macro2", 176 | "quote", 177 | "syn 1.0.109", 178 | ] 179 | 180 | [[package]] 181 | name = "gltf-json" 182 | version = "1.1.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "5511a759d99beeeef064bd6f81e207c77e3a3431c7499d7590929e35de371f31" 185 | dependencies = [ 186 | "gltf-derive", 187 | "serde", 188 | "serde_derive", 189 | "serde_json", 190 | ] 191 | 192 | [[package]] 193 | name = "image" 194 | version = "0.24.6" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" 197 | dependencies = [ 198 | "bytemuck", 199 | "byteorder", 200 | "color_quant", 201 | "jpeg-decoder", 202 | "num-rational", 203 | "num-traits", 204 | "png", 205 | ] 206 | 207 | [[package]] 208 | name = "inflections" 209 | version = "1.1.1" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" 212 | 213 | [[package]] 214 | name = "itoa" 215 | version = "1.0.6" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 218 | 219 | [[package]] 220 | name = "jpeg-decoder" 221 | version = "0.3.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" 224 | 225 | [[package]] 226 | name = "lazy_static" 227 | version = "1.4.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 230 | 231 | [[package]] 232 | name = "libc" 233 | version = "0.2.144" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" 236 | 237 | [[package]] 238 | name = "miniz_oxide" 239 | version = "0.7.1" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 242 | dependencies = [ 243 | "adler", 244 | "simd-adler32", 245 | ] 246 | 247 | [[package]] 248 | name = "num-integer" 249 | version = "0.1.45" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 252 | dependencies = [ 253 | "autocfg", 254 | "num-traits", 255 | ] 256 | 257 | [[package]] 258 | name = "num-rational" 259 | version = "0.4.1" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" 262 | dependencies = [ 263 | "autocfg", 264 | "num-integer", 265 | "num-traits", 266 | ] 267 | 268 | [[package]] 269 | name = "num-traits" 270 | version = "0.2.15" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 273 | dependencies = [ 274 | "autocfg", 275 | ] 276 | 277 | [[package]] 278 | name = "paste" 279 | version = "1.0.12" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" 282 | 283 | [[package]] 284 | name = "png" 285 | version = "0.17.8" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" 288 | dependencies = [ 289 | "bitflags 1.3.2", 290 | "crc32fast", 291 | "fdeflate", 292 | "flate2", 293 | "miniz_oxide", 294 | ] 295 | 296 | [[package]] 297 | name = "ppv-lite86" 298 | version = "0.2.17" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 301 | 302 | [[package]] 303 | name = "proc-macro2" 304 | version = "1.0.56" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 307 | dependencies = [ 308 | "unicode-ident", 309 | ] 310 | 311 | [[package]] 312 | name = "quote" 313 | version = "1.0.27" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" 316 | dependencies = [ 317 | "proc-macro2", 318 | ] 319 | 320 | [[package]] 321 | name = "rand" 322 | version = "0.8.5" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 325 | dependencies = [ 326 | "libc", 327 | "rand_chacha", 328 | "rand_core", 329 | ] 330 | 331 | [[package]] 332 | name = "rand_chacha" 333 | version = "0.3.1" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 336 | dependencies = [ 337 | "ppv-lite86", 338 | "rand_core", 339 | ] 340 | 341 | [[package]] 342 | name = "rand_core" 343 | version = "0.6.4" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 346 | dependencies = [ 347 | "getrandom", 348 | ] 349 | 350 | [[package]] 351 | name = "ryu" 352 | version = "1.0.13" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 355 | 356 | [[package]] 357 | name = "serde" 358 | version = "1.0.163" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" 361 | 362 | [[package]] 363 | name = "serde_derive" 364 | version = "1.0.163" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" 367 | dependencies = [ 368 | "proc-macro2", 369 | "quote", 370 | "syn 2.0.15", 371 | ] 372 | 373 | [[package]] 374 | name = "serde_json" 375 | version = "1.0.96" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" 378 | dependencies = [ 379 | "itoa", 380 | "ryu", 381 | "serde", 382 | ] 383 | 384 | [[package]] 385 | name = "simd-adler32" 386 | version = "0.3.5" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" 389 | 390 | [[package]] 391 | name = "syn" 392 | version = "1.0.109" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 395 | dependencies = [ 396 | "proc-macro2", 397 | "quote", 398 | "unicode-ident", 399 | ] 400 | 401 | [[package]] 402 | name = "syn" 403 | version = "2.0.15" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" 406 | dependencies = [ 407 | "proc-macro2", 408 | "quote", 409 | "unicode-ident", 410 | ] 411 | 412 | [[package]] 413 | name = "tiny-renderer" 414 | version = "0.1.0" 415 | dependencies = [ 416 | "fltk", 417 | "gltf", 418 | "rand", 419 | ] 420 | 421 | [[package]] 422 | name = "ttf-parser" 423 | version = "0.19.0" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "44dcf002ae3b32cd25400d6df128c5babec3927cd1eb7ce813cfff20eb6c3746" 426 | 427 | [[package]] 428 | name = "unicode-ident" 429 | version = "1.0.8" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 432 | 433 | [[package]] 434 | name = "urlencoding" 435 | version = "2.1.2" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" 438 | 439 | [[package]] 440 | name = "wasi" 441 | version = "0.11.0+wasi-snapshot-preview1" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 444 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tiny-renderer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | gltf = "1.1.0" 10 | rand = "0.8.5" 11 | 12 | [dev-dependencies] 13 | fltk = "1.4.4" 14 | 15 | [[example]] 16 | name = "bresenham_line" 17 | path = "examples/bresenham_line.rs" 18 | 19 | [[example]] 20 | name = "rendering" 21 | path = "examples/rendering.rs" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Night's Watch Games 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [English](./README_EN.md) 2 | 3 | # tiny-renderer 简单的光栅化软渲染器 4 | - [x] 基础数学库 5 | - [x] Bresenham画线算法 6 | - [x] Cohen-Sutherland线段裁剪算法 7 | - [x] glTF模型加载 8 | - [x] 模型/视图/投影变换 9 | - [x] 背面剔除 10 | - [ ] 视椎剔除 11 | - [ ] 齐次空间裁剪 12 | - [x] 深度测试 13 | - [x] 顶点和片段着色器 14 | - [x] 重心坐标插值 15 | - [x] 透视矫正 16 | - [x] Blinn–Phong反射模型 17 | - [x] 纹理映射 18 | - [x] 相机控制 19 | 20 | ## 运行 21 | 1. bresenham画线算法 22 | ``` 23 | cargo run --example bresenham_line 24 | ``` 25 | 2. 渲染 26 | ``` 27 | cargo run --example rendering 28 | ``` 29 | 30 | 控制 31 | - F1 切换线框渲染 32 | - F2 切换顶点颜色插值 33 | - F3 切换片段着色 34 | - F4 切换投影方式 35 | - F5 切换模型 36 | - W/A/S/D/Q/E 控制相机移动 37 | 38 | ## 截图 39 | 视频演示:[B站](https://www.bilibili.com/video/BV1Mu411b7f7) 40 | 41 | ![bresenham_line](./screenshots/bresenham_line.png) 42 | ![wireframe_rendering](./screenshots/wireframe_rendering.png) 43 | ![vertex_color_interpolation](./screenshots/vertex_color_interpolation.png) 44 | ![texture_mapping](./screenshots/texture_mapping.png) 45 | ![blinn_phong_texture](./screenshots/blinn_phong_texture.png) 46 | ![blinn_phong_color](./screenshots/blinn_phong_color.png) 47 | 48 | ## 参考 49 | - [ssloy/tinyrenderer](https://github.com/ssloy/tinyrenderer) and [wiki](https://github.com/ssloy/tinyrenderer/wiki) 50 | - [skywind3000/mini3d](https://github.com/skywind3000/mini3d) 51 | - [VisualGMQ/rs-cpurenderer](https://github.com/VisualGMQ/rs-cpurenderer) 52 | - [arrayJY/tiny-renderer](https://github.com/arrayJY/tiny-renderer) 53 | - [线性代数的本质 - B站](https://www.bilibili.com/video/BV1ys411472E) 54 | - [GAMES101 - B站](https://www.bilibili.com/video/BV1X7411F744/) 55 | - [bresenham算法绘制直线 - B站](https://www.bilibili.com/video/BV1364y1d7Lo) 56 | - [如何开始用 C++ 写一个光栅化渲染器? - 知乎](https://www.zhihu.com/question/24786878) 57 | - [四元数的可视化 - B站](https://www.bilibili.com/video/BV1SW411y7W1) 58 | - [如何形象地理解四元数? - 知乎](https://www.zhihu.com/question/23005815) 59 | - [Cohen–Sutherland线段裁剪算法 - Wikipedia](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 60 | - [图形学 - 关于透视矫正插值那些事 - 知乎](https://zhuanlan.zhihu.com/p/403259571) 61 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # tiny-renderer 2 | - [x] Basic math library 3 | - [x] Bresenham line drawing algorithm 4 | - [x] Cohen-Sutherland line clipping algorithm 5 | - [x] glTF model loading 6 | - [x] Model/view/projection transformation 7 | - [x] Back-face culling 8 | - [ ] View frustum culling 9 | - [ ] Homogeneous space clipping 10 | - [x] Depth testing 11 | - [x] Vertex and fragment shaders 12 | - [x] Barycentric coordinate interpolation 13 | - [x] Perspective correction 14 | - [x] Blinn–Phong reflection model 15 | - [x] Texture mapping 16 | - [x] Camera control 17 | 18 | ## Get started 19 | 1. Bresenham line drawing algorithm 20 | ``` 21 | cargo run --example bresenham_line 22 | ``` 23 | 2. Rendering 24 | ``` 25 | cargo run --example rendering 26 | ``` 27 | 28 | Control 29 | - F1 Toggle wireframe rendering 30 | - F2 Toggle vertex color interpolation 31 | - F3 Toggle fragment shading 32 | - F4 Toggle projection mode 33 | - F5 Switch model 34 | - W/A/S/D/Q/E Control camera movement 35 | 36 | ## Screenshots 37 | Video: [YouTube](https://www.youtube.com/watch?v=m8yv6-QOPoI) 38 | 39 | ![bresenham_line](./screenshots/bresenham_line.png) 40 | ![wireframe_rendering](./screenshots/wireframe_rendering.png) 41 | ![vertex_color_interpolation](./screenshots/vertex_color_interpolation.png) 42 | ![texture_mapping](./screenshots/texture_mapping.png) 43 | ![blinn_phong_texture](./screenshots/blinn_phong_texture.png) 44 | ![blinn_phong_color](./screenshots/blinn_phong_color.png) 45 | 46 | ## References 47 | - [ssloy/tinyrenderer](https://github.com/ssloy/tinyrenderer) and [wiki](https://github.com/ssloy/tinyrenderer/wiki) 48 | - [skywind3000/mini3d](https://github.com/skywind3000/mini3d) 49 | - [VisualGMQ/rs-cpurenderer](https://github.com/VisualGMQ/rs-cpurenderer) 50 | - [arrayJY/tiny-renderer](https://github.com/arrayJY/tiny-renderer) 51 | - [Cohen–Sutherland algorithm - Wikipedia](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) -------------------------------------------------------------------------------- /assets/box-textured/BoxTextured.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "generator": "COLLADA2GLTF", 4 | "version": "2.0" 5 | }, 6 | "scene": 0, 7 | "scenes": [ 8 | { 9 | "nodes": [ 10 | 0 11 | ] 12 | } 13 | ], 14 | "nodes": [ 15 | { 16 | "children": [ 17 | 1 18 | ], 19 | "matrix": [ 20 | 1.0, 21 | 0.0, 22 | 0.0, 23 | 0.0, 24 | 0.0, 25 | 0.0, 26 | -1.0, 27 | 0.0, 28 | 0.0, 29 | 1.0, 30 | 0.0, 31 | 0.0, 32 | 0.0, 33 | 0.0, 34 | 0.0, 35 | 1.0 36 | ] 37 | }, 38 | { 39 | "mesh": 0 40 | } 41 | ], 42 | "meshes": [ 43 | { 44 | "primitives": [ 45 | { 46 | "attributes": { 47 | "NORMAL": 1, 48 | "POSITION": 2, 49 | "TEXCOORD_0": 3 50 | }, 51 | "indices": 0, 52 | "mode": 4, 53 | "material": 0 54 | } 55 | ], 56 | "name": "Mesh" 57 | } 58 | ], 59 | "accessors": [ 60 | { 61 | "bufferView": 0, 62 | "byteOffset": 0, 63 | "componentType": 5123, 64 | "count": 36, 65 | "max": [ 66 | 23 67 | ], 68 | "min": [ 69 | 0 70 | ], 71 | "type": "SCALAR" 72 | }, 73 | { 74 | "bufferView": 1, 75 | "byteOffset": 0, 76 | "componentType": 5126, 77 | "count": 24, 78 | "max": [ 79 | 1.0, 80 | 1.0, 81 | 1.0 82 | ], 83 | "min": [ 84 | -1.0, 85 | -1.0, 86 | -1.0 87 | ], 88 | "type": "VEC3" 89 | }, 90 | { 91 | "bufferView": 1, 92 | "byteOffset": 288, 93 | "componentType": 5126, 94 | "count": 24, 95 | "max": [ 96 | 0.5, 97 | 0.5, 98 | 0.5 99 | ], 100 | "min": [ 101 | -0.5, 102 | -0.5, 103 | -0.5 104 | ], 105 | "type": "VEC3" 106 | }, 107 | { 108 | "bufferView": 2, 109 | "byteOffset": 0, 110 | "componentType": 5126, 111 | "count": 24, 112 | "max": [ 113 | 6.0, 114 | 1.0 115 | ], 116 | "min": [ 117 | 0.0, 118 | 0.0 119 | ], 120 | "type": "VEC2" 121 | } 122 | ], 123 | "materials": [ 124 | { 125 | "pbrMetallicRoughness": { 126 | "baseColorTexture": { 127 | "index": 0 128 | }, 129 | "metallicFactor": 0.0 130 | }, 131 | "name": "Texture" 132 | } 133 | ], 134 | "textures": [ 135 | { 136 | "sampler": 0, 137 | "source": 0 138 | } 139 | ], 140 | "images": [ 141 | { 142 | "uri": "CesiumLogoFlat.png" 143 | } 144 | ], 145 | "samplers": [ 146 | { 147 | "magFilter": 9729, 148 | "minFilter": 9986, 149 | "wrapS": 10497, 150 | "wrapT": 10497 151 | } 152 | ], 153 | "bufferViews": [ 154 | { 155 | "buffer": 0, 156 | "byteOffset": 768, 157 | "byteLength": 72, 158 | "target": 34963 159 | }, 160 | { 161 | "buffer": 0, 162 | "byteOffset": 0, 163 | "byteLength": 576, 164 | "byteStride": 12, 165 | "target": 34962 166 | }, 167 | { 168 | "buffer": 0, 169 | "byteOffset": 576, 170 | "byteLength": 192, 171 | "byteStride": 8, 172 | "target": 34962 173 | } 174 | ], 175 | "buffers": [ 176 | { 177 | "byteLength": 840, 178 | "uri": "BoxTextured0.bin" 179 | } 180 | ] 181 | } 182 | -------------------------------------------------------------------------------- /assets/box-textured/BoxTextured0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/assets/box-textured/BoxTextured0.bin -------------------------------------------------------------------------------- /assets/box-textured/CesiumLogoFlat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/assets/box-textured/CesiumLogoFlat.png -------------------------------------------------------------------------------- /assets/cornell-box.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset" : { 3 | "generator" : "Khronos glTF Blender I/O v3.2.40", 4 | "version" : "2.0" 5 | }, 6 | "scene" : 0, 7 | "scenes" : [ 8 | { 9 | "name" : "Scene", 10 | "nodes" : [ 11 | 0, 12 | 1, 13 | 2, 14 | 3, 15 | 4, 16 | 5, 17 | 6, 18 | 7 19 | ] 20 | } 21 | ], 22 | "nodes" : [ 23 | { 24 | "mesh" : 0, 25 | "name" : "Object_2", 26 | "rotation" : [ 27 | -0.7071068286895752, 28 | 0, 29 | 0, 30 | 0.7071067094802856 31 | ], 32 | "translation" : [ 33 | 0, 34 | -1, 35 | 0 36 | ] 37 | }, 38 | { 39 | "mesh" : 1, 40 | "name" : "Object_3", 41 | "rotation" : [ 42 | -0.7071068286895752, 43 | 0, 44 | 0, 45 | 0.7071068286895752 46 | ], 47 | "translation" : [ 48 | 0, 49 | -1, 50 | 0 51 | ] 52 | }, 53 | { 54 | "mesh" : 2, 55 | "name" : "Object_4", 56 | "rotation" : [ 57 | -0.7071068286895752, 58 | 0, 59 | 0, 60 | 0.7071068286895752 61 | ], 62 | "translation" : [ 63 | 0, 64 | -1, 65 | 0 66 | ] 67 | }, 68 | { 69 | "mesh" : 3, 70 | "name" : "Object_5", 71 | "rotation" : [ 72 | -0.7071068286895752, 73 | 0, 74 | 0, 75 | 0.7071067094802856 76 | ], 77 | "translation" : [ 78 | 0, 79 | -1, 80 | 0 81 | ] 82 | }, 83 | { 84 | "mesh" : 4, 85 | "name" : "Object_6", 86 | "rotation" : [ 87 | -0.7071068286895752, 88 | 0, 89 | 0, 90 | 0.7071067094802856 91 | ], 92 | "translation" : [ 93 | 0, 94 | -1, 95 | 0 96 | ] 97 | }, 98 | { 99 | "mesh" : 5, 100 | "name" : "Object_7", 101 | "rotation" : [ 102 | -0.7071068286895752, 103 | 0, 104 | 0, 105 | 0.7071067094802856 106 | ], 107 | "translation" : [ 108 | 0, 109 | -1, 110 | 0 111 | ] 112 | }, 113 | { 114 | "mesh" : 6, 115 | "name" : "Object_8", 116 | "rotation" : [ 117 | -0.7071068286895752, 118 | 0, 119 | 0, 120 | 0.7071067094802856 121 | ], 122 | "translation" : [ 123 | 0, 124 | -1, 125 | 0 126 | ] 127 | }, 128 | { 129 | "mesh" : 7, 130 | "name" : "Object_9", 131 | "rotation" : [ 132 | -0.7071068286895752, 133 | 0, 134 | 0, 135 | 0.7071067094802856 136 | ], 137 | "translation" : [ 138 | 0, 139 | -1, 140 | 0 141 | ] 142 | } 143 | ], 144 | "materials" : [ 145 | { 146 | "doubleSided" : true, 147 | "name" : "backWall.001", 148 | "pbrMetallicRoughness" : { 149 | "baseColorFactor" : [ 150 | 0.7250000238418579, 151 | 0.7099999785423279, 152 | 0.6800000071525574, 153 | 1 154 | ], 155 | "metallicFactor" : 0 156 | } 157 | }, 158 | { 159 | "doubleSided" : true, 160 | "name" : "ceiling.001", 161 | "pbrMetallicRoughness" : { 162 | "baseColorFactor" : [ 163 | 0.7250000238418579, 164 | 0.7099999785423279, 165 | 0.6800000071525574, 166 | 1 167 | ], 168 | "metallicFactor" : 0 169 | } 170 | }, 171 | { 172 | "doubleSided" : true, 173 | "name" : "floor.001", 174 | "pbrMetallicRoughness" : { 175 | "baseColorFactor" : [ 176 | 0.7250000238418579, 177 | 0.7099999785423279, 178 | 0.6800000071525574, 179 | 1 180 | ], 181 | "metallicFactor" : 0 182 | } 183 | }, 184 | { 185 | "doubleSided" : true, 186 | "name" : "leftWall.001", 187 | "pbrMetallicRoughness" : { 188 | "baseColorFactor" : [ 189 | 0.63, 190 | 0.065, 191 | 0.05, 192 | 1 193 | ], 194 | "metallicFactor" : 0 195 | } 196 | }, 197 | { 198 | "doubleSided" : true, 199 | "emissiveFactor" : [ 200 | 1.0, 0.80624145, 0.64975286 201 | ], 202 | "extras" : { 203 | "intensity" : 2.0 204 | }, 205 | "name" : "light.001", 206 | "pbrMetallicRoughness" : { 207 | "baseColorFactor" : [ 208 | 0.7799999713897705, 209 | 0.7799999713897705, 210 | 0.7799999713897705, 211 | 1 212 | ], 213 | "metallicFactor" : 0 214 | } 215 | }, 216 | { 217 | "doubleSided" : true, 218 | "name" : "rightWall.001", 219 | "pbrMetallicRoughness" : { 220 | "baseColorFactor" : [ 221 | 0.14, 222 | 0.45, 223 | 0.091, 224 | 1 225 | ], 226 | "metallicFactor" : 0 227 | } 228 | }, 229 | { 230 | "doubleSided" : true, 231 | "name" : "shortBox.001", 232 | "pbrMetallicRoughness" : { 233 | "baseColorFactor" : [ 234 | 0.7250000238418579, 235 | 0.7099999785423279, 236 | 0.6800000071525574, 237 | 1 238 | ], 239 | "metallicFactor" : 0 240 | } 241 | }, 242 | { 243 | "doubleSided" : true, 244 | "name" : "tallBox.001", 245 | "pbrMetallicRoughness" : { 246 | "baseColorFactor" : [ 247 | 0.7250000238418579, 248 | 0.7099999785423279, 249 | 0.6800000071525574, 250 | 1 251 | ], 252 | "metallicFactor" : 0 253 | } 254 | } 255 | ], 256 | "meshes" : [ 257 | { 258 | "name" : "Object_0.001", 259 | "primitives" : [ 260 | { 261 | "attributes" : { 262 | "POSITION" : 0, 263 | "NORMAL" : 1 264 | }, 265 | "indices" : 2, 266 | "material" : 0 267 | } 268 | ] 269 | }, 270 | { 271 | "name" : "Object_1.001", 272 | "primitives" : [ 273 | { 274 | "attributes" : { 275 | "POSITION" : 3, 276 | "NORMAL" : 4 277 | }, 278 | "indices" : 2, 279 | "material" : 1 280 | } 281 | ] 282 | }, 283 | { 284 | "name" : "Object_2.001", 285 | "primitives" : [ 286 | { 287 | "attributes" : { 288 | "POSITION" : 5, 289 | "NORMAL" : 6 290 | }, 291 | "indices" : 2, 292 | "material" : 2 293 | } 294 | ] 295 | }, 296 | { 297 | "name" : "Object_3.001", 298 | "primitives" : [ 299 | { 300 | "attributes" : { 301 | "POSITION" : 7, 302 | "NORMAL" : 8 303 | }, 304 | "indices" : 2, 305 | "material" : 3 306 | } 307 | ] 308 | }, 309 | { 310 | "name" : "Object_4.001", 311 | "primitives" : [ 312 | { 313 | "attributes" : { 314 | "POSITION" : 9, 315 | "NORMAL" : 10 316 | }, 317 | "indices" : 2, 318 | "material" : 4 319 | } 320 | ] 321 | }, 322 | { 323 | "name" : "Object_5.001", 324 | "primitives" : [ 325 | { 326 | "attributes" : { 327 | "POSITION" : 11, 328 | "NORMAL" : 12 329 | }, 330 | "indices" : 2, 331 | "material" : 5 332 | } 333 | ] 334 | }, 335 | { 336 | "name" : "Object_6.001", 337 | "primitives" : [ 338 | { 339 | "attributes" : { 340 | "POSITION" : 13, 341 | "NORMAL" : 14 342 | }, 343 | "indices" : 15, 344 | "material" : 6 345 | } 346 | ] 347 | }, 348 | { 349 | "name" : "Object_7.001", 350 | "primitives" : [ 351 | { 352 | "attributes" : { 353 | "POSITION" : 16, 354 | "NORMAL" : 17 355 | }, 356 | "indices" : 18, 357 | "material" : 7 358 | } 359 | ] 360 | } 361 | ], 362 | "accessors" : [ 363 | { 364 | "bufferView" : 0, 365 | "componentType" : 5126, 366 | "count" : 4, 367 | "max" : [ 368 | 1, 369 | 1.0399999618530273, 370 | 1.9900000095367432 371 | ], 372 | "min" : [ 373 | -1.0199999809265137, 374 | 1.0399999618530273, 375 | 0 376 | ], 377 | "type" : "VEC3" 378 | }, 379 | { 380 | "bufferView" : 1, 381 | "componentType" : 5126, 382 | "count" : 4, 383 | "type" : "VEC3" 384 | }, 385 | { 386 | "bufferView" : 2, 387 | "componentType" : 5123, 388 | "count" : 6, 389 | "type" : "SCALAR" 390 | }, 391 | { 392 | "bufferView" : 3, 393 | "componentType" : 5126, 394 | "count" : 4, 395 | "max" : [ 396 | 1, 397 | 1.0399999618530273, 398 | 1.9900000095367432 399 | ], 400 | "min" : [ 401 | -1.0199999809265137, 402 | -0.9900000095367432, 403 | 1.9900000095367432 404 | ], 405 | "type" : "VEC3" 406 | }, 407 | { 408 | "bufferView" : 4, 409 | "componentType" : 5126, 410 | "count" : 4, 411 | "type" : "VEC3" 412 | }, 413 | { 414 | "bufferView" : 5, 415 | "componentType" : 5126, 416 | "count" : 4, 417 | "max" : [ 418 | 1, 419 | 1.0399999618530273, 420 | 0 421 | ], 422 | "min" : [ 423 | -1.0099999904632568, 424 | -0.9900000095367432, 425 | 0 426 | ], 427 | "type" : "VEC3" 428 | }, 429 | { 430 | "bufferView" : 6, 431 | "componentType" : 5126, 432 | "count" : 4, 433 | "type" : "VEC3" 434 | }, 435 | { 436 | "bufferView" : 7, 437 | "componentType" : 5126, 438 | "count" : 4, 439 | "max" : [ 440 | -0.9900000095367432, 441 | 1.0399999618530273, 442 | 1.9900000095367432 443 | ], 444 | "min" : [ 445 | -1.0199999809265137, 446 | -0.9900000095367432, 447 | 0 448 | ], 449 | "type" : "VEC3" 450 | }, 451 | { 452 | "bufferView" : 8, 453 | "componentType" : 5126, 454 | "count" : 4, 455 | "type" : "VEC3" 456 | }, 457 | { 458 | "bufferView" : 9, 459 | "componentType" : 5126, 460 | "count" : 4, 461 | "max" : [ 462 | 0.23000000417232513, 463 | 0.2199999988079071, 464 | 1.9800000190734863 465 | ], 466 | "min" : [ 467 | -0.23999999463558197, 468 | -0.1599999964237213, 469 | 1.9800000190734863 470 | ], 471 | "type" : "VEC3" 472 | }, 473 | { 474 | "bufferView" : 10, 475 | "componentType" : 5126, 476 | "count" : 4, 477 | "type" : "VEC3" 478 | }, 479 | { 480 | "bufferView" : 11, 481 | "componentType" : 5126, 482 | "count" : 4, 483 | "max" : [ 484 | 1, 485 | 1.0399999618530273, 486 | 1.9900000095367432 487 | ], 488 | "min" : [ 489 | 1, 490 | -0.9900000095367432, 491 | 0 492 | ], 493 | "type" : "VEC3" 494 | }, 495 | { 496 | "bufferView" : 12, 497 | "componentType" : 5126, 498 | "count" : 4, 499 | "type" : "VEC3" 500 | }, 501 | { 502 | "bufferView" : 13, 503 | "componentType" : 5126, 504 | "count" : 26, 505 | "max" : [ 506 | 0.699999988079071, 507 | 0, 508 | 0.6000000238418579 509 | ], 510 | "min" : [ 511 | -0.05000000074505806, 512 | -0.75, 513 | 0 514 | ], 515 | "type" : "VEC3" 516 | }, 517 | { 518 | "bufferView" : 14, 519 | "componentType" : 5126, 520 | "count" : 26, 521 | "type" : "VEC3" 522 | }, 523 | { 524 | "bufferView" : 15, 525 | "componentType" : 5123, 526 | "count" : 30, 527 | "type" : "SCALAR" 528 | }, 529 | { 530 | "bufferView" : 16, 531 | "componentType" : 5126, 532 | "count" : 28, 533 | "max" : [ 534 | 0.03999999910593033, 535 | 0.6700000166893005, 536 | 1.2000000476837158 537 | ], 538 | "min" : [ 539 | -0.7099999785423279, 540 | -0.09000000357627869, 541 | 0 542 | ], 543 | "type" : "VEC3" 544 | }, 545 | { 546 | "bufferView" : 17, 547 | "componentType" : 5126, 548 | "count" : 28, 549 | "type" : "VEC3" 550 | }, 551 | { 552 | "bufferView" : 18, 553 | "componentType" : 5123, 554 | "count" : 30, 555 | "type" : "SCALAR" 556 | } 557 | ], 558 | "bufferViews" : [ 559 | { 560 | "buffer" : 0, 561 | "byteLength" : 48, 562 | "byteOffset" : 0 563 | }, 564 | { 565 | "buffer" : 0, 566 | "byteLength" : 48, 567 | "byteOffset" : 48 568 | }, 569 | { 570 | "buffer" : 0, 571 | "byteLength" : 12, 572 | "byteOffset" : 96 573 | }, 574 | { 575 | "buffer" : 0, 576 | "byteLength" : 48, 577 | "byteOffset" : 108 578 | }, 579 | { 580 | "buffer" : 0, 581 | "byteLength" : 48, 582 | "byteOffset" : 156 583 | }, 584 | { 585 | "buffer" : 0, 586 | "byteLength" : 48, 587 | "byteOffset" : 204 588 | }, 589 | { 590 | "buffer" : 0, 591 | "byteLength" : 48, 592 | "byteOffset" : 252 593 | }, 594 | { 595 | "buffer" : 0, 596 | "byteLength" : 48, 597 | "byteOffset" : 300 598 | }, 599 | { 600 | "buffer" : 0, 601 | "byteLength" : 48, 602 | "byteOffset" : 348 603 | }, 604 | { 605 | "buffer" : 0, 606 | "byteLength" : 48, 607 | "byteOffset" : 396 608 | }, 609 | { 610 | "buffer" : 0, 611 | "byteLength" : 48, 612 | "byteOffset" : 444 613 | }, 614 | { 615 | "buffer" : 0, 616 | "byteLength" : 48, 617 | "byteOffset" : 492 618 | }, 619 | { 620 | "buffer" : 0, 621 | "byteLength" : 48, 622 | "byteOffset" : 540 623 | }, 624 | { 625 | "buffer" : 0, 626 | "byteLength" : 312, 627 | "byteOffset" : 588 628 | }, 629 | { 630 | "buffer" : 0, 631 | "byteLength" : 312, 632 | "byteOffset" : 900 633 | }, 634 | { 635 | "buffer" : 0, 636 | "byteLength" : 60, 637 | "byteOffset" : 1212 638 | }, 639 | { 640 | "buffer" : 0, 641 | "byteLength" : 336, 642 | "byteOffset" : 1272 643 | }, 644 | { 645 | "buffer" : 0, 646 | "byteLength" : 336, 647 | "byteOffset" : 1608 648 | }, 649 | { 650 | "buffer" : 0, 651 | "byteLength" : 60, 652 | "byteOffset" : 1944 653 | } 654 | ], 655 | "buffers" : [ 656 | { 657 | "byteLength" : 2004, 658 | "uri" : "data:application/octet-stream;base64,pHB9v7gehT8AAAAAAACAP7gehT8AAAAAAACAP7gehT9SuP4/XI+Cv7gehT9SuP4/AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAABAAIAAAACAAMAXI+Cv6Rwfb9SuP4/XI+Cv7gehT9SuP4/AACAP7gehT9SuP4/AACAP6Rwfb9SuP4/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/rkeBv6Rwfb8AAAAAAACAP6Rwfb8AAAAAAACAP7gehT8AAAAApHB9v7gehT8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/rkeBv6Rwfb8AAAAApHB9v7gehT8AAAAAXI+Cv7gehT9SuP4/XI+Cv6Rwfb9SuP4/9Pt/PwRdn7s3nCM8YvV/P3ZkIbwx9HY81/t/P95xo7sasSU8Lf9/PwAAAAAVqqQ7j8J1vgrXI76kcP0/j8J1vq5HYT6kcP0/H4VrPq5HYT6kcP0/H4VrPgrXI76kcP0/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAP7gehT8AAAAAAACAP6Rwfb8AAAAAAACAP6Rwfb9SuP4/AACAP7gehT9SuP4/AACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAFK4HPwAAQL+amRk/FK4HPwAAQL+amRk/MzMzP3sULr6amRk/uB4FPgAAAICamRk/uB4FPgAAAICamRk/zcxMvYXrEb+amRk/zcxMvYXrEb8AAAAAuB4FPgAAAIAAAAAAFK4HPwAAQL8AAAAAFK4HPwAAQL8AAAAAMzMzP3sULr4AAAAAMzMzP3sULr4AAAAAMzMzP3sULr4AAAAAMzMzP3sULr6amRk/MzMzP3sULr6amRk/zcxMvYXrEb+amRk/zcxMvYXrEb+amRk/zcxMvYXrEb+amRk/zcxMvYXrEb8AAAAAFK4HPwAAQL8AAAAAFK4HPwAAQL+amRk/FK4HPwAAQL+amRk/FK4HPwAAQL+amRk/uB4FPgAAAIAAAAAAuB4FPgAAAICamRk/uB4FPgAAAICamRk/AAAAAP1OMLMAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAP1OMLMAAIA/AAAAAAAAAAAAAIA/AAAAAP1OMLMAAIA//h10v+Qtmj4AAACA/h10v+Qtmj4AAACApsGXvhR/dL8AAACAosGXvhV/dL8AAACAN6p1P68CkL4AAACAN6p1P60CkL4AAACADVWSPllSdT8AAACAN6p1P60CkL4AAACADVWSPllSdT8AAACA/h10v+Qtmj4AAACApsGXvhR/dL8AAACAosGXvhV/dL8AAACAosGXvhV/dL8AAACAN6p1P68CkL4AAACApsGXvhR/dL8AAACAN6p1P68CkL4AAACAN6p1P60CkL4AAACADVWSPllSdT8AAACA/h10v+Qtmj4AAACADVWSPllSdT8AAACAAQACAAQAAAADAAUABgAPABgABgAYAAcACAAUABAACQARABIACwANABYACgAVABMAFwAZAA4AFwAOAAwAFK4Hv+xRuL2amZk/FK4Hv+xRuL2amZk/CtcjPexRuD2amZk/KVwPvh+FKz+amZk/KVwPvh+FKz+amZk/j8I1v0jh+j6amZk/FK4Hv+xRuL0AAAAAFK4Hv+xRuL0AAAAAj8I1v0jh+j4AAAAAKVwPvh+FKz8AAAAACtcjPexRuD0AAAAACtcjPexRuD0AAAAACtcjPexRuD2amZk/CtcjPexRuD2amZk/CtcjPexRuD2amZk/KVwPvh+FKz8AAAAAKVwPvh+FKz8AAAAAKVwPvh+FKz+amZk/KVwPvh+FKz+amZk/KVwPvh+FKz+amZk/FK4Hv+xRuL2amZk/FK4Hv+xRuL2amZk/FK4Hv+xRuL0AAAAAj8I1v0jh+j4AAAAAj8I1v0jh+j4AAAAAj8I1v0jh+j6amZk/j8I1v0jh+j6amZk/j8I1v0jh+j6amZk/AAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AAAAAAAAAAD//38/Fn90v6TBl74AAACAFX90v6TBl74AAACAFn90v6TBl74AAACA4i2avv4ddD8AAACAFX90P6TBlz4AAACA5S2aPv4ddL8AAACAFX90P6TBlz4AAACAFn90P6PBlz4AAACA5S2aPv4ddL8AAACAFX90P6TBlz4AAACAFn90P6PBlz4AAACA5y2avv0ddD8AAACA4i2avv4ddD8AAACAFn90P6PBlz4AAACAFX90v6TBl74AAACA5S2aPv4ddL8AAACA5S2aPv4ddL8AAACA5y2avv0ddD8AAACA4i2avv4ddD8AAACAFn90v6TBl74AAACAFX90v6TBl74AAACA5y2avv0ddD8AAACAAQACAAQAAAADAAUABwAUABoABgAZAAgAFwAbABEAGAASAAkAEAATAA0ADwAMAAoACwAOABUACwAVABYA" 659 | } 660 | ] 661 | } 662 | -------------------------------------------------------------------------------- /assets/cube/cube.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/assets/cube/cube.bin -------------------------------------------------------------------------------- /assets/cube/cube.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset" : { 3 | "generator" : "Khronos glTF Blender I/O v1.1.46", 4 | "version" : "2.0" 5 | }, 6 | "scene" : 0, 7 | "scenes" : [ 8 | { 9 | "name" : "Scene", 10 | "nodes" : [ 11 | 0 12 | ] 13 | } 14 | ], 15 | "nodes" : [ 16 | { 17 | "mesh" : 0, 18 | "name" : "Cube" 19 | } 20 | ], 21 | "materials" : [ 22 | { 23 | "doubleSided" : true, 24 | "emissiveFactor" : [ 25 | 0, 26 | 0, 27 | 0 28 | ], 29 | "name" : "Material", 30 | "pbrMetallicRoughness" : { 31 | "baseColorFactor" : [ 32 | 0.800000011920929, 33 | 0.800000011920929, 34 | 0.800000011920929, 35 | 1 36 | ], 37 | "metallicFactor" : 0, 38 | "roughnessFactor" : 0.4000000059604645 39 | } 40 | } 41 | ], 42 | "meshes" : [ 43 | { 44 | "name" : "Cube", 45 | "primitives" : [ 46 | { 47 | "attributes" : { 48 | "POSITION" : 0, 49 | "NORMAL" : 1, 50 | "TEXCOORD_0" : 2 51 | }, 52 | "indices" : 3, 53 | "material" : 0 54 | } 55 | ] 56 | } 57 | ], 58 | "accessors" : [ 59 | { 60 | "bufferView" : 0, 61 | "componentType" : 5126, 62 | "count" : 24, 63 | "max" : [ 64 | 1, 65 | 1, 66 | 1 67 | ], 68 | "min" : [ 69 | -1, 70 | -1, 71 | -1 72 | ], 73 | "type" : "VEC3" 74 | }, 75 | { 76 | "bufferView" : 1, 77 | "componentType" : 5126, 78 | "count" : 24, 79 | "type" : "VEC3" 80 | }, 81 | { 82 | "bufferView" : 2, 83 | "componentType" : 5126, 84 | "count" : 24, 85 | "type" : "VEC2" 86 | }, 87 | { 88 | "bufferView" : 3, 89 | "componentType" : 5123, 90 | "count" : 36, 91 | "type" : "SCALAR" 92 | } 93 | ], 94 | "bufferViews" : [ 95 | { 96 | "buffer" : 0, 97 | "byteLength" : 288, 98 | "byteOffset" : 0 99 | }, 100 | { 101 | "buffer" : 0, 102 | "byteLength" : 288, 103 | "byteOffset" : 288 104 | }, 105 | { 106 | "buffer" : 0, 107 | "byteLength" : 192, 108 | "byteOffset" : 576 109 | }, 110 | { 111 | "buffer" : 0, 112 | "byteLength" : 72, 113 | "byteOffset" : 768 114 | } 115 | ], 116 | "buffers" : [ 117 | { 118 | "byteLength" : 840, 119 | "uri" : "cube.bin" 120 | } 121 | ] 122 | } 123 | -------------------------------------------------------------------------------- /assets/sphere/sphere.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/assets/sphere/sphere.bin -------------------------------------------------------------------------------- /assets/sphere/sphere.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset" : { 3 | "generator" : "Khronos glTF Blender I/O v1.1.46", 4 | "version" : "2.0" 5 | }, 6 | "scene" : 0, 7 | "scenes" : [ 8 | { 9 | "name" : "Scene", 10 | "nodes" : [ 11 | 0 12 | ] 13 | } 14 | ], 15 | "nodes" : [ 16 | { 17 | "mesh" : 0, 18 | "name" : "Sphere" 19 | } 20 | ], 21 | "meshes" : [ 22 | { 23 | "name" : "Sphere", 24 | "primitives" : [ 25 | { 26 | "attributes" : { 27 | "POSITION" : 0, 28 | "NORMAL" : 1, 29 | "TEXCOORD_0" : 2 30 | }, 31 | "indices" : 3 32 | } 33 | ] 34 | } 35 | ], 36 | "accessors" : [ 37 | { 38 | "bufferView" : 0, 39 | "componentType" : 5126, 40 | "count" : 2143, 41 | "max" : [ 42 | 1.0000005960464478, 43 | 1, 44 | 1.000001072883606 45 | ], 46 | "min" : [ 47 | -1.000000238418579, 48 | -1, 49 | -1 50 | ], 51 | "type" : "VEC3" 52 | }, 53 | { 54 | "bufferView" : 1, 55 | "componentType" : 5126, 56 | "count" : 2143, 57 | "type" : "VEC3" 58 | }, 59 | { 60 | "bufferView" : 2, 61 | "componentType" : 5126, 62 | "count" : 2143, 63 | "type" : "VEC2" 64 | }, 65 | { 66 | "bufferView" : 3, 67 | "componentType" : 5123, 68 | "count" : 11904, 69 | "type" : "SCALAR" 70 | } 71 | ], 72 | "bufferViews" : [ 73 | { 74 | "buffer" : 0, 75 | "byteLength" : 25716, 76 | "byteOffset" : 0 77 | }, 78 | { 79 | "buffer" : 0, 80 | "byteLength" : 25716, 81 | "byteOffset" : 25716 82 | }, 83 | { 84 | "buffer" : 0, 85 | "byteLength" : 17144, 86 | "byteOffset" : 51432 87 | }, 88 | { 89 | "buffer" : 0, 90 | "byteLength" : 23808, 91 | "byteOffset" : 68576 92 | } 93 | ], 94 | "buffers" : [ 95 | { 96 | "byteLength" : 92384, 97 | "uri" : "sphere.bin" 98 | } 99 | ] 100 | } 101 | -------------------------------------------------------------------------------- /assets/suzanne/Suzanne.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/assets/suzanne/Suzanne.bin -------------------------------------------------------------------------------- /assets/suzanne/Suzanne.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors" : [ 3 | { 4 | "bufferView" : 0, 5 | "byteOffset" : 0, 6 | "componentType" : 5123, 7 | "count" : 11808, 8 | "max" : [ 9 | 11807 10 | ], 11 | "min" : [ 12 | 0 13 | ], 14 | "type" : "SCALAR" 15 | }, 16 | { 17 | "bufferView" : 1, 18 | "byteOffset" : 0, 19 | "componentType" : 5126, 20 | "count" : 11808, 21 | "max" : [ 22 | 1.336914, 23 | 0.950195, 24 | 0.825684 25 | ], 26 | "min" : [ 27 | -1.336914, 28 | -0.974609, 29 | -0.800781 30 | ], 31 | "type" : "VEC3" 32 | }, 33 | { 34 | "bufferView" : 2, 35 | "byteOffset" : 0, 36 | "componentType" : 5126, 37 | "count" : 11808, 38 | "max" : [ 39 | 0.996339, 40 | 0.999958, 41 | 0.999929 42 | ], 43 | "min" : [ 44 | -0.996339, 45 | -0.985940, 46 | -0.999994 47 | ], 48 | "type" : "VEC3" 49 | }, 50 | { 51 | "bufferView" : 3, 52 | "byteOffset" : 0, 53 | "componentType" : 5126, 54 | "count" : 11808, 55 | "max" : [ 56 | 0.998570, 57 | 0.999996, 58 | 0.999487, 59 | 1.000000 60 | ], 61 | "min" : [ 62 | -0.999233, 63 | -0.999453, 64 | -0.999812, 65 | 1.000000 66 | ], 67 | "type" : "VEC4" 68 | }, 69 | { 70 | "bufferView" : 4, 71 | "byteOffset" : 0, 72 | "componentType" : 5126, 73 | "count" : 11808, 74 | "max" : [ 75 | 0.999884, 76 | 0.884359 77 | ], 78 | "min" : [ 79 | 0.000116, 80 | 0.000116 81 | ], 82 | "type" : "VEC2" 83 | } 84 | ], 85 | "asset" : { 86 | "generator" : "VKTS glTF 2.0 exporter", 87 | "version" : "2.0" 88 | }, 89 | "bufferViews" : [ 90 | { 91 | "buffer" : 0, 92 | "byteLength" : 23616, 93 | "byteOffset" : 0, 94 | "target" : 34963 95 | }, 96 | { 97 | "buffer" : 0, 98 | "byteLength" : 141696, 99 | "byteOffset" : 23616, 100 | "target" : 34962 101 | }, 102 | { 103 | "buffer" : 0, 104 | "byteLength" : 141696, 105 | "byteOffset" : 165312, 106 | "target" : 34962 107 | }, 108 | { 109 | "buffer" : 0, 110 | "byteLength" : 188928, 111 | "byteOffset" : 307008, 112 | "target" : 34962 113 | }, 114 | { 115 | "buffer" : 0, 116 | "byteLength" : 94464, 117 | "byteOffset" : 495936, 118 | "target" : 34962 119 | } 120 | ], 121 | "buffers" : [ 122 | { 123 | "byteLength" : 590400, 124 | "uri" : "Suzanne.bin" 125 | } 126 | ], 127 | "images" : [ 128 | { 129 | "uri" : "Suzanne_BaseColor.png" 130 | }, 131 | { 132 | "uri" : "Suzanne_MetallicRoughness.png" 133 | } 134 | ], 135 | "materials" : [ 136 | { 137 | "name" : "Suzanne", 138 | "pbrMetallicRoughness" : { 139 | "baseColorTexture" : { 140 | "index" : 0 141 | }, 142 | "metallicRoughnessTexture" : { 143 | "index" : 1 144 | } 145 | } 146 | } 147 | ], 148 | "meshes" : [ 149 | { 150 | "name" : "Suzanne", 151 | "primitives" : [ 152 | { 153 | "attributes" : { 154 | "NORMAL" : 2, 155 | "POSITION" : 1, 156 | "TANGENT" : 3, 157 | "TEXCOORD_0" : 4 158 | }, 159 | "indices" : 0, 160 | "material" : 0, 161 | "mode" : 4 162 | } 163 | ] 164 | } 165 | ], 166 | "nodes" : [ 167 | { 168 | "mesh" : 0, 169 | "name" : "Suzanne" 170 | } 171 | ], 172 | "samplers" : [ 173 | {} 174 | ], 175 | "scene" : 0, 176 | "scenes" : [ 177 | { 178 | "nodes" : [ 179 | 0 180 | ] 181 | } 182 | ], 183 | "textures" : [ 184 | { 185 | "sampler" : 0, 186 | "source" : 0 187 | }, 188 | { 189 | "sampler" : 0, 190 | "source" : 1 191 | } 192 | ] 193 | } 194 | -------------------------------------------------------------------------------- /assets/suzanne/Suzanne_BaseColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/assets/suzanne/Suzanne_BaseColor.png -------------------------------------------------------------------------------- /assets/suzanne/Suzanne_MetallicRoughness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/assets/suzanne/Suzanne_MetallicRoughness.png -------------------------------------------------------------------------------- /examples/bresenham_line.rs: -------------------------------------------------------------------------------- 1 | use fltk::{ 2 | app::set_visual, 3 | enums::Mode, 4 | prelude::{GroupExt, WidgetBase, WidgetExt}, 5 | window::Window, 6 | }; 7 | use rand::Rng; 8 | use tiny_renderer::{ 9 | camera::Camera, 10 | color::Color, 11 | math::{Vec2, Vec3}, 12 | renderer::{Renderer, RendererSettings, Viewport}, 13 | util::flip_vertically, 14 | }; 15 | 16 | const WINDOW_WIDTH: u32 = 1024; 17 | const WINDOW_HEIGHT: u32 = 720; 18 | 19 | pub fn main() { 20 | let app = fltk::app::App::default(); 21 | let mut wind = Window::new( 22 | 100, 23 | 100, 24 | WINDOW_WIDTH as i32, 25 | WINDOW_HEIGHT as i32, 26 | "bresenham line", 27 | ); 28 | 29 | let camera = Camera::new( 30 | -1.0, 31 | -1000.0, 32 | WINDOW_WIDTH as f32 / WINDOW_HEIGHT as f32, 33 | 30.0f32.to_radians(), 34 | Vec3::ZERO, 35 | ); 36 | let viewport = Viewport::new(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); 37 | let mut renderer = Renderer::new(camera, viewport, RendererSettings::default()); 38 | wind.draw(move |_| { 39 | renderer.clear(); 40 | let mut rng = rand::thread_rng(); 41 | renderer.draw_line( 42 | Vec2::new(10.0, WINDOW_HEIGHT as f32 / 2.0), 43 | Vec2::new(WINDOW_WIDTH as f32, WINDOW_HEIGHT as f32 / 2.0), 44 | Color::GREEN, 45 | ); 46 | renderer.draw_line( 47 | Vec2::new(WINDOW_WIDTH as f32 / 2.0, 10.0), 48 | Vec2::new(WINDOW_WIDTH as f32 / 2.0, WINDOW_HEIGHT as f32), 49 | Color::GREEN, 50 | ); 51 | for _ in 0..50 { 52 | renderer.draw_line( 53 | Vec2::new( 54 | rng.gen_range(0.0..WINDOW_WIDTH as f32), 55 | rng.gen_range(0.0..WINDOW_WIDTH as f32), 56 | ), 57 | Vec2::new( 58 | rng.gen_range(0.0..WINDOW_WIDTH as f32), 59 | rng.gen_range(0.0..WINDOW_WIDTH as f32), 60 | ), 61 | Color::GREEN, 62 | ); 63 | } 64 | fltk::draw::draw_image( 65 | &flip_vertically( 66 | &renderer.frame_buffer, 67 | WINDOW_WIDTH as usize, 68 | WINDOW_HEIGHT as usize, 69 | ), 70 | 0, 71 | 0, 72 | WINDOW_WIDTH as i32, 73 | WINDOW_HEIGHT as i32, 74 | fltk::enums::ColorDepth::Rgb8, 75 | ) 76 | .unwrap(); 77 | }); 78 | 79 | wind.end(); 80 | set_visual(Mode::Rgb).unwrap(); 81 | wind.show(); 82 | app.run().unwrap(); 83 | } 84 | -------------------------------------------------------------------------------- /examples/rendering.rs: -------------------------------------------------------------------------------- 1 | use fltk::{ 2 | app::{event_key_down, set_visual}, 3 | enums::{Key, Mode}, 4 | prelude::{GroupExt, WidgetBase, WidgetExt}, 5 | window::Window, 6 | }; 7 | use tiny_renderer::{ 8 | camera::Camera, 9 | color::Color, 10 | light::PointLight, 11 | loader::load_glft, 12 | math::{Quat, Vec3}, 13 | renderer::{Renderer, RendererSettings, Viewport}, 14 | shader::phong_shader, 15 | transform::translation_mat4, 16 | util::{custom_cube, flip_vertically, rand_color}, 17 | }; 18 | 19 | const WINDOW_WIDTH: u32 = 1024; 20 | const WINDOW_HEIGHT: u32 = 720; 21 | 22 | const MODEL_LIST: [&str; 3] = [ 23 | // "assets/cube/cube.gltf", 24 | "assets/monkey/monkey.gltf", 25 | "assets/box-textured/BoxTextured.gltf", 26 | "assets/sphere/sphere.gltf", 27 | // "assets/suzanne/Suzanne.gltf", 28 | // "assets/cornell-box.gltf", 29 | ]; 30 | 31 | pub fn main() { 32 | println!( 33 | " 34 | F1: toggle wireframe rendering 35 | F2: toggle vertex color interpolation 36 | F3: toggle fragment shading 37 | F4: toggle projection 38 | F5: switch model 39 | W/A/S/D/Q/E: move camera 40 | " 41 | ); 42 | let app = fltk::app::App::default(); 43 | let mut wind = Window::new( 44 | 100, 45 | 100, 46 | WINDOW_WIDTH as i32, 47 | WINDOW_HEIGHT as i32, 48 | "rendering", 49 | ); 50 | 51 | // let (meshes, texture_storage) = custom_cube(); 52 | let mut model_index = 0; 53 | let (mut meshes, mut texture_storage) = load_glft(MODEL_LIST[model_index % MODEL_LIST.len()]); 54 | let model_pos = Vec3::new(0.0, 0.0, 0.0); 55 | let model_transformation = translation_mat4(model_pos); 56 | 57 | let light = PointLight { 58 | position: Vec3::new(-5.0, 5.0, 5.0), 59 | intensity: 100.0, 60 | }; 61 | 62 | let mut camera = Camera::new( 63 | 5.0, 64 | 1000.0, 65 | WINDOW_WIDTH as f32 / WINDOW_HEIGHT as f32, 66 | 60.0f32.to_radians(), 67 | Vec3::new(1.0, 2.0, 3.0), 68 | ); 69 | camera.look_at(model_pos, Vec3::Y); 70 | 71 | let viewport = Viewport::new(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); 72 | let settings = RendererSettings { 73 | wireframe: false, 74 | vertex_color_interp: false, 75 | fragment_shading: true, 76 | ..Default::default() 77 | }; 78 | let mut renderer = Renderer::new(camera, viewport, settings); 79 | renderer.vertex_shader = Some(Box::new(|vertex| {})); 80 | renderer.fragment_shader = Some(phong_shader()); 81 | 82 | wind.draw(move |_| { 83 | if event_key_down(Key::F1) { 84 | renderer.settings.wireframe = !renderer.settings.wireframe; 85 | } 86 | if event_key_down(Key::F2) { 87 | renderer.settings.vertex_color_interp = !renderer.settings.vertex_color_interp; 88 | } 89 | if event_key_down(Key::F3) { 90 | renderer.settings.fragment_shading = !renderer.settings.fragment_shading; 91 | } 92 | if event_key_down(Key::F4) { 93 | renderer.settings.projection = match renderer.settings.projection { 94 | tiny_renderer::renderer::Projection::Perspective => { 95 | tiny_renderer::renderer::Projection::Orthographic 96 | } 97 | tiny_renderer::renderer::Projection::Orthographic => { 98 | tiny_renderer::renderer::Projection::Perspective 99 | } 100 | }; 101 | } 102 | if event_key_down(Key::F5) { 103 | model_index += 1; 104 | (meshes, texture_storage) = load_glft(MODEL_LIST[model_index % MODEL_LIST.len()]); 105 | } 106 | if event_key_down(Key::from_char('A')) { 107 | renderer 108 | .camera 109 | .rotate_around(model_pos, Quat::from_axis_angle(Vec3::Y, -0.1)) 110 | } 111 | if event_key_down(Key::from_char('D')) { 112 | renderer 113 | .camera 114 | .rotate_around(model_pos, Quat::from_axis_angle(Vec3::Y, 0.1)) 115 | } 116 | if event_key_down(Key::from_char('W')) { 117 | renderer 118 | .camera 119 | .rotate_around(model_pos, Quat::from_axis_angle(Vec3::X, 0.1)) 120 | } 121 | if event_key_down(Key::from_char('S')) { 122 | renderer 123 | .camera 124 | .rotate_around(model_pos, Quat::from_axis_angle(Vec3::X, -0.1)) 125 | } 126 | if event_key_down(Key::from_char('Q')) { 127 | renderer 128 | .camera 129 | .rotate_around(model_pos, Quat::from_axis_angle(Vec3::Z, 0.1)) 130 | } 131 | if event_key_down(Key::from_char('E')) { 132 | renderer 133 | .camera 134 | .rotate_around(model_pos, Quat::from_axis_angle(Vec3::Z, -0.1)) 135 | } 136 | 137 | renderer.clear(); 138 | renderer.draw(&meshes, model_transformation, light, &texture_storage); 139 | fltk::draw::draw_image( 140 | &flip_vertically( 141 | &renderer.frame_buffer, 142 | WINDOW_WIDTH as usize, 143 | WINDOW_HEIGHT as usize, 144 | ), 145 | 0, 146 | 0, 147 | WINDOW_WIDTH as i32, 148 | WINDOW_HEIGHT as i32, 149 | fltk::enums::ColorDepth::Rgb8, 150 | ) 151 | .unwrap(); 152 | }); 153 | 154 | wind.end(); 155 | set_visual(Mode::Rgb).unwrap(); 156 | wind.show(); 157 | 158 | fltk::app::add_idle3(move |_| { 159 | wind.redraw(); 160 | }); 161 | 162 | app.run().unwrap(); 163 | } 164 | -------------------------------------------------------------------------------- /screenshots/blinn_phong_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/screenshots/blinn_phong_color.png -------------------------------------------------------------------------------- /screenshots/blinn_phong_texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/screenshots/blinn_phong_texture.png -------------------------------------------------------------------------------- /screenshots/bresenham_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/screenshots/bresenham_line.png -------------------------------------------------------------------------------- /screenshots/texture_mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/screenshots/texture_mapping.png -------------------------------------------------------------------------------- /screenshots/vertex_color_interpolation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/screenshots/vertex_color_interpolation.png -------------------------------------------------------------------------------- /screenshots/wireframe_rendering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightsWatchGames/tiny-renderer/2f609def422d7e0f965a457dbad42aec0fe16d18/screenshots/wireframe_rendering.png -------------------------------------------------------------------------------- /src/camera.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | math::{Mat3, Mat4, Quat, Vec3}, 3 | transform::translation_mat4, 4 | }; 5 | 6 | //// 视椎体 7 | pub struct Frustum { 8 | // 垂直视野(弧度) 9 | pub fov: f32, 10 | // 宽高比 11 | pub aspect: f32, 12 | // 近平面(距离) 13 | pub near: f32, 14 | // 远平面(距离) 15 | pub far: f32, 16 | } 17 | impl Frustum { 18 | // 透视投影变换矩阵 19 | #[rustfmt::skip] 20 | pub fn persp_projection_transformation(&self) -> Mat4 { 21 | let near_z = -self.near; 22 | let far_z = -self.far; 23 | let persp_to_ortho = Mat4::from_rows_slice(&[ 24 | near_z, 0., 0., 0., 25 | 0., near_z, 0., 0., 26 | 0., 0., near_z + far_z, -near_z * far_z, 27 | 0., 0., 1., 0., 28 | ]); 29 | let ortho_translation = Mat4::from_rows_slice(&[ 30 | 1., 0., 0., 0., 31 | 0., 1., 0., 0., 32 | 0., 0., 1., -(near_z + far_z) / 2., 33 | 0., 0., 0., 1., 34 | ]); 35 | let ortho_scale = Mat4::from_rows_slice(&[ 36 | 2. / self.width_near(), 0., 0., 0., 37 | 0., 2. / self.height_near(), 0., 0., 38 | 0., 0., 2. / (near_z - far_z), 0., 39 | 0., 0., 0., 1., 40 | ]); 41 | ortho_scale * ortho_translation * persp_to_ortho 42 | } 43 | 44 | // 正交投影变换矩阵 45 | #[rustfmt::skip] 46 | pub fn ortho_projection_transformation(&self) -> Mat4 { 47 | let near_z = -self.near; 48 | let far_z = -self.far; 49 | let ortho_translation = Mat4::from_rows_slice(&[ 50 | 1., 0., 0., 0., 51 | 0., 1., 0., 0., 52 | 0., 0., 1., -(near_z + far_z) / 2., 53 | 0., 0., 0., 1., 54 | ]); 55 | let ortho_scale = Mat4::from_rows_slice(&[ 56 | 2. / self.width_near(), 0., 0., 0., 57 | 0., 2. / self.height_near(), 0., 0., 58 | 0., 0., 2. / (near_z - far_z), 0., 59 | 0., 0., 0., 1., 60 | ]); 61 | ortho_scale * ortho_translation 62 | } 63 | 64 | pub fn width_near(&self) -> f32 { 65 | self.aspect * self.height_near() 66 | } 67 | 68 | pub fn height_near(&self) -> f32 { 69 | 2.0 * (self.fov / 2.0).tan() * self.near 70 | } 71 | } 72 | 73 | //// 相机 74 | pub struct Camera { 75 | pub frustum: Frustum, 76 | pub position: Vec3, 77 | pub rotation: Quat, 78 | } 79 | impl Camera { 80 | pub fn new(near: f32, far: f32, aspect: f32, fov: f32, position: Vec3) -> Self { 81 | assert!(near > 0.0); 82 | assert!(far > 0.0); 83 | Self { 84 | frustum: Frustum { 85 | fov, 86 | aspect, 87 | near, 88 | far, 89 | }, 90 | position, 91 | rotation: Quat::IDENTITY, 92 | } 93 | } 94 | 95 | pub fn look_at(&mut self, target: Vec3, up: Vec3) { 96 | self.look_to(target - self.position, up); 97 | } 98 | 99 | pub fn look_to(&mut self, direction: Vec3, up: Vec3) { 100 | let back = -direction.normalize(); 101 | let right = up.cross(back).normalize(); 102 | let up = back.cross(right); 103 | // TODO 通过逆矩阵来计算旋转矩阵 104 | self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, back)); 105 | } 106 | 107 | // 视图变换矩阵 108 | pub fn view_transformation(&self) -> Mat4 { 109 | let rotation_mat4 = self.rotation.inverse().to_mat4(); 110 | let translation_mat4 = translation_mat4(-self.position); 111 | // 先平移再旋转 112 | rotation_mat4 * translation_mat4 113 | } 114 | 115 | pub fn rotate(&mut self, rotation: Quat) { 116 | self.rotation = rotation * self.rotation; 117 | } 118 | pub fn translate_around(&mut self, point: Vec3, rotation: Quat) { 119 | self.position = point + rotation * (self.position - point); 120 | } 121 | pub fn rotate_around(&mut self, point: Vec3, rotation: Quat) { 122 | self.translate_around(point, rotation); 123 | self.rotate(rotation); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul}; 2 | 3 | use crate::math::Vec3; 4 | 5 | #[derive(Debug, Clone, Copy)] 6 | pub struct Color { 7 | pub r: f32, 8 | pub g: f32, 9 | pub b: f32, 10 | } 11 | impl Color { 12 | pub const BLACK: Self = Self::new(0., 0., 0.); 13 | pub const RED: Self = Self::new(1., 0., 0.); 14 | pub const GREEN: Self = Self::new(0., 1., 0.); 15 | pub const BLUE: Self = Self::new(0., 0., 1.); 16 | pub const WHITE: Self = Self::new(1., 1., 1.); 17 | 18 | pub const fn new(r: f32, g: f32, b: f32) -> Self { 19 | Self { r, g, b } 20 | } 21 | 22 | pub fn from_vec3(v: Vec3) -> Self { 23 | assert!(v.x >= 0.0 && v.x <= 1.0); 24 | assert!(v.y >= 0.0 && v.y <= 1.0); 25 | assert!(v.z >= 0.0 && v.z <= 1.0); 26 | Self::new(v.x, v.y, v.z) 27 | } 28 | 29 | pub fn to_vec3(&self) -> Vec3 { 30 | Vec3::new(self.r, self.g, self.b) 31 | } 32 | } 33 | impl Default for Color { 34 | fn default() -> Self { 35 | Self::BLACK 36 | } 37 | } 38 | impl Add for Color { 39 | type Output = Self; 40 | 41 | fn add(self, rhs: Self) -> Self::Output { 42 | Self::new( 43 | (self.r + rhs.r).min(1.0), 44 | (self.g + rhs.g).min(1.0), 45 | (self.b + rhs.b).min(1.0), 46 | ) 47 | } 48 | } 49 | impl Mul for Color { 50 | type Output = Self; 51 | 52 | fn mul(self, rhs: f32) -> Self::Output { 53 | Self::new(self.r * rhs, self.g * rhs, self.b * rhs) 54 | } 55 | } 56 | impl Mul for Color { 57 | type Output = Self; 58 | fn mul(self, rhs: Color) -> Self::Output { 59 | Self::new(self.r * rhs.r, self.g * rhs.g, self.b * rhs.b) 60 | } 61 | } 62 | impl From<[f32; 3]> for Color { 63 | fn from(v: [f32; 3]) -> Self { 64 | Color::new(v[0], v[1], v[2]) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod camera; 2 | pub mod color; 3 | pub mod light; 4 | pub mod loader; 5 | pub mod material; 6 | pub mod math; 7 | pub mod mesh; 8 | pub mod renderer; 9 | pub mod shader; 10 | pub mod texture; 11 | pub mod transform; 12 | pub mod util; 13 | -------------------------------------------------------------------------------- /src/light.rs: -------------------------------------------------------------------------------- 1 | use crate::math::Vec3; 2 | 3 | // 点光源 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct PointLight { 6 | pub position: Vec3, 7 | // 光强度 8 | pub intensity: f32, 9 | } 10 | 11 | impl Default for PointLight { 12 | fn default() -> Self { 13 | Self { 14 | position: Vec3::new(100.0, 100.0, -100.0), 15 | intensity: 10000.0, 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/loader.rs: -------------------------------------------------------------------------------- 1 | use gltf::{buffer::Data, Document}; 2 | 3 | use crate::{ 4 | color::Color, 5 | material::Material, 6 | math::{Vec2, Vec3}, 7 | mesh::{Mesh, Vertex}, 8 | texture::{Sampler, Texture, TextureStorage}, 9 | util::rand_color, 10 | }; 11 | 12 | pub fn load_glft(path: &str) -> (Vec, TextureStorage) { 13 | let (document, buffers, images) = gltf::import(path).unwrap(); 14 | 15 | let textures = load_textures(&document, &images); 16 | let meshes = load_meshes(&document, &buffers); 17 | 18 | ( 19 | meshes, 20 | TextureStorage { 21 | texture_id_map: textures 22 | .into_iter() 23 | .map(|texture| (texture.id, texture)) 24 | .collect(), 25 | }, 26 | ) 27 | } 28 | 29 | pub fn load_textures(document: &Document, images: &Vec) -> Vec { 30 | let mut textures = Vec::new(); 31 | for texture in document.textures() { 32 | let source = texture.source(); 33 | let sampler = texture.sampler(); 34 | let image = images.get(source.index()).unwrap(); 35 | 36 | let texture = Texture { 37 | id: texture.index(), 38 | width: image.width, 39 | height: image.height, 40 | format: image.format, 41 | data: image.pixels.clone(), 42 | sampler: Sampler { 43 | mag_filter: sampler.mag_filter(), 44 | min_filter: sampler.min_filter(), 45 | wrap_s: sampler.wrap_s(), 46 | wrap_t: sampler.wrap_t(), 47 | }, 48 | }; 49 | // println!( 50 | // "Texture id: {:?}, width: {:?}, height: {:?}, format: {:?}, data len: {:?}, sampler: {:?}", 51 | // texture.id, 52 | // texture.width, 53 | // texture.height, 54 | // texture.format, 55 | // texture.data.len(), 56 | // texture.sampler 57 | // ); 58 | textures.push(texture); 59 | } 60 | textures 61 | } 62 | 63 | pub fn load_meshes(document: &Document, buffers: &Vec) -> Vec { 64 | let mut meshes = Vec::new(); 65 | 66 | for gltf_mesh in document.meshes() { 67 | for gltf_primitive in gltf_mesh.primitives() { 68 | let mut mesh = Mesh::default(); 69 | 70 | // 顶点数据 71 | if gltf_primitive.mode() != gltf::mesh::Mode::Triangles { 72 | println!("Primitive mode: {:?}", gltf_primitive.mode()); 73 | panic!("Only Triangles mode is supported"); 74 | } 75 | let reader = gltf_primitive.reader(|buffer| Some(&buffers[buffer.index()])); 76 | 77 | let mut positions: Vec<[f32; 3]> = Vec::new(); 78 | let mut normals: Vec<[f32; 3]> = Vec::new(); 79 | let mut colors: Vec<[f32; 3]> = Vec::new(); 80 | let mut texcoords: Vec<[f32; 2]> = Vec::new(); 81 | 82 | for (semantic, _) in gltf_primitive.attributes() { 83 | match semantic { 84 | gltf::Semantic::Positions => { 85 | positions = reader.read_positions().unwrap().collect(); 86 | } 87 | gltf::Semantic::Normals => { 88 | normals = reader.read_normals().unwrap().collect(); 89 | } 90 | gltf::Semantic::Colors(set) => { 91 | colors = reader.read_colors(set).unwrap().into_rgb_f32().collect(); 92 | } 93 | gltf::Semantic::TexCoords(set) => { 94 | texcoords = reader.read_tex_coords(set).unwrap().into_f32().collect(); 95 | } 96 | _ => {} 97 | } 98 | } 99 | 100 | let Some(indices) = reader.read_indices() else { 101 | continue; 102 | }; 103 | let indices = indices.into_u32(); 104 | 105 | for index in indices { 106 | let vertex_position: Vec3 = positions.get(index as usize).unwrap().clone().into(); 107 | let vertex_normal: Option = 108 | normals.get(index as usize).map(|v| v.clone().into()); 109 | let vertex_texcoord: Option = 110 | texcoords.get(index as usize).map(|v| v.clone().into()); 111 | let vertex_color: Option = 112 | colors.get(index as usize).map(|v| v.clone().into()); 113 | 114 | mesh.vertices.push(Vertex { 115 | position: vertex_position.extend(1.0), 116 | normal: vertex_normal, 117 | texcoord: vertex_texcoord, 118 | // 如果顶点没有颜色,就随机生成一个 119 | color: vertex_color.or(Some(rand_color())), 120 | }); 121 | } 122 | 123 | // 材质(采用默认的blinn-phong材质) 124 | let material = Material::default(); 125 | // let gltf_material = gltf_primitive.material(); 126 | mesh.material = material; 127 | 128 | meshes.push(mesh); 129 | } 130 | } 131 | meshes 132 | } 133 | -------------------------------------------------------------------------------- /src/material.rs: -------------------------------------------------------------------------------- 1 | use crate::math::Vec3; 2 | 3 | // Blinn-Phong 材质 4 | #[derive(Clone, Copy, Debug)] 5 | pub struct Material { 6 | // 环境光反射系数Ka 7 | pub ambient: Vec3, 8 | // 漫反射系数Kd 9 | pub diffuse: Vec3, 10 | // 镜面反射系数Ks 11 | pub specular: Vec3, 12 | // 镜面反射高光度p 13 | pub shininess: f32, 14 | } 15 | 16 | impl Default for Material { 17 | fn default() -> Self { 18 | Self { 19 | ambient: Vec3::new(1.0, 1.0, 1.0), 20 | diffuse: Vec3::new(0.14, 0.24, 0.34), 21 | specular: Vec3::new(0.5, 0.5, 0.5), 22 | shininess: 64.0, 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul, Neg, Sub}; 2 | 3 | //// 二维向量 4 | #[derive(Debug, Clone, Copy, Default)] 5 | pub struct Vec2 { 6 | pub x: f32, 7 | pub y: f32, 8 | } 9 | impl Vec2 { 10 | pub const ZERO: Self = Self::splat(0.0); 11 | pub const ONE: Self = Self::splat(1.0); 12 | pub const NEG_ONE: Self = Self::splat(-1.0); 13 | pub const NAN: Self = Self::splat(f32::NAN); 14 | pub const X: Self = Self::new(1.0, 0.0); 15 | pub const Y: Self = Self::new(0.0, 1.0); 16 | pub const NEG_X: Self = Self::new(-1.0, 0.0); 17 | pub const NEG_Y: Self = Self::new(0.0, -1.0); 18 | 19 | pub const fn new(x: f32, y: f32) -> Self { 20 | Self { x, y } 21 | } 22 | pub const fn splat(v: f32) -> Self { 23 | Self { x: v, y: v } 24 | } 25 | // 点乘 26 | pub fn dot(self, rhs: Self) -> f32 { 27 | self.x * rhs.x + self.y * rhs.y 28 | } 29 | pub fn cross(self, rhs: Self) -> f32 { 30 | self.x * rhs.y - self.y * rhs.x 31 | } 32 | // 长度 33 | pub fn length(self) -> f32 { 34 | self.dot(self).sqrt() 35 | } 36 | // 归一化 37 | pub fn normalize(self) -> Self { 38 | let normalized = self.mul(self.length().recip()); 39 | assert!(normalized.is_finite()); 40 | normalized 41 | } 42 | pub fn is_finite(self) -> bool { 43 | self.x.is_finite() && self.y.is_finite() 44 | } 45 | pub fn extend(self, z: f32) -> Vec3 { 46 | Vec3::new(self.x, self.y, z) 47 | } 48 | } 49 | impl Add for Vec2 { 50 | type Output = Self; 51 | fn add(self, other: Self) -> Self { 52 | Self { 53 | x: self.x + other.x, 54 | y: self.y + other.y, 55 | } 56 | } 57 | } 58 | impl Sub for Vec2 { 59 | type Output = Self; 60 | fn sub(self, rhs: Self) -> Self { 61 | Self { 62 | x: self.x - rhs.x, 63 | y: self.y - rhs.y, 64 | } 65 | } 66 | } 67 | impl Mul for Vec2 { 68 | type Output = Self; 69 | fn mul(self, rhs: f32) -> Self { 70 | Self { 71 | x: self.x * rhs, 72 | y: self.y * rhs, 73 | } 74 | } 75 | } 76 | impl From for (f32, f32) { 77 | fn from(v: Vec2) -> Self { 78 | (v.x, v.y) 79 | } 80 | } 81 | impl From<(f32, f32)> for Vec2 { 82 | fn from(v: (f32, f32)) -> Self { 83 | Vec2::new(v.0, v.1) 84 | } 85 | } 86 | impl From<[f32; 2]> for Vec2 { 87 | fn from(v: [f32; 2]) -> Self { 88 | Vec2::new(v[0], v[1]) 89 | } 90 | } 91 | 92 | //// 三维向量 93 | #[derive(Debug, Clone, Copy, Default)] 94 | pub struct Vec3 { 95 | pub x: f32, 96 | pub y: f32, 97 | pub z: f32, 98 | } 99 | impl Vec3 { 100 | pub const ZERO: Self = Self::splat(0.0); 101 | pub const ONE: Self = Self::splat(1.0); 102 | pub const NEG_ONE: Self = Self::splat(-1.0); 103 | pub const NAN: Self = Self::splat(f32::NAN); 104 | pub const X: Self = Self::new(1.0, 0.0, 0.0); 105 | pub const Y: Self = Self::new(0.0, 1.0, 0.0); 106 | pub const Z: Self = Self::new(0.0, 0.0, 1.0); 107 | pub const NEG_X: Self = Self::new(-1.0, 0.0, 0.0); 108 | pub const NEG_Y: Self = Self::new(0.0, -1.0, 0.0); 109 | pub const NEG_Z: Self = Self::new(0.0, 0.0, -1.0); 110 | 111 | pub const fn new(x: f32, y: f32, z: f32) -> Self { 112 | Self { x, y, z } 113 | } 114 | pub const fn splat(v: f32) -> Self { 115 | Self { x: v, y: v, z: v } 116 | } 117 | // 点乘 118 | pub fn dot(self, rhs: Self) -> f32 { 119 | self.x * rhs.x + self.y * rhs.y + self.z * rhs.z 120 | } 121 | // 叉乘 122 | pub fn cross(self, rhs: Self) -> Self { 123 | Self { 124 | x: self.y * rhs.z - self.z * rhs.y, 125 | y: self.z * rhs.x - self.x * rhs.z, 126 | z: self.x * rhs.y - self.y * rhs.x, 127 | } 128 | } 129 | // 长度 130 | pub fn length(self) -> f32 { 131 | self.dot(self).sqrt() 132 | } 133 | // 归一化 134 | pub fn normalize(self) -> Self { 135 | let normalized = self.mul(self.length().recip()); 136 | assert!(normalized.is_finite()); 137 | normalized 138 | } 139 | pub fn is_finite(self) -> bool { 140 | self.x.is_finite() && self.y.is_finite() && self.z.is_finite() 141 | } 142 | pub fn is_normalized(self) -> bool { 143 | (self.length() - 1.0).abs() < 1e-4 144 | } 145 | pub fn extend(self, w: f32) -> Vec4 { 146 | Vec4::new(self.x, self.y, self.z, w) 147 | } 148 | pub fn truncate(self) -> Vec2 { 149 | Vec2::new(self.x, self.y) 150 | } 151 | } 152 | impl Add for Vec3 { 153 | type Output = Self; 154 | fn add(self, other: Self) -> Self { 155 | Self { 156 | x: self.x + other.x, 157 | y: self.y + other.y, 158 | z: self.z + other.z, 159 | } 160 | } 161 | } 162 | impl Sub for Vec3 { 163 | type Output = Self; 164 | fn sub(self, rhs: Self) -> Self { 165 | Self { 166 | x: self.x - rhs.x, 167 | y: self.y - rhs.y, 168 | z: self.z - rhs.z, 169 | } 170 | } 171 | } 172 | impl Mul for Vec3 { 173 | type Output = Self; 174 | fn mul(self, rhs: f32) -> Self { 175 | Self { 176 | x: self.x * rhs, 177 | y: self.y * rhs, 178 | z: self.z * rhs, 179 | } 180 | } 181 | } 182 | impl Neg for Vec3 { 183 | type Output = Self; 184 | fn neg(self) -> Self { 185 | Self { 186 | x: -self.x, 187 | y: -self.y, 188 | z: -self.z, 189 | } 190 | } 191 | } 192 | impl From for (f32, f32, f32) { 193 | fn from(v: Vec3) -> Self { 194 | (v.x, v.y, v.z) 195 | } 196 | } 197 | impl From<[f32; 3]> for Vec3 { 198 | fn from(v: [f32; 3]) -> Self { 199 | Vec3::new(v[0], v[1], v[2]) 200 | } 201 | } 202 | 203 | //// 四维向量 204 | #[derive(Debug, Clone, Copy, Default)] 205 | pub struct Vec4 { 206 | pub x: f32, 207 | pub y: f32, 208 | pub z: f32, 209 | pub w: f32, 210 | } 211 | impl Vec4 { 212 | pub const ZERO: Self = Self::splat(0.0); 213 | pub const ONE: Self = Self::splat(1.0); 214 | pub const NEG_ONE: Self = Self::splat(-1.0); 215 | pub const NAN: Self = Self::splat(f32::NAN); 216 | pub const X: Self = Self::new(1.0, 0.0, 0.0, 0.0); 217 | pub const Y: Self = Self::new(0.0, 1.0, 0.0, 0.0); 218 | pub const Z: Self = Self::new(0.0, 0.0, 1.0, 0.0); 219 | pub const W: Self = Self::new(0.0, 0.0, 0.0, 1.0); 220 | pub const NEG_X: Self = Self::new(-1.0, 0.0, 0.0, 0.0); 221 | pub const NEG_Y: Self = Self::new(0.0, -1.0, 0.0, 0.0); 222 | pub const NEG_Z: Self = Self::new(0.0, 0.0, -1.0, 0.0); 223 | pub const NEG_W: Self = Self::new(0.0, 0.0, 0.0, -1.0); 224 | 225 | pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self { 226 | Self { x, y, z, w } 227 | } 228 | pub const fn splat(v: f32) -> Self { 229 | Self { 230 | x: v, 231 | y: v, 232 | z: v, 233 | w: v, 234 | } 235 | } 236 | // 点乘 237 | pub fn dot(self, rhs: Self) -> f32 { 238 | self.x * rhs.x + self.y * rhs.y + self.z * rhs.z + self.w * rhs.w 239 | } 240 | // 长度 241 | pub fn length(self) -> f32 { 242 | self.dot(self).sqrt() 243 | } 244 | pub fn length_squared(self) -> f32 { 245 | self.dot(self) 246 | } 247 | // 归一化 248 | pub fn normalize(self) -> Self { 249 | let normalized = self.mul(self.length().recip()); 250 | assert!(normalized.is_finite()); 251 | normalized 252 | } 253 | pub fn is_finite(self) -> bool { 254 | self.x.is_finite() && self.y.is_finite() && self.z.is_finite() && self.w.is_finite() 255 | } 256 | pub fn is_normalized(self) -> bool { 257 | (self.length_squared() - 1.0).abs() <= 1e-4 258 | } 259 | pub fn to_cartesian_point(self) -> Vec3 { 260 | assert!(self.w != 0.0); 261 | Vec3::new(self.x / self.w, self.y / self.w, self.z / self.w) 262 | } 263 | pub fn to_cartesian_vector(self) -> Vec3 { 264 | assert!(self.w == 0.0); 265 | Vec3::new(self.x, self.y, self.z) 266 | } 267 | pub fn truncate(self) -> Vec3 { 268 | Vec3::new(self.x, self.y, self.z) 269 | } 270 | } 271 | impl Add for Vec4 { 272 | type Output = Self; 273 | fn add(self, other: Self) -> Self { 274 | Self { 275 | x: self.x + other.x, 276 | y: self.y + other.y, 277 | z: self.z + other.z, 278 | w: self.w + other.w, 279 | } 280 | } 281 | } 282 | impl Sub for Vec4 { 283 | type Output = Self; 284 | fn sub(self, rhs: Self) -> Self { 285 | Self { 286 | x: self.x - rhs.x, 287 | y: self.y - rhs.y, 288 | z: self.z - rhs.z, 289 | w: self.w - rhs.w, 290 | } 291 | } 292 | } 293 | impl Mul for Vec4 { 294 | type Output = Self; 295 | fn mul(self, rhs: f32) -> Self { 296 | Self { 297 | x: self.x * rhs, 298 | y: self.y * rhs, 299 | z: self.z * rhs, 300 | w: self.w * rhs, 301 | } 302 | } 303 | } 304 | impl From<[f32; 4]> for Vec4 { 305 | fn from(v: [f32; 4]) -> Self { 306 | Vec4::new(v[0], v[1], v[2], v[3]) 307 | } 308 | } 309 | 310 | //// 2x2按列存储矩阵 311 | pub struct Mat2 { 312 | pub x_axis: Vec2, 313 | pub y_axis: Vec2, 314 | } 315 | impl Mat2 { 316 | pub const ZERO: Self = Self::from_cols(Vec2::ZERO, Vec2::ZERO); 317 | // 单位矩阵 318 | pub const IDENTITY: Self = Self::from_cols(Vec2::X, Vec2::Y); 319 | 320 | pub const fn from_cols(x_axis: Vec2, y_axis: Vec2) -> Self { 321 | Self { x_axis, y_axis } 322 | } 323 | // 转置 324 | pub fn transpose(self) -> Self { 325 | Self { 326 | x_axis: Vec2 { 327 | x: self.x_axis.x, 328 | y: self.y_axis.x, 329 | }, 330 | y_axis: Vec2 { 331 | x: self.x_axis.y, 332 | y: self.y_axis.y, 333 | }, 334 | } 335 | } 336 | // TODO 逆矩阵 337 | pub fn inverse(self) -> Self { 338 | todo!() 339 | } 340 | } 341 | impl Add for Mat2 { 342 | type Output = Self; 343 | fn add(self, rhs: Self) -> Self { 344 | Self { 345 | x_axis: self.x_axis + rhs.x_axis, 346 | y_axis: self.y_axis + rhs.y_axis, 347 | } 348 | } 349 | } 350 | impl Sub for Mat2 { 351 | type Output = Self; 352 | fn sub(self, rhs: Self) -> Self { 353 | Self { 354 | x_axis: self.x_axis - rhs.x_axis, 355 | y_axis: self.y_axis - rhs.y_axis, 356 | } 357 | } 358 | } 359 | impl Mul for Mat2 { 360 | type Output = Self; 361 | fn mul(self, rhs: f32) -> Self { 362 | Self { 363 | x_axis: self.x_axis * rhs, 364 | y_axis: self.y_axis * rhs, 365 | } 366 | } 367 | } 368 | impl Mul for Mat2 { 369 | type Output = Vec2; 370 | fn mul(self, rhs: Vec2) -> Self::Output { 371 | Self::Output { 372 | x: self.x_axis.x * rhs.x + self.y_axis.x * rhs.y, 373 | y: self.x_axis.y * rhs.x + self.y_axis.y * rhs.y, 374 | } 375 | } 376 | } 377 | 378 | //// 3x3按列存储矩阵 379 | #[derive(Clone, Copy, Debug)] 380 | pub struct Mat3 { 381 | pub x_axis: Vec3, 382 | pub y_axis: Vec3, 383 | pub z_axis: Vec3, 384 | } 385 | impl Mat3 { 386 | pub const ZERO: Self = Self::from_cols(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO); 387 | // 单位矩阵 388 | pub const IDENTITY: Self = Self::from_cols(Vec3::X, Vec3::Y, Vec3::Z); 389 | 390 | pub const fn from_cols(x_axis: Vec3, y_axis: Vec3, z_axis: Vec3) -> Self { 391 | Self { 392 | x_axis, 393 | y_axis, 394 | z_axis, 395 | } 396 | } 397 | // 转置 398 | pub fn transpose(self) -> Self { 399 | Self { 400 | x_axis: Vec3 { 401 | x: self.x_axis.x, 402 | y: self.y_axis.x, 403 | z: self.z_axis.x, 404 | }, 405 | y_axis: Vec3 { 406 | x: self.x_axis.y, 407 | y: self.y_axis.y, 408 | z: self.z_axis.y, 409 | }, 410 | z_axis: Vec3 { 411 | x: self.x_axis.z, 412 | y: self.y_axis.z, 413 | z: self.z_axis.z, 414 | }, 415 | } 416 | } 417 | // TODO 逆矩阵 418 | pub fn inverse(self) -> Self { 419 | todo!() 420 | } 421 | } 422 | impl Add for Mat3 { 423 | type Output = Self; 424 | fn add(self, rhs: Self) -> Self { 425 | Self { 426 | x_axis: self.x_axis + rhs.x_axis, 427 | y_axis: self.y_axis + rhs.y_axis, 428 | z_axis: self.z_axis + rhs.z_axis, 429 | } 430 | } 431 | } 432 | impl Sub for Mat3 { 433 | type Output = Self; 434 | fn sub(self, rhs: Self) -> Self { 435 | Self { 436 | x_axis: self.x_axis - rhs.x_axis, 437 | y_axis: self.y_axis - rhs.y_axis, 438 | z_axis: self.z_axis - rhs.z_axis, 439 | } 440 | } 441 | } 442 | impl Mul for Mat3 { 443 | type Output = Self; 444 | fn mul(self, rhs: f32) -> Self { 445 | Self { 446 | x_axis: self.x_axis * rhs, 447 | y_axis: self.y_axis * rhs, 448 | z_axis: self.z_axis * rhs, 449 | } 450 | } 451 | } 452 | impl Mul for Mat3 { 453 | type Output = Vec3; 454 | fn mul(self, rhs: Vec3) -> Self::Output { 455 | Self::Output { 456 | x: self.x_axis.x * rhs.x + self.y_axis.x * rhs.y + self.z_axis.x * rhs.z, 457 | y: self.x_axis.y * rhs.x + self.y_axis.y * rhs.y + self.z_axis.y * rhs.z, 458 | z: self.x_axis.z * rhs.x + self.y_axis.z * rhs.y + self.z_axis.z * rhs.z, 459 | } 460 | } 461 | } 462 | impl Mul for Mat3 { 463 | type Output = Self; 464 | fn mul(self, rhs: Self) -> Self { 465 | Self { 466 | x_axis: self * rhs.x_axis, 467 | y_axis: self * rhs.y_axis, 468 | z_axis: self * rhs.z_axis, 469 | } 470 | } 471 | } 472 | 473 | //// 4x4按列存储矩阵 474 | #[derive(Debug, Clone, Copy)] 475 | pub struct Mat4 { 476 | pub x_axis: Vec4, 477 | pub y_axis: Vec4, 478 | pub z_axis: Vec4, 479 | pub w_axis: Vec4, 480 | } 481 | impl Mat4 { 482 | pub const ZERO: Self = Self::from_cols(Vec4::ZERO, Vec4::ZERO, Vec4::ZERO, Vec4::ZERO); 483 | // 单位矩阵 484 | pub const IDENTITY: Self = Self::from_cols(Vec4::X, Vec4::Y, Vec4::Z, Vec4::W); 485 | 486 | pub const fn from_cols(x_axis: Vec4, y_axis: Vec4, z_axis: Vec4, w_axis: Vec4) -> Self { 487 | Self { 488 | x_axis, 489 | y_axis, 490 | z_axis, 491 | w_axis, 492 | } 493 | } 494 | pub const fn from_rows_slice(slice: &[f32]) -> Self { 495 | assert!(slice.len() >= 16); 496 | Self { 497 | x_axis: Vec4::new(slice[0], slice[4], slice[8], slice[12]), 498 | y_axis: Vec4::new(slice[1], slice[5], slice[9], slice[13]), 499 | z_axis: Vec4::new(slice[2], slice[6], slice[10], slice[14]), 500 | w_axis: Vec4::new(slice[3], slice[7], slice[11], slice[15]), 501 | } 502 | } 503 | // 转置 504 | pub fn transpose(self) -> Self { 505 | Self { 506 | x_axis: Vec4 { 507 | x: self.x_axis.x, 508 | y: self.y_axis.x, 509 | z: self.z_axis.x, 510 | w: self.w_axis.x, 511 | }, 512 | y_axis: Vec4 { 513 | x: self.x_axis.y, 514 | y: self.y_axis.y, 515 | z: self.z_axis.y, 516 | w: self.w_axis.y, 517 | }, 518 | z_axis: Vec4 { 519 | x: self.x_axis.z, 520 | y: self.y_axis.z, 521 | z: self.z_axis.z, 522 | w: self.w_axis.z, 523 | }, 524 | w_axis: Vec4 { 525 | x: self.x_axis.w, 526 | y: self.y_axis.w, 527 | z: self.z_axis.w, 528 | w: self.w_axis.w, 529 | }, 530 | } 531 | } 532 | // TODO 逆矩阵 533 | pub fn inverse(self) -> Self { 534 | todo!() 535 | } 536 | } 537 | impl Add for Mat4 { 538 | type Output = Self; 539 | fn add(self, rhs: Self) -> Self { 540 | Self { 541 | x_axis: self.x_axis + rhs.x_axis, 542 | y_axis: self.y_axis + rhs.y_axis, 543 | z_axis: self.z_axis + rhs.z_axis, 544 | w_axis: self.w_axis + rhs.w_axis, 545 | } 546 | } 547 | } 548 | impl Sub for Mat4 { 549 | type Output = Self; 550 | fn sub(self, rhs: Self) -> Self { 551 | Self { 552 | x_axis: self.x_axis - rhs.x_axis, 553 | y_axis: self.y_axis - rhs.y_axis, 554 | z_axis: self.z_axis - rhs.z_axis, 555 | w_axis: self.w_axis - rhs.w_axis, 556 | } 557 | } 558 | } 559 | impl Mul for Mat4 { 560 | type Output = Self; 561 | fn mul(self, rhs: f32) -> Self { 562 | Self { 563 | x_axis: self.x_axis * rhs, 564 | y_axis: self.y_axis * rhs, 565 | z_axis: self.z_axis * rhs, 566 | w_axis: self.w_axis * rhs, 567 | } 568 | } 569 | } 570 | impl Mul for Mat4 { 571 | type Output = Vec4; 572 | fn mul(self, rhs: Vec4) -> Self::Output { 573 | Self::Output { 574 | x: self.x_axis.x * rhs.x 575 | + self.y_axis.x * rhs.y 576 | + self.z_axis.x * rhs.z 577 | + self.w_axis.x * rhs.w, 578 | y: self.x_axis.y * rhs.x 579 | + self.y_axis.y * rhs.y 580 | + self.z_axis.y * rhs.z 581 | + self.w_axis.y * rhs.w, 582 | z: self.x_axis.z * rhs.x 583 | + self.y_axis.z * rhs.y 584 | + self.z_axis.z * rhs.z 585 | + self.w_axis.z * rhs.w, 586 | w: self.x_axis.w * rhs.x 587 | + self.y_axis.w * rhs.y 588 | + self.z_axis.w * rhs.z 589 | + self.w_axis.w * rhs.w, 590 | } 591 | } 592 | } 593 | impl Mul for Mat4 { 594 | type Output = Self; 595 | fn mul(self, rhs: Self) -> Self { 596 | Self { 597 | x_axis: self * rhs.x_axis, 598 | y_axis: self * rhs.y_axis, 599 | z_axis: self * rhs.z_axis, 600 | w_axis: self * rhs.w_axis, 601 | } 602 | } 603 | } 604 | 605 | //// 四元数 606 | #[derive(Copy, Clone, Debug)] 607 | pub struct Quat { 608 | pub x: f32, 609 | pub y: f32, 610 | pub z: f32, 611 | pub w: f32, 612 | } 613 | impl Quat { 614 | pub const ZERO: Self = Self::from_xyzw(0.0, 0.0, 0.0, 0.0); 615 | // 恒等四元数(无旋转) 616 | pub const IDENTITY: Self = Self::from_xyzw(0.0, 0.0, 0.0, 1.0); 617 | 618 | pub const fn from_vec4(v: Vec4) -> Self { 619 | Self { 620 | x: v.x, 621 | y: v.y, 622 | z: v.z, 623 | w: v.w, 624 | } 625 | } 626 | pub const fn from_xyzw(x: f32, y: f32, z: f32, w: f32) -> Self { 627 | Self { x, y, z, w } 628 | } 629 | // 共轭 630 | pub fn conjugate(self) -> Self { 631 | Self { 632 | x: -self.x, 633 | y: -self.y, 634 | z: -self.z, 635 | w: self.w, 636 | } 637 | } 638 | // 四元数的逆 639 | pub fn inverse(self) -> Self { 640 | assert!(self.is_normalized()); 641 | self.conjugate() 642 | } 643 | // 绕轴旋转 644 | pub fn from_axis_angle(axis: Vec3, angle: f32) -> Self { 645 | assert!(axis.is_normalized()); 646 | let (s, c) = (angle * 0.5).sin_cos(); 647 | let v = axis * s; 648 | Self { 649 | x: v.x, 650 | y: v.y, 651 | z: v.z, 652 | w: c, 653 | } 654 | } 655 | // TODO 三维旋转矩阵->四元数 参考glam 656 | pub fn from_mat3(mat: &Mat3) -> Self { 657 | // Based on https://github.com/microsoft/DirectXMath `XM$quaternionRotationMatrix` 658 | let (m00, m01, m02) = mat.x_axis.into(); 659 | let (m10, m11, m12) = mat.y_axis.into(); 660 | let (m20, m21, m22) = mat.z_axis.into(); 661 | if m22 <= 0.0 { 662 | // x^2 + y^2 >= z^2 + w^2 663 | let dif10 = m11 - m00; 664 | let omm22 = 1.0 - m22; 665 | if dif10 <= 0.0 { 666 | // x^2 >= y^2 667 | let four_xsq = omm22 - dif10; 668 | let inv4x = 0.5 / four_xsq.sqrt(); 669 | Self::from_xyzw( 670 | four_xsq * inv4x, 671 | (m01 + m10) * inv4x, 672 | (m02 + m20) * inv4x, 673 | (m12 - m21) * inv4x, 674 | ) 675 | } else { 676 | // y^2 >= x^2 677 | let four_ysq = omm22 + dif10; 678 | let inv4y = 0.5 / four_ysq.sqrt(); 679 | Self::from_xyzw( 680 | (m01 + m10) * inv4y, 681 | four_ysq * inv4y, 682 | (m12 + m21) * inv4y, 683 | (m20 - m02) * inv4y, 684 | ) 685 | } 686 | } else { 687 | // z^2 + w^2 >= x^2 + y^2 688 | let sum10 = m11 + m00; 689 | let opm22 = 1.0 + m22; 690 | if sum10 <= 0.0 { 691 | // z^2 >= w^2 692 | let four_zsq = opm22 - sum10; 693 | let inv4z = 0.5 / four_zsq.sqrt(); 694 | Self::from_xyzw( 695 | (m02 + m20) * inv4z, 696 | (m12 + m21) * inv4z, 697 | four_zsq * inv4z, 698 | (m01 - m10) * inv4z, 699 | ) 700 | } else { 701 | // w^2 >= z^2 702 | let four_wsq = opm22 + sum10; 703 | let inv4w = 0.5 / four_wsq.sqrt(); 704 | Self::from_xyzw( 705 | (m12 - m21) * inv4w, 706 | (m20 - m02) * inv4w, 707 | (m01 - m10) * inv4w, 708 | four_wsq * inv4w, 709 | ) 710 | } 711 | } 712 | } 713 | // 四元数转换为旋转矩阵(齐次坐标) 714 | pub fn to_mat4(self) -> Mat4 { 715 | assert!(self.is_normalized()); 716 | let (x, y, z, w) = (self.x, self.y, self.z, self.w); 717 | let (xx, yy, zz) = (x * x, y * y, z * z); 718 | let (xy, xz, yz) = (x * y, x * z, y * z); 719 | let (wx, wy, wz) = (w * x, w * y, w * z); 720 | Mat4::from_cols( 721 | Vec4::new(1.0 - 2.0 * (yy + zz), 2.0 * (xy + wz), 2.0 * (xz - wy), 0.0), 722 | Vec4::new(2.0 * (xy - wz), 1.0 - 2.0 * (xx + zz), 2.0 * (yz + wx), 0.0), 723 | Vec4::new(2.0 * (xz + wy), 2.0 * (yz - wx), 1.0 - 2.0 * (xx + yy), 0.0), 724 | Vec4::W, 725 | ) 726 | } 727 | pub fn length(self) -> f32 { 728 | Vec4::new(self.x, self.y, self.z, self.w).length() 729 | } 730 | pub fn length_squared(self) -> f32 { 731 | Vec4::new(self.x, self.y, self.z, self.w).length_squared() 732 | } 733 | pub fn is_normalized(self) -> bool { 734 | Vec4::new(self.x, self.y, self.z, self.w).is_normalized() 735 | } 736 | } 737 | impl Add for Quat { 738 | type Output = Self; 739 | fn add(self, rhs: Self) -> Self { 740 | Self { 741 | x: self.x + rhs.x, 742 | y: self.y + rhs.y, 743 | z: self.z + rhs.z, 744 | w: self.w + rhs.w, 745 | } 746 | } 747 | } 748 | impl Sub for Quat { 749 | type Output = Self; 750 | fn sub(self, rhs: Self) -> Self { 751 | Self { 752 | x: self.x - rhs.x, 753 | y: self.y - rhs.y, 754 | z: self.z - rhs.z, 755 | w: self.w - rhs.w, 756 | } 757 | } 758 | } 759 | impl Mul for Quat { 760 | type Output = Self; 761 | fn mul(self, rhs: f32) -> Self { 762 | Self { 763 | x: self.x * rhs, 764 | y: self.y * rhs, 765 | z: self.z * rhs, 766 | w: self.w * rhs, 767 | } 768 | } 769 | } 770 | impl Mul for Quat { 771 | type Output = Quat; 772 | fn mul(self, rhs: Quat) -> Self::Output { 773 | Self::Output { 774 | x: self.w * rhs.x + self.x * rhs.w + self.y * rhs.z - self.z * rhs.y, 775 | y: self.w * rhs.y + self.y * rhs.w + self.z * rhs.x - self.x * rhs.z, 776 | z: self.w * rhs.z + self.z * rhs.w + self.x * rhs.y - self.y * rhs.x, 777 | w: self.w * rhs.w - self.x * rhs.x - self.y * rhs.y - self.z * rhs.z, 778 | } 779 | } 780 | } 781 | impl Mul for Quat { 782 | type Output = Vec3; 783 | fn mul(self, rhs: Vec3) -> Self::Output { 784 | let q = Quat::from_vec4(Vec4::new(rhs.x, rhs.y, rhs.z, 0.0)); 785 | let self_inv = self.inverse(); 786 | let v = self * q * self_inv; 787 | Vec3::new(v.x, v.y, v.z) 788 | } 789 | } 790 | -------------------------------------------------------------------------------- /src/mesh.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | color::Color, 3 | material::Material, 4 | math::{Vec2, Vec3, Vec4}, 5 | }; 6 | 7 | #[derive(Clone, Copy, Debug, Default)] 8 | pub struct Vertex { 9 | // 位置坐标(齐次坐标) 10 | pub position: Vec4, 11 | // 法线向量 12 | pub normal: Option, 13 | // 纹理坐标 14 | pub texcoord: Option, 15 | // 顶点颜色 16 | pub color: Option, 17 | } 18 | #[derive(Clone, Debug, Default)] 19 | pub struct Mesh { 20 | // 顶点数据(拓扑类型为Triangles) 21 | pub vertices: Vec, 22 | pub material: Material, 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | camera::Camera, 3 | color::Color, 4 | light::PointLight, 5 | material::Material, 6 | math::{Mat4, Vec2, Vec3}, 7 | mesh::{Mesh, Vertex}, 8 | shader::{FragmentShader, FragmentShaderPayload, VertexShader}, 9 | texture::TextureStorage, 10 | }; 11 | 12 | //// 视口 13 | #[derive(Debug, Clone, Copy)] 14 | pub struct Viewport { 15 | // 视口左上角的坐标 16 | pub x: i32, 17 | pub y: i32, 18 | // 视口的宽高 19 | pub width: u32, 20 | pub height: u32, 21 | } 22 | impl Viewport { 23 | pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self { 24 | Self { 25 | x, 26 | y, 27 | width, 28 | height, 29 | } 30 | } 31 | } 32 | 33 | #[derive(Debug, Clone, Copy, Default)] 34 | pub struct RendererSettings { 35 | pub projection: Projection, 36 | // 是否绘制线框 37 | pub wireframe: bool, 38 | // 是否根据顶点颜色插值填充 39 | pub vertex_color_interp: bool, 40 | // 是否采用片段着色 41 | pub fragment_shading: bool, 42 | } 43 | #[derive(Debug, Clone, Copy, Default)] 44 | pub enum Projection { 45 | #[default] 46 | Perspective, 47 | Orthographic, 48 | } 49 | 50 | #[derive(Debug, Clone, Copy)] 51 | pub struct Aabb2d { 52 | pub min: Vec2, 53 | pub max: Vec2, 54 | } 55 | impl Aabb2d { 56 | pub fn new(min: Vec2, max: Vec2) -> Self { 57 | Self { min, max } 58 | } 59 | } 60 | 61 | pub struct Renderer { 62 | pub camera: Camera, 63 | pub viewport: Viewport, 64 | pub settings: RendererSettings, 65 | pub vertex_shader: Option, 66 | pub fragment_shader: Option, 67 | // 帧缓冲 68 | pub frame_buffer: Vec, 69 | // 深度缓冲 70 | pub depth_buffer: Vec, 71 | } 72 | impl Renderer { 73 | pub fn new(camera: Camera, viewport: Viewport, settings: RendererSettings) -> Self { 74 | let pixel_count = (viewport.width * viewport.height) as usize; 75 | Self { 76 | camera, 77 | viewport, 78 | settings, 79 | vertex_shader: None, 80 | fragment_shader: None, 81 | frame_buffer: vec![0; pixel_count * 3], 82 | depth_buffer: vec![std::f32::MIN; pixel_count], 83 | } 84 | } 85 | 86 | pub fn draw( 87 | &mut self, 88 | meshes: &Vec, 89 | model_transformation: Mat4, 90 | light: PointLight, 91 | texture_storage: &TextureStorage, 92 | ) { 93 | for mesh in meshes.iter() { 94 | for i in 0..mesh.vertices.len() / 3 { 95 | let mut triangle = [ 96 | mesh.vertices[i * 3], 97 | mesh.vertices[1 + i * 3], 98 | mesh.vertices[2 + i * 3], 99 | ]; 100 | 101 | let world_positions: [Vec3; 3] = triangle.map(|v| v.position.to_cartesian_point()); 102 | 103 | // 顶点着色 104 | self.vertex_shading(&mut triangle); 105 | 106 | // 模型变换 107 | self.apply_model_transformation(&mut triangle, model_transformation); 108 | 109 | // 视图变换 110 | self.apply_view_transformation(&mut triangle); 111 | 112 | // 背面剔除 113 | if Self::back_face_cull( 114 | triangle.map(|v| v.position.to_cartesian_point()), 115 | Vec3::NEG_Z, 116 | ) { 117 | continue; 118 | } 119 | 120 | // 保存视图空间坐标 121 | let view_space_positions: [Vec3; 3] = 122 | triangle.map(|v| v.position.to_cartesian_point()); 123 | 124 | // 投影变换 125 | self.apply_projection_transformation(&mut triangle); 126 | 127 | // 透视(齐次)除法 128 | Self::homogeneous_division(&mut triangle); 129 | 130 | // TODO 视椎体裁剪 131 | 132 | // 视口变换 133 | self.apply_viewport_transformation(&mut triangle); 134 | 135 | // 线框渲染 136 | if self.settings.wireframe { 137 | self.draw_wireframe(&triangle, Color::WHITE); 138 | } 139 | 140 | // 光栅化 141 | self.rasterize_trianlge( 142 | world_positions, 143 | view_space_positions, 144 | triangle, 145 | &mesh.material, 146 | &light, 147 | texture_storage, 148 | ); 149 | } 150 | } 151 | } 152 | 153 | pub fn rasterize_trianlge( 154 | &mut self, 155 | world_positions: [Vec3; 3], 156 | view_space_positions: [Vec3; 3], 157 | triangle: [Vertex; 3], 158 | material: &Material, 159 | light: &PointLight, 160 | texture_storage: &TextureStorage, 161 | ) { 162 | // 包围盒 163 | let aabb2d = bounding_box2d(&triangle.map(|v| Vec2::new(v.position.x, v.position.y))); 164 | 165 | // 光栅化 166 | for x in aabb2d.min.x as u32..=aabb2d.max.x as u32 { 167 | for y in aabb2d.min.y as u32..=aabb2d.max.y as u32 { 168 | // 计算屏幕三角形重心坐标 169 | let p = Vec2::new(x as f32, y as f32); 170 | let barycenter = barycentric_2d_triangle(p, &triangle); 171 | 172 | // 判断是否在三角形内 173 | if Self::inside_triangle(barycenter) { 174 | let z = Self::z_interpolation(&triangle, barycenter); 175 | let index = (y * self.viewport.width + x) as usize; 176 | 177 | // 深度测试 178 | if z > self.depth_buffer[index] { 179 | self.depth_buffer[index] = z; 180 | 181 | // 透视矫正 182 | let barycenter = Self::perspective_correct(&triangle, barycenter); 183 | 184 | if self.settings.fragment_shading { 185 | // 片段着色 186 | if let Some(fragment_shader) = &self.fragment_shader { 187 | let fragment_shader_payload = FragmentShaderPayload { 188 | triangle, 189 | world_positions, 190 | view_space_positions, 191 | barycenter, 192 | light: light.clone(), 193 | camera_world_position: self.camera.position, 194 | material: material.clone(), 195 | ..Default::default() 196 | }; 197 | let color = 198 | fragment_shader(&fragment_shader_payload, texture_storage); 199 | self.draw_pixel(p, color); 200 | } 201 | } else if self.settings.vertex_color_interp { 202 | // 顶点颜色插值 203 | if triangle[0].color.is_some() 204 | && triangle[1].color.is_some() 205 | && triangle[2].color.is_some() 206 | { 207 | let color = triangle[0].color.unwrap() * barycenter.0 208 | + triangle[1].color.unwrap() * barycenter.1 209 | + triangle[2].color.unwrap() * barycenter.2; 210 | self.draw_pixel(p, color); 211 | } 212 | } 213 | } 214 | } 215 | } 216 | } 217 | } 218 | 219 | pub fn vertex_shading(&self, vertices: &mut [Vertex]) { 220 | if let Some(vertex_shader) = &self.vertex_shader { 221 | for vertex in vertices.iter_mut() { 222 | vertex_shader(vertex); 223 | } 224 | } 225 | } 226 | 227 | pub fn apply_model_transformation(&self, vertices: &mut [Vertex], model_transformation: Mat4) { 228 | for vertex in vertices.iter_mut() { 229 | vertex.position = model_transformation * vertex.position; 230 | } 231 | } 232 | 233 | pub fn apply_view_transformation(&mut self, vertices: &mut [Vertex]) { 234 | let view_transformation = self.camera.view_transformation(); 235 | for vertex in vertices.iter_mut() { 236 | vertex.position = view_transformation * vertex.position; 237 | } 238 | } 239 | 240 | pub fn apply_projection_transformation(&self, vertices: &mut [Vertex]) { 241 | let projection_transformation = match self.settings.projection { 242 | Projection::Perspective => self.camera.frustum.persp_projection_transformation(), 243 | Projection::Orthographic => self.camera.frustum.ortho_projection_transformation(), 244 | }; 245 | for vertex in vertices.iter_mut() { 246 | vertex.position = projection_transformation * vertex.position; 247 | } 248 | } 249 | 250 | pub fn apply_viewport_transformation(&self, vertices: &mut [Vertex]) { 251 | // 视口变换 252 | // TODO 矩阵 253 | for vertex in vertices.iter_mut() { 254 | vertex.position.x = (vertex.position.x + 1.0) * (self.viewport.width as f32 - 1.0) 255 | / 2.0 256 | + self.viewport.x as f32; 257 | vertex.position.y = (vertex.position.y + 1.0) * (self.viewport.height as f32 - 1.0) 258 | / 2.0 259 | + self.viewport.y as f32; 260 | } 261 | } 262 | 263 | pub fn frustum_cull(&self, _triangle: [Vec3; 3]) -> bool { 264 | todo!() 265 | } 266 | 267 | pub fn homogeneous_clip() { 268 | todo!() 269 | } 270 | 271 | pub fn back_face_cull(triangle: [Vec3; 3], view_direction: Vec3) -> bool { 272 | // 默认三角形顶点顺序为逆时针 273 | let normal = (triangle[1] - triangle[0]).cross(triangle[2] - triangle[0]); 274 | normal.dot(view_direction) > 0.0 275 | } 276 | 277 | // 绘制像素点 278 | pub fn draw_pixel(&mut self, p0: Vec2, color: Color) { 279 | let x = p0.x as i32; 280 | let y = p0.y as i32; 281 | if x < self.viewport.x 282 | || x >= self.viewport.x + self.viewport.width as i32 283 | || y < self.viewport.y 284 | || y >= self.viewport.y + self.viewport.height as i32 285 | { 286 | println!("pixel out of viewport"); 287 | return; 288 | } 289 | // 以viewport左下角为原点 290 | let (x, y) = (x - self.viewport.x, y - self.viewport.y); 291 | let index = (y * self.viewport.width as i32 + x) as usize; 292 | self.frame_buffer[index * 3] = (color.r * 255.) as u8; 293 | self.frame_buffer[index * 3 + 1] = (color.g * 255.) as u8; 294 | self.frame_buffer[index * 3 + 2] = (color.b * 255.) as u8; 295 | } 296 | 297 | pub fn draw_wireframe(&mut self, vertices: &[Vertex], color: Color) { 298 | for i in 0..vertices.len() { 299 | let p0 = vertices[i].position; 300 | let p1 = vertices[(i + 1) % vertices.len()].position; 301 | self.draw_line(Vec2::new(p0.x, p0.y), Vec2::new(p1.x, p1.y), color); 302 | } 303 | } 304 | 305 | // Bresenham画线算法 306 | pub fn draw_line(&mut self, p0: Vec2, p1: Vec2, color: Color) { 307 | // 线段裁剪 308 | let clip_result = line_clip( 309 | p0, 310 | p1, 311 | Vec2::ZERO, 312 | Vec2::new(self.viewport.width as f32, self.viewport.height as f32), 313 | ); 314 | if clip_result.is_none() { 315 | return; 316 | } 317 | let (p0, p1) = clip_result.unwrap(); 318 | 319 | let mut x0 = p0.x as i32; 320 | let mut y0 = p0.y as i32; 321 | let mut x1 = p1.x as i32; 322 | let mut y1 = p1.y as i32; 323 | 324 | // 斜率无穷大 325 | if x0 == x1 { 326 | let mut y = y0; 327 | loop { 328 | self.draw_pixel(Vec2::new(x0 as f32, y as f32), color); 329 | if y == y1 { 330 | break; 331 | } 332 | y += if y1 > y0 { 1 } else { -1 }; 333 | } 334 | return; 335 | } 336 | // 斜率为0 337 | if y0 == y1 { 338 | let mut x = x0; 339 | loop { 340 | self.draw_pixel(Vec2::new(x as f32, y0 as f32), color); 341 | if x == x1 { 342 | break; 343 | } 344 | x += if x1 > x0 { 1 } else { -1 }; 345 | } 346 | return; 347 | } 348 | 349 | // 交换起始点和终点,使得永远保持从左到右画线的顺序(x1 - x0 > 0),不影响线段,此时线段只会在一四象限 350 | if x0 > x1 { 351 | let tmp_x = x0; 352 | let tmp_y = y0; 353 | x0 = x1; 354 | y0 = y1; 355 | x1 = tmp_x; 356 | y1 = tmp_y; 357 | } 358 | 359 | // 沿y=x对称 360 | let mut flag0 = false; 361 | // 沿x轴对称,再沿y=x对称 362 | let mut flag1 = false; 363 | // 沿x轴对称 364 | let mut flag2 = false; 365 | 366 | if y1 - y0 > x1 - x0 { 367 | // 斜率大于1,沿y=x对称(交换x和y) 368 | let temp = x0; 369 | x0 = y0; 370 | y0 = temp; 371 | let temp = x1; 372 | x1 = y1; 373 | y1 = temp; 374 | flag0 = true; 375 | } else if y1 < y0 { 376 | // 斜率小于0 377 | 378 | // 沿x轴对称 379 | y0 = -y0; 380 | y1 = -y1; 381 | 382 | if y1 - y0 > x1 - x0 { 383 | // 沿x轴对称后斜率大于1,则再沿y=x对称(交换x和y) 384 | let temp = x0; 385 | x0 = y0; 386 | y0 = temp; 387 | let temp = x1; 388 | x1 = y1; 389 | y1 = temp; 390 | flag1 = true; 391 | } else { 392 | // 沿x轴对称后斜率小于1 393 | flag2 = true; 394 | } 395 | } 396 | 397 | let dx = x1 - x0; 398 | let dy = y1 - y0; 399 | let incr_n = 2 * dy; 400 | let incr_ne = 2 * (dy - dx); 401 | let mut d = 2 * dy - dx; 402 | 403 | if flag0 { 404 | self.draw_pixel(Vec2::new(y0 as f32, x0 as f32), color); 405 | } else if flag1 { 406 | self.draw_pixel(Vec2::new(y0 as f32, -x0 as f32), color); 407 | } else if flag2 { 408 | self.draw_pixel(Vec2::new(x0 as f32, -y0 as f32), color); 409 | } else { 410 | self.draw_pixel(Vec2::new(x0 as f32, y0 as f32), color); 411 | } 412 | 413 | let mut y = y0; 414 | for x in x0 + 1..x1 { 415 | if d < 0 { 416 | d += incr_n; 417 | } else { 418 | y += 1; 419 | d += incr_ne; 420 | } 421 | if flag0 { 422 | self.draw_pixel(Vec2::new(y as f32, x as f32), color); 423 | } else if flag1 { 424 | self.draw_pixel(Vec2::new(y as f32, -x as f32), color); 425 | } else if flag2 { 426 | self.draw_pixel(Vec2::new(x as f32, -y as f32), color); 427 | } else { 428 | self.draw_pixel(Vec2::new(x as f32, y as f32), color); 429 | } 430 | } 431 | } 432 | 433 | pub fn homogeneous_division(vertices: &mut [Vertex]) { 434 | vertices.iter_mut().for_each(|v| { 435 | v.position.x /= v.position.w; 436 | v.position.y /= v.position.w; 437 | v.position.z /= v.position.w; 438 | // 保留w值 439 | }) 440 | } 441 | 442 | pub fn inside_triangle((alpha, beta, gamma): (f32, f32, f32)) -> bool { 443 | alpha > 0.0 && beta > 0.0 && gamma > 0.0 444 | } 445 | 446 | pub fn z_interpolation(triangle: &[Vertex; 3], (alpha, beta, gamma): (f32, f32, f32)) -> f32 { 447 | let v0 = triangle[0].position; 448 | let v1 = triangle[1].position; 449 | let v2 = triangle[2].position; 450 | let w_reciprocal = 1.0 / (alpha / v0.w + beta / v1.w + gamma / v2.w); 451 | let mut z_interpolated = alpha * v0.z / v0.w + beta * v1.z / v1.w + gamma * v2.z / v2.w; 452 | z_interpolated *= w_reciprocal; 453 | z_interpolated 454 | } 455 | 456 | // TODO 理解透视矫正 457 | pub fn perspective_correct( 458 | triangle: &[Vertex; 3], 459 | (alpha, beta, gamma): (f32, f32, f32), 460 | ) -> (f32, f32, f32) { 461 | let w0 = triangle[0].position.w.recip() * alpha; 462 | let w1 = triangle[1].position.w.recip() * beta; 463 | let w2 = triangle[2].position.w.recip() * gamma; 464 | let normalizer = 1.0 / (w0 + w1 + w2); 465 | (w0 * normalizer, w1 * normalizer, w2 * normalizer) 466 | } 467 | 468 | pub fn clear(&mut self) { 469 | self.frame_buffer.fill(0); 470 | self.depth_buffer.fill(f32::MIN); 471 | } 472 | } 473 | 474 | // 2D重心坐标 475 | pub fn barycentric_2d_triangle(p: Vec2, triangle: &[Vertex; 3]) -> (f32, f32, f32) { 476 | barycentric_2d( 477 | p, 478 | Vec2::new(triangle[0].position.x, triangle[0].position.y), 479 | Vec2::new(triangle[1].position.x, triangle[1].position.y), 480 | Vec2::new(triangle[2].position.x, triangle[2].position.y), 481 | ) 482 | } 483 | pub fn barycentric_2d(p: Vec2, a: Vec2, b: Vec2, c: Vec2) -> (f32, f32, f32) { 484 | let area_twice = (b - a).cross(c - a); 485 | let alpha = (b - p).cross(c - p) / area_twice; 486 | let beta = (c - p).cross(a - p) / area_twice; 487 | let gamma = (a - p).cross(b - p) / area_twice; 488 | (alpha, beta, gamma) 489 | } 490 | 491 | // Cohen-Sutherland线段裁剪算法 492 | const INSIDE: u8 = 0; // 0000 493 | const LEFT: u8 = 1; // 0001 494 | const RIGHT: u8 = 2; // 0010 495 | const BOTTOM: u8 = 4; // 0100 496 | const TOP: u8 = 8; // 1000 497 | fn compute_out_code(p: &Vec2, min: &Vec2, max: &Vec2) -> u8 { 498 | let horizontal_code = if p.x < min.x { 499 | LEFT 500 | } else if p.x > max.x { 501 | RIGHT 502 | } else { 503 | INSIDE 504 | }; 505 | let vertical_code = if p.y < min.y { 506 | BOTTOM 507 | } else if p.y > max.y { 508 | TOP 509 | } else { 510 | INSIDE 511 | }; 512 | horizontal_code | vertical_code 513 | } 514 | pub fn line_clip( 515 | mut p0: Vec2, 516 | mut p1: Vec2, 517 | rect_min: Vec2, 518 | rect_max: Vec2, 519 | ) -> Option<(Vec2, Vec2)> { 520 | let mut out_code0 = compute_out_code(&p0, &rect_min, &rect_max); 521 | let mut out_code1 = compute_out_code(&p1, &rect_min, &rect_max); 522 | 523 | loop { 524 | if out_code0 & out_code1 != 0 { 525 | // 两个点在inside外面的同一侧 526 | return None; 527 | } else if out_code0 | out_code1 == 0 { 528 | // 两个点都在inside内 529 | return Some((p0, p1)); 530 | } 531 | 532 | // 至少有一个outcode在inside外面 533 | let out_code = if out_code0 > out_code1 { 534 | out_code0 535 | } else { 536 | out_code1 537 | }; 538 | 539 | // 找到与矩形相交的边界 540 | let mut p = Vec2::ZERO; 541 | if out_code & TOP != 0 { 542 | p.x = p0.x + (p1.x - p0.x) * (rect_max.y - p0.y) / (p1.y - p0.y); 543 | p.y = rect_max.y; 544 | } else if out_code & BOTTOM != 0 { 545 | p.x = p0.x + (p0.x - p0.x) * (rect_min.y - p0.y) / (p1.y - p0.y); 546 | p.y = rect_min.y; 547 | } else if out_code & RIGHT != 0 { 548 | p.x = rect_max.x; 549 | p.y = p0.y + (p1.y - p0.y) * (rect_max.x - p0.x) / (p1.x - p0.x); 550 | } else if out_code & LEFT != 0 { 551 | p.x = rect_min.x; 552 | p.y = p0.y + (p1.y - p0.y) * (rect_min.x - p0.x) / (p1.x - p0.x); 553 | } 554 | 555 | // 用相交的边界点替换原来的点 556 | if out_code == out_code0 { 557 | p0.x = p.x; 558 | p0.y = p.y; 559 | out_code0 = compute_out_code(&p0, &rect_min, &rect_max); 560 | } else { 561 | p1.x = p.x; 562 | p1.y = p.y; 563 | out_code1 = compute_out_code(&p1, &rect_min, &rect_max); 564 | } 565 | } 566 | } 567 | 568 | // 三角形包围盒 569 | pub fn bounding_box2d(points: &[Vec2]) -> Aabb2d { 570 | let mut min = Vec2::new(f32::MAX, f32::MAX); 571 | let mut max = Vec2::new(f32::MIN, f32::MIN); 572 | for p in points { 573 | if p.x < min.x { 574 | min.x = p.x; 575 | } 576 | if p.y < min.y { 577 | min.y = p.y; 578 | } 579 | if p.x > max.x { 580 | max.x = p.x; 581 | } 582 | if p.y > max.y { 583 | max.y = p.y; 584 | } 585 | } 586 | Aabb2d::new(min, max) 587 | } 588 | -------------------------------------------------------------------------------- /src/shader.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::{ 4 | color::Color, 5 | light::PointLight, 6 | material::Material, 7 | math::{Mat4, Vec2, Vec3, Vec4}, 8 | mesh::Vertex, 9 | texture::TextureStorage, 10 | }; 11 | 12 | const AMBIENT_LIGHT_INTENSITY: f32 = 0.2; 13 | 14 | // TODO 使用引用+生命周期 15 | #[derive(Debug, Clone, Default)] 16 | pub struct FragmentShaderPayload { 17 | pub triangle: [Vertex; 3], 18 | pub world_positions: [Vec3; 3], 19 | pub view_space_positions: [Vec3; 3], 20 | pub barycenter: (f32, f32, f32), 21 | pub light: PointLight, 22 | pub camera_world_position: Vec3, 23 | pub material: Material, 24 | } 25 | 26 | pub type VertexShader = Box; 27 | pub type FragmentShader = Box Color>; 28 | 29 | #[derive(Debug, Default)] 30 | pub struct Uniforms { 31 | pub int: HashMap, 32 | pub float: HashMap, 33 | pub vec2: HashMap, 34 | pub vec3: HashMap, 35 | pub vec4: HashMap, 36 | pub mat4: HashMap, 37 | pub texture: HashMap, 38 | } 39 | 40 | pub fn phong_shader() -> FragmentShader { 41 | Box::new(|payload, texture_storage| { 42 | let world_positions = payload.world_positions; 43 | let camera_world_position = payload.camera_world_position; 44 | let triangle = payload.triangle; 45 | let (alpha, beta, gamma) = payload.barycenter; 46 | let light = payload.light; 47 | let material = payload.material; 48 | 49 | // 着色点 50 | let pos = 51 | world_positions[0] * alpha + world_positions[1] * beta + world_positions[2] * gamma; 52 | let texcoord = if triangle[0].texcoord.is_some() 53 | && triangle[1].texcoord.is_some() 54 | && triangle[2].texcoord.is_some() 55 | { 56 | Some( 57 | triangle[0].texcoord.unwrap() * alpha 58 | + triangle[1].texcoord.unwrap() * beta 59 | + triangle[2].texcoord.unwrap() * gamma, 60 | ) 61 | } else { 62 | None 63 | }; 64 | let texcolor = if texcoord.is_some() { 65 | texture_storage 66 | .texture_id_map 67 | .get(&0) 68 | .map(|texture| texture.sample(texcoord.unwrap())) 69 | } else { 70 | None 71 | }; 72 | 73 | // TODO 处理unwrap / 使用宏简化 74 | // 法线 75 | let n = (triangle[0].normal.unwrap() * alpha 76 | + triangle[1].normal.unwrap() * beta 77 | + triangle[2].normal.unwrap() * gamma) 78 | .normalize(); 79 | // 入射光线向量 80 | let l = (light.position - pos).normalize(); 81 | // 视线向量 82 | let v = (camera_world_position - pos).normalize(); 83 | // 半程向量 84 | let h = (l + v).normalize(); 85 | // 入射光线距离 86 | let r = (light.position - pos).length(); 87 | 88 | // 环境光 89 | let ambient = material.ambient * AMBIENT_LIGHT_INTENSITY; 90 | // 漫反射 91 | let diffuse = material.diffuse * (light.intensity / (r * r)) * n.dot(l).max(0.0); 92 | // 镜面反射 93 | let specular = material.specular 94 | * (light.intensity / (r * r)) 95 | * (n.dot(h).max(0.0).powf(material.shininess)); 96 | 97 | let light = ambient + diffuse + specular; 98 | let (mut r, mut g, mut b) = if let Some(texcolor) = texcolor { 99 | ( 100 | light.x * texcolor.r, 101 | light.y * texcolor.g, 102 | light.z * texcolor.b, 103 | ) 104 | } else { 105 | (light.x, light.y, light.z) 106 | }; 107 | r = r.clamp(0.0, 1.0); 108 | g = g.clamp(0.0, 1.0); 109 | b = b.clamp(0.0, 1.0); 110 | Color::new(r, g, b) 111 | }) 112 | } 113 | -------------------------------------------------------------------------------- /src/texture.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use gltf::{ 4 | image::Format, 5 | texture::{MagFilter, MinFilter, WrappingMode}, 6 | }; 7 | 8 | use crate::{color::Color, math::Vec2}; 9 | 10 | #[derive(Clone, Debug)] 11 | pub struct Sampler { 12 | pub mag_filter: Option, 13 | pub min_filter: Option, 14 | pub wrap_s: WrappingMode, 15 | pub wrap_t: WrappingMode, 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct Texture { 20 | pub id: usize, 21 | pub width: u32, 22 | pub height: u32, 23 | pub format: Format, 24 | pub data: Vec, 25 | pub sampler: Sampler, 26 | } 27 | impl Texture { 28 | pub fn sample(&self, mut texcoord: Vec2) -> Color { 29 | if self.sampler.wrap_s != WrappingMode::Repeat 30 | || self.sampler.wrap_t != WrappingMode::Repeat 31 | { 32 | panic!("Unsupported texture wrap mode: {:?}", self.sampler.wrap_s) 33 | } 34 | if texcoord.x > 1.0 { 35 | texcoord.x -= texcoord.x.floor(); 36 | } 37 | if texcoord.y > 1.0 { 38 | texcoord.y -= texcoord.y.floor(); 39 | } 40 | let x = (texcoord.x * (self.width - 1) as f32) as usize; 41 | let y = (texcoord.y * (self.height - 1) as f32) as usize; 42 | 43 | let index = if self.format == Format::R8G8B8 { 44 | (y * self.width as usize + x) * 3 45 | } else if self.format == Format::R8G8B8A8 { 46 | (y * self.width as usize + x) * 4 47 | } else { 48 | panic!("Unsupported texture format: {:?}", self.format); 49 | }; 50 | Color::new( 51 | self.data[index] as f32 / 255., 52 | self.data[index + 1] as f32 / 255., 53 | self.data[index + 2] as f32 / 255., 54 | ) 55 | } 56 | } 57 | 58 | #[derive(Debug, Default)] 59 | pub struct TextureStorage { 60 | pub texture_id_map: HashMap, 61 | } 62 | -------------------------------------------------------------------------------- /src/transform.rs: -------------------------------------------------------------------------------- 1 | use crate::math::{Mat4, Quat, Vec3, Vec4}; 2 | 3 | // 缩放矩阵 4 | pub fn scale_mat4(scale: Vec3) -> Mat4 { 5 | Mat4 { 6 | x_axis: Vec4 { 7 | x: scale.x, 8 | y: 0., 9 | z: 0., 10 | w: 0., 11 | }, 12 | y_axis: Vec4 { 13 | x: 0., 14 | y: scale.y, 15 | z: 0., 16 | w: 0., 17 | }, 18 | z_axis: Vec4 { 19 | x: 0., 20 | y: 0., 21 | z: scale.z, 22 | w: 0., 23 | }, 24 | w_axis: Vec4 { 25 | x: 0., 26 | y: 0., 27 | z: 0., 28 | w: 1., 29 | }, 30 | } 31 | } 32 | 33 | // 平移矩阵 34 | pub fn translation_mat4(translation: Vec3) -> Mat4 { 35 | Mat4 { 36 | x_axis: Vec4 { 37 | x: 1., 38 | y: 0., 39 | z: 0., 40 | w: 0., 41 | }, 42 | y_axis: Vec4 { 43 | x: 0., 44 | y: 1., 45 | z: 0., 46 | w: 0., 47 | }, 48 | z_axis: Vec4 { 49 | x: 0., 50 | y: 0., 51 | z: 1., 52 | w: 0., 53 | }, 54 | w_axis: Vec4 { 55 | x: translation.x, 56 | y: translation.y, 57 | z: translation.z, 58 | w: 1., 59 | }, 60 | } 61 | } 62 | 63 | // 旋转 64 | pub fn rotation_quat(axis: Vec3, angle: f32) -> Quat { 65 | Quat::from_axis_angle(axis, angle) 66 | } 67 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | 3 | use crate::{ 4 | color::Color, 5 | math::{Vec3, Vec4}, 6 | mesh::{Mesh, Vertex}, 7 | texture::TextureStorage, 8 | }; 9 | 10 | pub fn flip_vertically(frame_buffer: &Vec, width: usize, height: usize) -> Vec { 11 | let mut flipped_frame_buffer = frame_buffer.clone(); 12 | for y in 0..height / 2 { 13 | for x in 0..width { 14 | let top_index = (y * width + x) * 3; 15 | let bottom_index = ((height - y - 1) * width + x) * 3; 16 | flipped_frame_buffer.swap(top_index, bottom_index); 17 | flipped_frame_buffer.swap(top_index + 1, bottom_index + 1); 18 | flipped_frame_buffer.swap(top_index + 2, bottom_index + 2); 19 | } 20 | } 21 | flipped_frame_buffer 22 | } 23 | 24 | pub fn rand_color() -> Color { 25 | let mut rng = rand::thread_rng(); 26 | Color::new( 27 | rng.gen_range(0.0..=1.0), 28 | rng.gen_range(0.0..=1.0), 29 | rng.gen_range(0.0..=1.0), 30 | ) 31 | } 32 | 33 | pub fn custom_cube() -> (Vec, TextureStorage) { 34 | let p0 = Vec3::new(-1.0, 1.0, 1.0).extend(1.0); 35 | let p1 = Vec3::new(1.0, 1.0, 1.0).extend(1.0); 36 | let p2 = Vec3::new(-1.0, -1.0, 1.0).extend(1.0); 37 | let p3 = Vec3::new(1.0, -1.0, 1.0).extend(1.0); 38 | 39 | let p4 = Vec3::new(-1.0, 1.0, -1.0).extend(1.0); 40 | let p5 = Vec3::new(1.0, 1.0, -1.0).extend(1.0); 41 | let p6 = Vec3::new(-1.0, -1.0, -1.0).extend(1.0); 42 | let p7 = Vec3::new(1.0, -1.0, -1.0).extend(1.0); 43 | 44 | let mut vertices = Vec::new(); 45 | 46 | vertices.append(&mut build_trangle(p0, p1, p2)); 47 | vertices.append(&mut build_trangle(p1, p2, p3)); 48 | 49 | vertices.append(&mut build_trangle(p0, p1, p4)); 50 | vertices.append(&mut build_trangle(p1, p4, p5)); 51 | 52 | vertices.append(&mut build_trangle(p0, p2, p4)); 53 | vertices.append(&mut build_trangle(p2, p4, p6)); 54 | 55 | vertices.append(&mut build_trangle(p1, p3, p5)); 56 | vertices.append(&mut build_trangle(p3, p5, p7)); 57 | 58 | vertices.append(&mut build_trangle(p2, p3, p6)); 59 | vertices.append(&mut build_trangle(p3, p6, p7)); 60 | 61 | vertices.append(&mut build_trangle(p4, p5, p6)); 62 | vertices.append(&mut build_trangle(p5, p6, p7)); 63 | 64 | ( 65 | vec![Mesh { 66 | vertices, 67 | ..Default::default() 68 | }], 69 | TextureStorage::default(), 70 | ) 71 | } 72 | 73 | pub fn build_trangle(p0: Vec4, p1: Vec4, p2: Vec4) -> Vec { 74 | vec![ 75 | Vertex { 76 | position: p0, 77 | color: Some(rand_color()), 78 | ..Default::default() 79 | }, 80 | Vertex { 81 | position: p1, 82 | color: Some(rand_color()), 83 | ..Default::default() 84 | }, 85 | Vertex { 86 | position: p2, 87 | color: Some(rand_color()), 88 | ..Default::default() 89 | }, 90 | ] 91 | } 92 | --------------------------------------------------------------------------------