Skip to content

Commit 37bb4a2

Browse files
author
Mike Bond
committed
WIP OpenPBR anisotropy for IBL
1 parent 825149e commit 37bb4a2

16 files changed

+409
-54
lines changed

packages/dev/core/src/Materials/PBR/openPbrMaterial.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import { PushMaterial } from "../pushMaterial";
6363
import { SmartArray } from "../../Misc/smartArray";
6464
import type { RenderTargetTexture } from "../Textures/renderTargetTexture";
6565
import type { IAnimatable } from "../../Animations/animatable.interface";
66+
import { Texture } from "../Textures";
6667

6768
const onCreatedEffectParameters = { effect: null as unknown as Effect, subMesh: null as unknown as Nullable<SubMesh> };
6869

@@ -778,6 +779,8 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase {
778779
private _samplersList: { [name: string]: Sampler } = {};
779780
private _samplerDefines: { [name: string]: { type: string; default: any } } = {};
780781

782+
private static _noiseTexture: Nullable<BaseTexture> = null;
783+
781784
/**
782785
* Intensity of the direct lights e.g. the four lights available in your scene.
783786
* This impacts both the direct diffuse and specular highlights.
@@ -1352,6 +1355,16 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase {
13521355
Logger.Error("OpenPBRMaterial: WebGL 2.0 or above is required for this material.");
13531356
}
13541357

1358+
if (OpenPBRMaterial._noiseTexture === null || OpenPBRMaterial._noiseTexture.getScene()?.getEngine() !== scene?.getEngine()) {
1359+
OpenPBRMaterial._noiseTexture = new Texture(
1360+
"https://assets.babylonjs.com/textures/blue_noise/blue_noise_rgb.png",
1361+
scene,
1362+
false,
1363+
true,
1364+
Constants.TEXTURE_NEAREST_SAMPLINGMODE
1365+
);
1366+
}
1367+
13551368
// Setup the default processing configuration to the scene.
13561369
this._attachImageProcessingConfiguration(null);
13571370

@@ -1370,7 +1383,6 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase {
13701383

13711384
this._environmentBRDFTexture = GetEnvironmentBRDFTexture(this.getScene());
13721385
this.prePassConfiguration = new PrePassConfiguration();
1373-
this._environmentBRDFTexture = GetEnvironmentBRDFTexture(this.getScene());
13741386

13751387
// Build the internal property list that can be used to generate and update the uniform buffer
13761388
this._propertyList = {};
@@ -1697,6 +1709,12 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase {
16971709
return false;
16981710
}
16991711
}
1712+
1713+
if (OpenPBRMaterial._noiseTexture) {
1714+
if (!OpenPBRMaterial._noiseTexture.isReady()) {
1715+
return false;
1716+
}
1717+
}
17001718
}
17011719
}
17021720

@@ -1933,6 +1951,10 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase {
19331951
if (defines.ENVIRONMENTBRDF) {
19341952
ubo.setTexture("environmentBrdfSampler", this._environmentBRDFTexture);
19351953
}
1954+
1955+
if (defines.ANISOTROPIC) {
1956+
ubo.setTexture("blueNoiseSampler", OpenPBRMaterial._noiseTexture);
1957+
}
19361958
}
19371959

19381960
// OIT with depth peeling
@@ -2249,7 +2271,16 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase {
22492271
uniforms.push(uniformName);
22502272
}
22512273

2252-
const samplers = ["environmentBrdfSampler", "boneSampler", "morphTargets", "oitDepthSampler", "oitFrontColorSampler", "areaLightsLTC1Sampler", "areaLightsLTC2Sampler"];
2274+
const samplers = [
2275+
"environmentBrdfSampler",
2276+
"blueNoiseSampler",
2277+
"boneSampler",
2278+
"morphTargets",
2279+
"oitDepthSampler",
2280+
"oitFrontColorSampler",
2281+
"areaLightsLTC1Sampler",
2282+
"areaLightsLTC2Sampler",
2283+
];
22532284

22542285
for (const key in this._samplersList) {
22552286
const sampler = this._samplersList[key];
@@ -2474,7 +2505,7 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase {
24742505

24752506
defines.HORIZONOCCLUSION = this._useHorizonOcclusion;
24762507

2477-
if (this.specularRoughnessAnisotropy > 0.0) {
2508+
if (this.specularRoughnessAnisotropy > 0.0 && OpenPBRMaterial._noiseTexture) {
24782509
defines.ANISOTROPIC = true;
24792510
if (!mesh.isVerticesDataPresent(VertexBuffer.TangentKind)) {
24802511
defines._needUVs = true;

packages/dev/core/src/Shaders/ShadersInclude/openpbrBaseLayerData.fx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ vec2 geometry_tangent = vec2(1.0, 0.0);
7171
#endif
7272
#endif
7373

74+
#ifdef ANISOTROPIC
75+
vec3 noise = texture2D(blueNoiseSampler, gl_FragCoord.xy / 256.0).xyz;
76+
#endif
77+
7478
// Initalize base layer properties from uniforms
7579
base_color = vBaseColor.rgb;
7680
#if defined(VERTEXCOLOR) || defined(INSTANCESCOLOR) && defined(INSTANCES)

packages/dev/core/src/Shaders/ShadersInclude/openpbrEnvironmentLighting.fx

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,38 @@
3434
#else
3535
vec2 reflectionCoords = vec2(0., 0.);
3636
#endif
37-
reflectionCoords = createReflectionCoords(vPositionW, normalW);
37+
3838
float specularAlphaG = specular_roughness * specular_roughness;
39-
vec3 baseSpecularEnvironmentLight = sampleRadiance(specularAlphaG, vReflectionMicrosurfaceInfos.rgb, vReflectionInfos
40-
#if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
41-
, baseGeoInfo.NdotVUnclamped
42-
#endif
43-
, reflectionSampler
44-
, reflectionCoords
45-
#ifdef REALTIME_FILTERING
46-
, vReflectionFilteringInfo
47-
#endif
48-
);
39+
#ifdef ANISOTROPIC
40+
vec3 baseSpecularEnvironmentLight = sampleRadianceAnisotropic(specularAlphaG, vReflectionMicrosurfaceInfos.rgb, vReflectionInfos
41+
, baseGeoInfo
42+
, normalW
43+
, viewDirectionW
44+
, vPositionW
45+
, noise
46+
, reflectionSampler
47+
#ifdef REALTIME_FILTERING
48+
, vReflectionFilteringInfo
49+
#endif
50+
);
51+
#else
52+
reflectionCoords = createReflectionCoords(vPositionW, normalW);
53+
vec3 baseSpecularEnvironmentLight = sampleRadiance(specularAlphaG, vReflectionMicrosurfaceInfos.rgb, vReflectionInfos
54+
, baseGeoInfo
55+
, reflectionSampler
56+
, reflectionCoords
57+
#ifdef REALTIME_FILTERING
58+
, vReflectionFilteringInfo
59+
#endif
60+
);
61+
#endif
4962

50-
baseSpecularEnvironmentLight = mix(baseSpecularEnvironmentLight.rgb, baseDiffuseEnvironmentLight, specularAlphaG);
63+
// Purely empirical blend between diffuse and specular lobes when roughness gets very high.
64+
#ifdef ANISOTROPIC
65+
baseSpecularEnvironmentLight = mix(baseSpecularEnvironmentLight.rgb, baseDiffuseEnvironmentLight, min(specularAlphaG * specularAlphaG, 0.5));
66+
#else
67+
baseSpecularEnvironmentLight = mix(baseSpecularEnvironmentLight.rgb, baseDiffuseEnvironmentLight, specularAlphaG);
68+
#endif
5169

5270
vec3 coatEnvironmentLight = vec3(0., 0., 0.);
5371
if (coat_weight > 0.0) {
@@ -59,9 +77,7 @@
5977
reflectionCoords = createReflectionCoords(vPositionW, coatNormalW);
6078
float coatAlphaG = coat_roughness * coat_roughness;
6179
coatEnvironmentLight = sampleRadiance(coatAlphaG, vReflectionMicrosurfaceInfos.rgb, vReflectionInfos
62-
#if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
63-
, coatGeoInfo.NdotVUnclamped
64-
#endif
80+
, coatGeoInfo
6581
, reflectionSampler
6682
, reflectionCoords
6783
#ifdef REALTIME_FILTERING

packages/dev/core/src/Shaders/ShadersInclude/openpbrFragmentSamplersDeclaration.fx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@
6666
uniform sampler2D environmentBrdfSampler;
6767
#endif
6868

69+
#ifdef ANISOTROPIC
70+
uniform sampler2D blueNoiseSampler;
71+
#endif
72+
6973
#ifdef IBL_CDF_FILTERING
7074
uniform sampler2D icdfSampler;
7175
#endif

packages/dev/core/src/Shaders/ShadersInclude/openpbrGeometryInfo.fx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ struct geometryInfoOutParams
88
float anisotropy;
99
vec3 anisotropicTangent;
1010
vec3 anisotropicBitangent;
11+
mat3 TBN;
1112
#endif
1213
};
1314

@@ -52,7 +53,7 @@ geometryInfoOutParams geometryInfo(
5253
outParams.anisotropy = anisotropy;
5354
outParams.anisotropicTangent = anisotropicTangent;
5455
outParams.anisotropicBitangent = anisotropicBitangent;
55-
56+
outParams.TBN = TBN;
5657
#endif
5758

5859
return outParams;

packages/dev/core/src/Shaders/ShadersInclude/openpbrIblFunctions.fx

Lines changed: 146 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,9 @@
120120
#endif
121121
in vec3 vPositionW
122122
, in vec3 normalW
123-
#ifdef ANISOTROPIC
124-
, in anisotropicOutParams anisotropicOut
125-
#endif
126123
)
127124
{
128-
#ifdef ANISOTROPIC
129-
vec3 reflectionVector = computeReflectionCoords(vec4(vPositionW, 1.0), anisotropicOut.anisotropicNormal);
130-
#else
131-
vec3 reflectionVector = computeReflectionCoords(vec4(vPositionW, 1.0), normalW);
132-
#endif
125+
vec3 reflectionVector = computeReflectionCoords(vec4(vPositionW, 1.0), normalW);
133126

134127
#ifdef REFLECTIONMAP_OPPOSITEZ
135128
reflectionVector.z *= -1.0;
@@ -154,9 +147,7 @@
154147
in float alphaG
155148
, in vec3 vReflectionMicrosurfaceInfos
156149
, in vec2 vReflectionInfos
157-
#if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
158-
, in float NdotVUnclamped
159-
#endif
150+
, in geometryInfoOutParams geoInfo
160151
#ifdef REFLECTIONMAP_3D
161152
, in samplerCube reflectionSampler
162153
, const vec3 reflectionCoords
@@ -172,7 +163,7 @@
172163
vec4 environmentRadiance = vec4(0., 0., 0., 0.);
173164
// _____________________________ 2D vs 3D Maps ________________________________
174165
#if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
175-
float reflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, alphaG, NdotVUnclamped);
166+
float reflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, alphaG, geoInfo.NdotVUnclamped);
176167
#elif defined(LINEARSPECULARREFLECTION)
177168
float reflectionLOD = getLinearLodFromRoughness(vReflectionMicrosurfaceInfos.x, roughness);
178169
#else
@@ -184,6 +175,19 @@
184175

185176
#ifdef REALTIME_FILTERING
186177
environmentRadiance = vec4(radiance(alphaG, reflectionSampler, reflectionCoords, vReflectionFilteringInfo), 1.0);
178+
// #elif defined(ANISOTROPIC)
179+
// vec4 radianceSample = vec4(0.0);
180+
// const int samples = 16;
181+
// for (int i = 0; i < samples; ++i) {
182+
// float t = (float(i) * (1.0f / float(max(samples - 1, 1))) - 0.5f) * 3.14159 * alphaT;
183+
// vec3 perturbed_N = geoInfo.anisotropicTangent * t;
184+
// // Add noise (mostly in the tangent direction) to smooth out sampling
185+
// // perturbed_N.xy += new_noise.xy;
186+
187+
// vec3 newCoords = normalize(reflectionCoords + perturbed_N);
188+
// radianceSample += sampleReflectionLod(reflectionSampler, newCoords, reflectionLOD);
189+
// }
190+
// environmentRadiance = vec4(radianceSample.xyz / float(samples), 1.0);
187191
#else
188192
environmentRadiance = sampleReflectionLod(reflectionSampler, reflectionCoords, reflectionLOD);
189193
#endif
@@ -201,6 +205,136 @@
201205
return environmentRadiance.rgb;
202206
}
203207

208+
#if defined(ANISOTROPIC)
209+
#define pbr_inline
210+
#define inline
211+
vec3 sampleRadianceAnisotropic(
212+
in float alphaG
213+
, in vec3 vReflectionMicrosurfaceInfos
214+
, in vec2 vReflectionInfos
215+
, in geometryInfoOutParams geoInfo
216+
, const vec3 normalW
217+
, const vec3 viewDirectionW
218+
, const vec3 positionW
219+
, const vec3 noise
220+
#ifdef REFLECTIONMAP_3D
221+
, in samplerCube reflectionSampler
222+
#else
223+
, in sampler2D reflectionSampler
224+
#endif
225+
#ifdef REALTIME_FILTERING
226+
, in vec2 vReflectionFilteringInfo
227+
#endif
228+
)
229+
{
230+
vec4 environmentRadiance = vec4(0., 0., 0., 0.);
231+
232+
// Calculate alpha along tangent and bitangent according to equation 21 in the OpenPBR spec.
233+
float alphaT = alphaG * sqrt(2.0 / (1.0 + (1.0 - geoInfo.anisotropy) * (1.0 - geoInfo.anisotropy)));
234+
float alphaB = (1.0 - geoInfo.anisotropy) * alphaT;
235+
alphaG = mix(alphaG, alphaB, 0.95);
236+
237+
// _____________________________ 2D vs 3D Maps ________________________________
238+
#if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
239+
float reflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, alphaG, geoInfo.NdotVUnclamped);
240+
#elif defined(LINEARSPECULARREFLECTION)
241+
float reflectionLOD = getLinearLodFromRoughness(vReflectionMicrosurfaceInfos.x, roughness);
242+
#else
243+
float reflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, alphaG);
244+
#endif
245+
246+
// Apply environment convolution scale/offset filter tuning parameters to the mipmap LOD selection
247+
reflectionLOD = reflectionLOD * vReflectionMicrosurfaceInfos.y + vReflectionMicrosurfaceInfos.z;
248+
249+
#ifdef REALTIME_FILTERING
250+
environmentRadiance = vec4(radiance(alphaG, reflectionSampler, reflectionCoords, vReflectionFilteringInfo), 1.0);
251+
#else
252+
// We will sample multiple reflections using interpolated surface normals along
253+
// the tangent direction from -tangent to +tangent.
254+
// We don't want to waste samples where the view direction is back-facing so
255+
// we'll compress samples into the valid range.
256+
const int samples = 16;
257+
// Find the maximum safe interpolation range
258+
float normalDot = dot(viewDirectionW, normalW);
259+
float tangentDot = dot(viewDirectionW, geoInfo.anisotropicTangent);
260+
float negTangentDot = dot(viewDirectionW, -geoInfo.anisotropicTangent);
261+
262+
// Find the valid interpolation range on each side of the normal
263+
float maxPositiveT = 1.0; // Default: sample all the way to +tangent
264+
float maxNegativeT = -1.0; // Default: sample all the way to -tangent
265+
266+
// If +tangent is back-facing, find where the interpolation becomes back-facing
267+
if (tangentDot <= 0.0) {
268+
// Find t where mix(normalW, tangentW, t) becomes perpendicular to view
269+
if (abs(tangentDot - normalDot) > 0.001) {
270+
maxPositiveT = clamp(-normalDot / (tangentDot - normalDot), 0.0, 1.0);
271+
} else {
272+
maxPositiveT = 0.0; // Can't sample towards tangent
273+
}
274+
}
275+
276+
// If -tangent is back-facing, find where the interpolation becomes back-facing
277+
if (negTangentDot <= 0.0) {
278+
// Find t where mix(-tangentW, normalW, blend) becomes perpendicular to view
279+
// This is equivalent to mix(normalW, -tangentW, -t) for t < 0
280+
if (abs(negTangentDot - normalDot) > 0.001) {
281+
float negT = -normalDot / (negTangentDot - normalDot);
282+
maxNegativeT = clamp(-negT, -1.0, 0.0);
283+
} else {
284+
maxNegativeT = 0.0; // Can't sample towards -tangent
285+
}
286+
}
287+
288+
// Further compress the sampling range based on the level of anisotropic roughness
289+
float tangentRange = clamp(sqrt(sqrt(alphaT)) * geoInfo.anisotropy, 0.0, 1.0) * (0.25 * noise.x + 0.75);
290+
maxPositiveT *= maxPositiveT * tangentRange;
291+
maxNegativeT = -(maxNegativeT * maxNegativeT) * tangentRange;
292+
293+
vec4 radianceSample = vec4(0.0);
294+
vec3 reflectionCoords = vec3(0.0);
295+
float sample_weight = 0.0;
296+
float total_weight = 0.0;
297+
for (int i = 0; i < samples; ++i) {
298+
// Find interpolation parameter in our valid range
299+
float t = mix(maxNegativeT, maxPositiveT, float(i) / float(max(samples - 1, 1)));
300+
301+
// Generate sample direction
302+
vec3 sampleDirection;
303+
if (t < 0.0) {
304+
// Interpolate from -tangent towards normal
305+
float blend = t + 1.0;
306+
sampleDirection = normalize(mix(-geoInfo.anisotropicTangent, normalW, blend));
307+
} else if (t > 0.0) {
308+
// Interpolate from normal towards +tangent
309+
float blend = t;
310+
sampleDirection = normalize(mix(normalW, geoInfo.anisotropicTangent, blend));
311+
} else {
312+
// t = 0, sample the normal
313+
sampleDirection = normalW;
314+
}
315+
316+
// Empirical approximation of geometry masking.
317+
sample_weight = pow(clamp(dot(normalW, sampleDirection), 0.0, 1.0), 16.0);
318+
reflectionCoords = createReflectionCoords(positionW, sampleDirection);
319+
radianceSample = sampleReflectionLod(reflectionSampler, reflectionCoords, reflectionLOD);
320+
#ifdef RGBDREFLECTION
321+
environmentRadiance.rgb += sample_weight * fromRGBD(radianceSample);
322+
#elif defined(GAMMAREFLECTION)
323+
environmentRadiance.rgb += sample_weight * toLinearSpace(radianceSample.rgb);
324+
#else
325+
environmentRadiance.rgb += sample_weight * radianceSample.rgb;
326+
#endif
327+
total_weight += sample_weight;
328+
}
329+
environmentRadiance = vec4(environmentRadiance.xyz / float(total_weight), 1.0);
330+
#endif
331+
332+
// _____________________________ Levels _____________________________________
333+
environmentRadiance.rgb *= vec3(vReflectionInfos.x);
334+
return environmentRadiance.rgb;
335+
}
336+
#endif
337+
204338
#define pbr_inline
205339
vec3 conductorIblFresnel(in ReflectanceParams reflectance, in float NdotV, in float roughness, in vec3 environmentBrdf)
206340
{

packages/dev/core/src/Shaders/openpbr.fragment.fx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ precision highp float;
6464
#include<openpbrConductorReflectance>
6565

6666
#include<openpbrBlockAmbientOcclusion>
67-
#include<openpbrIblFunctions>
6867
#include<openpbrGeometryInfo>
68+
#include<openpbrIblFunctions>
6969

7070
// Do a mix between layers with additional multipliers for each layer.
7171
vec3 layer(vec3 slab_bottom, vec3 slab_top, float lerp_factor, vec3 bottom_multiplier, vec3 top_multiplier) {

0 commit comments

Comments
 (0)