Skip to main content

Custom Materials

bevy_cef supports custom materials through Bevy's MaterialExtension system, letting you apply custom shaders to your webview textures. This is useful for effects like masks, distortions, or any post-processing on the rendered web content.

Setting Up the Plugin​

Register WebviewExtendMaterialPlugin with your custom extension type:

use bevy::prelude::*;
use bevy_cef::prelude::*;

fn main() {
App::new()
.add_plugins((
DefaultPlugins,
CefPlugin::default(),
WebviewExtendMaterialPlugin::<CustomExtension>::default(),
))
.add_systems(Startup, (spawn_camera, spawn_webview))
.run();
}

Defining a Material Extension​

Create a struct that implements MaterialExtension, AsBindGroup, Asset, and Reflect:

#[derive(Asset, Reflect, Clone, Debug, AsBindGroup, Default)]
struct CustomExtension {
#[texture(0)]
#[sampler(1)]
mask: Handle<Image>,
}

impl MaterialExtension for CustomExtension {
fn fragment_shader() -> ShaderRef {
"shaders/custom_material.wesl".into()
}
}

Writing the Shader​

Place your WESL shader in the assets/shaders/ directory. The webview texture is sampled with surface_color(uv) from the bevy_cef::webview::util module, and your custom bindings (like the mask texture above) are accessible at the binding indices you specified:

import bevy_pbr::render::forward_io::VertexOutput;
import bevy_cef::webview::util::surface_color;

@group(constants::MATERIAL_BIND_GROUP) @binding(0) var mask_texture: texture_2d<f32>;
@group(constants::MATERIAL_BIND_GROUP) @binding(1) var mask_sampler: sampler;

@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
let mask = textureSample(mask_texture, mask_sampler, in.uv);
// Apply mask alpha to the webview color
return vec4<f32>(surface_color(in.uv).rgb, mask.a);
}

Spawning with the Custom Material​

Use WebviewExtendedMaterial<CustomExtension> as the material type:

fn spawn_webview(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<WebviewExtendedMaterial<CustomExtension>>>,
asset_server: Res<AssetServer>,
) {
commands.spawn((
WebviewSource::new("https://example.com"),
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
MeshMaterial3d(materials.add(WebviewExtendedMaterial {
extension: CustomExtension {
mask: asset_server.load("textures/mask.png"),
},
..default()
})),
));
}
note

WebviewExtendStandardMaterial (used in the getting-started guide) is actually WebviewExtendedMaterial<()> -- the default case with no custom extension.