Skip to main content

fyrox_impl/renderer/
utils.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21#![allow(missing_docs)] // TODO
22
23use crate::{
24    core::{
25        algebra::{Vector2, Vector3},
26        array_as_u8_slice,
27    },
28    graphics::{
29        error::FrameworkError,
30        gpu_texture::{CubeMapFace, GpuTexture, GpuTextureDescriptor, GpuTextureKind, PixelKind},
31        server::GraphicsServer,
32    },
33};
34use bytemuck::{Pod, Zeroable};
35use half::f16;
36use std::{fs::File, io::Write, path::Path};
37
38pub struct CubeMapFaceDescriptor {
39    pub face: CubeMapFace,
40    pub look: Vector3<f32>,
41    pub up: Vector3<f32>,
42}
43
44impl CubeMapFaceDescriptor {
45    pub fn cube_faces() -> [Self; 6] {
46        [
47            CubeMapFaceDescriptor {
48                face: CubeMapFace::PositiveX,
49                look: Vector3::new(1.0, 0.0, 0.0),
50                up: Vector3::new(0.0, -1.0, 0.0),
51            },
52            CubeMapFaceDescriptor {
53                face: CubeMapFace::NegativeX,
54                look: Vector3::new(-1.0, 0.0, 0.0),
55                up: Vector3::new(0.0, -1.0, 0.0),
56            },
57            CubeMapFaceDescriptor {
58                face: CubeMapFace::PositiveY,
59                look: Vector3::new(0.0, 1.0, 0.0),
60                up: Vector3::new(0.0, 0.0, 1.0),
61            },
62            CubeMapFaceDescriptor {
63                face: CubeMapFace::NegativeY,
64                look: Vector3::new(0.0, -1.0, 0.0),
65                up: Vector3::new(0.0, 0.0, -1.0),
66            },
67            CubeMapFaceDescriptor {
68                face: CubeMapFace::PositiveZ,
69                look: Vector3::new(0.0, 0.0, 1.0),
70                up: Vector3::new(0.0, -1.0, 0.0),
71            },
72            CubeMapFaceDescriptor {
73                face: CubeMapFace::NegativeZ,
74                look: Vector3::new(0.0, 0.0, -1.0),
75                up: Vector3::new(0.0, -1.0, 0.0),
76            },
77        ]
78    }
79}
80
81fn radical_inverse_vd_c(mut bits: u32) -> f32 {
82    bits = bits.rotate_right(16);
83    bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1);
84    bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2);
85    bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4);
86    bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8);
87    bits as f32 * 2.328_306_4e-10
88}
89
90fn hammersley(i: usize, n: usize) -> Vector2<f32> {
91    Vector2::new(i as f32 / n as f32, radical_inverse_vd_c(i as u32))
92}
93
94fn importance_sample_ggx(x_i: Vector2<f32>, roughness: f32, n: Vector3<f32>) -> Vector3<f32> {
95    let a = roughness * roughness;
96
97    let phi = 2.0 * std::f32::consts::PI * x_i.x;
98    let cos_theta = ((1.0 - x_i.y) / (1.0 + (a * a - 1.0) * x_i.y)).sqrt();
99    let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
100
101    // from spherical coordinates to cartesian coordinates
102    let h = Vector3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta);
103
104    // from tangent-space vector to world-space sample vector
105    let up = if n.z.abs() < 0.999 {
106        Vector3::new(0.0, 0.0, 1.0)
107    } else {
108        Vector3::new(1.0, 0.0, 0.0)
109    };
110    let tangent = up.cross(&n).normalize();
111    let bitangent = n.cross(&tangent);
112
113    (tangent * h.x + bitangent * h.y + n * h.z).normalize()
114}
115
116fn geometry_schlick_ggx(n_dot_v: f32, roughness: f32) -> f32 {
117    let a = roughness;
118    let k = (a * a) / 2.0;
119
120    let nom = n_dot_v;
121    let denom = n_dot_v * (1.0 - k) + k;
122
123    nom / denom
124}
125
126fn geometry_smith(roughness: f32, n_dot_v: f32, n_dot_l: f32) -> f32 {
127    let ggx2 = geometry_schlick_ggx(n_dot_v, roughness);
128    let ggx1 = geometry_schlick_ggx(n_dot_l, roughness);
129
130    ggx1 * ggx2
131}
132
133fn integrate_brdf(n_dot_v: f32, roughness: f32, samples: usize) -> Vector2<f32> {
134    let v = Vector3::new((1.0 - n_dot_v * n_dot_v).sqrt(), 0.0, n_dot_v);
135
136    let mut a = 0.0;
137    let mut b = 0.0;
138
139    let n = Vector3::new(0.0, 0.0, 1.0);
140
141    for i in 0..samples {
142        let x_i = hammersley(i, samples);
143        let h = importance_sample_ggx(x_i, roughness, n);
144        let l = (2.0 * v.dot(&h) * h - v).normalize();
145
146        let n_dot_l = l.z.max(0.0);
147        let n_dot_h = h.z.max(0.0);
148        let v_dot_h = v.dot(&h).max(0.0);
149        let n_dot_v = n.dot(&v).max(0.0);
150
151        if n_dot_l > 0.0 {
152            let g = geometry_smith(roughness, n_dot_v, n_dot_l);
153
154            let g_vis = (g * v_dot_h) / (n_dot_h * n_dot_v);
155            let fc = (1.0 - v_dot_h).powf(5.0);
156
157            a += (1.0 - fc) * g_vis;
158            b += fc * g_vis;
159        }
160    }
161
162    Vector2::new(a / samples as f32, b / samples as f32)
163}
164
165#[derive(Default, Copy, Clone, Pod, Zeroable)]
166#[repr(C)]
167pub struct Pixel {
168    pub x: f16,
169    pub y: f16,
170}
171
172pub fn make_brdf_lut_image(size: usize, sample_count: usize) -> Vec<Pixel> {
173    let mut pixels = vec![Pixel::default(); size * size];
174
175    for y in 0..size {
176        for x in 0..size {
177            let n_dot_v = (y as f32 + 0.5) * (1.0 / size as f32);
178            let roughness = (x as f32 + 0.5) * (1.0 / size as f32);
179            let pair = integrate_brdf(n_dot_v, roughness, sample_count);
180            let pixel = &mut pixels[y * size + x];
181            pixel.x = f16::from_f32(pair.x);
182            pixel.y = f16::from_f32(pair.y);
183        }
184    }
185
186    pixels
187}
188
189pub fn generate_brdf_lut_texture(
190    server: &dyn GraphicsServer,
191    size: usize,
192    sample_count: usize,
193) -> Result<GpuTexture, FrameworkError> {
194    let pixels = make_brdf_lut_image(size, sample_count);
195    make_brdf_lut(server, size, array_as_u8_slice(&pixels))
196}
197
198pub fn make_brdf_lut(
199    server: &dyn GraphicsServer,
200    size: usize,
201    pixels: &[u8],
202) -> Result<GpuTexture, FrameworkError> {
203    server.create_texture(GpuTextureDescriptor {
204        name: "BrdfLut",
205        kind: GpuTextureKind::Rectangle {
206            width: size,
207            height: size,
208        },
209        pixel_kind: PixelKind::RG16F,
210        mip_count: 1,
211        data: Some(pixels),
212        ..Default::default()
213    })
214}
215
216pub fn write_brdf_lut(path: &Path, size: usize, sample_count: usize) -> std::io::Result<()> {
217    let pixels = make_brdf_lut_image(size, sample_count);
218    let mut file = File::create(path)?;
219    file.write_all(array_as_u8_slice(&pixels))?;
220    Ok(())
221}
222
223#[cfg(test)]
224mod test {
225    use crate::renderer::utils::write_brdf_lut;
226    use std::path::Path;
227
228    // Use this test to write BRDF use by the lighting module.
229    #[test]
230    fn test_write_brdf_lut() {
231        write_brdf_lut(
232            Path::new("src/renderer/brdf_256x256_256samples.bin"),
233            256,
234            256,
235        )
236        .unwrap();
237    }
238}