├── .gitignore ├── Cargo.toml ├── LICENSE.md ├── README.md ├── assets ├── african_head │ ├── african_head.obj │ ├── african_head_diffuse.tga │ ├── african_head_nm.tga │ ├── african_head_nm_tangent.tga │ └── african_head_spec.tga └── diablo3_pose │ ├── diablo3_pose.obj │ ├── diablo3_pose_diffuse.tga │ ├── diablo3_pose_glow.tga │ ├── diablo3_pose_nm.tga │ ├── diablo3_pose_nm_tangent.tga │ └── diablo3_pose_spec.tga ├── imgs ├── lesson0.png ├── lesson1.png ├── lesson2.png ├── lesson3.png ├── lesson4.png ├── lesson5.png ├── lesson6.png ├── lesson7.png ├── lesson8_1.png ├── lesson8_2.png └── persp_corr.png └── src ├── main.rs ├── tinyrenderer.rs └── tinyrenderer ├── gl.rs └── shaders.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | *.~undo-tree~ 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tinyrenderer_rs" 3 | version = "0.0.0" 4 | description = "Rust implementation of Tiny Renderer" 5 | authors = ["Emmanuel Bustos "] 6 | 7 | [dependencies] 8 | image = "0.24.5" 9 | piston_window = "0.127.0" 10 | obj-rs = "0.6" 11 | nalgebra = "0.31.4" 12 | 13 | [profile.release] 14 | debug = true -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Emmanuel Bustos Torres 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 | # tinyrenderer_rs 2 | This repo consists of a Rust implementation of the [tinyrenderer](https://github.com/ssloy/tinyrenderer) walkthrough by professor Dmitry V. Sokolov. For a tinyraytracer Rust implementation, check the [tinyraytracer_rs](https://github.com/ema2159/tinyraytracer_rs) repo. 3 | 4 | ## Dependencies: 5 | - image 0.24.5: Image loading and manipulation 6 | - piston_window 1.127.0: Window to display rendered frames 7 | - obj-rs 0.6: To retrieve information from .obj files 8 | - nalgebra 0.31.4: For vector and matrix calculations 9 | 10 | ## Usage 11 | To run, just clone any of the branches and do: 12 | 13 | ``` 14 | cargo run --release 15 | ``` 16 | where `` is the directory in which the corresponding assets of the model are. For example, to run *Lesson 7* you must do: 17 | 18 | ``` 19 | cargo run --release assets/diablo3_pose 20 | ``` 21 | At the moment, if you want to use other models/textures, you would have to modify the respective assets names in main. 22 | 23 | 24 | 25 | ## Lessons 26 | 27 | ### Lesson 0 28 | Write to an image buffer and render it in a window. 29 | 30 | **Branch:** [Lesson_0](https://github.com/ema2159/tinyrenderer_rs/tree/Lesson_0) 31 | 32 | **Preview:** 33 | ![Lesson 0 image](./imgs/lesson0.png) 34 | ### Lesson 1 35 | Implement Bressenham's line algorithm. Then, use it to draw the wireframe model of a mesh. 36 | 37 | **Branch:** [Lesson_1](https://github.com/ema2159/tinyrenderer_rs/tree/Lesson_1) 38 | 39 | **Preview:** 40 | ![Lesson 1 image](./imgs/lesson1.png) 41 | 42 | ### Lesson 2 43 | Implement triangle filling using both line sweeping algorithm and barycentric coordinates algorithm. Then implement a basic directional lighting model, computing the lighting of each triangle face using its normals. 44 | 45 | **Branch:** [Lesson_2](https://github.com/ema2159/tinyrenderer_rs/tree/Lesson_2) 46 | 47 | **Preview:** 48 | ![Lesson 2 image](./imgs/lesson2.png) 49 | 50 | ### Lesson 3 51 | Implement Z-buffer algorithm for back-face culling. Then, apply textures to the mesh. 52 | 53 | **Branch:** [Lesson_3](https://github.com/ema2159/tinyrenderer_rs/tree/Lesson_3) 54 | 55 | **Preview:** 56 | ![Lesson 3 image](./imgs/lesson3.png) 57 | 58 | ### Lesson 4 59 | Implement perspective projection. 60 | 61 | **Branch:** [Lesson_4](https://github.com/ema2159/tinyrenderer_rs/tree/Lesson_4) 62 | 63 | **Preview:** 64 | ![Lesson 4 image](./imgs/lesson4.png) 65 | 66 | ### Lesson 5 67 | Implement Gouraud shading. Then, implement model view, projection, and viewport transformation matrices. Lastly, apply several transformations to the model through matrices transformation chaining. 68 | 69 | **Branch:** [Lesson_5](https://github.com/ema2159/tinyrenderer_rs/tree/Lesson_5) 70 | 71 | **Preview:** 72 | ![Lesson 5 image](./imgs/lesson5.png) 73 | 74 | ### Lesson 6/6bis 75 | Structure code into shaders form. Then, implement texture-based normal mapping for the model, using both global coordinate system normal mapping and Darboux frame normal mapping. Lastly, improve lighting by composing the lighting of the model using ambient, diffuse, and specular lighting (Phong shading). 76 | 77 | **Branches:** [Lesson_6](https://github.com/ema2159/tinyrenderer_rs/tree/Lesson_6) [Lesson_6bis](https://github.com/ema2159/tinyrenderer_rs/tree/Lesson_6bis) 78 | 79 | **Preview:** 80 | ![Lesson 6 image](./imgs/lesson6.png) 81 | 82 | ### Lesson 7 83 | Implement hard shadow computation through shadow mapping. 84 | 85 | **Branch:** [Lesson_7](https://github.com/ema2159/tinyrenderer_rs/tree/Lesson_7) 86 | 87 | **Preview:** 88 | ![Lesson 7 image](./imgs/lesson7.png) 89 | 90 | ### Lesson 8 91 | Implement screen space ambient occlusion. 92 | 93 | **Branch:** [Lesson_8](https://github.com/ema2159/tinyrenderer_rs/tree/Lesson_8) 94 | 95 | **Preview:** 96 | ![Lesson 8 image](./imgs/lesson8_1.png) 97 | ![Lesson 8 image](./imgs/lesson8_2.png) 98 | 99 | ### Technical difficulties: linear interpolation with perspective deformations 100 | Implement perspective correction for perspective deformations during linear interpolation. 101 | 102 | **Branch:** [persp_correction](https://github.com/ema2159/tinyrenderer_rs/tree/persp_correction) 103 | 104 | **Preview:** 105 | ![Lesson 8 image](./imgs/persp_corr.png) 106 | -------------------------------------------------------------------------------- /assets/african_head/african_head_diffuse.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/assets/african_head/african_head_diffuse.tga -------------------------------------------------------------------------------- /assets/african_head/african_head_nm.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/assets/african_head/african_head_nm.tga -------------------------------------------------------------------------------- /assets/african_head/african_head_nm_tangent.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/assets/african_head/african_head_nm_tangent.tga -------------------------------------------------------------------------------- /assets/african_head/african_head_spec.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/assets/african_head/african_head_spec.tga -------------------------------------------------------------------------------- /assets/diablo3_pose/diablo3_pose_diffuse.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/assets/diablo3_pose/diablo3_pose_diffuse.tga -------------------------------------------------------------------------------- /assets/diablo3_pose/diablo3_pose_glow.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/assets/diablo3_pose/diablo3_pose_glow.tga -------------------------------------------------------------------------------- /assets/diablo3_pose/diablo3_pose_nm.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/assets/diablo3_pose/diablo3_pose_nm.tga -------------------------------------------------------------------------------- /assets/diablo3_pose/diablo3_pose_nm_tangent.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/assets/diablo3_pose/diablo3_pose_nm_tangent.tga -------------------------------------------------------------------------------- /assets/diablo3_pose/diablo3_pose_spec.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/assets/diablo3_pose/diablo3_pose_spec.tga -------------------------------------------------------------------------------- /imgs/lesson0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/lesson0.png -------------------------------------------------------------------------------- /imgs/lesson1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/lesson1.png -------------------------------------------------------------------------------- /imgs/lesson2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/lesson2.png -------------------------------------------------------------------------------- /imgs/lesson3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/lesson3.png -------------------------------------------------------------------------------- /imgs/lesson4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/lesson4.png -------------------------------------------------------------------------------- /imgs/lesson5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/lesson5.png -------------------------------------------------------------------------------- /imgs/lesson6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/lesson6.png -------------------------------------------------------------------------------- /imgs/lesson7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/lesson7.png -------------------------------------------------------------------------------- /imgs/lesson8_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/lesson8_1.png -------------------------------------------------------------------------------- /imgs/lesson8_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/lesson8_2.png -------------------------------------------------------------------------------- /imgs/persp_corr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ema2159/tinyrenderer_rs/8b39d35e68bbf5b2548c80509ce004f7652ca937/imgs/persp_corr.png -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | extern crate nalgebra; 3 | extern crate obj; 4 | extern crate piston_window; 5 | 6 | mod tinyrenderer; 7 | 8 | use image::{Rgba, RgbaImage}; 9 | use nalgebra::{Matrix2x3, Matrix3, Point3, Vector3}; 10 | use obj::{load_obj, Obj, TexturedVertex}; 11 | use piston_window::EventLoop; 12 | use std::env; 13 | use std::error::Error; 14 | use std::fs::File; 15 | use std::io::BufReader; 16 | use std::path::Path; 17 | use tinyrenderer::draw_faces; 18 | use tinyrenderer::gl::{get_model_view_matrix, get_projection_matrix, get_viewport_matrix}; 19 | use tinyrenderer::shaders::{RenderingShader, ShadowShader}; 20 | 21 | const WIDTH: u32 = 800; 22 | const HEIGHT: u32 = 800; 23 | 24 | pub struct Camera { 25 | pub position: Point3, 26 | pub focal_length: f32, 27 | pub view_point: Point3, 28 | } 29 | 30 | fn main() -> Result<(), Box> { 31 | let mut color_buffer = RgbaImage::from_pixel(WIDTH, HEIGHT, Rgba([0, 0, 0, 255])); 32 | let mut _buffer = RgbaImage::from_pixel(WIDTH, HEIGHT, Rgba([0, 0, 0, 255])); 33 | 34 | // Assets dir 35 | let args: Vec = env::args().collect(); 36 | if args.len() <= 1 { 37 | panic!("No assets directory provided!"); 38 | } 39 | let assets_dir = Path::new(&args[1]) 40 | .canonicalize() 41 | .unwrap_or_else(|_| panic!("Wrong path for assets directory!")); 42 | 43 | // Load model 44 | let obj_path = assets_dir.join("diablo3_pose.obj"); 45 | let input = BufReader::new(File::open(obj_path)?); 46 | let model: Obj = load_obj(input)?; 47 | 48 | // Load texture 49 | let texture_path = assets_dir.join("diablo3_pose_diffuse.tga"); 50 | let mut texture = image::open(texture_path) 51 | .expect("Opening image failed") 52 | .into_rgba8(); 53 | image::imageops::flip_vertical_in_place(&mut texture); 54 | 55 | // Load normal map 56 | let normal_map_path = assets_dir.join("diablo3_pose_nm_tangent.tga"); 57 | let mut normal_map = image::open(normal_map_path) 58 | .expect("Opening image failed") 59 | .into_rgba8(); 60 | image::imageops::flip_vertical_in_place(&mut normal_map); 61 | 62 | // Load specular map 63 | let specular_map_path = assets_dir.join("diablo3_pose_spec.tga"); 64 | let mut specular_map = image::open(specular_map_path) 65 | .expect("Opening image failed") 66 | .into_rgba8(); 67 | image::imageops::flip_vertical_in_place(&mut specular_map); 68 | 69 | use std::time::Instant; 70 | let now = Instant::now(); 71 | 72 | // Frame properties 73 | let (width, height) = (color_buffer.width() as f32, color_buffer.height() as f32); 74 | let depth = 1024.; 75 | 76 | // Model configuration 77 | let model_pos = Point3::new(0., 0., 0.); 78 | let model_scale = Vector3::new(1., 1., 1.); 79 | 80 | // Camera configuration 81 | let camera = Camera { 82 | position: Point3::new(0., 0., 1.), 83 | focal_length: 3., 84 | view_point: model_pos, 85 | }; 86 | 87 | // Light configuration 88 | let ambient_light = 5.; 89 | let dir_light = Vector3::new(-1., 0., 1.5); 90 | 91 | // Z buffer 92 | let mut z_buffer = vec![vec![f32::NEG_INFINITY; height as usize]; width as usize]; 93 | 94 | // Shadow buffer 95 | let mut shadow_buffer = vec![vec![f32::NEG_INFINITY; height as usize]; width as usize]; 96 | 97 | // Transformation matrices 98 | let model_view = get_model_view_matrix( 99 | camera.position, 100 | camera.view_point, 101 | model_pos, 102 | model_scale, 103 | Vector3::new(0., 1., 0.), 104 | ); 105 | let projection = get_projection_matrix(camera.focal_length); 106 | let model_view_it = model_view.try_inverse().unwrap().transpose(); 107 | let viewport = get_viewport_matrix(height, width, depth); 108 | 109 | let shadow_mat = get_model_view_matrix( 110 | Point3::::origin() + dir_light, 111 | model_pos, 112 | model_pos, 113 | model_scale, 114 | Vector3::new(0., 1., 0.), 115 | ); 116 | 117 | // Shaders 118 | let mut shadow_shader = ShadowShader { 119 | model: &model, 120 | uniform_shadow_mv_mat: shadow_mat, 121 | uniform_viewport: viewport, 122 | 123 | varying_view_tri: Matrix3::::zeros(), 124 | }; 125 | // Compute shadows 126 | draw_faces(&model, &mut _buffer, &mut shadow_buffer, &mut shadow_shader); 127 | 128 | let mut rendering_shader = RenderingShader { 129 | model: &model, 130 | shadow_buffer: &shadow_buffer, 131 | uniform_model_view: model_view, 132 | uniform_model_view_it: model_view_it, 133 | uniform_shadow_mv_mat: shadow_mat, 134 | uniform_projection: projection, 135 | uniform_viewport: viewport, 136 | uniform_ambient_light: ambient_light, 137 | uniform_dir_light: (model_view * dir_light.insert_row(3, 0.)).xyz().normalize(), 138 | uniform_texture: texture, 139 | uniform_normal_map: normal_map, 140 | uniform_specular_map: specular_map, 141 | 142 | varying_uv: Matrix2x3::::zeros(), 143 | varying_normals: Matrix3::::zeros(), 144 | varying_view_tri: Matrix3::::zeros(), 145 | varying_shadow_tri: Matrix3::::zeros(), 146 | }; 147 | 148 | // Render model 149 | draw_faces( 150 | &model, 151 | &mut color_buffer, 152 | &mut z_buffer, 153 | &mut rendering_shader, 154 | ); 155 | 156 | image::imageops::flip_vertical_in_place(&mut color_buffer); 157 | 158 | let elapsed = now.elapsed(); 159 | println!("Elapsed: {:.2?}", elapsed); 160 | 161 | // Rendering window 162 | let mut window: piston_window::PistonWindow = 163 | piston_window::WindowSettings::new("tinyrenderer_rs", [WIDTH, HEIGHT]) 164 | .exit_on_esc(true) 165 | .build() 166 | .unwrap_or_else(|_e| panic!("Could not create window!")); 167 | 168 | // Configure window properties 169 | window.set_lazy(true); 170 | window.set_max_fps(60); 171 | 172 | let rendered_img = piston_window::Texture::from_image( 173 | &mut window.create_texture_context(), 174 | &color_buffer, 175 | &piston_window::TextureSettings::new(), 176 | ) 177 | .unwrap(); 178 | 179 | while let Some(event) = window.next() { 180 | window.draw_2d(&event, |c, g, _| { 181 | piston_window::clear([0.0, 0.0, 0.0, 1.0], g); 182 | piston_window::image(&rendered_img, c.transform, g); 183 | }); 184 | } 185 | Ok(()) 186 | } 187 | -------------------------------------------------------------------------------- /src/tinyrenderer.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | pub mod gl; 3 | pub mod shaders; 4 | 5 | use self::shaders::Shader; 6 | use image::RgbaImage; 7 | use nalgebra::{clamp, Point2, Point4, Vector2, Vector3}; 8 | use obj::{Obj, TexturedVertex}; 9 | 10 | /// Implementation of barycentric algorithm for triangle filling. Works as the rasterizer. 11 | fn draw_face_barycentric( 12 | screen_coords: [Point4; 3], 13 | shaders: &dyn Shader, 14 | color_buffer: &mut RgbaImage, 15 | z_buffer: &mut [Vec], 16 | ) { 17 | // Screen coordinates pre-perspective division 18 | let [v0_c, v1_c, v2_c] = screen_coords; 19 | // Screen coordinates post-perspective division 20 | let mut screen_coords2 = [v0_c / v0_c.w, v1_c / v1_c.w, v2_c / v2_c.w]; 21 | for coord in screen_coords2.iter_mut() { 22 | coord.x = clamp(coord.x, 0., (color_buffer.width() - 1) as f32); 23 | coord.y = clamp(coord.y, 0., (color_buffer.height() - 1) as f32); 24 | } 25 | let [v0_s, v1_s, v2_s] = screen_coords2; 26 | // Define triangle bounding box 27 | let max_x = f32::max(v0_s.x, f32::max(v1_s.x, v2_s.x)) as i32; 28 | let max_y = f32::max(v0_s.y, f32::max(v1_s.y, v2_s.y)) as i32; 29 | let min_x = f32::min(v0_s.x, f32::min(v1_s.x, v2_s.x)) as i32; 30 | let min_y = f32::min(v0_s.y, f32::min(v1_s.y, v2_s.y)) as i32; 31 | 32 | let vec1: Vector2 = (v1_s - v0_s).xy(); 33 | let vec2: Vector2 = (v2_s - v0_s).xy(); 34 | 35 | let vec1_x_vec2 = vec1.perp(&vec2); 36 | 37 | // Calculate if point2 of the bounding box is inside triangle 38 | for x in min_x..=max_x { 39 | for y in min_y..=max_y { 40 | let pv0 = Point2::::new(x as f32, y as f32) - v0_s.xy(); 41 | let vec1_x_pv0 = vec1.perp(&pv0); 42 | let pv0_x_vec2 = pv0.perp(&vec2); 43 | // Barycentric coordinates 44 | let s = vec1_x_pv0 / vec1_x_vec2; 45 | let t = pv0_x_vec2 / vec1_x_vec2; 46 | let t_s_1 = 1. - (t + s); 47 | // Perspective correction for barycentric coordinates 48 | let mut bar_coords = Vector3::::new(t_s_1 / v0_c.w, t / v1_c.w, s / v2_c.w); 49 | bar_coords /= bar_coords.x + bar_coords.y + bar_coords.z; 50 | 51 | if s >= 0. && t >= 0. && t_s_1 >= 0. { 52 | let z_value = t_s_1 * v0_s.z + t * v1_s.z + s * v2_s.z; 53 | if z_buffer[x as usize][y as usize] <= z_value { 54 | z_buffer[x as usize][y as usize] = z_value; 55 | if let Some(frag) = shaders.fragment_shader(bar_coords) { 56 | color_buffer.put_pixel(x as u32, y as u32, frag); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | fn get_face_world_coords(model: &Obj, face: &[u16]) -> [Point4; 3] { 65 | let [v0x, v0y, v0z] = model.vertices[face[0] as usize].position; 66 | let [v1x, v1y, v1z] = model.vertices[face[1] as usize].position; 67 | let [v2x, v2y, v2z] = model.vertices[face[2] as usize].position; 68 | let point0 = Point4::::new(v0x, v0y, v0z, 1.); 69 | let point1 = Point4::::new(v1x, v1y, v1z, 1.); 70 | let point2 = Point4::::new(v2x, v2y, v2z, 1.); 71 | [point0, point1, point2] 72 | } 73 | 74 | /// Draw triangle faces of given 3D object. Works as the primitive processor. 75 | pub fn draw_faces( 76 | model: &Obj, 77 | color_buffer: &mut RgbaImage, 78 | z_buffer: &mut [Vec], 79 | shaders: &mut dyn Shader, 80 | ) { 81 | let faces_num = model.indices.len(); 82 | let faces = &model.indices[..faces_num]; 83 | 84 | for face in faces.chunks(3) { 85 | let mut verts = get_face_world_coords(model, face); 86 | for (i, vert) in verts.iter_mut().enumerate() { 87 | shaders.vertex_shader(face[i], i, vert); 88 | } 89 | 90 | // Draw face 91 | draw_face_barycentric(verts, shaders, color_buffer, z_buffer); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/tinyrenderer/gl.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{Matrix4, Point3, RowVector4, Vector3}; 2 | 3 | pub fn get_model_view_matrix( 4 | eye_pos: Point3, 5 | view_point: Point3, 6 | model_pos: Point3, 7 | model_scale: Vector3, 8 | up_vector: Vector3, 9 | ) -> Matrix4 { 10 | let new_z = (eye_pos - view_point).normalize(); 11 | let new_x = up_vector.cross(&new_z).normalize(); 12 | let new_y = new_z.cross(&new_x).normalize(); 13 | 14 | let mut model_mat = Matrix4::from_diagonal(&model_scale.insert_row(3, 1.)); 15 | let eye_vec = model_pos - eye_pos; 16 | model_mat.set_column(3, &(eye_vec.insert_row(3, 1.))); 17 | 18 | let view_mat = Matrix4::from_rows(&[ 19 | new_x.transpose().insert_column(3, 0.), 20 | new_y.transpose().insert_column(3, 0.), 21 | new_z.transpose().insert_column(3, 0.), 22 | RowVector4::new(0., 0., 0., 1.), 23 | ]); 24 | 25 | view_mat * model_mat 26 | } 27 | 28 | pub fn get_projection_matrix(f: f32) -> Matrix4 { 29 | Matrix4::::from_rows(&[ 30 | RowVector4::new(1., 0., 0., 0.), 31 | RowVector4::new(0., 1., 0., 0.), 32 | RowVector4::new(0., 0., 1., 0.), 33 | RowVector4::new(0., 0., -1. / f, 1.), 34 | ]) 35 | } 36 | 37 | pub fn get_viewport_matrix(screen_width: f32, screen_height: f32, depth: f32) -> Matrix4 { 38 | let half_w = (screen_width - 1.) / 2.; 39 | let half_h = (screen_height - 1.) / 2.; 40 | let half_d = (depth - 1.) / 2.; 41 | Matrix4::::from_rows(&[ 42 | RowVector4::new(half_w, 0., 0., half_w), 43 | RowVector4::new(0., half_h, 0., half_h), 44 | RowVector4::new(0., 0., half_d, half_d), 45 | RowVector4::new(0., 0., 0., 1.), 46 | ]) 47 | } 48 | -------------------------------------------------------------------------------- /src/tinyrenderer/shaders.rs: -------------------------------------------------------------------------------- 1 | use image::{Pixel, Rgba, RgbaImage}; 2 | use nalgebra::{clamp, Matrix2x3, Matrix3, Matrix4, Point2, Point4, Vector2, Vector3, Vector4}; 3 | use obj::{Obj, TexturedVertex}; 4 | 5 | pub trait Shader { 6 | fn vertex_shader(&mut self, face: u16, nthvert: usize, gl_position: &mut Point4); 7 | fn fragment_shader(&self, bar_coords: Vector3) -> Option>; 8 | } 9 | 10 | fn sample_2d(texture: &RgbaImage, uv: Point2) -> Rgba { 11 | *texture.get_pixel( 12 | ((uv.x * texture.width() as f32) - 1.) as u32, 13 | ((uv.y * texture.height() as f32) - 1.) as u32, 14 | ) 15 | } 16 | 17 | pub struct RenderingShader<'a> { 18 | pub model: &'a Obj, 19 | pub shadow_buffer: &'a [Vec], 20 | pub uniform_model_view: Matrix4, 21 | pub uniform_model_view_it: Matrix4, 22 | pub uniform_shadow_mv_mat: Matrix4, 23 | pub uniform_projection: Matrix4, 24 | pub uniform_viewport: Matrix4, 25 | pub uniform_ambient_light: f32, 26 | pub uniform_dir_light: Vector3, 27 | pub uniform_texture: RgbaImage, 28 | pub uniform_specular_map: RgbaImage, 29 | pub uniform_normal_map: RgbaImage, 30 | 31 | pub varying_uv: Matrix2x3, 32 | pub varying_normals: Matrix3, 33 | pub varying_view_tri: Matrix3, 34 | pub varying_shadow_tri: Matrix3, 35 | } 36 | 37 | impl Shader for RenderingShader<'_> { 38 | fn vertex_shader(&mut self, face_idx: u16, nthvert: usize, gl_position: &mut Point4) { 39 | let [u, v, _] = self.model.vertices[face_idx as usize].texture; 40 | self.varying_uv.set_column(nthvert, &Vector2::new(u, v)); 41 | 42 | let [i, j, k] = self.model.vertices[face_idx as usize].normal; 43 | let normal = (self.uniform_model_view_it * Vector4::new(i, j, k, 0.)) 44 | .xyz() 45 | .normalize(); 46 | self.varying_normals.set_column(nthvert, &normal); 47 | 48 | // Calculate position in shadow buffer coords 49 | let mut shadow_pos = Point4::from(self.uniform_shadow_mv_mat * gl_position.coords); 50 | // Clip out of frame points 51 | shadow_pos.x = clamp(shadow_pos.x, -1.0, 1.0); 52 | shadow_pos.y = clamp(shadow_pos.y, -1.0, 1.0); 53 | shadow_pos = Point4::from(self.uniform_viewport * shadow_pos.coords); 54 | self.varying_shadow_tri 55 | .set_column(nthvert, &shadow_pos.xyz().coords); 56 | 57 | // Process vertices 58 | let mv_coords = self.uniform_model_view * gl_position.coords; 59 | self.varying_view_tri 60 | .set_column(nthvert, &gl_position.xyz().coords); 61 | *gl_position = Point4::from(self.uniform_viewport * self.uniform_projection * mv_coords); 62 | } 63 | fn fragment_shader(&self, bar_coords: Vector3) -> Option> { 64 | // Texture coords 65 | let uv = Point2::::from(self.varying_uv * bar_coords); 66 | 67 | // Shadow calculation. Compute fragment position in shadow buffer screen space and check in 68 | // the shadow buffer if there is another fragment in between it and the light source. 69 | let shad_buf_p = (self.varying_shadow_tri * bar_coords).insert_row(3, 1.); 70 | const SHADOW_TOLERANCE: f32 = 10.; 71 | let shadow = if self.shadow_buffer[shad_buf_p.x as usize][shad_buf_p.y as usize] 72 | < shad_buf_p.z + SHADOW_TOLERANCE 73 | { 74 | 1. 75 | } else { 76 | 0.1 77 | }; 78 | 79 | // Normal computing using Darboux tangent space normal mapping 80 | let bnormal = self.varying_normals * bar_coords; 81 | 82 | let a_row0 = 83 | (self.varying_view_tri.column(1) - self.varying_view_tri.column(0)).transpose(); 84 | let a_row1 = 85 | (self.varying_view_tri.column(2) - self.varying_view_tri.column(0)).transpose(); 86 | let a_row2 = bnormal.transpose(); 87 | let a_inv_mat = Matrix3::from_rows(&[a_row0, a_row1, a_row2]) 88 | .try_inverse() 89 | .unwrap(); 90 | 91 | let i = a_inv_mat 92 | * Vector3::new( 93 | self.varying_uv.column(1)[0] - self.varying_uv.column(0)[0], 94 | self.varying_uv.column(2)[0] - self.varying_uv.column(0)[0], 95 | 0., 96 | ); 97 | 98 | let j = a_inv_mat 99 | * Vector3::new( 100 | self.varying_uv.column(1)[1] - self.varying_uv.column(0)[1], 101 | self.varying_uv.column(2)[1] - self.varying_uv.column(0)[1], 102 | 0., 103 | ); 104 | 105 | let b_mat = Matrix3::from_columns(&[i.normalize(), j.normalize(), bnormal]); 106 | 107 | let Rgba([x, y, z, _]) = sample_2d(&self.uniform_normal_map, uv); 108 | let darboux_mapping = Vector3::new(x, y, z).map(|w| ((w as f32 / 255.) * 2.) - 1.); 109 | let normal = b_mat * darboux_mapping; 110 | 111 | // Lighting computing 112 | let reflected = (normal * (normal.dot(&self.uniform_dir_light) * 2.) 113 | - self.uniform_dir_light) 114 | .normalize(); 115 | 116 | let specular = f32::powi( 117 | f32::max(0., reflected.z), 118 | (1 + sample_2d(&self.uniform_specular_map, uv)[0]).into(), // The 1 prevents pow to 0 bug 119 | ); 120 | let diffuse = f32::max(0., self.uniform_dir_light.dot(&normal)); 121 | 122 | // Fragment calculation 123 | let mut gl_frag_color = sample_2d(&self.uniform_texture, uv); 124 | gl_frag_color.apply_without_alpha(|ch| { 125 | (self.uniform_ambient_light + (ch as f32) * shadow * (diffuse + 0.6 * specular)) as u8 126 | }); 127 | Some(gl_frag_color) 128 | } 129 | } 130 | 131 | pub struct ShadowShader<'a> { 132 | pub model: &'a Obj, 133 | pub uniform_shadow_mv_mat: Matrix4, 134 | pub uniform_viewport: Matrix4, 135 | 136 | pub varying_view_tri: Matrix3, 137 | } 138 | 139 | impl Shader for ShadowShader<'_> { 140 | fn vertex_shader(&mut self, _face_idx: u16, nthvert: usize, gl_position: &mut Point4) { 141 | *gl_position = Point4::from(self.uniform_shadow_mv_mat * gl_position.coords); 142 | gl_position.x = clamp(gl_position.x, -1.0, 1.0); 143 | gl_position.y = clamp(gl_position.y, -1.0, 1.0); 144 | *gl_position = Point4::from(self.uniform_viewport * gl_position.coords); 145 | self.varying_view_tri 146 | .set_column(nthvert, &gl_position.xyz().coords); 147 | } 148 | fn fragment_shader(&self, _bar_coords: Vector3) -> Option> { 149 | None 150 | } 151 | } 152 | --------------------------------------------------------------------------------