1
- use bevy_reflect:: { Reflect , TypeUuid } ;
2
- use bevy_render2:: color:: Color ;
1
+ use bevy_app:: { App , CoreStage , EventReader , Plugin } ;
2
+ use bevy_asset:: { AddAsset , AssetEvent , Assets , Handle } ;
3
+ use bevy_ecs:: prelude:: * ;
4
+ use bevy_math:: Vec4 ;
5
+ use bevy_reflect:: TypeUuid ;
6
+ use bevy_render2:: {
7
+ color:: Color ,
8
+ render_command:: RenderCommandQueue ,
9
+ render_resource:: { BufferId , BufferInfo , BufferUsage } ,
10
+ renderer:: { RenderResourceContext , RenderResources } ,
11
+ } ;
12
+ use bevy_utils:: HashSet ;
13
+ use crevice:: std140:: { AsStd140 , Std140 } ;
3
14
4
- #[ derive( Debug , Default , Clone , TypeUuid , Reflect ) ]
15
+ // TODO: this shouldn't live in the StandardMaterial type
16
+ #[ derive( Debug , Clone , Copy ) ]
17
+ pub struct StandardMaterialGpuData {
18
+ pub buffer : BufferId ,
19
+ }
20
+
21
+ /// A material with "standard" properties used in PBR lighting
22
+ /// Standard property values with pictures here https://google.github.io/filament/Material%20Properties.pdf
23
+ #[ derive( Debug , Clone , TypeUuid ) ]
5
24
#[ uuid = "7494888b-c082-457b-aacf-517228cc0c22" ]
6
25
pub struct StandardMaterial {
26
+ /// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
27
+ /// in between.
7
28
pub color : Color ,
29
+ /// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
30
+ /// Defaults to minimum of 0.089
31
+ pub roughness : f32 ,
32
+ /// From [0.0, 1.0], dielectric to pure metallic
33
+ pub metallic : f32 ,
34
+ /// Specular intensity for non-metals on a linear scale of [0.0, 1.0]
35
+ /// defaults to 0.5 which is mapped to 4% reflectance in the shader
36
+ pub reflectance : f32 ,
37
+ // Use a color for user friendliness even though we technically don't use the alpha channel
38
+ // Might be used in the future for exposure correction in HDR
39
+ pub emissive : Color ,
40
+ pub gpu_data : Option < StandardMaterialGpuData > ,
41
+ }
42
+
43
+ impl StandardMaterial {
44
+ pub fn gpu_data ( & self ) -> Option < & StandardMaterialGpuData > {
45
+ self . gpu_data . as_ref ( )
46
+ }
47
+ }
48
+
49
+ impl Default for StandardMaterial {
50
+ fn default ( ) -> Self {
51
+ StandardMaterial {
52
+ color : Color :: rgb ( 1.0 , 1.0 , 1.0 ) ,
53
+ // This is the minimum the roughness is clamped to in shader code
54
+ // See https://google.github.io/filament/Filament.html#materialsystem/parameterization/
55
+ // It's the minimum floating point value that won't be rounded down to 0 in the
56
+ // calculations used. Although technically for 32-bit floats, 0.045 could be
57
+ // used.
58
+ roughness : 0.089 ,
59
+ // Few materials are purely dielectric or metallic
60
+ // This is just a default for mostly-dielectric
61
+ metallic : 0.01 ,
62
+ // Minimum real-world reflectance is 2%, most materials between 2-5%
63
+ // Expressed in a linear scale and equivalent to 4% reflectance see https://google.github.io/filament/Material%20Properties.pdf
64
+ reflectance : 0.5 ,
65
+ emissive : Color :: BLACK ,
66
+ gpu_data : None ,
67
+ }
68
+ }
8
69
}
9
70
10
71
impl From < Color > for StandardMaterial {
@@ -15,3 +76,112 @@ impl From<Color> for StandardMaterial {
15
76
}
16
77
}
17
78
}
79
+
80
+ #[ derive( Clone , AsStd140 ) ]
81
+ pub struct StandardMaterialUniformData {
82
+ /// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
83
+ /// in between.
84
+ pub color : Vec4 ,
85
+ /// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
86
+ /// Defaults to minimum of 0.089
87
+ pub roughness : f32 ,
88
+ /// From [0.0, 1.0], dielectric to pure metallic
89
+ pub metallic : f32 ,
90
+ /// Specular intensity for non-metals on a linear scale of [0.0, 1.0]
91
+ /// defaults to 0.5 which is mapped to 4% reflectance in the shader
92
+ pub reflectance : f32 ,
93
+ // Use a color for user friendliness even though we technically don't use the alpha channel
94
+ // Might be used in the future for exposure correction in HDR
95
+ pub emissive : Vec4 ,
96
+ }
97
+
98
+ pub struct StandardMaterialPlugin ;
99
+
100
+ impl Plugin for StandardMaterialPlugin {
101
+ fn build ( & self , app : & mut App ) {
102
+ app. add_asset :: < StandardMaterial > ( ) . add_system_to_stage (
103
+ CoreStage :: PostUpdate ,
104
+ standard_material_resource_system. system ( ) ,
105
+ ) ;
106
+ }
107
+ }
108
+
109
+ pub fn standard_material_resource_system (
110
+ render_resource_context : Res < RenderResources > ,
111
+ mut render_command_queue : ResMut < RenderCommandQueue > ,
112
+ mut materials : ResMut < Assets < StandardMaterial > > ,
113
+ mut material_events : EventReader < AssetEvent < StandardMaterial > > ,
114
+ ) {
115
+ let mut changed_materials = HashSet :: default ( ) ;
116
+ let render_resource_context = & * * render_resource_context;
117
+ for event in material_events. iter ( ) {
118
+ match event {
119
+ AssetEvent :: Created { ref handle } => {
120
+ changed_materials. insert ( handle. clone_weak ( ) ) ;
121
+ }
122
+ AssetEvent :: Modified { ref handle } => {
123
+ changed_materials. insert ( handle. clone_weak ( ) ) ;
124
+ // TODO: uncomment this to support mutated materials
125
+ // remove_current_material_resources(render_resource_context, handle, &mut materials);
126
+ }
127
+ AssetEvent :: Removed { ref handle } => {
128
+ remove_current_material_resources ( render_resource_context, handle, & mut materials) ;
129
+ // if material was modified and removed in the same update, ignore the modification
130
+ // events are ordered so future modification events are ok
131
+ changed_materials. remove ( handle) ;
132
+ }
133
+ }
134
+ }
135
+
136
+ // update changed material data
137
+ for changed_material_handle in changed_materials. iter ( ) {
138
+ if let Some ( material) = materials. get_mut ( changed_material_handle) {
139
+ // TODO: this avoids creating new materials each frame because storing gpu data in the material flags it as
140
+ // modified. this prevents hot reloading and therefore can't be used in an actual impl.
141
+ if material. gpu_data . is_some ( ) {
142
+ continue ;
143
+ }
144
+
145
+ let value = StandardMaterialUniformData {
146
+ color : material. color . into ( ) ,
147
+ roughness : material. roughness ,
148
+ metallic : material. metallic ,
149
+ reflectance : material. reflectance ,
150
+ emissive : material. emissive . into ( ) ,
151
+ } ;
152
+ let value_std140 = value. as_std140 ( ) ;
153
+
154
+ let size = StandardMaterialUniformData :: std140_size_static ( ) ;
155
+
156
+ let staging_buffer = render_resource_context. create_buffer_with_data (
157
+ BufferInfo {
158
+ size,
159
+ buffer_usage : BufferUsage :: COPY_SRC | BufferUsage :: MAP_WRITE ,
160
+ mapped_at_creation : true ,
161
+ } ,
162
+ value_std140. as_bytes ( ) ,
163
+ ) ;
164
+
165
+ let buffer = render_resource_context. create_buffer ( BufferInfo {
166
+ size,
167
+ buffer_usage : BufferUsage :: COPY_DST | BufferUsage :: UNIFORM ,
168
+ mapped_at_creation : false ,
169
+ } ) ;
170
+
171
+ render_command_queue. copy_buffer_to_buffer ( staging_buffer, 0 , buffer, 0 , size as u64 ) ;
172
+ render_command_queue. free_buffer ( staging_buffer) ;
173
+
174
+ material. gpu_data = Some ( StandardMaterialGpuData { buffer } ) ;
175
+ }
176
+ }
177
+ }
178
+
179
+ fn remove_current_material_resources (
180
+ render_resource_context : & dyn RenderResourceContext ,
181
+ handle : & Handle < StandardMaterial > ,
182
+ materials : & mut Assets < StandardMaterial > ,
183
+ ) {
184
+ if let Some ( gpu_data) = materials. get_mut ( handle) . and_then ( |t| t. gpu_data . take ( ) ) {
185
+ render_resource_context. remove_buffer ( gpu_data. buffer ) ;
186
+ }
187
+ }
0 commit comments