- Qt 6.7
- Qt Quick 3D
- Qt Quick 3D Examples and Tutorials
- Qt Quick 3D - Principled Material Example
- Qt Quick 3D | Commercial or GPLv3

# Qt Quick 3D - Principled Material Example

     // Copyright (C) 2023 The Qt Company Ltd.
     // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

     import QtQuick
     import QtQuick.Controls
     import QtQuick.Layouts
     import QtQuick3D

     // qmllint disable missing-property
     // Disabling missing-property because the targetMaterial property
     // will either be a PrincipaledMaterial or SpecularGlossyMaterial
     // but the shared properties are not part of the common base class
     ScrollView {
         id: rootView
         required property Material targetMaterial
         ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
         width: availableWidth
         property bool specularGlossyMode: false

         ColumnLayout {
             width: rootView.availableWidth

             MarkdownLabel {
                 text: `# Refraction
     The properties in this section would probably be best described as advanced
     transparency. In the previous section on transparency we discussed alpha
     blending, which is about blending colors together using the alpha channel of
     the material's color. What makes the transparency effects in this section
     different is that the goal is to handle transparency in a way that more
     physically represents how light works. To achieve this blending requires that
     all content that is blended with needs to be rendered to a texture in separate
     pass. Using any properties on this page is as expensive as rendering all opaque
     content in the scene at least twice. Once to get the background items, and again
     including the items using the refractive transparency effects. The advantage of
     this approach though is that we are not limited in how we can blend, but comes
     with the caveat that only opaque items are visible through refracted objects.`
             }

             MarkdownLabel {
                 text: `## Transmission
     Transmission refers to lights ability to transmit, or pass through a surface.  Not all
     light will penetrate a surface and some will still be reflected as a specular
     reflection. This ability to transmit light only concerns the surface of a material, and
     not its depth. Without the use of further properties in this section, a material that
     has transmission alone can be assumed to be infinitely thin.
     ### Transmission Factor
     The Transmission Factor property controls the percentage of light that is transmitted by
     a materials surface.  This value is a single value between 0.0 meaning no light is
     transmitted and 1.0 meaning that 100% of the light that penetrates the surface of the
     material is transmitted through.

     Note: If you adjust the Transmission Factor to 1.0 and you still can't see
     through the models, it could be that your material is metallic. Metallic materials
     cannot transmit light.`
             }
             Button {
                 text: "Reset Metalness to 0.0"
                 onClicked: rootView.targetMaterial.metalness = 0.0
             }

             RowLayout {
                 Label {
                     text: "Transmission Factor (" + rootView.targetMaterial.transmissionFactor.toFixed(2) + ")"
                     Layout.fillWidth: true
                 }
                 Slider {
                     from: 0
                     to: 1
                     value: rootView.targetMaterial.transmissionFactor
                     onValueChanged: rootView.targetMaterial.transmissionFactor = value
                 }
             }
             MarkdownLabel {
                 text: `### Transmission Map
     Like most other single floating point value properties, the Transmission property
     also allows for the use of a single channel of a texture to map transmission values
     to a mesh. And like many other textures, the final value of transmission will be
     the multiplication of Transmission Factor and the value sampled from Transmission Map.
     So when using a Transmission Map, it typically makes sense to set the Transmission
     Factor to 1.0.
     `
             }
             Button {
                 text: "Reset Transmission"
                 onClicked: rootView.targetMaterial.transmissionFactor = 1.0
             }

             ComboBox {
                 id: transmissionChannelComboBox
                 textRole: "text"
                 valueRole: "value"
                 implicitContentWidthPolicy: ComboBox.WidestText
                 onActivated: rootView.targetMaterial.transmissionChannel = currentValue
                 Component.onCompleted: currentIndex = indexOfValue(rootView.targetMaterial.transmissionChannel)
                 model: [
                     { value: PrincipledMaterial.R, text: "Red Channel"},
                     { value: PrincipledMaterial.G, text: "Green Channel"},
                     { value: PrincipledMaterial.B, text: "Blue Channel"},
                     { value: PrincipledMaterial.A, text: "Alpha Channel"}
                 ]
             }
             TextureSourceControl {
                 defaultTexture: "maps/noise.png"
                 defaultClearColor: "black"
                 onTargetTextureChanged: {
                     rootView.targetMaterial.transmissionMap = targetTexture
                 }
             }

             VerticalSectionSeparator {}

             ColumnLayout {
                 visible: !rootView.specularGlossyMode
                 MarkdownLabel {
                     text: `## Index of Refraction (IOR)
     The Index of Refraction or refraction index refers to the physical property of
     how fast light passes through a material. This number then is used to determine
     how light is bent or refracted when it enters a material. Since this value is
     a physical value, it's possible to plug in the same values as real life
     materials as well. The default value that the PrincipledMaterial uses for all
     lighting calculations is 1.5, which is very close to window glass. Below are
     several other materials' IOR values that will produce different results when
     used with a refractive material (especially ones with thickness).`
                 }

                 ComboBox {
                     id: iorChannelComboBox
                     textRole: "text"
                     valueRole: "value"
                     implicitContentWidthPolicy: ComboBox.WidestText
                     onActivated: rootView.targetMaterial.indexOfRefraction = currentValue
                     Component.onCompleted: currentIndex = 0
                     model: [
                         { value: 1.5, text: "Custom"},
                         { value: 1.4, text: "Acrylic glass"},
                         { value: 1.0, text: "Air"},
                         { value: 1.33, text: "Water"},
                         { value: 1.76, text: "Sapphire"},
                         { value: 2.42, text: "Diamond"}
                     ]
                 }
                 RowLayout {
                     Label {
                         text: "IOR (" + iorSlider.value.toFixed(2) + ")"
                         Layout.fillWidth: true
                     }
                     Slider {
                         id: iorSlider
                         from: 1.0
                         to: 3.0
                         value: rootView.targetMaterial.indexOfRefraction ?? 1.5
                         onValueChanged: {
                             if (iorChannelComboBox.currentValue != value)
                                 iorChannelComboBox.currentIndex = 0;
                             rootView.targetMaterial.indexOfRefraction = value
                         }
                     }
                 }

                 VerticalSectionSeparator {}
             }

             MarkdownLabel {
                 text: `## Thickness
     The Thickness properties are for giving refractive materials volume.  A
     transmissive material alone is considered to be infinitely thin so any
     Index of Refraction values will only affect the specular and fresnel
     effects of a material.  However when a transmissive material is given
     volume via the thickness properties, then light passing through the
     material is bent as it passes through.
     ### Thickness Factor
     The Thickness Factor property defines the thickness of the volume beneath
     the surface of the mesh.  Unlike other factors, the Thickness Factor
     Property is not clipped at 1.0, but rather refers to the distance in the
     coordinate space of the mesh itself.  When used in conjunction with the
     Thickness Map, the Thickness Factor would be the point of maximum thickness.
     `
             }

             RowLayout {
                 Label {
                     text: "Thickness Factor (" + rootView.targetMaterial.thicknessFactor.toFixed(2) + ")"
                     Layout.fillWidth: true
                 }
                 Slider {
                     from: 0
                     to: 100.0
                     value: rootView.targetMaterial.thicknessFactor
                     onValueChanged: rootView.targetMaterial.thicknessFactor = value
                 }
             }

             MarkdownLabel {
                 text: `### Thickness Map
     The Thickness Map is a single channel (greyscale) texture that defines
     the thickness (or volume) of a mesh.  The values sampled from the
     Thickness Map are multiplied against the value of Thickness Factor to
     get the thickness of the mesh under the surface in the meshe's coordinate
     space.  Thickness Maps are baked in 3D content creation tools using ray
     tracers. The process of baking thickness is similar to the process for
     baking ambient occlusion, but the rays are cast in the opposite direction
     of the surface normal (into the mesh).  Darker values represent thin
     sections, and lighter values will be thicker.  Provided is a baked thickness
     map of the Monkey model. (The other models would have uniform thicknesses).`
             }

             ComboBox {
                 id: thicknessChannelComboBox
                 textRole: "text"
                 valueRole: "value"
                 implicitContentWidthPolicy: ComboBox.WidestText
                 onActivated: rootView.targetMaterial.thicknessChannel = currentValue
                 Component.onCompleted: currentIndex = indexOfValue(rootView.targetMaterial.thicknessChannel)
                 model: [
                     { value: PrincipledMaterial.R, text: "Red Channel"},
                     { value: PrincipledMaterial.G, text: "Green Channel"},
                     { value: PrincipledMaterial.B, text: "Blue Channel"},
                     { value: PrincipledMaterial.A, text: "Alpha Channel"}
                 ]
             }
             TextureSourceControl {
                 defaultTexture: "maps/monkey_thickness.jpg"
                 defaultClearColor: "black"
                 onTargetTextureChanged: {
                     rootView.targetMaterial.thicknessMap = targetTexture
                 }
             }

             VerticalSectionSeparator {}

             MarkdownLabel {
                 text: `## Attenuation
     As light passes through a volume it will be subject to absorption and scattering.
     To simulate this interaction, two properties are provided for determining this
     attenuation.
     ### Attenuation Color
     The Attenuation Color property refers to the color that white light turns into
     due to the absorption when reaching the attenuation distance.
     `
             }
             RowLayout {
                 Label {
                     text: "Red (" + rootView.targetMaterial.attenuationColor.r.toFixed(2) + ")"
                     Layout.fillWidth: true
                 }
                 Slider {
                     from: 0
                     to: 1
                     value: rootView.targetMaterial.attenuationColor.r
                     onValueChanged: rootView.targetMaterial.attenuationColor.r = value
                 }
             }
             RowLayout {
                 Label {
                     text: "Green  (" + rootView.targetMaterial.attenuationColor.g.toFixed(2) + ")"
                     Layout.fillWidth: true
                 }

                 Slider {
                     from: 0
                     to: 1
                     value: rootView.targetMaterial.attenuationColor.g
                     onValueChanged: rootView.targetMaterial.attenuationColor.g = value
                 }
             }
             RowLayout {
                 Label {
                     text: "Blue (" + rootView.targetMaterial.attenuationColor.b.toFixed(2) + ")"
                     Layout.fillWidth: true
                 }

                 Slider {
                     from: 0
                     to: 1
                     value: rootView.targetMaterial.attenuationColor.b
                     onValueChanged: rootView.targetMaterial.attenuationColor.b = value
                 }
             }
             MarkdownLabel {
                 text: `### Attenuation Distance
     Attenuation Distance defines material density, but does so by describing the
     average distance light must travel through the medium before interacting with a
     particle (absorption). In this case the distance is specified in world
     coordinate space (scene space). This distance can be any positive floating point
     value. This means the attenuation color will start to appear when the thickness
     is greater than the attenuation distance, with the caveat that the Attenuation
     Color assumes white light is passing through the model, so any other light will
     create a blended result. For this demonstration the slider value is limited to
     100, which should be the maximum thickness for all 3 models.`
             }

             RowLayout {
                 Label {
                     text: "Attenuation Distance (" + rootView.targetMaterial.attenuationDistance.toFixed(2) + ")"
                     Layout.fillWidth: true
                 }
                 Slider {
                     from: 0
                     to: 100
                     value: rootView.targetMaterial.attenuationDistance
                     onValueChanged:  {
                         if (value != rootView.targetMaterial.attenuationDistance)
                             rootView.targetMaterial.attenuationDistance = value
                     }
                 }
             }
         }
     }
     // qmllint enable missing-property