Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions source/GltfState/gltf_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ class GltfState {
KHR_materials_dispersion: true,
/** KHR_materials_emissive_strength enables emissive factors larger than 1.0 */
KHR_materials_emissive_strength: true,
/** KHR_materials_retroreflection adds a retroreflective response to the metallic and dielectric BRDFs */
KHR_materials_retroreflection: true,
/** KHR_interactivity enables execution of a behavior graph */
KHR_interactivity: true,
/** KHR_node_hoverability enables hovering over nodes */
Expand Down Expand Up @@ -257,6 +259,12 @@ GltfState.DebugOutput = {
IRIDESCENCE_THICKNESS: "Iridescence Thickness"
},

/** KHR_materials_retroreflection */
retroreflection: {
/** output the retroreflection strength*/
RETROREFLECTION_FACTOR: "Retroreflection Strength"
},

/** KHR_materials_anisotropy */
anisotropy: {
/** output the anisotropic strength*/
Expand Down
8 changes: 8 additions & 0 deletions source/Renderer/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1882,6 +1882,9 @@ class gltfRenderer {
this.shader.updateUniform("u_IridescenceThicknessUVSet", material.extensions?.KHR_materials_iridescence?.iridescenceThicknessTexture?.texCoord);
this.shader.updateUniform("u_IridescenceThicknessMinimum", material.extensions?.KHR_materials_iridescence?.iridescenceThicknessMinimum);

this.shader.updateUniform("u_RetroreflectionFactor", material.extensions?.KHR_materials_retroreflection?.retroreflectionFactor);
this.shader.updateUniform("u_RetroreflectionUVSet", material.extensions?.KHR_materials_retroreflection?.retroreflectionTexture?.texCoord);

this.shader.updateUniform("u_SheenRoughnessFactor", material.extensions?.KHR_materials_sheen?.sheenRoughnessFactor);
this.shader.updateUniform("u_SheenColorFactor", jsToGl(material.extensions?.KHR_materials_sheen?.sheenColorFactor));
this.shader.updateUniform("u_SheenRoughnessUVSet", material.extensions?.KHR_materials_sheen?.sheenRoughnessTexture?.texCoord);
Expand Down Expand Up @@ -2222,6 +2225,11 @@ class gltfRenderer {
shaderDefine: "DEBUG_IRIDESCENCE_THICKNESS"
},

{
debugOutput: GltfState.DebugOutput.retroreflection.RETROREFLECTION_FACTOR,
shaderDefine: "DEBUG_RETROREFLECTION_FACTOR"
},

{
debugOutput: GltfState.DebugOutput.anisotropy.ANISOTROPIC_STRENGTH,
shaderDefine: "DEBUG_ANISOTROPIC_STRENGTH"
Expand Down
17 changes: 17 additions & 0 deletions source/Renderer/shaders/ibl.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@ vec3 getIBLRadianceAnisotropy(vec3 n, vec3 v, float roughness, float anisotropy,
#endif


#ifdef MATERIAL_RETROREFLECTION
// MRM (Portsmouth et al., JCGT 15(1), 2026): substitute the view direction with
// V_retro = reflect(-v, n) before evaluating the specular lobe. Since the regular
// mirror lookup already reflects the view about n (`reflect(-v, n)`), and that
// reflection is idempotent, sampling the environment at V_retro reflects again and
// simplifies to sampling directly along v - i.e. looking back toward the viewer.
vec3 getIBLRadianceGGXRetroreflection(vec3 n, vec3 v, float roughness)
{
float lod = roughness * float(u_MipCount - 1);
vec3 reflection = normalize(v);
vec4 specularSample = getSpecularSample(reflection, lod);

return specularSample.rgb;
}
#endif


vec3 getIBLRadianceCharlie(vec3 n, vec3 v, float sheenRoughness, vec3 sheenColor)
{
float NdotV = clampedDot(n, v);
Expand Down
20 changes: 20 additions & 0 deletions source/Renderer/shaders/material_info.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ uniform float u_IridescenceIor;
uniform float u_IridescenceThicknessMinimum;
uniform float u_IridescenceThicknessMaximum;

// Retroreflection
uniform float u_RetroreflectionFactor;

// Diffuse Transmission
uniform float u_DiffuseTransmissionFactor;
uniform vec3 u_DiffuseTransmissionColorFactor;
Expand Down Expand Up @@ -102,6 +105,9 @@ struct MaterialInfo
float iridescenceIor;
float iridescenceThickness;

// KHR_materials_retroreflection
float retroreflectionFactor;

float diffuseTransmissionFactor;
vec3 diffuseTransmissionColorFactor;

Expand Down Expand Up @@ -329,6 +335,20 @@ MaterialInfo getIridescenceInfo(MaterialInfo info)
#endif


#ifdef MATERIAL_RETROREFLECTION
MaterialInfo getRetroreflectionInfo(MaterialInfo info)
{
info.retroreflectionFactor = u_RetroreflectionFactor;

#ifdef HAS_RETROREFLECTION_MAP
info.retroreflectionFactor *= texture(u_RetroreflectionSampler, getRetroreflectionUV()).r;
#endif

return info;
}
#endif


#ifdef MATERIAL_DIFFUSE_TRANSMISSION
MaterialInfo getDiffuseTransmissionInfo(MaterialInfo info)
{
Expand Down
41 changes: 41 additions & 0 deletions source/Renderer/shaders/pbr.frag
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ void main()
materialInfo = getIridescenceInfo(materialInfo);
#endif

#ifdef MATERIAL_RETROREFLECTION
materialInfo = getRetroreflectionInfo(materialInfo);
#endif

#ifdef MATERIAL_DIFFUSE_TRANSMISSION
materialInfo = getDiffuseTransmissionInfo(materialInfo);
#endif
Expand Down Expand Up @@ -224,6 +228,17 @@ void main()
f_dielectric_brdf_ibl = mix(f_dielectric_brdf_ibl, rgb_mix(f_diffuse, f_specular_dielectric, iridescenceFresnel_dielectric), materialInfo.iridescenceFactor);
#endif

#ifdef MATERIAL_RETROREFLECTION
// Retroreflective variant of the metallic and dielectric BRDFs (MRM model): only the
// specular lobe's view direction is substituted with V_retro; the Fresnel mix weight is
// unchanged because NdotV is invariant under the V -> V_retro substitution.
vec3 f_specular_retro = getIBLRadianceGGXRetroreflection(n, v, materialInfo.perceptualRoughness);
vec3 f_metal_brdf_retro = f_metal_fresnel_ibl * f_specular_retro;
vec3 f_dielectric_brdf_retro = mix(f_diffuse, f_specular_retro, f_dielectric_fresnel_ibl);
f_metal_brdf_ibl = mix(f_metal_brdf_ibl, f_metal_brdf_retro, materialInfo.retroreflectionFactor);
f_dielectric_brdf_ibl = mix(f_dielectric_brdf_ibl, f_dielectric_brdf_retro, materialInfo.retroreflectionFactor);
#endif


#ifdef MATERIAL_CLEARCOAT
clearcoat_brdf = getIBLRadianceGGX(materialInfo.clearcoatNormal, v, materialInfo.clearcoatRoughness);
Expand Down Expand Up @@ -350,6 +365,25 @@ void main()
l_dielectric_brdf = mix(l_dielectric_brdf, rgb_mix(l_diffuse, l_specular_dielectric, iridescenceFresnel_dielectric), materialInfo.iridescenceFactor);
#endif

#ifdef MATERIAL_RETROREFLECTION
// V_retro = reflect(-v, n); NdotV_retro == NdotV, so only the half vector (and thus
// NdotH/VdotH) needs recomputing for the retroreflective specular lobe.
vec3 v_retro = normalize(reflect(-v, n));
vec3 h_retro = normalize(l + v_retro);
float NdotH_retro = clampedDot(n, h_retro);
float VdotH_retro = clampedDot(v_retro, h_retro);

vec3 dielectric_fresnel_retro = F_Schlick(materialInfo.f0_dielectric * materialInfo.specularWeight, materialInfo.f90_dielectric, abs(VdotH_retro));
vec3 metal_fresnel_retro = F_Schlick(baseColor.rgb, vec3(1.0), abs(VdotH_retro));

vec3 l_specular_retro = intensity * NdotL * BRDF_specularGGX(materialInfo.alphaRoughness, NdotL, NdotV, NdotH_retro);
vec3 l_metal_brdf_retro = metal_fresnel_retro * l_specular_retro;
vec3 l_dielectric_brdf_retro = mix(l_diffuse, l_specular_retro, dielectric_fresnel_retro);

l_metal_brdf = mix(l_metal_brdf, l_metal_brdf_retro, materialInfo.retroreflectionFactor);
l_dielectric_brdf = mix(l_dielectric_brdf, l_dielectric_brdf_retro, materialInfo.retroreflectionFactor);
#endif

#ifdef MATERIAL_CLEARCOAT
l_clearcoat_brdf = intensity * getPunctualRadianceClearCoat(materialInfo.clearcoatNormal, v, l, h, VdotH,
materialInfo.clearcoatF0, materialInfo.clearcoatF90, materialInfo.clearcoatRoughness);
Expand Down Expand Up @@ -525,6 +559,13 @@ vec3 specularTexture = vec3(1.0);
#if DEBUG == DEBUG_IRIDESCENCE_THICKNESS
g_finalColor.rgb = vec3(materialInfo.iridescenceThickness / 1200.0);
#endif
#endif

// Retroreflection:
#ifdef MATERIAL_RETROREFLECTION
#if DEBUG == DEBUG_RETROREFLECTION_FACTOR
g_finalColor.rgb = vec3(materialInfo.retroreflectionFactor);
#endif
#endif

// Anisotropy:
Expand Down
22 changes: 22 additions & 0 deletions source/Renderer/shaders/textures.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,28 @@ vec2 getIridescenceThicknessUV()
#endif


// Retroreflection


#ifdef MATERIAL_RETROREFLECTION

uniform sampler2D u_RetroreflectionSampler;
uniform int u_RetroreflectionUVSet;
uniform mat3 u_RetroreflectionUVTransform;


vec2 getRetroreflectionUV()
{
vec3 uv = vec3(u_RetroreflectionUVSet < 1 ? v_texcoord_0 : v_texcoord_1, 1.0);
#ifdef HAS_RETROREFLECTION_UV_TRANSFORM
uv = u_RetroreflectionUVTransform * uv;
#endif
return uv.xy;
}

#endif


// Diffuse Transmission

#ifdef MATERIAL_DIFFUSE_TRANSMISSION
Expand Down
1 change: 1 addition & 0 deletions source/gltf/gltf.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const allowedExtensions = [
"KHR_materials_ior",
"KHR_materials_iridescence",
"KHR_materials_pbrSpecularGlossiness",
"KHR_materials_retroreflection",
"KHR_materials_sheen",
"KHR_materials_specular",
"KHR_materials_transmission",
Expand Down
48 changes: 48 additions & 0 deletions source/gltf/material.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class gltfMaterial extends GltfObject {
this.hasIridescence = false;
this.hasAnisotropy = false;
this.hasDispersion = false;
this.hasRetroreflection = false;

// non gltf properties
this.type = "unlit";
Expand Down Expand Up @@ -106,6 +107,12 @@ class gltfMaterial extends GltfObject {
if (this.hasDispersion && renderingParameters.enabledExtensions.KHR_materials_dispersion) {
defines.push("MATERIAL_DISPERSION 1");
}
if (
this.hasRetroreflection &&
renderingParameters.enabledExtensions.KHR_materials_retroreflection
) {
defines.push("MATERIAL_RETROREFLECTION 1");
}

return defines;
}
Expand Down Expand Up @@ -482,6 +489,22 @@ class gltfMaterial extends GltfObject {
if (this.extensions.KHR_materials_dispersion !== undefined) {
this.hasDispersion = true;
}

// KHR Extension: Retroreflection
// See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_retroreflection
if (this.extensions.KHR_materials_retroreflection !== undefined) {
this.hasRetroreflection = true;

const retroreflectionTexture =
this.extensions.KHR_materials_retroreflection.retroreflectionTexture;

if (retroreflectionTexture !== undefined) {
retroreflectionTexture.samplerName = "u_RetroreflectionSampler";
this.parseTextureInfoExtensions(retroreflectionTexture, "Retroreflection");
this.textures.push(retroreflectionTexture);
this.defines.push("HAS_RETROREFLECTION_MAP 1");
}
}
}

initGlForMembers(this, gltf, webGlContext);
Expand Down Expand Up @@ -686,6 +709,13 @@ class gltfMaterial extends GltfObject {
this.extensions.KHR_materials_ior = new KHR_materials_ior();
this.extensions.KHR_materials_ior.fromJson(jsonExtensions.KHR_materials_ior);
}

if (jsonExtensions.KHR_materials_retroreflection !== undefined) {
this.extensions.KHR_materials_retroreflection = new KHR_materials_retroreflection();
this.extensions.KHR_materials_retroreflection.fromJson(
jsonExtensions.KHR_materials_retroreflection
);
}
}
}

Expand Down Expand Up @@ -825,6 +855,24 @@ class KHR_materials_iridescence extends GltfObject {
}
}

class KHR_materials_retroreflection extends GltfObject {
static animatedProperties = ["retroreflectionFactor"];
constructor() {
super();
this.retroreflectionFactor = 0;
this.retroreflectionTexture = undefined;
}

fromJson(jsonRetroreflection) {
super.fromJson(jsonRetroreflection);
if (jsonRetroreflection.retroreflectionTexture !== undefined) {
const retroreflectionTexture = new gltfTextureInfo();
retroreflectionTexture.fromJson(jsonRetroreflection.retroreflectionTexture);
this.retroreflectionTexture = retroreflectionTexture;
}
}
}

class KHR_materials_sheen extends GltfObject {
static animatedProperties = ["sheenRoughnessFactor", "sheenColorFactor"];
constructor() {
Expand Down