RustとWebGL が少しずつ分かってきたので記事にしようと思います。今回はRustのクレートである、web_sysを主に使ったWebGLContextの操作がテーマです。慣れるまではかなり苦労しますが、公式ドキュメントを見ながらやりましょう。
前提条件
- WebGLの基礎
- Rustの基礎
- JavaScriptの組み込み関数の基礎
- 気合い
[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',
]
use wasm_bindgen::JsCast;
use web_sys::WebGlRenderingContext as GL;
use web_sys::*;
なお、<使用例>に出てくる glはweb_sys::WebGlRenderingContextを表します。
WebGlRenderingContextの取得
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
<使用例>
let gl = webgl::get_webgl_context(500, 500).unwrap();
Vertex/Fragment shader をコンパイル
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をリンク
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
<使用例>
//vertex_shader &str, fragment_shader &str
let program = webgl::link_program(
&gl,
vertex_shader,
fragment_shader,
)
.unwrap();
VertexBufferObjectの作成(固定長配列&[f32]の場合)
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の場合多少やっかいです。
<使用例>
//position [f32; N] (0 < N < limit size)
let position_vbo = webgl::create_vbo_array(&gl, &position).unwrap();
VertexBufferObjectの作成(可変長配列Vecの場合)
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
固定長配列のときと違いヒープ領域のポインタの参照を渡します。コンパイル時にはエラーが出ないので要注意です(実行時エラーになる)。
すみません。なんか勘違いしてました。固定長配列と可変長配列はともに同じ関数で処理できます。
<使用例>
//position Vec<f32>
let position_vbo = webgl::create_vbo_vector(&gl, &position).unwrap();
IndexBufferObjectの作成(可変長配列)
Vertex Buffer Objectのバインド関数とほぼ同じです。可変長配列の場合のみ載せておきます。
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
<使用例>
//index Vec<u16>
let ibo = webgl::create_ibo_vector(&gl, &index).unwrap();
Attribute属性をバインド
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
<使用例>
//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型に変換してくれます。ファイルが存在しない場合、コンパイルエラーになります。