Drawing a 3D-Looking Rotating Triangle

pub struct Renderer {
    gl: gl::Gl,
    program: gl::types::GLuint,
    vao: gl::types::GLuint,
    vbo: gl::types::GLuint,
    rotation: gl::types::GLint,
    perspective: gl::types::GLint,
}
const VERTEX_SHADER_SOURCE: &CStr = c"
#version 410 core

uniform mat3 rotation;
uniform mat4 perspective;

const mat4 translation = mat4(1.0, 0.0, 0.0, 0.0,
                              0.0, 1.0, 0.0, 0.0,
                              0.0, 0.0, 1.0, 0.0,
                              0.0, 0.0, -1.5, 1.0);

in vec3 position;
in vec3 color;

out vec3 v_color;

void main() {
    gl_Position = perspective * translation * vec4(rotation * position, 1.0);
    v_color = color;
}
";
            let perspective =
                gl.GetUniformLocation(program, c"perspective".as_ptr() as *const _);
            Self { gl, program, vao, vbo, rotation, perspective }
        let near: f32 = 1.0;
        let far: f32 = 10.0;
        let left: f32 = -0.5;
        let right: f32 = 0.5;
        let bottom: f32 = -0.5;
        let top: f32 = 0.5;

        // from http://learnwebgl.brown37.net/08_projections/projections_perspective.html
        let perspective: [f32; 16]  = [ 2.0*near/(right-left),  0.0,  0.0,  -near*(right+left)/(right-left),
                                        0.0,  2.0*near/(top-bottom),  0.0,  -near*(top+bottom)/(top-bottom),
                                        0.0,  0.0,  -(far+near)/(far-near),  2.0*far*near/(near-far),
                                        0.0,  0.0,  -1.0,  0.0];
            self.gl.UniformMatrix3fv(self.rotation, 1, 1, rotation.as_ptr());
            self.gl.UniformMatrix4fv(self.perspective, 1, 1, perspective.as_ptr());

Using a Math Library

