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

  • 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.

Who am I?

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.

How do we do it?

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.

Details

New X3DOneSidedMaterialNode node with emissive and normalmap textures

Descendant of X3DMaterialNode. The X3DOneSidedMaterialNode (which is a basis for Material, PhysicalMaterial, UnlitMaterial in X3D v4) has fields:

  • To specify emission (emissiveColor, emissiveTexture and emissiveTextureMapping). "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 extended to specify textures

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 and ambientTextureMapping
  • diffuseTexture and diffuseTextureMapping
  • specularTexture and specularTextureMapping
  • shininessTexture and shininessTextureMapping

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 now ambient = light.ambientIntensity * material.ambientIntensity * material.diffuseColor * material.ambientTexture. Previously it was ambient = 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 as diffuseTexture. 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 multiplies diffuseColor (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.

New PhysicalMaterial node

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.

New UnlitMaterial node

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.

(Deferred to X3D 4.1) New EnvironmentLight node

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]
}

New Appearance.backMaterial field, deprecate TwoSidedMaterial node

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.

Treatment of RGB and grayscale textures consistent

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

Links

TODOs deferred to X3D 4.1

⚠️ **GitHub.com Fallback** ⚠️