LoginSignup
8

More than 1 year has passed since last update.

posted at

updated at

🦀Rust + 💻WebGL = 💛 1.WebGL基本操作編

RustとWebGL が少しずつ分かってきたので記事にしようと思います。今回はRustのクレートである、web_sysを主に使ったWebGLContextの操作がテーマです。慣れるまではかなり苦労しますが、公式ドキュメントを見ながらやりましょう。

前提条件

  • WebGLの基礎
  • Rustの基礎
  • JavaScriptの組み込み関数の基礎
  • 気合い
Cargo.toml
[package]
name = "webgl"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
js-sys = "0.3.53"
wasm-bindgen = "0.2.76"
console_error_panic_hook = "=0.1.5"

[dependencies.web-sys]
version = "0.3.4"
features = [
  'Document',
  'Element',
  'HtmlCanvasElement',
  'WebGlBuffer',
  'WebGlVertexArrayObject',
  'WebGlRenderingContext',
  'WebGlProgram',
  'WebGlShader',
  'WebGlUniformLocation',
  'Window',
]
webgl.rs
use wasm_bindgen::JsCast;
use web_sys::WebGlRenderingContext as GL;
use web_sys::*;

なお、<使用例>に出てくる glはweb_sys::WebGlRenderingContextを表します。

WebGlRenderingContextの取得

webgl.rs
pub fn get_webgl_context(height: u32, width: u32) -> Result<WebGlRenderingContext, String> {
    //Get WebGLContext
    let document = window().unwrap().document().unwrap();
    let canvas = document.get_element_by_id("canvas").ok_or_else(|| String::from("canvas doesn't exist :("))?;
    let canvas: web_sys::HtmlCanvasElement =
        canvas.dyn_into::<web_sys::HtmlCanvasElement>().unwrap();

    canvas.set_height(height);
    canvas.set_width(width);

    let gl: WebGlRenderingContext = canvas
        .get_context("webgl")
        .unwrap()
        .ok_or_else(|| String::from("webgl is not supported in this browser :("))?
        .dyn_into()
        .unwrap();

    //Initialize WebGLContext
    gl.enable(GL::BLEND);
    gl.blend_func(GL::SRC_ALPHA, GL::ONE_MINUS_SRC_ALPHA);
    gl.clear_color(0.0, 0.0, 0.0, 1.0); //RGBA
    gl.clear_depth(1.);

    Ok(gl)
}

第一引数 u32: Canvas height
第二引数 u32: Canvas width
<使用例>

lib.rs
let gl = webgl::get_webgl_context(500, 500).unwrap();

Vertex/Fragment shader をコンパイル

webgl.rs

fn compile_shader(
    gl: &WebGlRenderingContext,
    shader_type: u32,
    source: &str,
) -> Result<WebGlShader, String> {
    let shader = gl
        .create_shader(shader_type)
        .ok_or_else(|| String::from("Error creating shader :("))?;
    gl.shader_source(&shader, source);
    gl.compile_shader(&shader);

    if gl
        .get_shader_parameter(&shader, WebGlRenderingContext::COMPILE_STATUS)
        .as_bool()
        .unwrap_or(false)
    {
        Ok(shader)
    } else {
        Err(gl
            .get_shader_info_log(&shader)
            .unwrap_or_else(|| String::from("Unable to get shader info log :(")))
    }
}

WebGlProgramのリンクの中で使われます。直接いじることはあまりないです。

WebGlProgramをリンク

webgl.rs
pub fn link_program(
    gl: &WebGlRenderingContext,
    vert_source: &str,
    frag_source: &str,
) -> Result<WebGlProgram, String> {
    let program = gl
        .create_program()
        .ok_or_else(|| String::from("Error creating program"))?;

    let vert_shader = compile_shader(&gl, GL::VERTEX_SHADER, vert_source).unwrap();

    let frag_shader = compile_shader(&gl, GL::FRAGMENT_SHADER, frag_source).unwrap();

    gl.attach_shader(&program, &vert_shader);
    gl.attach_shader(&program, &frag_shader);
    gl.link_program(&program);

    if gl
        .get_program_parameter(&program, WebGlRenderingContext::LINK_STATUS)
        .as_bool()
        .unwrap_or(false)
    {
        gl.use_program(Some(&program));
        Ok(program)
    } else {
        Err(gl
            .get_program_info_log(&program)
            .unwrap_or_else(|| String::from("Unknown error creating program object")))
    }
}

第一引数 &WebGlRenderingContext: WebGlRenderingContext
第二引数 &str: Vertex Shader
第三引数 &str: Fragment Shader
<使用例>

lib.rs
//vertex_shader &str, fragment_shader &str
let program = webgl::link_program(
    &gl,
    vertex_shader,
    fragment_shader,
)
    .unwrap();

VertexBufferObjectの作成(固定長配列&[f32]の場合)

