React + Rust + Wasm: Render a 3D Scene

Neo Quest | Nikhil Gupta

Neo Quest | Nikhil Gupta / November 20, 2022

2 min read

Summary

In this article, we will use Bevy game engine in our React application from our Rust WASM library to render a 3D scene. We will build on the previous tutorial available here.

Run a Bevy App using Rust

Last time, we used a setup function with our Bevy app to draw a 2D image. Now, let's modify our lib.rs to create another setup function that will render a 3D scene.

Update the setup function

#[wasm_bindgen]
pub fn run_bevy_app() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup_3d) # Defined below
        .run();
}

Define setup_3d

fn setup_3d(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    # ...
}

Add a plane

fn setup_3d(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    commands.spawn(PbrBundle {
        mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
        material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
        ..default()
    });
}

Add a cube

fn setup_3d(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    # ...
    commands.spawn(PbrBundle {
        mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
        material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
        transform: Transform::from_xyz(0.0, 0.5, 0.0),
        ..default()
    });
}

Add some light

fn setup_3d(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    # ...
    commands.spawn(PointLightBundle {
        point_light: PointLight {
            intensity: 1500.0,
            shadows_enabled: true,
            ..default()
        },
        transform: Transform::from_xyz(4.0, 8.0, 4.0),
        ..default()
    });
}

Position the camera

fn setup_3d(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    # ...
    commands.spawn(Camera3dBundle {
        transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
        ..default()
    });
}

Build the new wasm library

Let's run wasm-pack again to build the updated library

wasm-pack build --target web

Call the function from the demo app

Finally, let's add a button to load the wasm and call run_bevy_app function from our App.ts file like so:

// App.ts

import init, { run_bevy_app } from "rust-wasm-lib";
import './App.css';

function App() {
  const runBevyApp = async () => {
    await init();
    run_bevy_app();
  };
  
  return (
    <div className="App">
      <button onClick={runBevyApp}>Run Bevy App</button>
    </div>
  );
}

export default App;

Now, if you run the updated app and click on Run Bevy App, you should see a 3d scene :)

If you liked this article, subscribe here to get the complete code and updates for the entire collection: Rust & Wasm.