three_d/renderer/material/
physical_material.rs1use crate::core::*;
2use crate::renderer::*;
3
4#[derive(Clone)]
9pub struct PhysicalMaterial {
10 pub name: String,
12 pub albedo: Srgba,
14 pub albedo_texture: Option<Texture2DRef>,
17 pub metallic: f32,
19 pub roughness: f32,
21 pub metallic_roughness_texture: Option<Texture2DRef>,
24 pub occlusion_strength: f32,
26 pub occlusion_texture: Option<Texture2DRef>,
29 pub normal_scale: f32,
31 pub normal_texture: Option<Texture2DRef>,
33 pub render_states: RenderStates,
35 pub is_transparent: bool,
37 pub emissive: Srgba,
39 pub emissive_texture: Option<Texture2DRef>,
42 pub lighting_model: LightingModel,
44}
45
46impl PhysicalMaterial {
47 pub fn new(context: &Context, cpu_material: &CpuMaterial) -> Self {
55 Self::new_internal(context, cpu_material, super::is_transparent(cpu_material))
56 }
57
58 pub fn new_opaque(context: &Context, cpu_material: &CpuMaterial) -> Self {
62 Self::new_internal(context, cpu_material, false)
63 }
64
65 pub fn new_transparent(context: &Context, cpu_material: &CpuMaterial) -> Self {
69 Self::new_internal(context, cpu_material, true)
70 }
71
72 fn new_internal(context: &Context, cpu_material: &CpuMaterial, is_transparent: bool) -> Self {
73 let albedo_texture =
74 cpu_material
75 .albedo_texture
76 .as_ref()
77 .map(|cpu_texture| match &cpu_texture.data {
78 TextureData::RgbU8(_) | TextureData::RgbaU8(_) => {
79 let mut cpu_texture = cpu_texture.clone();
80 cpu_texture.data.to_linear_srgb();
81 Texture2DRef::from_cpu_texture(context, &cpu_texture)
82 }
83 _ => Texture2DRef::from_cpu_texture(context, cpu_texture),
84 });
85 let metallic_roughness_texture =
86 if let Some(ref cpu_texture) = cpu_material.occlusion_metallic_roughness_texture {
87 Some(Texture2DRef::from_cpu_texture(context, cpu_texture))
88 } else {
89 cpu_material
90 .metallic_roughness_texture
91 .as_ref()
92 .map(|cpu_texture| Texture2DRef::from_cpu_texture(context, cpu_texture))
93 };
94 let occlusion_texture = if cpu_material.occlusion_metallic_roughness_texture.is_some() {
95 metallic_roughness_texture.clone()
96 } else {
97 cpu_material
98 .occlusion_texture
99 .as_ref()
100 .map(|cpu_texture| Texture2DRef::from_cpu_texture(context, cpu_texture))
101 };
102 let normal_texture = cpu_material
103 .normal_texture
104 .as_ref()
105 .map(|cpu_texture| Texture2DRef::from_cpu_texture(context, cpu_texture));
106 let emissive_texture =
107 cpu_material
108 .emissive_texture
109 .as_ref()
110 .map(|cpu_texture| match &cpu_texture.data {
111 TextureData::RgbU8(_) | TextureData::RgbaU8(_) => {
112 let mut cpu_texture = cpu_texture.clone();
113 cpu_texture.data.to_linear_srgb();
114 Texture2DRef::from_cpu_texture(context, &cpu_texture)
115 }
116 _ => Texture2DRef::from_cpu_texture(context, cpu_texture),
117 });
118 Self {
119 name: cpu_material.name.clone(),
120 albedo: cpu_material.albedo,
121 albedo_texture,
122 metallic: cpu_material.metallic,
123 roughness: cpu_material.roughness,
124 metallic_roughness_texture,
125 normal_texture,
126 normal_scale: cpu_material.normal_scale,
127 occlusion_texture,
128 occlusion_strength: cpu_material.occlusion_strength,
129 render_states: if is_transparent {
130 RenderStates {
131 write_mask: WriteMask::COLOR,
132 blend: Blend::TRANSPARENCY,
133 ..Default::default()
134 }
135 } else {
136 RenderStates::default()
137 },
138 is_transparent,
139 emissive: cpu_material.emissive,
140 emissive_texture,
141 lighting_model: cpu_material.lighting_model,
142 }
143 }
144}
145
146impl FromCpuMaterial for PhysicalMaterial {
147 fn from_cpu_material(context: &Context, cpu_material: &CpuMaterial) -> Self {
148 Self::new(context, cpu_material)
149 }
150}
151
152impl Material for PhysicalMaterial {
153 fn id(&self) -> EffectMaterialId {
154 EffectMaterialId::PhysicalMaterial(
155 self.albedo_texture.is_some(),
156 self.metallic_roughness_texture.is_some(),
157 self.occlusion_texture.is_some(),
158 self.normal_texture.is_some(),
159 self.emissive_texture.is_some(),
160 )
161 }
162
163 fn fragment_shader_source(&self, lights: &[&dyn Light]) -> String {
164 let mut output = lights_shader_source(lights);
165 if self.albedo_texture.is_some()
166 || self.metallic_roughness_texture.is_some()
167 || self.normal_texture.is_some()
168 || self.occlusion_texture.is_some()
169 || self.emissive_texture.is_some()
170 {
171 output.push_str("in vec2 uvs;\n");
172 if self.albedo_texture.is_some() {
173 output.push_str("#define USE_ALBEDO_TEXTURE;\n");
174 }
175 if self.metallic_roughness_texture.is_some() {
176 output.push_str("#define USE_METALLIC_ROUGHNESS_TEXTURE;\n");
177 }
178 if self.occlusion_texture.is_some() {
179 output.push_str("#define USE_OCCLUSION_TEXTURE;\n");
180 }
181 if self.normal_texture.is_some() {
182 output.push_str("#define USE_NORMAL_TEXTURE;\nin vec3 tang;\nin vec3 bitang;\n");
183 }
184 if self.emissive_texture.is_some() {
185 output.push_str("#define USE_EMISSIVE_TEXTURE;\n");
186 }
187 }
188 output.push_str(ToneMapping::fragment_shader_source());
189 output.push_str(ColorMapping::fragment_shader_source());
190 output.push_str(include_str!("shaders/physical_material.frag"));
191 output
192 }
193
194 fn use_uniforms(&self, program: &Program, viewer: &dyn Viewer, lights: &[&dyn Light]) {
195 program.use_uniform_if_required("lightingModel", lighting_model_to_id(self.lighting_model));
196 viewer.tone_mapping().use_uniforms(program);
197 viewer.color_mapping().use_uniforms(program);
198 program.use_uniform_if_required("cameraPosition", viewer.position());
199 for (i, light) in lights.iter().enumerate() {
200 light.use_uniforms(program, i as u32);
201 }
202 program.use_uniform_if_required("metallic", self.metallic);
203 program.use_uniform_if_required("roughness", self.roughness);
204 if program.requires_uniform("albedoTexture") {
205 if let Some(ref texture) = self.albedo_texture {
206 program.use_uniform("albedoTexTransform", texture.transformation);
207 program.use_texture("albedoTexture", texture);
208 }
209 }
210 if program.requires_uniform("metallicRoughnessTexture") {
211 if let Some(ref texture) = self.metallic_roughness_texture {
212 program.use_uniform("metallicRoughnessTexTransform", texture.transformation);
213 program.use_texture("metallicRoughnessTexture", texture);
214 }
215 }
216 if program.requires_uniform("occlusionTexture") {
217 if let Some(ref texture) = self.occlusion_texture {
218 program.use_uniform("occlusionTexTransform", texture.transformation);
219 program.use_uniform("occlusionStrength", self.occlusion_strength);
220 program.use_texture("occlusionTexture", texture);
221 }
222 }
223 if program.requires_uniform("normalTexture") {
224 if let Some(ref texture) = self.normal_texture {
225 program.use_uniform("normalTexTransform", texture.transformation);
226 program.use_uniform("normalScale", self.normal_scale);
227 program.use_texture("normalTexture", texture);
228 }
229 }
230
231 program.use_uniform("albedo", self.albedo.to_linear_srgb());
232 program.use_uniform("emissive", self.emissive.to_linear_srgb());
233 if program.requires_uniform("emissiveTexture") {
234 if let Some(ref texture) = self.emissive_texture {
235 program.use_uniform("emissiveTexTransform", texture.transformation);
236 program.use_texture("emissiveTexture", texture);
237 }
238 }
239 }
240
241 fn render_states(&self) -> RenderStates {
242 self.render_states
243 }
244 fn material_type(&self) -> MaterialType {
245 if self.is_transparent {
246 MaterialType::Transparent
247 } else {
248 MaterialType::Opaque
249 }
250 }
251}
252
253impl Default for PhysicalMaterial {
254 fn default() -> Self {
255 Self {
256 name: "default".to_string(),
257 albedo: Srgba::WHITE,
258 albedo_texture: None,
259 metallic: 0.0,
260 roughness: 1.0,
261 metallic_roughness_texture: None,
262 normal_texture: None,
263 normal_scale: 1.0,
264 occlusion_texture: None,
265 occlusion_strength: 1.0,
266 render_states: RenderStates::default(),
267 is_transparent: false,
268 emissive: Srgba::BLACK,
269 emissive_texture: None,
270 lighting_model: LightingModel::Blinn,
271 }
272 }
273}