webgl.rs
pub fn create_vbo_array(gl: &GL, data: &[f32]) -> Result<WebGlBuffer, String> {
    let vbo = gl.create_buffer().ok_or("Failed to create buffer :(")?;
    gl.bind_buffer(GL::ARRAY_BUFFER, Some(&vbo));
    unsafe {
        let f32_array = js_sys::Float32Array::view(data);
        gl.buffer_data_with_array_buffer_view(GL::ARRAY_BUFFER, &f32_array, GL::STATIC_DRAW)
    }
    gl.bind_buffer(GL::ARRAY_BUFFER, None);

    Ok(vbo)
}

第一引数 &WebGlRenderingContext: WebGlRenderingContext
第二引数 &[f32]: data you want to bind

js_sys::Float32Array::viewですが固定長配列の場合何も考えずただ渡すだけでいいです。Vecの場合多少やっかいです。

<使用例>

lib.rs
//position [f32; N] (0 < N < limit size)
let position_vbo = webgl::create_vbo_array(&gl, &position).unwrap();

VertexBufferObjectの作成(可変長配列Vecの場合)

webgl.rs
pub fn create_vbo_vector(gl: &GL, data: &Vec<f32>) -> Result<WebGlBuffer, String> {
    let vbo = gl.create_buffer().ok_or("Failed to create buffer :(")?;
    gl.bind_buffer(GL::ARRAY_BUFFER, Some(&vbo));
    unsafe {
        let f32_array = js_sys::Float32Array::view(data);
        gl.buffer_data_with_array_buffer_view(GL::ARRAY_BUFFER, &f32_array, GL::STATIC_DRAW)
    }
    gl.bind_buffer(GL::ARRAY_BUFFER, None);

    Ok(vbo)
}

第一引数 &WebGlRenderingContext: WebGlRenderingContext
第二引数 &Vec: data you want to bind

固定長配列のときと違いヒープ領域のポインタの参照を渡します。コンパイル時にはエラーが出ないので要注意です(実行時エラーになる)。
すみません。なんか勘違いしてました。固定長配列と可変長配列はともに同じ関数で処理できます。

<使用例>

lib.rs
//position Vec<f32>
let position_vbo = webgl::create_vbo_vector(&gl, &position).unwrap();

IndexBufferObjectの作成(可変長配列)

Vertex Buffer Objectのバインド関数とほぼ同じです。可変長配列の場合のみ載せておきます。

webgl.rs
pub fn create_ibo_vector(gl: &GL, data: &Vec<u16>) -> Result<WebGlBuffer, String> {
    let ibo = gl.create_buffer().ok_or("Failed to create buffer")?;

    gl.bind_buffer(GL::ELEMENT_ARRAY_BUFFER, Some(&ibo));
    unsafe {
        let ui16_array = js_sys::Uint16Array::view(data);
        gl.buffer_data_with_array_buffer_view(
            GL::ELEMENT_ARRAY_BUFFER,
            &ui16_array,
            GL::STATIC_DRAW,
        );
    }
    gl.bind_buffer(GL::ELEMENT_ARRAY_BUFFER, None);

    Ok(ibo)
}

第一引数 &WebGlRenderingContext: WebGlRenderingContext
第二引数 &Vec: data you want to bind

<使用例>

lib.rs
//index Vec<u16>
let ibo = webgl::create_ibo_vector(&gl, &index).unwrap();

Attribute属性をバインド

webgl.rs
pub fn set_attribute(gl: &GL, vbo: &[WebGlBuffer], att_location: &[u32], att_stride: &[i32]) {
    for i in 0..vbo.len() {
        gl.bind_buffer(GL::ARRAY_BUFFER, Some(&vbo[i]));
        gl.enable_vertex_attrib_array(att_location[i]);
        gl.vertex_attrib_pointer_with_i32(att_location[i], att_stride[i], GL::FLOAT, false, 0, 0);
    }
}

第一引数 &WebGlRendering : WebGlRenderingContext
第二引数 &[WebGlBuffer]: VertexBufferObject
第三引数 &[u32]: Attribute location
第四引数 &[i32]: VertexBufferObject stride

<使用例>

lib.rs
//position Vec<f32>, normal Vec<f32>, color <f32>
let position_vbo = webgl::create_vbo_vector(&gl, &position).unwrap();
let normal_vbo = webgl::create_vbo_vector(&gl, &normal).unwrap();
let color_vbo = webgl::create_vbo_vector(&gl, &color).unwrap();
webgl::set_attribute(
    &gl,
    &[position_vbo, normal_vbo, color_vbo],
    &att_location,
    &att_stride,
);

おまけ .vert/.fragファイルの読み込み

VScodeの拡張機能などを有効にするために.vertファイルや.fragファイルを使いコーディングしたいということはよくあることです。Rustではinclude_str!という便利なマクロがあります。include_str!(path)のpathの部分にファイルのパスを指定すると、そのファイルをコンパイル時に読み込んで&str型に変換してくれます。ファイルが存在しない場合、コンパイルエラーになります。

関連記事

参照文献

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8