~luyu/comp5411-rendering-project

76e49b50a8f1fba714deaadc515b9225356fed29 — Luyu Cheng 2 months ago 506ad1f
feat: apply a handmade 2D texture to cubes
7 files changed, 230 insertions(+), 54 deletions(-)

M .gitignore
M Cargo.toml
M src/lib.rs
M src/shader.frag
M src/shader.vert
A src/texture.png
A src/texture.rs
M .gitignore => .gitignore +33 -0
@@ 154,3 154,36 @@ dist
.svelte-kit

# End of https://www.toptal.com/developers/gitignore/api/node

# Created by https://www.toptal.com/developers/gitignore/api/macos
# Edit at https://www.toptal.com/developers/gitignore?templates=macos

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

# End of https://www.toptal.com/developers/gitignore/api/macos

M Cargo.toml => Cargo.toml +2 -0
@@ 13,6 13,7 @@ wasm-bindgen = "0.2.78"
cgmath = "0.18.0"
rand = "0.8.4"
getrandom = { version = "0.2.3", features = ["js"] }
png = "0.17.2"

[dependencies.web-sys]
version = "0.3.4"


@@ 22,6 23,7 @@ features = [
  'HtmlCanvasElement',
  'WebGlBuffer',
  'WebGlVertexArrayObject',
  'WebGlTexture',
  'WebGl2RenderingContext',
  'WebGlProgram',
  'WebGlShader',

M src/lib.rs => src/lib.rs +104 -47
@@ 1,11 1,14 @@
use cgmath::SquareMatrix;
use rand::prelude::*;
use std::cell::Cell;
use std::cell::RefCell;
use std::rc::Rc;
use std::vec;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{WebGl2RenderingContext, WebGlProgram, WebGlShader};
use rand::prelude::*;

mod texture;

/// Defined in https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#animation-frames
type FrameRequestCallback = dyn FnMut(f64) + 'static;


@@ 65,6 68,35 @@ pub fn start() -> Result<(), JsValue> {
    let projection_attribute_location = context
        .get_uniform_location(&program, "projection")
        .ok_or("Failed to get uniform location")?;
    let sampler_uniform_location = context
        .get_uniform_location(&program, "textureSampler")
        .ok_or("Failed to get the texture sampler.")?;

    let texture = texture::load_texture(&context).ok_or("Failed to load the texture.")?;

    // Get attributes in the program. TODO: move other attributes here.
    let texture_attribute_location = (match context.get_attrib_location(&program, "textureCoord") {
        location if location >= 0 => Some(location as u32),
        _ => None,
    })
    .ok_or("Failed to get the location of attribute \"textureCoord\"")?;

    // Create and fill the buffer for texture coordinates.
    let texture_coordinates_buffer = context
        .create_buffer()
        .ok_or("Failed to create buffer for texture coordinates")?;
    context.bind_buffer(
        WebGl2RenderingContext::ARRAY_BUFFER,
        Some(&texture_coordinates_buffer),
    );
    unsafe {
        let texture_coordinates_buffer_view = js_sys::Float32Array::view(&cube.texture_coordinates);
        context.buffer_data_with_array_buffer_view(
            WebGl2RenderingContext::ARRAY_BUFFER,
            &texture_coordinates_buffer_view,
            WebGl2RenderingContext::STATIC_DRAW,
        );
    }

    let position_attribute_location = context.get_attrib_location(&program, "position");
    let position_buffer = context.create_buffer().ok_or("Failed to create buffer")?;


@@ 102,19 134,6 @@ pub fn start() -> Result<(), JsValue> {
        );
    }

    let color_attribute_location = context.get_attrib_location(&program, "color");
    let color_buffer = context.create_buffer().ok_or("Failed to create buffer")?;
    context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&color_buffer));

    unsafe {
        let colors_array_buf_view = js_sys::Float32Array::view(&cube.colors);
        context.buffer_data_with_array_buffer_view(
            WebGl2RenderingContext::ARRAY_BUFFER,
            &colors_array_buf_view,
            WebGl2RenderingContext::STATIC_DRAW,
        );
    }

    let vao = context
        .create_vertex_array()
        .ok_or("Could not create vertex array object")?;


