Skip to content

Instantly share code, notes, and snippets.

@ethereumdegen
Created February 19, 2024 21:19
Show Gist options
  • Save ethereumdegen/cd7da0e1c7d227864b45ff2b028fac2b to your computer and use it in GitHub Desktop.
Save ethereumdegen/cd7da0e1c7d227864b45ff2b028fac2b to your computer and use it in GitHub Desktop.
Bevy 0.12 example for an animated material shader
use bevy::prelude::*;
use bevy::reflect::{TypePath, TypeUuid};
use bevy::render::render_resource::*;
use bevy::pbr::MaterialExtension;
#[derive(Clone, ShaderType ,Debug)]
pub struct CustomMaterialUniforms {
pub distortion_speed_x: f32,
pub distortion_speed_y: f32,
pub scroll_repeats_x: f32 ,
pub scroll_repeats_y: f32 ,
pub scroll_speed_x: f32,
pub scroll_speed_y: f32,
pub distortion_amount: f32 ,
pub distortion_cutoff: f32
}
impl Default for CustomMaterialUniforms{
fn default() -> Self{
Self{
scroll_speed_x : 0.1,
scroll_speed_y : 1.0,
distortion_speed_x: 3.0,
distortion_speed_y: 1.0,
distortion_amount: 0.03,
distortion_cutoff: 1.0,
scroll_repeats_x: 12.0,
scroll_repeats_y: 3.0,
}
}
}
#[derive(Asset, AsBindGroup, TypePath, Debug, Clone,Default )]
pub struct ScrollingMaterial {
// We need to ensure that the bindings of the base material and the extension do not conflict,
// so we start from binding slot 100, leaving slots 0-99 for the base material.
#[uniform(20)]
pub custom_uniforms: CustomMaterialUniforms,
#[texture(21)]
#[sampler(22)]
pub base_color_texture: Option<Handle<Image>>,
}
impl MaterialExtension for ScrollingMaterial {
fn fragment_shader() -> ShaderRef {
"shaders/scrolling.wgsl".into()
}
fn deferred_fragment_shader() -> ShaderRef {
"shaders/scrolling.wgsl".into()
}
}
//! This example demonstrates the built-in 3d shapes in Bevy.
//! The scene includes a patterned texture and a rotation for visualizing the normals and UVs.
use std::f32::consts::PI;
use bevy::{
prelude::*,
render::render_resource::{Extent3d, TextureDimension, TextureFormat}, gltf::GltfMesh,
};
use bevy::gltf::Gltf;
use bevy::core_pipeline::bloom::BloomSettings;
use crate::custom_material::{ CustomMaterialUniforms};
use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::pbr::ExtendedMaterial;
use bevy::pbr::OpaqueRendererMethod;
pub type AnimatedMaterial = ExtendedMaterial<StandardMaterial,custom_material::ScrollingMaterial>;
pub type CustomPbrBundle = MaterialMeshBundle<AnimatedMaterial>;
mod custom_material;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
.add_plugins(bevy_obj::ObjPlugin)
.add_plugins(MaterialPlugin::<AnimatedMaterial>::default())
.add_systems(Startup, setup)
.add_systems(Update, rotate)
.insert_resource( AssetHandlesResource::default() )
.run();
}
#[derive(Resource,Default)]
pub struct AssetHandlesResource {
bullet_mesh: Handle<Mesh>,
anim_material: Handle<AnimatedMaterial>
}
fn setup(
mut commands: Commands,
mut asset_server: ResMut< AssetServer>,
mut asset_handles_resource: ResMut<AssetHandlesResource>,
mut meshes: ResMut<Assets<Mesh>>,
mut images: ResMut<Assets<Image>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut custom_materials: ResMut<Assets<AnimatedMaterial>>,
) {
let magic_texture = asset_server.load("textures/fire_01.png");
asset_handles_resource.bullet_mesh = asset_server.load("meshes/projectile.obj");
let base_color = Color::PURPLE.set_a(0.4).clone();
asset_handles_resource.anim_material = custom_materials.add(ExtendedMaterial {
base: StandardMaterial {
base_color ,
emissive: Color::rgb_linear(50.2, 1.2, 0.8),
// can be used in forward or deferred mode.
opaque_render_method: OpaqueRendererMethod::Auto,
alpha_mode: AlphaMode::Multiply,
..Default::default()
},
extension:custom_material::ScrollingMaterial {
base_color_texture: Some( magic_texture ),
custom_uniforms: CustomMaterialUniforms{
scroll_speed_x : 0.1,
scroll_speed_y : 1.0,
distortion_speed_x: 3.0,
distortion_speed_y: 1.0,
distortion_amount: 0.03,
distortion_cutoff: 1.0,
scroll_repeats_x: 12.0,
scroll_repeats_y: 3.0,
..default()
},
..default()
},
});
let bullet_mesh_handle = &asset_handles_resource.bullet_mesh;
let anim_mat_handle = &asset_handles_resource.anim_material;
commands.spawn((
CustomPbrBundle {
mesh: bullet_mesh_handle.clone(),
material: anim_mat_handle.clone(),
transform: Transform::from_xyz(
3.0,
2.0,
0.0,
)
.with_rotation(Quat::from_rotation_x(-PI / 5.)),
..default()
},
bevy::pbr::NotShadowCaster
));
//custom_materials.add();
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 9000.0,
range: 100.,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(8.0, 16.0, 8.0),
..default()
});
// ground plane
commands.spawn(PbrBundle {
mesh: meshes.add(shape::Plane::from_size(50.0).into()),
material: materials.add(Color::SILVER.into()),
..default()
});
commands.spawn((
Camera3dBundle {
camera: Camera {
hdr: true, // 1. HDR must be enabled on the camera
..default()
},
tonemapping: Tonemapping::TonyMcMapface,
transform: Transform::from_xyz(0.0, 6., 12.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y),
..default()
},
BloomSettings::default(), // 2. Enable bloom for the camera
));
}
fn rotate(mut query: Query<&mut Transform , With<Handle<Mesh>>>, time: Res<Time>) {
for mut transform in &mut query {
transform.rotate_y(time.delta_seconds() / 2.);
}
}
//see bindings in terrain_material.rs
#import bevy_pbr::{
mesh_view_bindings::globals,
forward_io::{VertexOutput, FragmentOutput},
pbr_fragment::pbr_input_from_standard_material,
pbr_functions::{alpha_discard, apply_pbr_lighting, main_pass_post_lighting_processing},
pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT,
pbr_deferred_functions::deferred_output,
}
struct StandardMaterial {
time: f32,
base_color: vec4<f32>,
emissive: vec4<f32>,
perceptual_roughness: f32,
metallic: f32,
reflectance: f32,
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
flags: u32,
alpha_cutoff: f32,
};
struct CustomMaterialUniforms {
distortion_speed_x: f32 ,
distortion_speed_y: f32 ,
scroll_repeats_x: f32 ,
scroll_repeats_y: f32 ,
scroll_speed_x: f32,
scroll_speed_y: f32,
distortion_amount: f32 ,
distortion_cutoff: f32 ,
};
@group(1) @binding(20)
var<uniform> custom_uniforms: CustomMaterialUniforms;
@group(1) @binding(21)
var base_color_texture: texture_2d<f32>;
@group(1) @binding(22)
var base_color_sampler: sampler;
fn get_repeated_uv_coords(coords: vec2<f32>) -> vec2<f32> {
let repeated_coords = vec2<f32>(
(coords.x % (1. / f32(custom_uniforms.scroll_repeats_x))) * f32(custom_uniforms.scroll_repeats_x),
(coords.y % (1. / f32(custom_uniforms.scroll_repeats_y))) * f32(custom_uniforms.scroll_repeats_y)
);
return repeated_coords;
}
//should consider adding vertex painting to this .. need another binding of course.. performs a color shift
@fragment
fn fragment(
mesh: VertexOutput,
@builtin(front_facing) is_front: bool,
) -> FragmentOutput {
let scroll_amount_x = (globals.time * custom_uniforms.scroll_speed_x) ;
let scroll_amount_y = (globals.time * custom_uniforms.scroll_speed_y) ;
//make the cutoff big and it wont have any effect
let distortion_radians_x = 6.28 * (globals.time * custom_uniforms.distortion_speed_x % mesh.uv[0]) ;
let distortion_amount_x = ( sin(distortion_radians_x) * custom_uniforms.distortion_amount ) % custom_uniforms.distortion_cutoff ;
let distortion_radians_y = 6.28 * (globals.time * custom_uniforms.distortion_speed_y % mesh.uv[1]) ;
let distortion_amount_y = ( cos(distortion_radians_y) * custom_uniforms.distortion_amount ) % custom_uniforms.distortion_cutoff ;
let tiled_uv = get_repeated_uv_coords (mesh.uv + vec2(scroll_amount_x,scroll_amount_y) )
+ vec2( distortion_amount_x, distortion_amount_y ) ;
//this technique lets us use 255 total textures BUT we can only layer 2 at a time.
let color_from_texture_0 = textureSample(base_color_texture, base_color_sampler, tiled_uv );
let blended_color = color_from_texture_0 ;
// generate a PbrInput struct from the StandardMaterial bindings
var pbr_input = pbr_input_from_standard_material(mesh, is_front);
//hack the material (StandardMaterialUniform) so the color is from the terrain splat
// alpha discard
pbr_input.material.base_color = pbr_input.material.base_color * blended_color ;
var final_color = alpha_discard(pbr_input.material, pbr_input.material.base_color ) ;
var pbr_out: FragmentOutput;
//only apply lighting if bit is set
if ((pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
// pbr_input.material.base_color = blended_color;
pbr_out.color = apply_pbr_lighting(pbr_input);
pbr_out.color = main_pass_post_lighting_processing(pbr_input, pbr_out.color);
final_color = pbr_out.color;
}
// -----
pbr_out.color = final_color;
// pbr_out.emissive = pbr_input.material.emissive;
// if (final_color.a < 0.1) { // Use your threshold value here
// discard;
// }
return pbr_out;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment