mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Add WebGL example (#908)
This commit is contained in:
parent
92aab6c97c
commit
90a7cdb9b5
@ -24,4 +24,5 @@ members = [
|
||||
"todomvc",
|
||||
"two_apps",
|
||||
"pub_sub",
|
||||
"webgl"
|
||||
]
|
||||
|
||||
10
examples/webgl/Cargo.toml
Normal file
10
examples/webgl/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "webgl"
|
||||
version = "0.1.0"
|
||||
authors = ["Miklós Tusz <mdtusz@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
stdweb = "0.4.20"
|
||||
yew = { path = "../.." }
|
||||
webgl_stdweb = "0.3.0"
|
||||
5
examples/webgl/README.md
Normal file
5
examples/webgl/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
## Yew WebGL Demo
|
||||
|
||||
This is a simple demo using WebGL with Yew to initialize the GL context, create a render loop, and draw to the canvas with basic shaders.
|
||||
|
||||
The `stdweb` and `webgl_stdweb` crates are required.
|
||||
11
examples/webgl/src/basic.frag
Normal file
11
examples/webgl/src/basic.frag
Normal file
@ -0,0 +1,11 @@
|
||||
precision mediump float;
|
||||
|
||||
uniform float u_time;
|
||||
|
||||
void main() {
|
||||
float r = sin(u_time * 0.0003);
|
||||
float g = sin(u_time * 0.0005);
|
||||
float b = sin(u_time * 0.0007);
|
||||
|
||||
gl_FragColor = vec4(r, g, b, 1.0);
|
||||
}
|
||||
7
examples/webgl/src/basic.vert
Normal file
7
examples/webgl/src/basic.vert
Normal file
@ -0,0 +1,7 @@
|
||||
precision mediump float;
|
||||
|
||||
attribute vec2 a_position;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
}
|
||||
134
examples/webgl/src/lib.rs
Normal file
134
examples/webgl/src/lib.rs
Normal file
@ -0,0 +1,134 @@
|
||||
#![recursion_limit = "512"]
|
||||
extern crate stdweb;
|
||||
|
||||
use stdweb::unstable::TryInto;
|
||||
use stdweb::web::html_element::CanvasElement;
|
||||
use stdweb::web::TypedArray;
|
||||
use webgl_stdweb::{GLfloat, GLuint, WebGLRenderingContext as GL};
|
||||
use yew::services::{RenderService, Task};
|
||||
use yew::{html, Component, ComponentLink, Html, NodeRef, ShouldRender};
|
||||
|
||||
pub struct Model {
|
||||
canvas: Option<CanvasElement>,
|
||||
gl: Option<GL>,
|
||||
link: ComponentLink<Self>,
|
||||
node_ref: NodeRef,
|
||||
render_loop: Option<Box<dyn Task>>,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Render(f64),
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
Model {
|
||||
canvas: None,
|
||||
gl: None,
|
||||
link: link,
|
||||
node_ref: NodeRef::default(),
|
||||
render_loop: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn mounted(&mut self) -> ShouldRender {
|
||||
// Once mounted, store references for the canvas and GL context. These can be used for
|
||||
// resizing the rendering area when the window or canvas element are resized, as well as
|
||||
// for making GL calls.
|
||||
let c: CanvasElement = self.node_ref.get().unwrap().try_into().unwrap();
|
||||
let gl: GL = c.get_context().expect("WebGL not supported!");
|
||||
|
||||
self.canvas = Some(c);
|
||||
self.gl = Some(gl);
|
||||
|
||||
// In a more complex use-case, there will be additional WebGL initialization that should be
|
||||
// done here, such as enabling or disabling depth testing, depth functions, face
|
||||
// culling etc.
|
||||
|
||||
// The callback to request animation frame is passed a time value which can be used for
|
||||
// rendering motion independent of the framerate which may vary.
|
||||
let render_frame = self.link.callback(|time: f64| Msg::Render(time));
|
||||
let handle = RenderService::new().request_animation_frame(render_frame);
|
||||
|
||||
// A reference to the handle must be stored, otherwise it is dropped and the render won't
|
||||
// occur.
|
||||
self.render_loop = Some(Box::new(handle));
|
||||
|
||||
// Since WebGL is rendered to the canvas "separate" from the DOM, there is no need to
|
||||
// render the DOM element(s) again.
|
||||
false
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Render(timestamp) => {
|
||||
// Render functions are likely to get quite large, so it is good practice to split
|
||||
// it into it's own function rather than keeping it inline in the update match
|
||||
// case. This also allows for updating other UI elements that may be rendered in
|
||||
// the DOM like a framerate counter, or other overlaid textual elements.
|
||||
self.render_gl(timestamp);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<canvas ref={self.node_ref.clone()} />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn render_gl(&mut self, timestamp: f64) {
|
||||
let gl = self.gl.as_ref().expect("GL Context not initialized!");
|
||||
|
||||
let vert_code = include_str!("./basic.vert");
|
||||
let frag_code = include_str!("./basic.frag");
|
||||
|
||||
// This list of vertices will draw two triangles to cover the entire canvas.
|
||||
let vertices: Vec<f32> = vec![
|
||||
-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0,
|
||||
];
|
||||
let vertex_buffer = gl.create_buffer().unwrap();
|
||||
let verts = TypedArray::<f32>::from(vertices.as_slice());
|
||||
|
||||
gl.bind_buffer(GL::ARRAY_BUFFER, Some(&vertex_buffer));
|
||||
gl.buffer_data_1(GL::ARRAY_BUFFER, Some(&verts.buffer()), GL::STATIC_DRAW);
|
||||
|
||||
let vert_shader = gl.create_shader(GL::VERTEX_SHADER).unwrap();
|
||||
gl.shader_source(&vert_shader, &vert_code);
|
||||
gl.compile_shader(&vert_shader);
|
||||
|
||||
let frag_shader = gl.create_shader(GL::FRAGMENT_SHADER).unwrap();
|
||||
gl.shader_source(&frag_shader, &frag_code);
|
||||
gl.compile_shader(&frag_shader);
|
||||
|
||||
let shader_program = gl.create_program().unwrap();
|
||||
gl.attach_shader(&shader_program, &vert_shader);
|
||||
gl.attach_shader(&shader_program, &frag_shader);
|
||||
gl.link_program(&shader_program);
|
||||
|
||||
gl.use_program(Some(&shader_program));
|
||||
|
||||
// Attach the position vector as an attribute for the GL context.
|
||||
let position = gl.get_attrib_location(&shader_program, "a_position") as GLuint;
|
||||
gl.vertex_attrib_pointer(position, 2, GL::FLOAT, false, 0, 0);
|
||||
gl.enable_vertex_attrib_array(position);
|
||||
|
||||
// Attach the time as a uniform for the GL context.
|
||||
let time = gl.get_uniform_location(&shader_program, "u_time");
|
||||
gl.uniform1f(time.as_ref(), timestamp as GLfloat);
|
||||
|
||||
gl.draw_arrays(GL::TRIANGLES, 0, 6);
|
||||
|
||||
let render_frame = self.link.callback(|time: f64| Msg::Render(time));
|
||||
let handle = RenderService::new().request_animation_frame(render_frame);
|
||||
|
||||
// A reference to the new handle must be retained for the next render to run.
|
||||
self.render_loop = Some(Box::new(handle));
|
||||
}
|
||||
}
|
||||
3
examples/webgl/src/main.rs
Normal file
3
examples/webgl/src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
yew::start_app::<webgl::Model>();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user