diff --git a/.gitignore b/.gitignore index ea8c4bf..daf2e00 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ /target +**png +**jpg +**jpeg \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 55ccabe..da2e81c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,6 +200,9 @@ dependencies = [ "glfw", "human-panic", "image", + "num", + "rand 0.7.3", + "tobj", ] [[package]] @@ -320,7 +323,7 @@ dependencies = [ "gif", "jpeg-decoder", "num-iter", - "num-rational", + "num-rational 0.2.4", "num-traits", "png", "scoped_threadpool", @@ -418,6 +421,40 @@ dependencies = [ "adler", ] +[[package]] +name = "num" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3e176191bc4faad357e3122c4747aa098ac880e88b168f106386128736cf4a" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational 0.3.0", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f3fc75e3697059fb1bc465e3d8cca6cf92f56854f201158b3f9c77d5a3cfa0" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05ad05bd8977050b171b3f6b48175fea6e0565b7981059b486075e1026a9fb5" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.2.5" @@ -461,6 +498,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" +dependencies = [ + "autocfg 1.0.0", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.12" @@ -868,6 +917,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "tobj" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6172100cd5b17cdd085c94f261e31101ca31886c86a2337a6687dac6d2fb3cf1" + [[package]] name = "toml" version = "0.5.6" diff --git a/Cargo.toml b/Cargo.toml index 97ac7c5..7594633 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ gl = "0.14.0" glfw = "0.39.1" image = "0.22.5" # only needed from chapter 3 on -# tobj = "0.1.6" -# num = "0.2.0" -# rand = "0.5.5" +tobj = "2.0.2" +num = "0.3.0" +rand = "0.7.3" human-panic = "1.0.3" \ No newline at end of file diff --git a/resources/shaders/lamp.fs b/resources/shaders/lamp.fs new file mode 100644 index 0000000..36ec83a --- /dev/null +++ b/resources/shaders/lamp.fs @@ -0,0 +1,7 @@ +#version 330 core +out vec4 FragColor; + +void main() +{ + FragColor = vec4(1.0); // set alle 4 vector values to 1.0 +} diff --git a/resources/shaders/lamp.vs b/resources/shaders/lamp.vs new file mode 100644 index 0000000..70a22c7 --- /dev/null +++ b/resources/shaders/lamp.vs @@ -0,0 +1,11 @@ +#version 330 core +layout (location = 0) in vec3 aPos; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + gl_Position = projection * view * model * vec4(aPos, 1.0); +} diff --git a/resources/shaders/model_loading.fs b/resources/shaders/model_loading.fs new file mode 100644 index 0000000..66d5e6c --- /dev/null +++ b/resources/shaders/model_loading.fs @@ -0,0 +1,11 @@ +#version 330 core +out vec4 FragColor; + +in vec2 TexCoords; + +uniform sampler2D texture_diffuse1; + +void main() +{ + FragColor = texture(texture_diffuse1, TexCoords); +} diff --git a/resources/shaders/model_loading.vs b/resources/shaders/model_loading.vs new file mode 100644 index 0000000..ed5332c --- /dev/null +++ b/resources/shaders/model_loading.vs @@ -0,0 +1,16 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; +layout (location = 2) in vec2 aTexCoords; + +out vec2 TexCoords; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + TexCoords = aTexCoords; + gl_Position = projection * view * model * vec4(aPos, 1.0); +} diff --git a/resources/shaders/multiple_lights.fs b/resources/shaders/multiple_lights.fs new file mode 100644 index 0000000..f49fba7 --- /dev/null +++ b/resources/shaders/multiple_lights.fs @@ -0,0 +1,147 @@ +#version 330 core +out vec4 FragColor; + +struct Material { + sampler2D diffuse; + sampler2D specular; + float shininess; +}; + +struct DirLight { + vec3 direction; + + vec3 ambient; + vec3 diffuse; + vec3 specular; +}; + +struct PointLight { + vec3 position; + + float constant; + float linear; + float quadratic; + + vec3 ambient; + vec3 diffuse; + vec3 specular; +}; + +struct SpotLight { + vec3 position; + vec3 direction; + float cutOff; + float outerCutOff; + + float constant; + float linear; + float quadratic; + + vec3 ambient; + vec3 diffuse; + vec3 specular; +}; + +#define NR_POINT_LIGHTS 4 + +in vec3 FragPos; +in vec3 Normal; +in vec2 TexCoords; + +uniform vec3 viewPos; +uniform DirLight dirLight; +uniform PointLight pointLights[NR_POINT_LIGHTS]; +uniform SpotLight spotLight; +uniform Material material; + +// function prototypes +vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir); +vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir); +vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir); + +void main() +{ + // properties + vec3 norm = normalize(Normal); + vec3 viewDir = normalize(viewPos - FragPos); + + // == ===================================================== + // Our lighting is set up in 3 phases: directional, point lights and an optional flashlight + // For each phase, a calculate function is defined that calculates the corresponding color + // per lamp. In the main() function we take all the calculated colors and sum them up for + // this fragment's final color. + // == ===================================================== + // phase 1: directional lighting + vec3 result = CalcDirLight(dirLight, norm, viewDir); + // phase 2: point lights + for(int i = 0; i < NR_POINT_LIGHTS; i++) + result += CalcPointLight(pointLights[i], norm, FragPos, viewDir); + // phase 3: spot light + result += CalcSpotLight(spotLight, norm, FragPos, viewDir); + + FragColor = vec4(result, 1.0); +} + +// calculates the color when using a directional light. +vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir) +{ + vec3 lightDir = normalize(-light.direction); + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + // specular shading + vec3 reflectDir = reflect(-lightDir, normal); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); + // combine results + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + return (ambient + diffuse + specular); +} + +// calculates the color when using a point light. +vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) +{ + vec3 lightDir = normalize(light.position - fragPos); + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + // specular shading + vec3 reflectDir = reflect(-lightDir, normal); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); + // attenuation + float distance = length(light.position - fragPos); + float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); + // combine results + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + ambient *= attenuation; + diffuse *= attenuation; + specular *= attenuation; + return (ambient + diffuse + specular); +} + +// calculates the color when using a spot light. +vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) +{ + vec3 lightDir = normalize(light.position - fragPos); + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + // specular shading + vec3 reflectDir = reflect(-lightDir, normal); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); + // attenuation + float distance = length(light.position - fragPos); + float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); + // spotlight intensity + float theta = dot(lightDir, normalize(-light.direction)); + float epsilon = light.cutOff - light.outerCutOff; + float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); + // combine results + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + ambient *= attenuation * intensity; + diffuse *= attenuation * intensity; + specular *= attenuation * intensity; + return (ambient + diffuse + specular); +} diff --git a/resources/shaders/multiple_lights.vs b/resources/shaders/multiple_lights.vs new file mode 100644 index 0000000..52febc4 --- /dev/null +++ b/resources/shaders/multiple_lights.vs @@ -0,0 +1,21 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; +layout (location = 2) in vec2 aTexCoords; + +out vec3 FragPos; +out vec3 Normal; +out vec2 TexCoords; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + FragPos = vec3(model * vec4(aPos, 1.0)); + Normal = mat3(transpose(inverse(model))) * aNormal; + TexCoords = aTexCoords; + + gl_Position = projection * view * vec4(FragPos, 1.0); +} diff --git a/src/consts.rs b/src/consts.rs index e587db1..5e5ab8b 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -35,3 +35,27 @@ void main() FragColor = texture(texture1, TexCoord); } "#; + +pub const SH_FRAG_LAMP: &str = r#" +#version 330 core +out vec4 FragColor; + +void main() +{ + FragColor = vec4(1.0); // set alle 4 vector values to 1.0 +} +"#; + +pub const SH_VERT_LAMP: &str = r#" +#version 330 core +layout (location = 0) in vec3 aPos; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + gl_Position = projection * view * model * vec4(aPos, 1.0); +} +"#; diff --git a/src/engine.rs b/src/engine.rs index e69de29..dae63c7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -0,0 +1,60 @@ +use std::os::raw::c_void; +use std::path::Path; +use std::sync::mpsc::Receiver; + +use gl; +extern crate glfw; +use self::glfw::{Action, Key}; + +use image; +use image::DynamicImage::*; +use image::GenericImage; +use image::*; + +pub unsafe fn loadTexture(path: &str) -> u32 { + let mut textureID = 0; + + gl::GenTextures(1, &mut textureID); + let img = image::open(&Path::new(path)).expect("Texture failed to load"); + let format = match img { + ImageLuma8(_) => gl::RED, + ImageLumaA8(_) => gl::RG, + ImageRgb8(_) => gl::RGB, + ImageRgba8(_) => gl::RGBA, + _ => gl::RGB, + }; + + let data = img.raw_pixels(); + let dim = img.dimensions(); + + gl::BindTexture(gl::TEXTURE_2D, textureID); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + format as i32, + dim.0 as i32, + dim.1 as i32, + 0, + format, + gl::UNSIGNED_BYTE, + &data[0] as *const u8 as *const c_void, + ); + gl::GenerateMipmap(gl::TEXTURE_2D); + + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MIN_FILTER, + gl::LINEAR_MIPMAP_LINEAR as i32, + ); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); + + textureID +} + +pub unsafe fn loadTextureFromDir(filename: &str, directory: &str) -> u32 { + let fullpath = format!("{}/{}", directory, filename); + + loadTexture(&fullpath) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 73ed972..1fa2805 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,8 +16,11 @@ use std::ptr; use std::sync::mpsc::Receiver; mod camera; mod consts; +mod engine; mod macros; mod shader; +mod model; +mod mesh; const CUBES_POS: [Vector3; 10] = [ vec3(0.0, 0.0, 0.0), @@ -68,42 +71,91 @@ pub fn main() { .expect("Failed to create GLFW window"); window.make_current(); - window.set_key_polling(true); window.set_framebuffer_size_polling(true); + window.set_cursor_pos_polling(true); + window.set_scroll_polling(true); + + // tell GLFW to capture our mouse + window.set_cursor_mode(glfw::CursorMode::Disabled); // gl: load all OpenGL function pointers // --------------------------------------- gl::load_with(|symbol| window.get_proc_address(symbol) as *const _); - let (shader_object, vbo, vao, texture) = unsafe { + let ( + lightingShader, + lampShader, + VBO, + cubeVAO, + lightVAO, + diffuseMap, + specularMap, + cubePositions, + pointLightPositions + + ) = unsafe { + // configure global opengl state + // ----------------------------- gl::Enable(gl::DEPTH_TEST); - let v_shader = CString::new(consts::VERTEX_SHADER_SRC.as_bytes()).unwrap(); - let f_shader = CString::new(consts::FRAGMENT_SHADER_SRC.as_bytes()).unwrap(); - let shader = crate::shader::Shader::new(v_shader, f_shader); + + // build and compile our shader program + // ------------------------------------ + let lightingShader = shader::Shader::from_file( + "resources/shaders/multiple_lights.vs", + "resources/shaders/multiple_lights.fs", + ); + let lampShader = + shader::Shader::from_file("resources/shaders/lamp.vs", "resources/shaders/lamp.fs"); + // set up vertex data (and buffer(s)) and configure vertex attributes // ------------------------------------------------------------------ - // HINT: type annotation is crucial since default for float literals is f64 - let vertices: [f32; 180] = [ - -0.5, -0.5, -0.5, 0.0, 0.0, 0.5, -0.5, -0.5, 1.0, 0.0, 0.5, 0.5, -0.5, 1.0, 1.0, 0.5, - 0.5, -0.5, 1.0, 1.0, -0.5, 0.5, -0.5, 0.0, 1.0, -0.5, -0.5, -0.5, 0.0, 0.0, -0.5, -0.5, - 0.5, 0.0, 0.0, 0.5, -0.5, 0.5, 1.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, 0.5, 1.0, - 1.0, -0.5, 0.5, 0.5, 0.0, 1.0, -0.5, -0.5, 0.5, 0.0, 0.0, -0.5, 0.5, 0.5, 1.0, 0.0, - -0.5, 0.5, -0.5, 1.0, 1.0, -0.5, -0.5, -0.5, 0.0, 1.0, -0.5, -0.5, -0.5, 0.0, 1.0, - -0.5, -0.5, 0.5, 0.0, 0.0, -0.5, 0.5, 0.5, 1.0, 0.0, 0.5, 0.5, 0.5, 1.0, 0.0, 0.5, 0.5, - -0.5, 1.0, 1.0, 0.5, -0.5, -0.5, 0.0, 1.0, 0.5, -0.5, -0.5, 0.0, 1.0, 0.5, -0.5, 0.5, - 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 0.0, -0.5, -0.5, -0.5, 0.0, 1.0, 0.5, -0.5, -0.5, 1.0, - 1.0, 0.5, -0.5, 0.5, 1.0, 0.0, 0.5, -0.5, 0.5, 1.0, 0.0, -0.5, -0.5, 0.5, 0.0, 0.0, - -0.5, -0.5, -0.5, 0.0, 1.0, -0.5, 0.5, -0.5, 0.0, 1.0, 0.5, 0.5, -0.5, 1.0, 1.0, 0.5, - 0.5, 0.5, 1.0, 0.0, 0.5, 0.5, 0.5, 1.0, 0.0, -0.5, 0.5, 0.5, 0.0, 0.0, -0.5, 0.5, -0.5, - 0.0, 1.0, + let vertices: [f32; 288] = [ + // positions // normals // texture coords + -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0, 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 0.0, + 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0, 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0, + -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 1.0, -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0, + -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 0.0, 0.5, + 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, -0.5, 0.5, + 0.5, 0.0, 0.0, 1.0, 0.0, 1.0, -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, -0.5, 0.5, 0.5, + -1.0, 0.0, 0.0, 1.0, 0.0, -0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 1.0, -0.5, -0.5, -0.5, + -1.0, 0.0, 0.0, 0.0, 1.0, -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 0.0, 1.0, -0.5, -0.5, 0.5, + -1.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 1.0, 0.0, 0.5, 0.5, 0.5, 1.0, + 0.0, 0.0, 1.0, 0.0, 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 1.0, 1.0, 0.5, -0.5, -0.5, 1.0, 0.0, + 0.0, 0.0, 1.0, 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 1.0, 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 0.0, -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, + 0.0, 1.0, 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 1.0, 1.0, 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, + 1.0, 0.0, 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 0.0, -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, + 0.0, 0.0, -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 0.0, 1.0, -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, + 0.0, 1.0, 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, + 0.0, 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 0.0, -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 0.0, + -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 1.0, ]; - let (mut vbo, mut vao) = (0, 0); - gl::GenVertexArrays(1, &mut vao); - gl::GenBuffers(1, &mut vbo); + // positions all containers + let cubePositions: [Vector3; 10] = [ + vec3(0.0, 0.0, 0.0), + vec3(2.0, 5.0, -15.0), + vec3(-1.5, -2.2, -2.5), + vec3(-3.8, -2.0, -12.3), + vec3(2.4, -0.4, -3.5), + vec3(-1.7, 3.0, -7.5), + vec3(1.3, -2.0, -2.5), + vec3(1.5, 2.0, -2.5), + vec3(1.5, 0.2, -1.5), + vec3(-1.3, 1.0, -1.5), + ]; + // positions of the point lights + let pointLightPositions: [Vector3; 4] = [ + vec3(0.7, 0.2, 2.0), + vec3(2.3, -3.3, -4.0), + vec3(-4.0, 2.0, -12.0), + vec3(0.0, 0.0, -3.0), + ]; + // first, configure the cube's VAO (and VBO) + let (mut VBO, mut cubeVAO) = (0, 0); + gl::GenVertexArrays(1, &mut cubeVAO); + gl::GenBuffers(1, &mut VBO); - gl::BindVertexArray(vao); - - gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + gl::BindBuffer(gl::ARRAY_BUFFER, VBO); gl::BufferData( gl::ARRAY_BUFFER, (vertices.len() * mem::size_of::()) as GLsizeiptr, @@ -111,62 +163,67 @@ pub fn main() { gl::STATIC_DRAW, ); - let stride = 5 * mem::size_of::() as GLsizei; - // position attribute + gl::BindVertexArray(cubeVAO); + let stride = 8 * mem::size_of::() as GLsizei; gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, stride, ptr::null()); gl::EnableVertexAttribArray(0); - // texture coord attribute gl::VertexAttribPointer( 1, - 2, + 3, gl::FLOAT, gl::FALSE, stride, (3 * mem::size_of::()) as *const c_void, ); gl::EnableVertexAttribArray(1); - - // uncomment this call to draw in wireframe polygons. - // gl::PolygonMode(gl::FRONT_AND_BACK, gl::LINE); - - // ------------------------- - let mut texture = 0; - gl::GenTextures(1, &mut texture); - gl::BindTexture(gl::TEXTURE_2D, texture); // all upcoming GL_TEXTURE_2D operations now have effect on this texture object - // set the texture wrapping parameters - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32); // set texture wrapping to gl::REPEAT (default wrapping method) - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32); - // set texture filtering parameters - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); - // load image, create texture and generate mipmaps - let img = image::open("resources/garlic_dog_space.jpg") - .unwrap() - .flipv(); - let data = img.raw_pixels(); - - // let data = flipped - let dimensions = (img.width(), img.height()); - gl::TexImage2D( - gl::TEXTURE_2D, - 0, - gl::RGB as i32, - dimensions.0 as i32, - dimensions.1 as i32, - 0, - gl::RGB, - gl::UNSIGNED_BYTE, - &data[0] as *const u8 as *const c_void, + gl::VertexAttribPointer( + 2, + 2, + gl::FLOAT, + gl::FALSE, + stride, + (6 * mem::size_of::()) as *const c_void, ); - gl::GenerateMipmap(gl::TEXTURE_2D); - (shader, vbo, vao, texture) + gl::EnableVertexAttribArray(2); + + // second, configure the light's VAO (VBO stays the same; the vertices are the same for the light object which is also a 3D cube) + let mut lightVAO = 0; + gl::GenVertexArrays(1, &mut lightVAO); + gl::BindVertexArray(lightVAO); + + gl::BindBuffer(gl::ARRAY_BUFFER, VBO); + // note that we update the lamp's position attribute's stride to reflect the updated buffer data + gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, stride, ptr::null()); + gl::EnableVertexAttribArray(0); + + // load textures (we now use a utility function to keep the code more organized) + // ----------------------------------------------------------------------------- + let diffuseMap = engine::loadTexture("resources/pattern.png"); + let specularMap = engine::loadTexture("resources/textures/container2_specular.png"); + + // shader configuration + // -------------------- + lightingShader.useProgram(); + lightingShader.setInt(c_str!("material.diffuse"), 0); + lightingShader.setInt(c_str!("material.specular"), 1); + + ( + lightingShader, + lampShader, + VBO, + cubeVAO, + lightVAO, + diffuseMap, + specularMap, + cubePositions, + pointLightPositions + ) }; + // render loop // ----------- - let color_r = 0.188; - let color_g = 0.22; - let color_b = 0.235; + let (r, g, b) = (0.188, 0.22, 0.235); while !window.should_close() { // per-frame time logic // -------------------- @@ -188,39 +245,124 @@ pub fn main() { // ----- process_input(&mut window, delta_time, &mut camera); + // render + // ------ // render // ------ unsafe { - gl::ClearColor(color_r, color_g, color_b, 1.0); + gl::ClearColor(r, g, b, 1.0); gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); - gl::BindTexture(gl::TEXTURE_2D, texture); - shader_object.useProgram(); + // be sure to activate shader when setting uniforms/drawing objects + lightingShader.useProgram(); + lightingShader.setVector3(c_str!("viewPos"), &camera.Position.to_vec()); + lightingShader.setFloat(c_str!("material.shininess"), 32.0); + /* + Here we set all the uniforms for the 5/6 types of lights we have. We have to set them manually and index + the proper PointLight struct in the array to set each uniform variable. This can be done more code-friendly + by defining light types as classes and set their values in there, or by using a more efficient uniform approach + by using 'Uniform buffer objects', but that is something we'll discuss in the 'Advanced GLSL' tutorial. + */ + // directional light + lightingShader.setVec3(c_str!("dirLight.direction"), -0.2, -1.0, -0.3); + lightingShader.setVec3(c_str!("dirLight.ambient"), 0.05, 0.05, 0.05); + lightingShader.setVec3(c_str!("dirLight.diffuse"), 0.4, 0.4, 0.4); + lightingShader.setVec3(c_str!("dirLight.specular"), 0.5, 0.5, 0.5); + // point light 1 + lightingShader.setVector3(c_str!("pointLights[0].position"), &pointLightPositions[0]); + lightingShader.setVec3(c_str!("pointLights[0].ambient"), 0.05, 0.05, 0.05); + lightingShader.setVec3(c_str!("pointLights[0].diffuse"), 0.8, 0.8, 0.8); + lightingShader.setVec3(c_str!("pointLights[0].specular"), 1.0, 1.0, 1.0); + lightingShader.setFloat(c_str!("pointLights[0].constant"), 1.0); + lightingShader.setFloat(c_str!("pointLights[0].linear"), 0.09); + lightingShader.setFloat(c_str!("pointLights[0].quadratic"), 0.032); + // point light 2 + lightingShader.setVector3(c_str!("pointLights[1].position"), &pointLightPositions[1]); + lightingShader.setVec3(c_str!("pointLights[1].ambient"), 0.05, 0.05, 0.05); + lightingShader.setVec3(c_str!("pointLights[1].diffuse"), 0.8, 0.8, 0.8); + lightingShader.setVec3(c_str!("pointLights[1].specular"), 1.0, 1.0, 1.0); + lightingShader.setFloat(c_str!("pointLights[1].constant"), 1.0); + lightingShader.setFloat(c_str!("pointLights[1].linear"), 0.09); + lightingShader.setFloat(c_str!("pointLights[1].quadratic"), 0.032); + // point light 3 + lightingShader.setVector3(c_str!("pointLights[2].position"), &pointLightPositions[2]); + lightingShader.setVec3(c_str!("pointLights[2].ambient"), 0.05, 0.05, 0.05); + lightingShader.setVec3(c_str!("pointLights[2].diffuse"), 0.8, 0.8, 0.8); + lightingShader.setVec3(c_str!("pointLights[2].specular"), 1.0, 1.0, 1.0); + lightingShader.setFloat(c_str!("pointLights[2].constant"), 1.0); + lightingShader.setFloat(c_str!("pointLights[2].linear"), 0.09); + lightingShader.setFloat(c_str!("pointLights[2].quadratic"), 0.032); + // point light 4 + lightingShader.setVector3(c_str!("pointLights[3].position"), &pointLightPositions[3]); + lightingShader.setVec3(c_str!("pointLights[3].ambient"), 0.05, 0.05, 0.05); + lightingShader.setVec3(c_str!("pointLights[3].diffuse"), 0.8, 0.8, 0.8); + lightingShader.setVec3(c_str!("pointLights[3].specular"), 1.0, 1.0, 1.0); + lightingShader.setFloat(c_str!("pointLights[3].constant"), 1.0); + lightingShader.setFloat(c_str!("pointLights[3].linear"), 0.09); + lightingShader.setFloat(c_str!("pointLights[3].quadratic"), 0.032); + // spotLight + lightingShader.setVector3(c_str!("spotLight.position"), &camera.Position.to_vec()); + lightingShader.setVector3(c_str!("spotLight.direction"), &camera.Front); + lightingShader.setVec3(c_str!("spotLight.ambient"), 0.0, 0.0, 0.0); + lightingShader.setVec3(c_str!("spotLight.diffuse"), 1.0, 1.0, 1.0); + lightingShader.setVec3(c_str!("spotLight.specular"), 1.0, 1.0, 1.0); + lightingShader.setFloat(c_str!("spotLight.constant"), 1.0); + lightingShader.setFloat(c_str!("spotLight.linear"), 0.09); + lightingShader.setFloat(c_str!("spotLight.quadratic"), 0.032); + lightingShader.setFloat(c_str!("spotLight.cutOff"), 12.5f32.to_radians().cos()); + lightingShader.setFloat(c_str!("spotLight.outerCutOff"), 15.0f32.to_radians().cos()); - // pass projection matrix to shader (note that in this case it could change every frame) + // view/projection transformations let projection: Matrix4 = perspective( Deg(camera.Zoom), consts::SCR_WIDTH as f32 / consts::SCR_HEIGHT as f32, 0.1, 100.0, ); - shader_object.setMat4(c_str!("projection"), &projection); - - // camera/view transformation let view = camera.get_view_matrix(); - shader_object.setMat4(c_str!("view"), &view); + lightingShader.setMat4(c_str!("projection"), &projection); + lightingShader.setMat4(c_str!("view"), &view); - gl::BindVertexArray(vao); - for (i, position) in CUBES_POS.iter().enumerate() { + // world transformation + let mut model = Matrix4::::identity(); + lightingShader.setMat4(c_str!("model"), &model); + + // bind diffuse map + gl::ActiveTexture(gl::TEXTURE0); + gl::BindTexture(gl::TEXTURE_2D, diffuseMap); + // bind specular map + gl::ActiveTexture(gl::TEXTURE1); + gl::BindTexture(gl::TEXTURE_2D, specularMap); + + // render containers + gl::BindVertexArray(cubeVAO); + for (i, position) in cubePositions.iter().enumerate() { // calculate the model matrix for each object and pass it to shader before drawing let mut model: Matrix4 = Matrix4::from_translation(*position); let angle = 20.0 * i as f32; + // don't forget to normalize the axis! model = model * Matrix4::from_axis_angle(vec3(1.0, 0.3, 0.5).normalize(), Deg(angle)); - shader_object.setMat4(c_str!("model"), &model); + lightingShader.setMat4(c_str!("model"), &model); gl::DrawArrays(gl::TRIANGLES, 0, 36); } + + // also draw the lamp object(s) + lampShader.useProgram(); + lampShader.setMat4(c_str!("projection"), &projection); + lampShader.setMat4(c_str!("view"), &view); + + // we now draw as many light bulbs as we have point lights. + gl::BindVertexArray(lightVAO); + for position in &pointLightPositions { + model = Matrix4::from_translation(*position); + model = model * Matrix4::from_scale(0.2); // Make it a smaller cube + lampShader.setMat4(c_str!("model"), &model); + + gl::DrawArrays(gl::TRIANGLES, 0, 36); + } + } // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) @@ -230,8 +372,9 @@ pub fn main() { } unsafe { - gl::DeleteVertexArrays(1, &vao); - gl::DeleteBuffers(1, &vbo); + gl::DeleteVertexArrays(1, &cubeVAO); + gl::DeleteVertexArrays(1, &lightVAO); + gl::DeleteBuffers(1, &VBO); } } diff --git a/src/mesh.rs b/src/mesh.rs new file mode 100644 index 0000000..27eb5d3 --- /dev/null +++ b/src/mesh.rs @@ -0,0 +1,162 @@ +#![allow(non_snake_case)] +#![allow(dead_code)] + +use std::ffi::CString; +use std::mem::size_of; +use std::os::raw::c_void; +use std::ptr; + +use cgmath::{ Vector3, Vector2 }; +use cgmath::prelude::*; +use gl; + +use crate::shader::Shader; + +// NOTE: without repr(C) the compiler may reorder the fields or use different padding/alignment than C. +// Depending on how you pass the data to OpenGL, this may be bad. In this case it's not strictly +// necessary though because of the `offset!` macro used below in setupMesh() +#[repr(C)] +pub struct Vertex { + // position + pub Position: Vector3, + // normal + pub Normal: Vector3, + // texCoords + pub TexCoords: Vector2, + // tangent + pub Tangent: Vector3, + // bitangent + pub Bitangent: Vector3, +} + +impl Default for Vertex { + fn default() -> Self { + Vertex { + Position: Vector3::zero(), + Normal: Vector3::zero(), + TexCoords: Vector2::zero(), + Tangent: Vector3::zero(), + Bitangent: Vector3::zero(), + } + } +} + +#[derive(Clone)] +pub struct Texture { + pub id: u32, + pub type_: String, + pub path: String, +} + +pub struct Mesh { + /* Mesh Data */ + pub vertices: Vec, + pub indices: Vec, + pub textures: Vec, + pub VAO: u32, + + /* Render data */ + VBO: u32, + EBO: u32, +} + +impl Mesh { + pub fn new(vertices: Vec, indices: Vec, textures: Vec) -> Mesh { + let mut mesh = Mesh { + vertices, indices, textures, + VAO: 0, VBO: 0, EBO: 0 + }; + + // now that we have all the required data, set the vertex buffers and its attribute pointers. + unsafe { mesh.setupMesh() } + mesh + } + + /// render the mesh + pub unsafe fn Draw(&self, shader: &Shader) { + // bind appropriate textures + let mut diffuseNr = 0; + let mut specularNr = 0; + let mut normalNr = 0; + let mut heightNr = 0; + for (i, texture) in self.textures.iter().enumerate() { + gl::ActiveTexture(gl::TEXTURE0 + i as u32); // active proper texture unit before binding + // retrieve texture number (the N in diffuse_textureN) + let name = &texture.type_; + let number = match name.as_str() { + "texture_diffuse" => { + diffuseNr += 1; + diffuseNr + }, + "texture_specular" => { + specularNr += 1; + specularNr + } + "texture_normal" => { + normalNr += 1; + normalNr + } + "texture_height" => { + heightNr += 1; + heightNr + } + _ => panic!("unknown texture type") + }; + // now set the sampler to the correct texture unit + let sampler = CString::new(format!("{}{}", name, number)).unwrap(); + gl::Uniform1i(gl::GetUniformLocation(shader.ID, sampler.as_ptr()), i as i32); + // and finally bind the texture + gl::BindTexture(gl::TEXTURE_2D, texture.id); + } + + // draw mesh + gl::BindVertexArray(self.VAO); + gl::DrawElements(gl::TRIANGLES, self.indices.len() as i32, gl::UNSIGNED_INT, ptr::null()); + gl::BindVertexArray(0); + + // always good practice to set everything back to defaults once configured. + gl::ActiveTexture(gl::TEXTURE0); + } + + unsafe fn setupMesh(&mut self) { + // create buffers/arrays + gl::GenVertexArrays(1, &mut self.VAO); + gl::GenBuffers(1, &mut self.VBO); + gl::GenBuffers(1, &mut self.EBO); + + gl::BindVertexArray(self.VAO); + // load data into vertex buffers + gl::BindBuffer(gl::ARRAY_BUFFER, self.VBO); + // A great thing about structs with repr(C) is that their memory layout is sequential for all its items. + // The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2 array which + // again translates to 3/2 floats which translates to a byte array. + let size = (self.vertices.len() * size_of::()) as isize; + let data = &self.vertices[0] as *const Vertex as *const c_void; + gl::BufferData(gl::ARRAY_BUFFER, size, data, gl::STATIC_DRAW); + + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.EBO); + let size = (self.indices.len() * size_of::()) as isize; + let data = &self.indices[0] as *const u32 as *const c_void; + gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, size, data, gl::STATIC_DRAW); + + // set the vertex attribute pointers + let size = size_of::() as i32; + // vertex Positions + gl::EnableVertexAttribArray(0); + gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, size, offset_of!(Vertex, Position) as *const c_void); + // vertex normals + gl::EnableVertexAttribArray(1); + gl::VertexAttribPointer(1, 3, gl::FLOAT, gl::FALSE, size, offset_of!(Vertex, Normal) as *const c_void); + // vertex texture coords + gl::EnableVertexAttribArray(2); + gl::VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, size, offset_of!(Vertex, TexCoords) as *const c_void); + // vertex tangent + gl::EnableVertexAttribArray(3); + gl::VertexAttribPointer(3, 3, gl::FLOAT, gl::FALSE, size, offset_of!(Vertex, Tangent) as *const c_void); + // vertex bitangent + gl::EnableVertexAttribArray(4); + gl::VertexAttribPointer(4, 3, gl::FLOAT, gl::FALSE, size, offset_of!(Vertex, Bitangent) as *const c_void); + + gl::BindVertexArray(0); + } +} diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..78ac951 --- /dev/null +++ b/src/model.rs @@ -0,0 +1,117 @@ +#![allow(non_snake_case)] +#![allow(dead_code)] + +use std::os::raw::c_void; +use std::path::Path; + +use cgmath::{vec2, vec3}; +use gl; +use image; +use image::DynamicImage::*; +use image::GenericImage; +use tobj; + +use crate::mesh::{ Mesh, Texture, Vertex }; +use crate::shader::Shader; +use crate::engine::*; + +// #[derive(Default)] +pub struct Model { + /* Model Data */ + pub meshes: Vec, + pub textures_loaded: Vec, // stores all the textures loaded so far, optimization to make sure textures aren't loaded more than once. + directory: String, +} + +impl Model { + /// constructor, expects a filepath to a 3D model. + pub fn new(path: &str) -> Model { + let pathObj = Path::new(path); + let mut model = Model{ + meshes: Vec::::new(), + textures_loaded: Vec::::new(), + directory: pathObj.parent().unwrap_or_else(|| Path::new("")).to_str().unwrap().into() + }; + model.loadModel(path); + model + } + + pub fn Draw(&self, shader: &Shader) { + for mesh in &self.meshes { + unsafe { mesh.Draw(shader); } + } + } + + // loads a model from file and stores the resulting meshes in the meshes vector. + fn loadModel(&mut self, path: &str) { + let path = Path::new(path); + + // retrieve the directory path of the filepath + self.directory = path.parent().unwrap_or_else(|| Path::new("")).to_str().unwrap().into(); + let obj = tobj::load_obj(path, true); + + let (models, materials) = obj.unwrap(); + for model in models { + let mesh = &model.mesh; + let num_vertices = mesh.positions.len() / 3; + + // data to fill + let mut vertices: Vec = Vec::with_capacity(num_vertices); + let indices: Vec = mesh.indices.clone(); + + let (p, n, t) = (&mesh.positions, &mesh.normals, &mesh.texcoords); + for i in 0..num_vertices { + vertices.push(Vertex { + Position: vec3(p[i*3], p[i*3+1], p[i*3+2]), + Normal: vec3(n[i*3], n[i*3+1], n[i*3+2]), + TexCoords: vec2(t[i*2], t[i*2+1]), + ..Vertex::default() + }) + } + + // process material + let mut textures = Vec::new(); + if let Some(material_id) = mesh.material_id { + let material = &materials[material_id]; + + // 1. diffuse map + if !material.diffuse_texture.is_empty() { + let texture = self.loadMaterialTexture(&material.diffuse_texture, "texture_diffuse"); + textures.push(texture); + } + // 2. specular map + if !material.specular_texture.is_empty() { + let texture = self.loadMaterialTexture(&material.specular_texture, "texture_specular"); + textures.push(texture); + } + // 3. normal map + if !material.normal_texture.is_empty() { + let texture = self.loadMaterialTexture(&material.normal_texture, "texture_normal"); + textures.push(texture); + } + // NOTE: no height maps + } + + self.meshes.push(Mesh::new(vertices, indices, textures)); + } + + } + + fn loadMaterialTexture(&mut self, path: &str, typeName: &str) -> Texture { + { + let texture = self.textures_loaded.iter().find(|t| t.path == path); + if let Some(texture) = texture { + return texture.clone(); + } + } + + let texture = Texture { + id: unsafe { loadTextureFromDir(path, &self.directory) }, + type_: typeName.into(), + path: path.into() + }; + self.textures_loaded.push(texture.clone()); + texture + } +} +