pub struct Renderer {
    gl: gl::Gl,
    viewport_size: (i32, i32),
    program: gl::types::GLuint,
    vao: gl::types::GLuint,
    vbo: gl::types::GLuint,
    rotation: gl::types::GLint,
    perspective: gl::types::GLint,
}
        }
        let perspective = glam::Mat4::perspective_rh_gl(1.0, aspect_ratio, 0.5, 10.0);

            self.gl.UniformMatrix4fv(self.perspective, 1, 0, (&perspective as *const _) as *const _);

            self.gl.BindVertexArray(self.vao);
        unsafe {

Full code

As always, here comes the full code of everything we've done in all the chapters before and this chapter (though some things might just reference previous chapters):

Cargo.toml

[package]
name = "rotating_triangle_3d_glam"
edition = "2021"

[dependencies]
glam = "0.29.2"
glwindow = "0.1"

[build-dependencies]
gl_generator = "0.14"

build.rs

Unchanged from Chapter 2's build.rs.

src/main.rs

use std::error::Error;
use std::ffi::{CStr, CString};
use std::time::Instant;
use glwindow::AppControl;
use glwindow::event::{WindowEvent, KeyEvent};
use glwindow::keyboard::{Key, NamedKey::Escape};

pub mod gl {
    #![allow(clippy::all)]
    include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
    pub use Gles2 as Gl;
}

pub struct State {
    begin: Instant,
}

pub struct Renderer {
    gl: gl::Gl,
    viewport_size: (i32, i32),
    program: gl::types::GLuint,
    vao: gl::types::GLuint,
    vbo: gl::types::GLuint,
    rotation: gl::types::GLint,
    perspective: gl::types::GLint,
}

static VERTEX_DATA: [f32; 18] = [
    -0.5, -0.5,  0.0,     0.8,  0.8,  0.0,
     0.0,  0.5,  0.0,     0.0,  0.8,  0.8,
     0.5, -0.5,  0.0,     0.8,  0.0,  0.8,
];

const VERTEX_SHADER_SOURCE: &CStr = c"
#version 410 core

uniform mat3 rotation;
uniform mat4 perspective;

const mat4 translation = mat4(1.0, 0.0, 0.0, 0.0,
                              0.0, 1.0, 0.0, 0.0,
                              0.0, 0.0, 1.0, 0.0,
                              0.0, 0.0, -1.5, 1.0);

in vec3 position;
in vec3 color;

out vec3 v_color;

void main() {
    gl_Position = perspective * translation * vec4(rotation * position, 1.0);
    v_color = color;
}
";

const FRAGMENT_SHADER_SOURCE: &CStr = c"
#version 410 core

out vec4 color;

in vec3 v_color;

void main() {
    color = vec4(v_color, 1.0);
}
";

impl glwindow::AppRenderer for Renderer {
    type AppState = State;

    fn new<D: glwindow::GlDisplay>(gl_display: &D) -> Self {
        unsafe {
            let gl = gl::Gl::load_with(|symbol| {
                let symbol = CString::new(symbol).unwrap();
                gl_display.get_proc_address(symbol.as_c_str()).cast()
            });

            let vertex_shader = gl.CreateShader(gl::VERTEX_SHADER);
            gl.ShaderSource(vertex_shader, 1, [VERTEX_SHADER_SOURCE.as_ptr()].as_ptr(), std::ptr::null());
            gl.CompileShader(vertex_shader);

            let fragment_shader = gl.CreateShader(gl::FRAGMENT_SHADER);
            gl.ShaderSource(fragment_shader, 1, [FRAGMENT_SHADER_SOURCE.as_ptr()].as_ptr(), std::ptr::null());
            gl.CompileShader(fragment_shader);

            let program = gl.CreateProgram();

            gl.AttachShader(program, vertex_shader);
            gl.AttachShader(program, fragment_shader);

            gl.LinkProgram(program);

            gl.UseProgram(program);

            gl.DeleteShader(vertex_shader);
            gl.DeleteShader(fragment_shader);

            let mut vao = std::mem::zeroed();
            gl.GenVertexArrays(1, &mut vao);
            gl.BindVertexArray(vao);

            let mut vbo = std::mem::zeroed();
            gl.GenBuffers(1, &mut vbo);
            gl.BindBuffer(gl::ARRAY_BUFFER, vbo);
            gl.BufferData(
                gl::ARRAY_BUFFER,
                (VERTEX_DATA.len() * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr,
                VERTEX_DATA.as_ptr() as *const _,
                gl::STATIC_DRAW,
            );

            let pos_attrib =
                gl.GetAttribLocation(program, c"position".as_ptr() as *const _);
            gl.VertexAttribPointer(
                pos_attrib as gl::types::GLuint,
                3,
                gl::FLOAT,
                0,
                6 * std::mem::size_of::<f32>() as gl::types::GLsizei,
                std::ptr::null(),
            );
            gl.EnableVertexAttribArray(pos_attrib as gl::types::GLuint);

            let color_attrib =
                gl.GetAttribLocation(program, c"color".as_ptr() as *const _);
            gl.VertexAttribPointer(
                color_attrib as gl::types::GLuint,
                3,
                gl::FLOAT,
                0,
                6 * std::mem::size_of::<f32>() as gl::types::GLsizei,
                (3 * std::mem::size_of::<f32>()) as *const () as *const _,
            );
            gl.EnableVertexAttribArray(color_attrib as gl::types::GLuint);

            let rotation =
                gl.GetUniformLocation(program, c"rotation".as_ptr() as *const _);
            let perspective =
                gl.GetUniformLocation(program, c"perspective".as_ptr() as *const _);

            Self { gl, viewport_size: (0, 0), program, vao, vbo, rotation, perspective }
        }
    }

    fn draw(&self, state: &mut State) {
        let time = Instant::now().duration_since(state.begin).as_millis() % 5000;
        let phi = (time as f32) / 5000.0 * 2.0 * std::f32::consts::PI;

        let rotation = glam::Mat3::from_rotation_y(phi);
        let aspect_ratio = self.viewport_size.0 as f32 / self.viewport_size.1 as f32;
        let perspective = glam::Mat4::perspective_rh_gl(1.0, aspect_ratio, 0.5, 10.0);

        unsafe {
            self.gl.UseProgram(self.program);
            self.gl.UniformMatrix3fv(self.rotation, 1, 0, (&rotation as *const _) as *const _);
            self.gl.UniformMatrix4fv(self.perspective, 1, 0, (&perspective as *const _) as *const _);

            self.gl.BindVertexArray(self.vao);
            self.gl.BindBuffer(gl::ARRAY_BUFFER, self.vbo);

            self.gl.ClearColor(0.1, 0.1, 0.1, 0.9);
            self.gl.Clear(gl::COLOR_BUFFER_BIT);
            self.gl.DrawArrays(gl::TRIANGLES, 0, 3);
        }
    }

    fn resize(&mut self, width: i32, height: i32) {
        self.viewport_size = (width, height);
        unsafe {
            self.gl.Viewport(0, 0, width, height);
        }
    }
}

impl Drop for Renderer {
    fn drop(&mut self) {
        unsafe {
            self.gl.DeleteProgram(self.program);
            self.gl.DeleteBuffers(1, &self.vbo);
            self.gl.DeleteVertexArrays(1, &self.vao);
        }
    }
}

fn handle_event(_app_state: &mut State, event: WindowEvent)
                -> Result<AppControl, Box<dyn Error>> {
    let mut exit = false;
    match event {
        WindowEvent::CloseRequested => {
            exit = true;
        }
        WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Named(Escape), .. }, .. } => {
            exit = true;
        }
        _ => (),
    }

    Ok(if exit { AppControl::Exit } else { AppControl::Continue })
}

fn main() -> Result<(), Box<dyn Error>> {
    let app_state = State{
        begin: Instant::now(),
    };
    glwindow::Window::<_,_,Renderer>::new()
        .run(app_state, handle_event as glwindow::HandleFn<_>)
}