📚
WebGLのチュートリアルをRustのWebAssemblyで書く 〜その2〜
Cargo.toml
web-sysのfeaturesのみ抜粋します
...
[dependencies.web-sys]
version = "0.3.60"
features = [
"console",
"Window",
"Document",
"HtmlElement",
"Headers",
"Request",
"RequestInit",
"RequestMode",
"Response",
"HtmlCanvasElement",
"WebGl2RenderingContext",
"WebGlShader",
"WebGlProgram",
"WebGlUniformLocation",
"WebGlBuffer",
"WebGlTexture",
]
rs
GLMのような機能を使うためnalgebra_glmを使います。
use nalgebra_glm as glm;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::WebGl2RenderingContext as GL;
WebGLプログラムを構造体にします。今回使うのはPositionだけですが、後々のためにそのほかもOption型で入れておきます
pub struct ProgramInfo {
program: web_sys::WebGlProgram,
attrib_locations: AttribLocations,
uniform_locations: UniformLocations,
}
struct AttribLocations {
vertex_position: Option<i32>,
vertex_color: Option<i32>,
texture_coord: Option<i32>,
vertex_normal: Option<i32>,
}
struct UniformLocations {
projection_matrix: Option<web_sys::WebGlUniformLocation>,
model_view_matrix: Option<web_sys::WebGlUniformLocation>,
u_sampler: Option<web_sys::WebGlUniformLocation>,
normal_matrix: Option<web_sys::WebGlUniformLocation>,
}
impl ProgramInfo {
pub fn new(gl: &GL, shader_program: web_sys::WebGlProgram) -> Self {
ProgramInfo {
attrib_locations: AttribLocations {
vertex_position: Some(gl.get_attrib_location(&shader_program, "aVertexPosition")),
vertex_normal: Some(gl.get_attrib_location(&shader_program, "aVertexNormal")),
vertex_color: Some(gl.get_attrib_location(&shader_program, "aVertexColor")),
texture_coord: Some(gl.get_attrib_location(&shader_program, "aTextureCoord")),
},
uniform_locations: UniformLocations {
projection_matrix: gl.get_uniform_location(&shader_program, "uProjectionMatrix"),
model_view_matrix: gl.get_uniform_location(&shader_program, "uModelViewMatrix"),
normal_matrix: gl.get_uniform_location(&shader_program, "uNormalMatrix"),
u_sampler: gl.get_uniform_location(&shader_program, "uSampler"),
},
program: shader_program,
}
}
}
同様にWebGLバッファーについても下記の構造体を用意しておきます
struct Buffers {
position: Option<web_sys::WebGlBuffer>,
color: Option<web_sys::WebGlBuffer>,
texture_coord: Option<web_sys::WebGlBuffer>,
indices: Option<web_sys::WebGlBuffer>,
normal: Option<web_sys::WebGlBuffer>,
}
shaderを読み込むのにjsのfetchのような関数を書きました
async fn fetch(url: &str) -> Result<JsValue, JsValue> {
let mut opts = web_sys::RequestInit::new();
opts.method("GET");
opts.mode(web_sys::RequestMode::Cors);
let request = web_sys::Request::new_with_str_and_init(url, &opts)?;
request.headers().set("Accept", "application/text")?;
let window = window()?;
let resp: web_sys::Response = JsFuture::from(window.fetch_with_request(&request))
.await?
.dyn_into()?;
let text = JsFuture::from(resp.text()?).await?;
Ok(text)
}
fn window() -> Result<web_sys::Window, JsValue> {
web_sys::window().ok_or_else(|| JsValue::from_str("no window exists"))
}
シェーダーを書いて静的ファイルとして置いておきます
static/glsl/square.vert
attribute vec4 aVertexPosition;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
void main() {
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
}
static/glsl/square.frag
void main() {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
シェーダーの初期化関数とローダー関数です
fn init_shader_program(
gl: &GL,
vs_source: &str,
fs_source: &str,
) -> Result<web_sys::WebGlProgram, String> {
let vertex_shader = load_shader(gl, GL::VERTEX_SHADER, vs_source)?;
let fragment_shader = load_shader(gl, GL::FRAGMENT_SHADER, fs_source)?;
let shader_program = gl
.create_program()
.ok_or_else(|| String::from("Unknown error creating program object"))?;
gl.attach_shader(&shader_program, &vertex_shader);
gl.attach_shader(&shader_program, &fragment_shader);
gl.link_program(&shader_program);
if gl
.get_program_parameter(&shader_program, GL::LINK_STATUS)
.as_bool()
.unwrap_or(false)
{
Ok(shader_program)
} else {
Err(gl
.get_program_info_log(&shader_program)
.unwrap_or_else(|| String::from("Unknown error creating program object")))
}
}
fn load_shader(gl: &GL, type_: u32, source: &str) -> Result<web_sys::WebGlShader, String> {
let shader = gl
.create_shader(type_)
.ok_or_else(|| String::from("Unable to create shader object"))?;
gl.shader_source(&shader, source);
gl.compile_shader(&shader);
if gl
.get_shader_parameter(&shader, GL::COMPILE_STATUS)
.as_bool()
.unwrap_or(false)
{
Ok(shader)
} else {
Err(gl
.get_shader_info_log(&shader)
.unwrap_or_else(|| String::from("Unknown error creating shader")))
}
}
バッファーの初期化関数です
fn init_buffers(gl: &GL) -> Result<Buffers, JsValue> {
let position_buffer = init_position_buffer(gl)?;
Ok(Buffers {
position: Some(position_buffer),
color: None,
texture_coord: None,
indices: None,
normal: None,
})
}
fn init_position_buffer(gl: &GL) -> Result<web_sys::WebGlBuffer, String> {
let position_buffer = gl
.create_buffer()
.ok_or_else(|| String::from("Failed to create buffer"))?;
gl.bind_buffer(GL::ARRAY_BUFFER, Some(&position_buffer));
let positions = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0];
gl.buffer_data_with_array_buffer_view(
GL::ARRAY_BUFFER,
unsafe { &js_sys::Float32Array::view(&positions) },
GL::STATIC_DRAW,
);
Ok(position_buffer)
}
ドロー関数です
fn draw_scene(
gl: &GL,
program_info: &ProgramInfo,
buffers: &Buffers,
) -> Result<(), String> {
gl.clear_color(0.0, 0.0, 0.0, 1.0);
gl.clear_depth(1.0);
gl.enable(GL::DEPTH_TEST); // Enable depth testing
gl.depth_func(GL::LEQUAL); // Near things obscure far things
gl.clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT);
let canvas = gl.canvas().expect("Unable get canvas");
let canvas = canvas
.dyn_into::<web_sys::HtmlElement>()
.expect("Unable to get canvas");
let field_of_view = (45.0 * std::f32::consts::PI) / 180.0;
let aspect = canvas.client_width() as f32 / canvas.client_height() as f32;
let z_near = 0.1;
let z_far = 100.0;
let projection_matrix = glm::perspective(aspect, field_of_view, z_near, z_far);
let model_view_matrix =
glm::translate(&glm::Mat4::identity(), &glm::TVec3::new(0.0, 0.0, -6.0));
let normal_matrix = glm::inverse(&model_view_matrix);
let normal_matrix = glm::transpose(&normal_matrix);
set_position_attribute(gl, buffers, program_info);
gl.use_program(Some(&program_info.program));
if let Some(projection_matrix_location) = &program_info.uniform_locations.projection_matrix {
gl.uniform_matrix4fv_with_f32_array(
Some(projection_matrix_location),
false,
&projection_matrix.iter().map(|v| *v).collect::<Vec<_>>(),
);
}
if let Some(model_view_matrix_location) = &program_info.uniform_locations.model_view_matrix {
gl.uniform_matrix4fv_with_f32_array(
Some(model_view_matrix_location),
false,
&model_view_matrix.iter().map(|v| *v).collect::<Vec<_>>(),
);
}
if let Some(normal_matrix_location) = &program_info.uniform_locations.normal_matrix {
gl.uniform_matrix4fv_with_f32_array(
Some(normal_matrix_location),
false,
&normal_matrix.iter().map(|v| *v).collect::<Vec<_>>(),
)
}
{
let offset = 0;
let vertex_count = 4;
gl.draw_arrays(GL::TRIANGLE_STRIP, offset, vertex_count);
}
Ok(())
}
fn set_position_attribute(gl: &GL, buffers: &Buffers, program_info: &ProgramInfo) {
let num_components = 2;
//let num_components = 3; // added z-component
let type_ = GL::FLOAT;
let normalize = false;
let stride = 0;
let offset = 0.0;
if let Some(position) = &buffers.position {
gl.bind_buffer(GL::ARRAY_BUFFER, Some(&position));
}
if let Some(vertex_position) = program_info.attrib_locations.vertex_position {
gl.vertex_attrib_pointer_with_f64(
vertex_position as u32,
num_components,
type_,
normalize,
stride,
offset,
);
gl.enable_vertex_attrib_array(vertex_position as u32);
}
}
start関数を修正します
#[wasm_bindgen]
pub async fn start() -> Result<(), JsValue> {
let vs_source = fetch("./static/glsl/square.vert")
.await?
.as_string()
.unwrap();
let fs_source = fetch("./static/glsl/square.frag")
.await?
.as_string()
.unwrap();
let window = window()?;
let document = window
.document()
.ok_or_else(|| JsValue::from_str("should have document"))?;
let app = document
.get_element_by_id("app")
.ok_or_else(|| JsValue::from_str("no #app exists"))?;
let canvas = document
.create_element("canvas")?
.dyn_into::<web_sys::HtmlCanvasElement>()?;
canvas.set_attribute("width", "640")?;
canvas.set_attribute("height", "480")?;
app.append_child(&canvas)?;
let gl = canvas
.get_context("webgl2")?
.ok_or_else(|| JsValue::from_str("fail to get context"))?
.dyn_into::<web_sys::WebGl2RenderingContext>()?;
let shader_program = init_shader_program(&gl, &vs_source, &fs_source)?;
let program_info = ProgramInfo::new(&gl, shader_program);
let buffers = init_buffers(&gl)?;
{
// tutorial 01
// gl.clear_color(0.0, 0.0, 0.0, 1.0);
// gl.clear(GL::COLOR_BUFFER_BIT);
}
draw_scene(&gl, &program_info, &buffers)?;
Ok(())
}
Discussion