@@ 122,16 141,38 @@ pub fn start() -> Result<(), JsValue> {

    context.enable_vertex_attrib_array(position_attribute_location as u32);
    context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&position_buffer));
    context.vertex_attrib_pointer_with_i32(0, 3, WebGl2RenderingContext::FLOAT, false, 0, 0);
    context.vertex_attrib_pointer_with_i32(
        position_attribute_location as u32,
        3,
        WebGl2RenderingContext::FLOAT,
        false,
        0,
        0,
    );

    context.bind_buffer(
        WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER,
        Some(&index_buffer),
    );

    context.enable_vertex_attrib_array(color_attribute_location as u32);
    context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&color_buffer));
    context.vertex_attrib_pointer_with_i32(1, 3, WebGl2RenderingContext::FLOAT, false, 0, 0);
    // Tell WebGL how to pull out the texture coordinates from the texture coordinate buffer.
    context.bind_buffer(
        WebGl2RenderingContext::ARRAY_BUFFER,
        Some(&texture_coordinates_buffer),
    );
    context.vertex_attrib_pointer_with_i32(
        texture_attribute_location as u32,
        2,
        WebGl2RenderingContext::FLOAT,
        false,
        0,
        0,
    );
    context.enable_vertex_attrib_array(texture_attribute_location as u32);

    context.active_texture(WebGl2RenderingContext::TEXTURE0);
    context.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
    context.uniform1i(Some(&sampler_uniform_location), 0);

    let pressed = Rc::new(Cell::new(false));
    let model = Rc::new(Cell::new(cgmath::Matrix4::identity()));


