#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![warn(clippy::pedantic, clippy::nursery)]
use bevy::{
asset::load_internal_asset,
pbr::{MaterialPipeline, MaterialPipelineKey, StandardMaterialUniform},
prelude::*,
reflect::TypeUuid,
render::{
mesh::MeshVertexBufferLayout,
render_asset::RenderAssets,
render_resource::{
AsBindGroup, AsBindGroupShaderType, Face, RenderPipelineDescriptor, ShaderRef,
ShaderType, SpecializedMeshPipelineError,
},
},
};
#[allow(clippy::unreadable_literal)]
const PARALLAX_MAPPING_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 9592100656503623734);
impl From<&'_ ParallaxMaterial> for StandardMaterial {
fn from(mat: &'_ ParallaxMaterial) -> Self {
let opt_clone_weak = |opt: &Option<_>| opt.as_ref().map(Handle::clone_weak);
Self {
base_color: mat.base_color,
base_color_texture: opt_clone_weak(&mat.base_color_texture),
emissive: mat.emissive,
emissive_texture: opt_clone_weak(&mat.emissive_texture),
perceptual_roughness: mat.perceptual_roughness,
metallic: mat.metallic,
metallic_roughness_texture: opt_clone_weak(&mat.metallic_roughness_texture),
reflectance: mat.reflectance,
normal_map_texture: Some(mat.normal_map_texture.clone_weak()),
flip_normal_map_y: mat.flip_normal_map_y,
occlusion_texture: opt_clone_weak(&mat.occlusion_texture),
double_sided: mat.double_sided,
cull_mode: mat.cull_mode,
unlit: mat.unlit,
alpha_mode: mat.alpha_mode,
depth_bias: mat.depth_bias,
fog_enabled: mat.fog_enabled,
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct ParallaxMaterialKey {
relief_mapping: bool,
cull_mode: Option<Face>,
}
impl From<&'_ ParallaxMaterial> for ParallaxMaterialKey {
fn from(material: &ParallaxMaterial) -> Self {
Self {
relief_mapping: material.algorithm == ParallaxAlgo::ReliefMapping,
cull_mode: material.cull_mode,
}
}
}
#[derive(Clone, Default, ShaderType)]
pub struct ParallaxMaterialUniform {
pub base_color: Vec4,
pub emissive: Vec4,
pub roughness: f32,
pub metallic: f32,
pub reflectance: f32,
pub flags: u32,
pub alpha_cutoff: f32,
pub height_depth: f32,
pub max_height_layers: f32,
}
impl AsBindGroupShaderType<ParallaxMaterialUniform> for ParallaxMaterial {
fn as_bind_group_shader_type(&self, images: &RenderAssets<Image>) -> ParallaxMaterialUniform {
let standard_material: StandardMaterial = self.into();
let standard_uniform: StandardMaterialUniform =
standard_material.as_bind_group_shader_type(images);
ParallaxMaterialUniform {
base_color: standard_uniform.base_color,
emissive: standard_uniform.emissive,
roughness: standard_uniform.roughness,
metallic: standard_uniform.metallic,
reflectance: standard_uniform.reflectance,
flags: standard_uniform.flags,
alpha_cutoff: standard_uniform.alpha_cutoff,
height_depth: self.height_depth,
max_height_layers: self.max_height_layers,
}
}
}
#[derive(AsBindGroup, Debug, Clone, TypeUuid, Reflect, FromReflect)]
#[uuid = "5bc9c7a3-fb25-4202-b91f-bc4c7d300d82"]
#[bind_group_data(ParallaxMaterialKey)]
#[uniform(0, ParallaxMaterialUniform)]
#[reflect(Default, Debug)]
pub struct ParallaxMaterial {
pub base_color: Color,
#[texture(1)]
#[sampler(2)]
pub base_color_texture: Option<Handle<Image>>,
pub emissive: Color,
#[texture(3)]
#[sampler(4)]
pub emissive_texture: Option<Handle<Image>>,
pub perceptual_roughness: f32,
pub metallic: f32,
#[texture(5)]
#[sampler(6)]
pub metallic_roughness_texture: Option<Handle<Image>>,
pub reflectance: f32,
#[texture(9)]
#[sampler(10)]
pub normal_map_texture: Handle<Image>,
pub flip_normal_map_y: bool,
#[texture(7)]
#[sampler(8)]
pub occlusion_texture: Option<Handle<Image>>,
pub double_sided: bool,
#[reflect(ignore)]
pub cull_mode: Option<Face>,
pub unlit: bool,
pub alpha_mode: AlphaMode,
pub depth_bias: f32,
#[texture(11)]
#[sampler(12)]
pub height_map: Handle<Image>,
pub height_depth: f32,
pub algorithm: ParallaxAlgo,
pub max_height_layers: f32,
pub fog_enabled: bool,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Reflect, FromReflect)]
#[reflect(Default, Debug)]
pub enum ParallaxAlgo {
#[default]
ParallaxOcclusionMapping,
ReliefMapping,
}
impl Default for ParallaxMaterial {
fn default() -> Self {
Self {
base_color: Color::rgb(1.0, 1.0, 1.0),
base_color_texture: None,
emissive: Color::BLACK,
emissive_texture: None,
perceptual_roughness: 0.089,
metallic: 0.01,
metallic_roughness_texture: None,
reflectance: 0.5,
occlusion_texture: None,
normal_map_texture: default(),
flip_normal_map_y: false,
double_sided: false,
cull_mode: Some(Face::Back),
unlit: false,
alpha_mode: AlphaMode::Opaque,
depth_bias: 0.0,
height_map: default(),
height_depth: 0.1,
max_height_layers: 16.0,
algorithm: default(),
fog_enabled: true,
}
}
}
impl Material for ParallaxMaterial {
fn specialize(
_pipeline: &MaterialPipeline<Self>,
descriptor: &mut RenderPipelineDescriptor,
_layout: &MeshVertexBufferLayout,
key: MaterialPipelineKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
let defs = &mut descriptor.fragment.as_mut().unwrap().shader_defs;
if key.bind_group_data.relief_mapping {
defs.push("RELIEF_MAPPING".into());
}
descriptor.primitive.cull_mode = key.bind_group_data.cull_mode;
if let Some(label) = &mut descriptor.label {
*label = format!("parallax_{}", *label).into();
}
Ok(())
}
#[cfg(not(feature = "debug"))]
fn fragment_shader() -> ShaderRef {
PARALLAX_MAPPING_SHADER_HANDLE.typed::<Shader>().into()
}
#[cfg(feature = "debug")]
fn fragment_shader() -> ShaderRef {
"parallax_map.wgsl".into()
}
#[inline]
fn alpha_mode(&self) -> AlphaMode {
self.alpha_mode
}
#[inline]
fn depth_bias(&self) -> f32 {
self.depth_bias
}
}
pub struct ParallaxMaterialPlugin;
impl Plugin for ParallaxMaterialPlugin {
fn build(&self, app: &mut App) {
#[cfg(not(feature = "debug"))]
load_internal_asset!(
app,
PARALLAX_MAPPING_SHADER_HANDLE,
"parallax_map.wgsl",
Shader::from_wgsl
);
app.add_plugin(MaterialPlugin::<ParallaxMaterial>::default());
app.register_type::<ParallaxMaterial>()
.register_type::<ParallaxAlgo>();
}
}