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
     property string colorString: specularGlossyMode ? "Albedo" : "Base"

     ColumnLayout {
         width: rootView.availableWidth

         MarkdownLabel {

             text: `# Alpha Transparency
 Material transparency can be achieved through Alpha Blending. The preferred method
 is to just use the Alpha channel of the ${rootView.colorString} Color property. This is just part
 of the ${rootView.colorString} Color and can be set either through the scalar ${rootView.colorString} Color value or by
 using a Texture for the ${rootView.colorString} Color that contains an alpha channel. When using this
 method it is important to set the correct Alpha mode to get the desired effect.`
         }

         MarkdownLabel {
             text: `## ${rootView.colorString} Color Alpha`
         }
         RowLayout {
             Label {
                 text: "Alpha (" + rootView.targetMaterial.baseColor.a.toFixed(2) + ")"
                 Layout.fillWidth: true
             }
             Slider {
                 from: 0
                 to: 1
                 value: rootView.targetMaterial.baseColor.a
                 onValueChanged: rootView.targetMaterial.baseColor.a = value
             }
         }

         MarkdownLabel {
             text: `## Alpha Mode
 The Alpha Mode defines how the alpha channel of the ${rootView.colorString} Color is used by the
 material.  If the mode is set to *Default* and you adjust the alpha value of ${rootView.colorString}
 Color you should notice a grid pattern.  That is because the *Default* mode will
 just write the alpha value to the output surface without blending. In our case
 there just so happens to be a grid pattern behind the 3D Viewport to demonstrate
 this effect. In this case the blend is with the 2D scene, not the 3D scene.

 To do Alpha Blending with the 3D scene the mode should be set to *Blend*.  If you
 know an item should always be opaque and you want to just ignore the alpha
 value all together, then the mode should be set to *Opaque* which will avoid the
 alpha passthrough effect you get with the *Default* mode.

 The last mode is *Mask* which works in conjunction with the Alpha Cutoff property.
 If the Alpha is greater than the value in Alpha Cutoff, it will be rendered, and
 if it is not then it will not. This is useful for certain effects, as well as
 rendering leaves using on a plane and an image with alpha.`
         }
         ComboBox {
             id: alphaModeComboBox
             textRole: "text"
             valueRole: "value"
             implicitContentWidthPolicy: ComboBox.WidestText
             onActivated: rootView.targetMaterial.alphaMode = currentValue
             Component.onCompleted: currentIndex = indexOfValue(rootView.targetMaterial.alphaMode)
             model: [
                 { value: PrincipledMaterial.Default, text: "Default"},
                 { value: PrincipledMaterial.Blend, text: "Blend"},
                 { value: PrincipledMaterial.Opaque, text: "Opaque"},
                 { value: PrincipledMaterial.Mask, text: "Mask"}
             ]
         }

         VerticalSectionSeparator {}

         MarkdownLabel {
             text: `## Alpha Cutoff
 To demonstrate the behavior of Alpha Cutoff with the *Mask* Alpha Mode we need to
 have a ${rootView.colorString} Color map with an Alpha map. Pressing the \"Enable Alpha Mask\"
 button will setup a ${rootView.specularGlossyMode ? "AlbedoMap" : "BaseColorMap"} that looks like this:`
         }
         Item {
             implicitHeight: 256
             implicitWidth: 256
             Image {
                 anchors.fill: parent
                 source: "maps/grid.png"
                 fillMode: Image.Tile
                 horizontalAlignment: Image.AlignLeft
                 verticalAlignment: Image.AlignTop
                 Image {
                     anchors.fill: parent
                     source: "maps/alpha_gradient.png"
                 }
             }
         }

         Button {
             property bool isEnabled: false
             property Texture revertTexture: null
             text: isEnabled ? rootView.specularGlossyMode ? "Revert Albedo Map" : "Revert Base Color Map" : "Enable Alpha Mask"
             onClicked:  {
                 if (!isEnabled) {
                     if (rootView.specularGlossyMode) {
                         revertTexture = rootView.targetMaterial.albedoMap
                         rootView.targetMaterial.albedoColor.a = 1.0
                         rootView.targetMaterial.albedoMap = alphaGradientTexture
                     } else {
                         revertTexture = rootView.targetMaterial.baseColorMap
                         rootView.targetMaterial.baseColor.a = 1.0
                         rootView.targetMaterial.baseColorMap = alphaGradientTexture
                     }
                     rootView.targetMaterial.alphaMode = PrincipledMaterial.Mask
                     alphaModeComboBox.currentIndex = alphaModeComboBox.indexOfValue(rootView.targetMaterial.alphaMode)
                     isEnabled = true
                 } else {
                     if (specularGlossyMode)
                         rootView.targetMaterial.albedoMap = revertTexture
                     else
                         rootView.targetMaterial.baseColorMap = revertTexture
                     revertTexture = null
                     isEnabled = false
                 }
             }
         }

         Texture {
             id: alphaGradientTexture
             source: "maps/alpha_gradient.png"
         }

         RowLayout {
             Label {
                 text: "Alpha Cutoff (" + rootView.targetMaterial.alphaCutoff.toFixed(2) + ")"
                 Layout.fillWidth: true
             }
             Slider {
                 from: 0
                 to: 1
                 value: rootView.targetMaterial.alphaCutoff
                 onValueChanged: rootView.targetMaterial.alphaCutoff = value
             }
         }

         VerticalSectionSeparator {}

         MarkdownLabel {
             text: `## Culling
 While not strictly related to transparency the concept of face culling is
 relevant to getting the desired results. If you cut holes into the models
 you see that the inside faces of the models don't render.  This is because
 *Back Face* culling is on by default. The culling property decides which side
 of a triangle being rendered gets culled (discarded). By changing the cull
 mode of the material to *No Culling* both sides of geometry will be rendered`
         }
         ComboBox {
             id: cullModeComboBox
             textRole: "text"
             valueRole: "value"
             implicitContentWidthPolicy: ComboBox.WidestText
             onActivated: rootView.targetMaterial.cullMode = currentValue
             Component.onCompleted: currentIndex = indexOfValue(rootView.targetMaterial.cullMode)
             model: [
                 { value: Material.BackFaceCulling, text: "Back Face"},
                 { value: Material.FrontFaceCulling, text: "Front Face"},
                 { value: Material.NoCulling, text: "None"}
             ]
         }

         VerticalSectionSeparator {}

         MarkdownLabel {
             text: `## Depth Draw Mode
 Maybe you noticed that when the Blend Alpha Mode is enabled that one of the
 models doesn't always look correct depending on the angle of viewing. That is
 because while the rendering order of individual models are determined based on
 distance they are to the camera, so models have multiple parts and how they are
 rendered depends on the order the triangles appear in.  This isn't something
 that can be fixed for every model, so instead we use a feature called the depth
 buffer. We do not normally write to the depth buffer for transparent items though,
 but sometimes it is still necessary to get the correct rendering.

 The default Mode is *Opaque Only*, which means the material will only write to the
 depth buffer if the material doesn't use transparency.

 *Always* means that the material will write to the Depth buffer no matter what it does.

 *Never* means that the material will never write to the Depth buffer even though it may
 be opaque.

 The special mode, and the one likely best suited to fix Alpha Cutoff related depth
 errors is *Opaque Prepass*. In this case before any item is rendered, a separate pass is
 done where materials will write their opaque pixels to the depth buffer while skipping
 any transparent pixels. Then in the main pass everything is done as normal, but now will
 be rendered correctly.
 `
         }
         ComboBox {
             id: depthDrawModeComboBox
             textRole: "text"
             valueRole: "value"
             implicitContentWidthPolicy: ComboBox.WidestText
             onActivated: rootView.targetMaterial.depthDrawMode = currentValue
             Component.onCompleted: currentIndex = indexOfValue(rootView.targetMaterial.depthDrawMode)
             model: [
                 { value: Material.OpaqueOnlyDepthDraw, text: "Opaque Only"},
                 { value: Material.AlwaysDepthDraw, text: "Always"},
                 { value: Material.NeverDepthDraw, text: "Never"},
                 { value: Material.OpaquePrePassDepthDraw, text: "Opaque Prepass"}
             ]
         }

         VerticalSectionSeparator {}

         MarkdownLabel {
             text: `## Opacity
 Another option for transparency is through the Opacity properties. Most effects
 can be achieved using only the above properties, but these additional properties
 will set the minimum level of opacity for the properties above.  It is also import
 to point out that by using any of these Opacity properties will force alpha blending.`
         }

         RowLayout {
             Label {
                 text: "Opacity Factor  (" + rootView.targetMaterial.opacity.toFixed(2) + ")"
                 Layout.fillWidth: true
             }
             Slider {
                 from: 0
                 to: 1
                 value: rootView.targetMaterial.opacity
                 onValueChanged: rootView.targetMaterial.opacity = value
             }
         }
         MarkdownLabel {
             text: `### Opacity (Map)
 The Opacity Map property specifies a texture to sample the Opacity value
 from. Since the Opacity property is only a single floating point value between
 0.0 and 1.0, it's only necessary to use a single color channel of the image, or
 a greyscale image. By default PrincipledMaterial will use the value in the alpha
 channel of the texture, but it's possible to change which color channel is used.
 `
         }
         ComboBox {
             id: opacityChannelComboBox
             textRole: "text"
             valueRole: "value"
             implicitContentWidthPolicy: ComboBox.WidestText
             onActivated: rootView.targetMaterial.opacityChannel = currentValue
             Component.onCompleted: currentIndex = indexOfValue(rootView.targetMaterial.opacityChannel)
             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"}
             ]
         }
         MarkdownLabel {
             text: `
 When using a Opacity Map the value sampled from the map file is multiplied by
 the value of the Opacity property. In practice this means that the maximum
 Opacity value possible will be the value set by the Opacity map is the
 value in the Opacity property. So most of the time when using a Opacity
 Map it will make sense to leave the value of Opacity to 1.0.
 `
         }
         Button {
             text: "Reset Opacity Value"
             onClicked: rootView.targetMaterial.opacity = 1.0
         }

         TextureSourceControl {
             defaultClearColor: "white"
             defaultTexture: "maps/metallic/metallic.jpg"
             onTargetTextureChanged: {
                 rootView.targetMaterial.opacityMap = targetTexture
             }
         }
     }
 }
 // qmllint enable missing-property