Skip to main content

grafix_toolbox/kit/opengl/utility/
pbrt.rs

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);