Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KHR_materials_specular #1719

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3847e6b
Added KHR_materials_specular.
proog128 Dec 2, 2019
cbd0d0e
Split texture.
proog128 Dec 18, 2019
3c3e048
Added more details, removed energy-preserving diffuse BRDF for now.
proog128 Mar 2, 2020
0cf4352
Allow 1-channel texture for specular color.
proog128 Mar 3, 2020
e9fc32b
Added conversion from spec-gloss.
proog128 Mar 4, 2020
2102679
Implementation section now aligns better to current glTF 2.0 spec; cl…
proog128 May 8, 2020
cdad1e2
Describe conversion from KHR_materials_pbrSpecularGlossiness.
proog128 May 8, 2020
75083f1
Pack all values in one texture, use enum to indicate texture type.
proog128 May 8, 2020
758eef8
Add images.
proog128 May 8, 2020
20aa586
Several minor improvements.
proog128 May 8, 2020
975a226
Clarify requirement of specularTextureType.
proog128 May 8, 2020
63d8343
Remove texture type.
proog128 May 18, 2020
e73ff06
Add non-normative marker.
proog128 May 18, 2020
05e0f7b
Remove all traces of specularColorTexture (it is now merged into spec…
proog128 May 20, 2020
a2b0c05
Add contributors and Khronos copyright.
proog128 May 22, 2020
a62f4c9
Removed empty section.
proog128 Jun 15, 2020
3da2dfc
Add details about complementary color weight.
proog128 Jul 15, 2020
c4f87b2
Add exclusions section and fix small issues.
proog128 Aug 4, 2020
1cd78c6
Suggest ior=1000 for conversion from spec-gloss to metal-rough, as 0 …
proog128 Aug 19, 2020
06f85d7
Increase recommended ior for spec-gloss conversion.
proog128 Jan 12, 2021
e41ed16
Update copyright year.
proog128 Jan 12, 2021
abf2200
Add note to explain spec-gloss conversion.
proog128 Jan 12, 2021
e53e478
Make compatible to new Appendix B.
proog128 Jan 18, 2021
229d53a
Recommend ior=0 for spec-gloss conversion.
proog128 Jan 19, 2021
7a3c821
Split specularTexture.
proog128 Jan 29, 2021
2002ef9
Remove upper limit of specular color and change recommendation for re…
proog128 Jan 29, 2021
8ae8ce6
Fix bug in fresnel_mix.
proog128 Feb 18, 2021
2c45562
Update contributor list
emackey Apr 5, 2021
4251448
Draft status
emackey Apr 5, 2021
395c52e
More contributors
emackey Apr 5, 2021
fc08213
Add suggestion from Alexey regarding clamping.
proog128 Apr 6, 2021
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
100 changes: 100 additions & 0 deletions extensions/2.0/Khronos/KHR_materials_specular/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# KHR\_materials\_specular

## Khronos 3D Formats Working Group

* TODO

## Acknowledgments

* TODO

## Status

Experimental

## Dependencies

Written against the glTF 2.0 spec.

## Overview

This extension adds two parameters to the metallic-roughness material: `specular` and `specularColor`.

`specular` allows users to configure the strength of the specular reflection in the dielectric BRDF. A value of zero disables the specular reflection, resulting in a pure diffuse material. The metal BRDF is not affected by the parameter.
rsahlin marked this conversation as resolved.
Show resolved Hide resolved

`specularColor` changes the F0 color of the specular reflection in the dielectric BRDF, allowing artists to use effects known from the specular-glossiness material (`KHR_materials_pbrSpecularGlossiness`) in the metallic-roughness material.
rsahlin marked this conversation as resolved.
Show resolved Hide resolved


## Extending Materials

The strength of the specular reflection is defined by adding the `KHR_materials_specular` extension to any glTF material.

```json
{
"materials": [
{
"extensions": {
"KHR_materials_specular": {
"specularFactor": 1.0,
"specularTexture": 0,
emackey marked this conversation as resolved.
Show resolved Hide resolved
"specularColorFactor": [1.0, 1.0, 1.0],
"specularColorTexture": 0
}
}
}
]
}
```

Factor and texture are combined by multiplication to describe a single value.

| |Type|Description|Required|
|-|----|-----------|--------|
| **specularFactor** | `number` | The strength of the specular reflection. | No, default: `1.0`|
rsahlin marked this conversation as resolved.
Show resolved Hide resolved
| **specularTexture** | [`textureInfo`](/specification/2.0/README.md#reference-textureInfo) | A grayscale texture that defines the specular factor. Will be multiplied by specularFactor. | No |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that the current materials use a factor to scale texture values - however I would just like to raise the question if this property is used, eg how many models have an occlusion texture AND and the strength factor?

For this extension the question is simply - does anyone forsee using a specularTexture with a specularFactor other than 1.0?
Or can the specularTexture * specularFactor multiplication be removed?

| **specularColorFactor** | `number[3]` | The F0 color of the specular reflection (RGB). | No, default: `[1.0, 1.0, 1.0]`|
| **specularColorTexture** | [`textureInfo`](/specification/2.0/README.md#reference-textureInfo) | A 1-channel luminance or 3-channel RGB texture that defines the F0 color of the specular reflection. Will be multiplied by specularColorFactor. | No |

### Conversions

Material models that define f0 in terms of reflectance at normal incidence (with 0 meaning 0%, 0.5 meaning 4%, and 1 meaning 8%) can be converted to `specularColorFactor` and `specularColorTexture` as follows:

```
specularColorFactor = 2 * reflectanceFactor
specularColorTexture = reflectanceTexture
```

Alternatively, the index of refraction defined in `KHR_materials_ior` can be set to the value 1.788789, resulting in a f0 scale factor of 0.08.

## Implementation

The specular factor scales the microfacet BRDF in the dielectric BRDF. It also affects the diffuse BRDF; the less energy is reflected by the microfacet BRDF, the more can be shifted to the diffuse BRDF.

The specular color changes the F0 color of the Fresnel that is multiplied to the microfacet BRDF. The color at grazing angles (F90) is not changed.

```
dielectric_brdf =
fresnel_mix(
diffuse_brdf(baseColor),
microfacet_brdf(roughness^2),
f0 = 0.04 * specular * specularColor,
f90 = specular)
```

Based on [Appendix B](/specification/2.0/README.md#appendix-b-brdf-implementation), the modified `fresnel_mix` operation is defined as follows:

```
fresnel_mix(base, layer, ior) = base * (1 - fr(ior)) + layer * fr(ior)
fr(ior) = f0 + (f90 - f0) * (1 - cos)^5
f0 = 0.04 * specular * specularColor
f90 = specular
```

If `KHR_materials_ior` is used in combination with `KHR_materials_specular`, the constant `0.04` is replaced by the value computed from the IOR:

```
f0 = ((ior - outside_ior) / (ior + outside_ior))^2 * specular * specularColor
f90 = specular
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can avoid the specular factor texture read if we premultiply it with the specular color.
In order to do this we need to change the calculations a bit:

dielectric_brdf =
  fresnel_mix(
    diffuse_brdf(baseColor),
    microfacet_brdf(roughness^2),
    specularColor)

fresnel_mix would be defined as:

fresnel_mix(base, layer, ior) = base * (1 - fr(ior) * specularColor) + layer * fr(ior) * specularColor
fr(ior) = f0 + (f90 - f0) * (1 - cos)^5
f0 = 0.04
f90 = 1.0

And the version using IOR becomes:

f0 = ((ior - outside_ior) / (ior + outside_ior))^2
f90 = 1.0

This changes the attenuation of the base layer to include the specular color as well, which is actually more physically plausible.
This way we decouple the specular color from the Fresnel calculation and we are free to change the Fresnel implementation if we want.
Additionally we avoid modifying f90, which should always be 1.
This is actually what V-Ray does and alSurface does a monochrome version of it:

fresnel_mix(base, layer, ior) = base * (1 - fr(ior) * maxComponent(specularColor)) + layer * fr(ior) * maxComponent(specularColor))

where maxComponent returns the maximum of the R,G and B components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of reasons why specular_color is not used as a directional-independent multiplier to the specular BSDF in this extension. We thought about it a lot when we designed the Enterprise PBR Material, which is the foundation for #1718, #1719, and a few other extensions proposed for glTF PBR Next (click here for an overview). I think our guidelines and trade-offs apply equally well to glTF, therefore I'd like to explain them now.

Compatibility to other material models

As far as I know there are two competing camps wrt. specular color.

  1. The Disney Principled BRDF uses a single-channel parameter called specularTint to blend F0 color of the Fresnel to the base color. This also applies to its derivatives, like the Blender Principled BSDF.
  2. Others like Standard Surface and V-Ray use a color parameter to tint the specular BSDF independent of the direction.

Moreover, there is the specular-glossiness workflow and the glTF extension KHR_materials_pbrSpecularGlossiness. In this case, the specular color is also used as F0 color. Therefore, although not directly comparable (the workflow is different) I would put them into the first camp, together with Disney BRDF.

Our proposal/Enterprise PBR now is a compromise that can represent all these material models to a certain extend.

  • There is a lossless conversion from Disney BRDF: Disney parameters specular and specularTint can be mapped to specularColor. specular alone can be mapped without preprocessing (mentioned in the "Conversion" section in the extension), specularTint needs preprocessing at export time.
  • There is a lossless conversion from KHR_materials_pbrSpecularGlossiness. It doesn't need preprocessing of textures. I think this will help a lot in adopting the new material model, because a) engines need to implement only one Ubershader and b) artists can slowly migrate their materials to the new model (for example if they want a clear-coat for an existing spec-gloss model, they can transition to glTF PBR Next without rebuilding the whole material).
  • There is a lossy conversion from Standard Surface and V-Ray: the intensity of the directional-independent specular color can map to the specular parameter, the color to the specularColor parameter. It's lossy, but I think it is acceptable. Given the fact that glTF PBR Next has a lot less parameters than Standard Surface/V-Ray to make it rasterizer-friendly (Mobile, WebGL, ...), there will be some loss anyway, from other areas as well.

IOR and number of texture fetches

We can avoid the specular factor texture read if we premultiply it with the specular color.

I guess in your proposal the IOR is texturable, right? If yes, then we need a 4-channel texture: IOR (float) + specular color (float3). This is the same amount as in #1718 (KHR_materials_ior) and #1719.

The IOR in #1718 is not texturable, because it conceptually describes the refractive index of interface and the volume beneath the surface, not only the refractive index of the interface. I think this distinction is important, because only a uniform volume IOR ensures reciprocity (think of light tracing and/or nested dielectrics). Of course, artists want to use a texture to modulate the specular contribution, and, therefore, we provide them the parameters specular and specularColor. These parameters don't mess with the volume IOR, so they are reciprocal. I think we should be very strict about reciprocity (and energy conservation). The normative part of glTF must not require something that violates these principles. This way we ensure consistency across a large variety of rendering algorithms, from rasterizers to bidirectional path tracing, and be somewhat future-proof.

To conclude, with #1718 and #1719, we also need only a 4-channel texture: specular (float) + specular color (float3). The uniform IOR is not textured.

In addition, there was some concerns regarding an IOR texture (see this comment), because of the additional overhead it introduces to convert from 1/ior (stored in the texture) to F0 (used in Fresnel).

Decoupling Fresnel and specular color

This way we decouple the specular color from the Fresnel calculation and we are free to change the Fresnel implementation if we want.

This is a good point, because probably some renderers want to use the full Fresnel equations, not the Schlick approximation. The specular color as it is defined in the extension works best with the Schlick approximation. However, I would consider this an implementation detail, therefore it is part of the non-normative section in the extension (not yet marked as such). It is possible to deal with it in combination with the full Fresnel equation, and I am happy to include this solution in the extension.

For example, I think it is possible to implement fresnel_mix as follows (untested, should work):

fresnel_mix(base, layer, ior) = base * (1 - specular * colored_fresnel(ior, specularColor)) + layer * specular * colored_fresnel(ior, specularColor)
colored_fresnel(ior, specularColor) = mix(specularColor * f0, 1, (fr(ior) - f0)/(1 - f0))
fr(ior) = ... full fresnel with ior and outside_ior ...
f0 = ((ior - outside_ior) / (ior + outside_ior))^2

In case fr(ior) is implemented with Schlick, it simplifies to

fresnel_mix(base, layer, ior) = base * (1 - specular * fr(ior, specularColor)) + layer * specular * fr(ior, specularColor)
fr(ior, specularColor) = f0 + (1 - f0) * (1 - cos)^5
f0 = ((ior - outside_ior) / (ior + outside_ior))^2 * specularColor


The specular factor and specular color do not affect the index of refraction of the volume below the surface, defined in `KHR_materials_volume`. As a result, `specular` and `specularColor` do not take part in computing the outgoing direction for refraction.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"$schema": "http://json-schema.org/draft-04/schema",
"title": "KHR_materials_specular glTF extension",
"type": "object",
"description": "glTF extension that defines the strength of the specular reflection.",
"allOf": [ { "$ref": "glTFProperty.schema.json" } ],
"properties": {
"specularFactor": {
"type": "number",
"description": "The strength of the specular reflection.",
"default": 1.0,
"minimum": 0.0,
"maximum": 1.0,
"gltf_detailedDescription": "This parameter scales the amount of specular reflection on non-metallic surfaces. It has no effect on metals."
},
"specularTexture": {
"allOf": [ { "$ref": "textureInfo.schema.json" } ],
"description": "A 1-channel luminance texture that defines the strength of the specular reflection.",
"gltf_detailedDescription": "A 1-channel luminance texture that defines the strength of the specular reflection. Will be multiplied by specularFactor."
},
"specularColorFactor": {
"type": "array",
"items": {
"type": "number",
"minimum": 0.0,
"maximum": 1.0
},
"description": "The F0 RGB color of the specular reflection.",
"default": [ 1.0, 1.0, 1.0 ],
"minItems": 3,
"maxItems": 3,
"gltf_detailedDescription": "This is an additional RGB color parameter that tints the specular reflection of non-metallic surfaces. At grazing angles, the reflection still blends to white, and the parameter has not effect on metals. The value is linear."
},
"specularColorTexture": {
"allOf": [ { "$ref": "textureInfo.schema.json" } ],
"description": "An 1-channel luminance or 3-channel RGB texture that defines the F0 color of the specular reflection.",
"gltf_detailedDescription": "A luminance or RGB texture that defines the F0 color of the specular reflection. Will be multiplied by specularColorFactor."
},
"extensions": { },
"extras": { }
}
}