1use crate::{GL::*, lib::*, math::*};
2
3pub struct EnvTex {
4 pub mip_levels: f32,
5 pub specular: CubeTex<RGB, f16>,
6 pub irradiance: CubeTex<RGB, f16>,
7}
8impl<T: Borrow<Environment>> From<T> for EnvTex {
9 fn from(e: T) -> Self {
10 let Environment { specular, diffuse } = e.borrow();
11 let (specular, irradiance) = (specular.pipe_as(CubeTex::from), diffuse.into());
12 let mip_levels = f32(specular.param().l);
13 Self { mip_levels, specular, irradiance }
14 }
15}
16
17#[derive_as_obj]
18pub struct Environment {
19 specular: Box<[[fImage<RGB>; 6]]>,
20 diffuse: [fImage<RGB>; 6],
21}
22impl Environment {
23 #[cfg(all(feature = "adv_fs", feature = "hdr"))]
24 pub fn new_cached(name: &str) -> Res<EnvTex> {
25 (|| {
26 let cache = format!("{name}.hdr.z");
27 if let env @ Ok(_) = FS::Load::Archive(&cache).and_then(ser::from_vec) {
28 return env;
29 }
30
31 FS::Load::File(format!("res/{name}.hdr"))
32 .pipe(Image::<RGB, f32>::load)?
33 .pipe(Tex2d::from)
34 .pipe(Self::new)
35 .tap(|env| ser::to_vec(env).map(|v| FS::Save::Archive((cache, v))).warn())
36 .pipe(Ok)
37 })()?
38 .pipe(EnvTex::from)
39 .pipe(Ok)
40 }
41 #[cfg(feature = "adv_fs")]
42 pub fn lut_cached() -> Tex2d<RG, f16> {
43 let cache = "brdf_lut.pbrt.z";
44 if let Ok(lut) = FS::Load::Archive(cache).and_then(ser::from_vec) {
45 return fImage::into(lut);
46 }
47
48 Self::lut().tap(|lut| ser::to_vec(lut).map(|v| FS::Save::Archive((cache, v, 10))).warn()).into()
49 }
50 pub fn lut() -> fImage<RG> {
51 let mut lut = [vs_mesh__2d_screen, ps_env__gen_lut].pipe(Shader::pure);
52 let surf = Fbo::<RGBA, f32>::new((512, 512));
53 {
54 let _ = Uniforms!(lut, ("iSamples", 4096_u32));
55 surf.bind();
56 Screen::Draw();
57 }
58 surf.tex.into()
59 }
60 pub fn new<S, F>(equirect: Tex2d<S, F>) -> Self {
61 let VP_mats = {
62 let (v3, o3) = (la::V3::new, na::Point3::new);
63 let s = |to, up| la::M4::look_at_rh(&na::OPoint::origin(), &to, &up);
64 let proj = la::perspective(1., 90f32.to_radians(), 0.1, 10.);
65 [
66 s(o3(1., 0., 0.), v3(0., -1., 0.)),
67 s(o3(-1., 0., 0.), v3(0., -1., 0.)),
68 s(o3(0., 1., 0.), v3(0., 0., 1.)),
69 s(o3(0., -1., 0.), v3(0., 0., -1.)),
70 s(o3(0., 0., 1.), v3(0., -1., 0.)),
71 s(o3(0., 0., -1.), v3(0., -1., 0.)),
72 ]
73 .map(|side| proj * side)
74 };
75
76 let sampl = &Sampler::linear();
77 let mut equirect_shd = [vs_env__gen, ps_env__unwrap_equirect].pipe(Shader::pure);
78 let mut irradiance_shd = [vs_env__gen, ps_env__gen_irradiance].pipe(Shader::pure);
79 let mut specular_shd = [vs_env__gen, ps_env__gen_spec].pipe(Shader::pure);
80
81 let color = VP_mats
82 .iter()
83 .map(|&cam| {
84 let e = equirect.Bind(sampl);
85 let _ = Uniforms!(equirect_shd, ("iEquirect", e), ("MVPMat", cam));
86 let surf = Fbo::<RGBA, f32>::new((512, 512));
87 surf.bind();
88 Skybox::Draw();
89 surf.tex.into()
90 })
91 .collect_arr();
92 let cubemap = CubeTex::from(&color);
93
94 let diffuse = VP_mats
95 .iter()
96 .map(|&cam| {
97 let e = cubemap.Bind(sampl);
98 let _ = Uniforms!(irradiance_shd, ("iEnv", e), ("MVPMat", cam), ("iDelta", 0.025));
99 let surf = Fbo::<RGBA, f32>::new((64, 64));
100 surf.bind();
101 Skybox::Draw();
102 surf.tex.into()
103 })
104 .collect_arr();
105
106 let mips = cubemap.param().mips_max();
107 let specular = vec![color]
108 .into_iter()
109 .chain(
110 (1..mips)
111 .map(|l| {
112 let r = f32(l) / f32(mips - 1);
113 let wh = cubemap.param().dim_unchecked(u32(l)).xy();
114 let mip = VP_mats
115 .iter()
116 .map(|&cam| {
117 let e = cubemap.Bind(sampl);
118 let _ = Uniforms!(specular_shd, ("iEnv", e), ("MVPMat", cam), ("iSamples", 4096_u32), ("iRoughness", r));
119 let surf = Fbo::<RGBA, f32>::new(wh);
120 surf.bind();
121 Skybox::Draw();
122 surf.tex.into()
123 })
124 .collect_arr();
125 mip
126 })
127 .collect_vec(),
128 )
129 .collect();
130
131 Self { diffuse, specular }
132 }
133}
134
135SHADER!(
136 vs_env__gen,
137 r"layout(location = 0) in vec3 Position;
138 uniform mat4 MVPMat;
139 out vec3 glUV;
140
141 void main() {
142 vec4 pos = vec4(Position, 1);
143 gl_Position = MVPMat * pos;
144 glUV = Position;
145 }"
146);
147
148SHADER!(
149 ps_env__unwrap_equirect,
150 r"in vec3 glUV;
151 layout(location = 0) out vec4 glFragColor;
152 uniform sampler2D iEquirect;
153
154 void main() {
155 vec3 v = normalize(glUV);
156 vec2 uv = vec2(atan(v.z, v.x), asin(v.y)) * vec2(.1591, .3183) + .5;
157 vec3 c = texture(iEquirect, uv).rgb;
158 glFragColor = vec4(c, 1);
159 }"
160);
161
162SHADER!(
163 ps_env__gen_irradiance,
164 r"in vec3 glUV;
165 layout(location = 0) out vec4 glFragColor;
166 uniform samplerCube iEnv;
167 uniform float iDelta;
168
169 const float PI = 3.1415927;
170
171 void main() {
172 vec3 normal = normalize(glUV);
173 vec3 right = cross(vec3(0, 1, 0), normal);
174 vec3 up = cross(normal, right);
175
176 vec3 irradiance = vec3(0);
177 float n_samples = 0;
178 for (float phi = 0; phi < PI * 2; phi += iDelta) {
179 for (float theta = 0; theta < .5 * PI; theta += iDelta) {
180 vec3 tangent_sample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
181 vec3 sample_vec = tangent_sample.x * right + tangent_sample.y * up + tangent_sample.z * normal;
182 irradiance += texture(iEnv, sample_vec).rgb * cos(theta) * sin(theta);
183 ++n_samples;
184 }
185 }
186
187 irradiance = PI * irradiance / n_samples;
188 glFragColor = vec4(irradiance, 1);
189 }"
190);
191
192const TRANSFORM: STR = r"
193 uniform uint iSamples;
194 const float PI_2 = 3.1415927 * 2;
195
196 float RadicalInverse_VdC(uint bits) {
197 bits = (bits << 16u) | (bits >> 16u);
198 bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
199 bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
200 bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
201 bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
202 return float(bits) * 2.3283064e-10; // / 0x100000000
203 }
204
205 vec2 Hammersley(uint i, uint N) { return vec2(float(i) / float(N), RadicalInverse_VdC(i)); }
206
207 vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) {
208 float a = roughness * roughness;
209
210 float phi = Xi.x * PI_2;
211 float cosTheta = sqrt((1. - Xi.y) / ((a * a - 1) * Xi.y + 1));
212 float sinTheta = sqrt(1. - cosTheta * cosTheta);
213
214 vec3 H = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
215
216 vec3 up = abs(N.z) < .999 ? vec3(0, 0, 1) : vec3(1, 0, 0);
217 vec3 tangent = normalize(cross(up, N));
218 vec3 bitangent = cross(N, tangent);
219
220 vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
221 return normalize(sampleVec);
222 }";
223
224SHADER!(
225 ps_env__gen_spec,
226 r"in vec3 glUV;
227 layout(location = 0) out vec4 glFragColor;
228 uniform samplerCube iEnv;
229 uniform float iRoughness;
230 ",
231 TRANSFORM,
232 r"
233 void main()
234 {
235 vec3 N = normalize(glUV);
236
237 float totalWeight = 0;
238 vec3 prefilteredColor = vec3(0);
239 for (uint i = 0u; i < iSamples; ++i) {
240 vec2 Xi = Hammersley(i, iSamples);
241 vec3 H = ImportanceSampleGGX(Xi, N, iRoughness);
242 vec3 L = normalize(dot(N, H) * H * 2 - N);
243
244 float NdotL = max(dot(N, L), 0);
245 if (NdotL > 0) {
246 prefilteredColor += texture(iEnv, L).rgb * NdotL;
247 totalWeight += NdotL;
248 }
249 }
250 prefilteredColor /= totalWeight;
251
252 glFragColor = vec4(prefilteredColor, 1);
253 }"
254);
255
256SHADER!(
257 ps_env__gen_lut,
258 r"in vec2 glUV;
259 layout(location = 0) out vec4 glFragColor;
260 ",
261 TRANSFORM,
262 r"
263 float GeometrySchlickGGX(float NdotV, float roughness)
264 {
265 float k = (roughness * roughness) / 2;
266 float denom = NdotV * (1. - k) + k;
267 return NdotV / denom;
268 }
269
270 float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
271 float NdotV = max(dot(N, V), 0);
272 float NdotL = max(dot(N, L), 0);
273 float ggx2 = GeometrySchlickGGX(NdotV, roughness);
274 float ggx1 = GeometrySchlickGGX(NdotL, roughness);
275
276 return ggx1 * ggx2;
277 }
278
279 vec2 IntegrateBRDF(float NdotV, float roughness) {
280 vec3 V = vec3(sqrt(1. - NdotV * NdotV), 0, NdotV);
281
282 float A = 0;
283 float B = 0;
284 vec3 N = vec3(0, 0, 1);
285 for (uint i = 0u; i < iSamples; ++i) {
286 vec2 Xi = Hammersley(i, iSamples);
287 vec3 H = ImportanceSampleGGX(Xi, N, roughness);
288 vec3 L = normalize(dot(V, H) * H * 2 - V);
289
290 float NdotL = max(L.z, 0);
291 if (NdotL > 0) {
292 float NdotH = max(H.z, 0);
293 float VdotH = max(dot(V, H), 0);
294
295 float G = GeometrySmith(N, V, L, roughness);
296 float G_Vis = (G * VdotH) / (NdotH * NdotV);
297 float Fc = pow(1. - VdotH, 5);
298
299 A += (1. - Fc) * G_Vis;
300 B += Fc * G_Vis;
301 }
302 }
303 A /= float(iSamples);
304 B /= float(iSamples);
305 return vec2(A, B);
306 }
307
308 void main() { glFragColor = vec4(IntegrateBRDF(glUV.x, glUV.y), 0, 1); }"
309);