X3D version 4: New features of materials, lights and textures - michaliskambi/x3d-tests GitHub Wiki
This is a summary of changes to X3Dv4 by Michalis Kamburelis.
These changes are now merged to the X3D 4.0 and are part of X3D 4 spec.
Table of Contents:
- Goals
- Who am I?
- How do we do it?
-
Details
- New X3DOneSidedMaterialNode node with emissive and normalmap textures
- Material node extended to specify textures
- New PhysicalMaterial node
- New UnlitMaterial node
- (Deferred to X3D 4.1) New EnvironmentLight node
- New Appearance.backMaterial field, deprecate
TwoSidedMaterial
node - Treatment of RGB and grayscale textures consistent
- Links
- TODOs deferred to X3D 4.1
-
Add Physically-Based Rendering to X3D. For this, we add new X3D node
PhysicalMaterial
. -
The specification of
PhysicalMaterial
is deliberately perfectly consistent with glTF -- so X3D benefits, and also browsers that want to support glTF format benefit, as now it's easy to handle glTF by converting it to X3D nodes "under the hood". -
We also add
UnlitMaterial
node. See Why is UnlitMaterial useful. -
We also significantly extend
Material
(using Phong lighting model, as in X3D 3). You can now use normal maps, specular maps etc. -- all material parameters can be adjusted by textures. -
(Deferred to X3D 4.1) Image-based lighting (for both Phong and PBR) using new
EnvironmentLight
node -
All of this in a way completely backward-compatible with X3D 3. Your existing files will continue to render correctly. Even if you bump the header from
#X3D V3.2
to#X3D V4.0
-- merely upgrading the X3D version number doesn't mess the look of your models. -
We also address Making RGB and grayscale textures consistent (this one is not necessarily backward-compatible, but also X3D browsers are incompatible with each other at this detail as of X3D 3; we want to fix this). And some other related things.
I'm the author of Castle Game Engine (CGE) and view3dscene. Within CGE:
- we use X3D nodes as a scene graph in our engine. In simple terms: you have an API to build/modify X3D nodes in our engine.
- we also use X3D as a format to interchange assets (along with other formats, like glTF).
As of 2020-07, almost all of the changes described on this page are already implemented in CGE. So you can download view3dscene 3.19 to get X3D browser that supports most of these features right now.
The pull request to the Web3D spec (only for Web3D members) contained them all. It is now merged to master branch.
There are a number of test files to check these features in https://github.com/michaliskambi/x3d-tests/ . You can download this repository, and open X3D files in the pbr/
subdirectory with snapshot version of view3dscene.
Descendant of X3DMaterialNode
. The X3DOneSidedMaterialNode
(which is a basis for Material
, PhysicalMaterial
, UnlitMaterial
in X3D v4) has fields:
- To specify emission (
emissiveColor
,emissiveTexture
andemissiveTextureMapping
). "Emission" makes sense in all possible lighting models. - Normal maps (for bump mapping) (
normalTexture
,normalTextureMapping
,normalScale
).
Reasons: New useful features :) These are common features in modern 3D graphics.
We need a new node for this (we cannot just add them to X3DMaterialNode
) because the (deprecated) TwoSidedMaterial
should not have these additions.
Exact new specification (not showing fields inherited from X3DAppearanceChildNode
):
X3DOneSidedMaterialNode : X3DMaterialNode {
SFColor [in,out] emissiveColor 0 0 0 # [0, 1]
SFNode [in,out] emissiveTexture NULL # [X3DSingleTextureNode]
SFString [in,out] emissiveTextureMapping ""
SFNode [in,out] normalTexture NULL # [X3DTexture2DNode]
SFString [in,out] normalTextureMapping ""
SFFloat [in,out] normalScale 1 # [0, infinity]
}
Implementors note: emissiveColor
is moved here, from Material
.
Note: normalScale
is a trick from glTF to make the normal map effect more emphasized or less emphasized. glTF also has this, see normalTextureInfo.scale. The exact equation is
normal.xyz = normalize(
(textureSample(normalTexture).rgb * vec3(2,2,2) - vec3(1,1,1)) *
vec3(normalScale, normalScale, 1)
)
Do not confuse this with CommonSurfaceShader.normalScale which is used to unpack normalTexture. We are inconsistent here with CommonSurfaceShader
-- using the same field name, but for a different purpose. TODO: should we change our name?
Material
node now includes fields to configure all it's parameters by textures. The texture field names are deliberately consistent with the scalar/vector field names.
So you get new Material
fields like:
-
ambientTexture
andambientTextureMapping
-
diffuseTexture
anddiffuseTextureMapping
-
specularTexture
andspecularTextureMapping
-
shininessTexture
andshininessTextureMapping
Reasons: Ability to configure every material "slot" by a texture is a popular feature now in 3D graphics, present also in Blender, Unity materials, as well as CommonSurfaceShader. It is also consistent with new PhysicalMaterial
.
Backward compatibility: almost 100%. You can still specify textures within Appearance.texture
, they will work as before (will be applied to diffuse). And Material
node still performs Phong shading, 100% compatible with X3D 3.
-
The one change in compatibility is that now the
Appearance.texture
doesn't affect the final ambient color. That is, final ambient is nowambient = light.ambientIntensity * material.ambientIntensity * material.diffuseColor * material.ambientTexture
. Previously it wasambient = light.ambientIntensity * material.ambientIntensity * (material.diffuseColor or material.diffuseTexture)
. This change makes ambient more independent from other factors, just like diffuse and specular are independent.You can always pass as
ambientTexture
the same value asdiffuseTexture
. This effectively brings back old behavior. -
The bigger, but coming from a long time, change in compatibility is that now grayscale textures always work just like RGB textures with equal red = green = blue channels, and so
diffuseTexture
always multipliesdiffuseColor
(doesn't replace it). I wrote about this change previously here: Make RGB and grayscale textures treatment consistent, I did tests here (showing that browsers apply this bad rule inconsistently) and there was a consensus on x3d-public that we should just fix it in X3D 4.0.
Exact new specification (not showing fields inherited from X3DOneSidedMaterialNode
):
Material : X3DOneSidedMaterialNode {
SFFloat [in,out] ambientIntensity 0.2 [0,1]
SFNode [in,out] ambientTexture NULL [X3DSingleTextureNode]
SFString [in,out] ambientTextureMapping ""
SFColor [in,out] diffuseColor 0.8 0.8 0.8 [0,1]
SFNode [in,out] diffuseTexture NULL [X3DSingleTextureNode]
SFString [in,out] diffuseTextureMapping ""
SFFloat [in,out] occlusionStrength 1 [0,1]
SFNode [in,out] occlusionTexture NULL [X3DTexture2DNode]
SFString [in,out] occlusionTextureMapping ""
SFFloat [in,out] shininess 0.2 [0,1]
SFNode [in,out] shininessTexture NULL [X3DSingleTextureNode]
SFString [in,out] shininessTextureMapping ""
SFColor [in,out] specularColor 0 0 0 [0,1]
SFNode [in,out] specularTexture NULL [X3DSingleTextureNode]
SFString [in,out] specularTextureMapping ""
SFFloat [in,out] transparency 0 [0,1]
}
Note: specularTexture
and shininessTexture
deliberately look at different channels (specular in RGB, shininess in A). They can naturally use a single texture image. This is consistent with CommonSurfaceShader
defaults.
The xxxTextureMapping
fields refer to the mapping
identifier at each texture coordinate / texture transformation node. See the PR for details.
This node is an alternative to Material
node (that is, you can place it within Appearance.material
slot). It indicates we should use physically-based calculation for lighting.
It has different parameters (and equations) than Material
node.
Reasons: This is a common feature on modern 3D graphics (Blender, Unity, Unreal Engine, glTF -- some examples of software/formats that embrace physical lighting model). Our PhysicalMaterial
is also deliberately designed to "match" glTF 2.0 parameters, to allow converting glTF 2.0 into X3D nodes a straightforward process.
Exact new specification (not showing fields inherited from X3DOneSidedMaterialNode
):
PhysicalMaterial : X3DOneSidedMaterialNode {
SFColor [in,out] baseColor 1 1 1 [0,1]
SFNode [in,out] baseTexture NULL [X3DSingleTextureNode]
SFString [in,out] baseTextureMapping ""
SFFloat [in,out] metallic 1 [0,1]
SFNode [in,out] metallicRoughnessTexture NULL [X3DSingleTextureNode]
SFString [in,out] metallicRoughnessTextureMapping ""
SFFloat [in,out] occlusionStrength 1 [0,1]
SFNode [in,out] occlusionTexture NULL [X3DTexture2DNode]
SFString [in,out] occlusionTextureMapping ""
SFFloat [in,out] roughness 1 [0,1]
SFFloat [in,out] transparency 0 [0,1]
}
Note: metallicRoughnessTexture
and occlusionTexture
deliberately look at different channels (metallicRoughness in GB, occlusion in R). They can naturally use a single texture image. This is consistent with glTF.
This node is an alternative to Material
node (that is, you can place it within Appearance.material
slot). It indicates that shape is unlit (not affected by light sources). It allows to specify emissiveColor
, emissiveTexture
(inherited from X3DOneSidedMaterialNode
in fact).
Reason: While "unlit" material was already possible in X3D 3, the existing ways to achieve it are problematic, see Why is UnlitMaterial useful. Having an explicit node to request "unlit" allows to configure the color/transparency (consistent with Material
and PhysicalMateral
) using vector/scalar and a texture (emissiveColor, emissiveTexture, transparency). It interacts naturally with Color
/ ColorRGBA
too (which is also important for glTF compatibility with glTF unlit material).
Exact new specification (not showing fields inherited from X3DOneSidedMaterialNode
):
UnlitMaterial : X3DOneSidedMaterialNode {
SFFloat [in,out] transparency 0 # [0,1]
# Also: emissiveColor default changes to 1 1 1
}
Note: The burden to implement this node should be minimal. That's because the X3D 3 spec already describes a "pure white unlit" shape (when material=NULL) that interacts nicely with Appearance.texture
and Color
/ ColorRGBA
. Our UnlitMaterial
is just a more explicit way of requesting it, with customizable color/transparency/texture, consistent with Material
and PhysicalMaterial
.
New light node, that "casts" light from a sphere around the entire scene, with the colors specified using a cubemap.
Reason: This is a modern way to define surrounding light. While the existing lights (point, directional, spot) will also continue work (on both Material
and PhysicalMaterial
), but environment light allows to achieve much more impressive lighting.
Exact new specification (not showing fields inherited from X3DLightNode
):
EnvironmentLight : X3DLightNode {
SFNode [in,out] diffuseTexture NULL [X3DEnvironmentTextureNode]
SFNode [in,out] specularTexture NULL [X3DEnvironmentTextureNode]
}
Appearance {
...
SFNode [in,out] backMaterial NULL [X3DOneSidedMaterialNode]
}
TwoSidedMaterial
node with separateBackColor=FALSE
is useless, as we found out. The solid=TRUE
spec already requires you to render the shape with 2-sided lighting.
TwoSidedMaterial
node with separateBackColor=TRUE
may have some usage. But it's only for Phong lighting. Adding TwoSidedPhysicalMaterial
and TwoSidedUnlitMaterial
would be cumbersome (to specify and implement), so instead Appearance.backMaterial
will allow you to specify parameters for back faces. There are some limitations to make implementation easy (e.g. you cannot use Phong Material
for front faces, and PBR PhysicalMaterial
for back faces; and you have to use the same textures).
Reason: Without this, physically-based lighting would be "crippled" compared to Phong lighting.
Since X3D 4, the specification will always treat the "grayscale texture" the same as an "RGB texture with the red, green and blue components equal, on all pixels".
Reason: The X3D 3 specification wanted something more complicated -- to detect whether a texture is grayscale or RGB (by looking at image header, ideally, although in practice some viewers do this by analyzing image contents) and treat them differently: grayscale texture is multiplied by color, while RGB texture replaces the color. I argue in Make RGB and grayscale textures treatment consistent why I consider this a bad idea -- it is not expected by X3D authors, it is not consistent with behavior of everyone other graphic software, it's an unnecessary limit on X3D authors. I think this rule was introduced in the ancient times, hoping that "replacing color with RGB texture" will be more efficient than "component-wise multiplication with RGB texture", but it really doesn't matter on modern GPUs that have this operation extremely optimized.
Backward compatibility: While theoretically it breaks X3D 3 compatibility, in practice the cross-browser compatibility of this in X3D 3 doesn't exist. See Make RGB and grayscale textures treatment consistent and tests here. Every browser now does something different here, and only 1 browser out of 7 manages to be 100% conforming to the X3D 3 specification.
The new rule can be applied by browsers conditionally, like if (X3D header indicates version >= 4) then (multiply texture with color) else (do whatever you were doing previously)
.
-
Pull request: https://github.com/Web3DConsortium/X3D/pull/8 (requires access to Web3D closed repository)
-
How to add PBR to X3D? contains a huge collection of thoughts and reasons behind some specification decisions.
-
Converting glTF to X3D correspondence between glTF and X3D concepts.
-
(Deferred to X3D 4.1) Image Based Lighting (EnvironmentLight node)
-
(Deferred to X3D 4.1) Gamma correction. We need gamma correction to actually reproduce glTF rendering results correctly, since glTF requires gamma correction, it's not even optional in glTF.
-
Castle Game Engine implements gamma correction and makes it default for
PhysicalMaterial
. - X3DOM implements gamma correction and makes it default for all materials.
- Further discussion needed on what to do within X3D spec.
-
Castle Game Engine implements gamma correction and makes it default for