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