@@ 220,8 261,12 @@ pub fn start() -> Result<(), JsValue> {
            for (transformation, rotation_speed) in replicas.iter() {
                let model = transformation
                    * model.get()
                    * cgmath::Matrix4::from_angle_y(cgmath::Deg(time_stamp as f32 / rotation_speed))
                    * cgmath::Matrix4::from_angle_z(cgmath::Deg(time_stamp as f32 / rotation_speed));
                    * cgmath::Matrix4::from_angle_y(cgmath::Deg(
                        time_stamp as f32 / rotation_speed,
                    ))
                    * cgmath::Matrix4::from_angle_z(cgmath::Deg(
                        time_stamp as f32 / rotation_speed,
                    ));
                let model_slice: &[f32; 16] = model.as_ref();
                context.uniform_matrix4fv_with_f32_array(
                    Some(&model_attribute_location),


@@ 248,37 293,50 @@ struct Model {
    pub vertices: Vec<f32>,
    pub indices: Vec<u16>,
    pub colors: Vec<f32>,
    pub texture_coordinates: Vec<f32>,
}

impl Model {
    fn cube() -> Model {
        let mut vertices = Vec::with_capacity(3 * 8);
        let mut vertices = Vec::with_capacity(4 * 6 * 3);
        let mut indices = Vec::with_capacity(6 * 6);
        let mut colors = Vec::with_capacity(3 * 8);
        // Generate 8 vertices of a cube.
        for i in 0..=0b111 {
            let x = if i & 0b100 != 0 { 1.0 } else { -1.0 };
            let y = if i & 0b010 != 0 { 1.0 } else { -1.0 };
            let z = if i & 0b001 != 0 { 1.0 } else { -1.0 };
            vertices.extend_from_slice(&[x, y, z]);
            colors.extend_from_slice(&make_color());
        }
        // For each face,
        // TODO: generate automatically.
        let mut texture_coordinates = Vec::with_capacity(6 * 4 * 2);
        // Generate four vertices for each face.
        // Therefore, there are 4 * 6 = 24 vertices.
        for i in 0..6 {
            let i0 = i / 2;
            let i1 = (i0 + 1) % 3;
            let i2 = (i0 + 2) % 3;
            let a = i & 1;
            for j in [0b00, 0b01, 0b10, 0b01, 0b10, 0b11] {
                let b = j & 0b01;
                let c = (j & 0b10) >> 1;
                indices.push((a << i0) | (b << i1) | (c << i2) as u16);
            // Fix an axis (x, y, or z).
            let axis0 = i >> 1;
            let axis1 = (axis0 + 1) % 3;
            let axis2 = (axis0 + 2) % 3;
            let mut position = [0.0; 3];
            position[axis0] = if i & 1 == 0 { -1.0 } else { 1.0 };
            // Enumerate 4 combinations of remaining axes (axis 1 and axis 2).
            // The order is in a Z shape: (-1, -1), (-1, 1), (1, -1), (1, 1).
            for j in 0b00..=0b11 {
                position[axis1] = if j & 0b01 == 0 { -1.0 } else { 1.0 };
                position[axis2] = if j & 0b10 == 0 { -1.0 } else { 1.0 };
                // Save the position.
                vertices.extend_from_slice(&position);
            }
            // Generate four vertex indices for each face.
            // Because the vertices are in a Z shape, the indices are same.
            indices.extend_from_slice(&[
                i as u16 * 4 + 0,
                i as u16 * 4 + 1,
                i as u16 * 4 + 2,
                i as u16 * 4 + 1,
                i as u16 * 4 + 2,
                i as u16 * 4 + 3,
            ]);
            // Generate texture coordinates for the face.
            texture_coordinates.extend_from_slice(&[0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]);
        }
        Model {
            vertices,
            indices,
            colors,
            colors: Vec::new(),
            texture_coordinates,
        }
    }



@@ 321,6 379,7 @@ impl Model {
        );
    }

    #[allow(dead_code)]
    fn two_squares() -> Model {
        let mut front = Model::square(1.0);
        let mut back = Model::square(-1.0);


@@ 331,24 390,22 @@ impl Model {
        front.indices.extend_from_slice(&back.indices);
        front.colors.extend_from_slice(&back.colors);
        front
            .texture_coordinates
            .extend_from_slice(&back.texture_coordinates);
        front
    }

    #[allow(dead_code)]
    fn square(z: f32) -> Model {
        Model {
            vertices: vec![1.0, 1.0, z, 1.0, -1.0, z, -1.0, 1.0, z, -1.0, -1.0, z],
            indices: vec![0, 1, 2, 1, 2, 3],
            colors: vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0],
            texture_coordinates: vec![],
        }
    }
}

fn make_color() -> [f32; 3] {
    let r = js_sys::Math::random() as f32;
    let g = js_sys::Math::random() as f32;
    let b = js_sys::Math::random() as f32;
    [r, g, b]
}

fn request_animation_frame(f: &Closure<FrameRequestCallback>) {
    web_sys::window()
        .unwrap()

M src/shader.frag => src/shader.frag +12 -3
@@ 1,9 1,18 @@
#version 300 es

precision highp float;
in vec4 fragmentColor;
out vec4 outColor;

// Inputs from the vertex shader.
in vec2 vTextureCoord;

// Uniform variables shared during all runs.
uniform sampler2D textureSampler;

// Output of the fragment shader.
// Note that `gl_FragColor` is deprecated.
layout(location = 0) out vec4 color;

void main() {
    outColor = fragmentColor;
    // outColor = fragmentColor;
    color = texture(textureSampler, vTextureCoord);
}
\ No newline at end of file

M src/shader.vert => src/shader.vert +12 -4
@@ 1,13 1,21 @@
#version 300 es

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
precision highp float;

// Inputs from the application code.
in vec3 position;
in vec2 textureCoord;

// Uniform variables shared during all runs.
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec4 fragmentColor;

// Outputs to the fragment shader.
out vec2 vTextureCoord;

void main() {
    fragmentColor = vec4(color, 1.0);
    // fragmentColor = vec4(color, 1.0);
    gl_Position = projection * view * model * vec4(position, 1.0);
    vTextureCoord = textureCoord;
}

A src/texture.png => src/texture.png +0 -0
A src/texture.rs => src/texture.rs +67 -0
@@ 0,0 1,67 @@
use std::io::BufReader;
use web_sys::{WebGl2RenderingContext, WebGlTexture};

pub fn is_power_of_two(n: u32) -> bool {
    (n & (n - 1)) == 0
}

pub fn load_texture(context: &WebGl2RenderingContext) -> Option<WebGlTexture> {
    let texture = context.create_texture()?;
    context.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
    let raw_data = include_bytes!("texture.png");
    let decoder = png::Decoder::new(BufReader::new(raw_data.as_ref()));
    let mut reader = decoder.read_info().unwrap();
    // Allocate buffer for actually image data.
    let mut image_buffer = vec![0; reader.output_buffer_size()];
    // Read the next frame. An APNG might contain multiple frames.
    let image_info = reader.next_frame(&mut image_buffer).unwrap();
    web_sys::console::log_1(
        &format!(
            "Texture image: width = {}, height = {}, color type = {:?}, bit depth = {:?}",
            image_info.width, image_info.height, image_info.color_type, image_info.bit_depth
        )
        .into(),
    );
    let format = match image_info.color_type {
        // See https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D
        png::ColorType::Grayscale => WebGl2RenderingContext::ALPHA,
        png::ColorType::Rgb => WebGl2RenderingContext::RGB,
        png::ColorType::Rgba => WebGl2RenderingContext::RGBA,
        _ => return None,
    };
    unsafe {
        let array_buffer_view = js_sys::Uint8Array::view(&image_buffer);
        context.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_array_buffer_view_and_src_offset(
            WebGl2RenderingContext::TEXTURE_2D,
            0,
            format as i32,
            image_info.width as i32,
            image_info.height as i32,
            0,
            format, // same as internal format
            WebGl2RenderingContext::UNSIGNED_BYTE,
            &array_buffer_view,
            0
        ).ok()?;
    }
    if is_power_of_two(image_info.width) && is_power_of_two(image_info.height) {
        context.generate_mipmap(WebGl2RenderingContext::TEXTURE_2D);
    } else {
        context.tex_parameteri(
            WebGl2RenderingContext::TEXTURE_2D,
            WebGl2RenderingContext::TEXTURE_WRAP_S,
            WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
        );
        context.tex_parameteri(
            WebGl2RenderingContext::TEXTURE_2D,
            WebGl2RenderingContext::TEXTURE_WRAP_T,
            WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
        );
        context.tex_parameteri(
            WebGl2RenderingContext::TEXTURE_2D,
            WebGl2RenderingContext::TEXTURE_MIN_FILTER,
            WebGl2RenderingContext::LINEAR as i32,
        );
    }
    Some(texture)
}