Revision control

+ // Copyright (C) 2026 The Qt Company Ltd.
+ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+ import QtQuick
+ import QtQuick.Controls
+ import QtQuick.Layouts
+ import QtQuick3D
+
+ ScrollView {
+ id: rootView
+ required property PrincipledMaterial targetMaterial
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+ width: availableWidth
+ property bool specularGlossyMode: false
+
+ ColumnLayout {
+ width: rootView.availableWidth
+
+ MarkdownLabel {
+ text: `# Iridescence
+ Iridescence is what gives a soap bubble, an oil slick and a beetle shell their
+ shifting colors. A film only a few hundred nanometers thick sits over the
+ surface, and light reflecting off its front face interferes with light that
+ went through and reflected off the back. Some wavelengths cancel out and others
+ reinforce, so a white light comes back colored, and the color changes as the
+ surface curves away from you.
+
+ Iridescence is only available on PrincipledMaterial.${rootView.specularGlossyMode ? "\n\n**The scene is currently showing the SpecularGlossyMaterial, so switch back to the PrincipledMaterial in the Basics tab to see these controls take effect.**" : ""}
+
+ Like anisotropy, this is not an extra layer of reflection. It replaces the
+ Fresnel response of the specular reflection the material already has, which
+ means it is most visible where that reflection is strongest. Turning Metalness
+ up in the Basics tab makes the effect far more dramatic; over a dielectric it
+ is deliberately subtle, because a real soap film only reflects a few percent of
+ the light that hits it.
+
+ ## Iridescence Factor
+ This blends between the material without and with the film. At \`0.0\`, the
+ default, there is no film and nothing changes.
+ `
+ }
+
+ RowLayout {
+ Label {
+ text: "Iridescence Factor (" + rootView.targetMaterial.iridescenceFactor.toFixed(2) + ")"
+ Layout.fillWidth: true
+ }
+ Slider {
+ from: 0
+ to: 1
+ value: rootView.targetMaterial.iridescenceFactor
+ onValueChanged: rootView.targetMaterial.iridescenceFactor = value
+ }
+ }
+
+ MarkdownLabel {
+ text: `### Iridescence Map
+ The Iridescence Map is a single channel texture multiplied against the
+ Iridescence Factor, so it can fade the film in and out across the surface.
+ `
+ }
+
+ ComboBox {
+ id: iridescenceChannelComboBox
+ textRole: "text"
+ valueRole: "value"
+ implicitContentWidthPolicy: ComboBox.WidestText
+ onActivated: rootView.targetMaterial.iridescenceChannel = currentValue
+ Component.onCompleted: currentIndex = indexOfValue(rootView.targetMaterial.iridescenceChannel)
+ 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.iridescenceMap = targetTexture
+ }
+ }
+
+ MarkdownLabel {
+ text: `## Index Of Refraction
+ This is the index of refraction of the film itself, not of the material under
+ it. Together with the thickness it decides which colors come back. The default
+ of \`1.3\` is about that of a soap film; raising it strengthens the effect and
+ shifts the palette.
+ `
+ }
+
+ RowLayout {
+ Label {
+ text: "Iridescence IOR (" + rootView.targetMaterial.iridescenceIndexOfRefraction.toFixed(2) + ")"
+ Layout.fillWidth: true
+ }
+ Slider {
+ from: 1
+ to: 3
+ value: rootView.targetMaterial.iridescenceIndexOfRefraction
+ onValueChanged: rootView.targetMaterial.iridescenceIndexOfRefraction = value
+ }
+ }
+
+ MarkdownLabel {
+ text: `## Thickness
+ Thickness is measured in nanometers, and it is what picks the hue. Sweeping it
+ across the range of visible light, roughly \`200\` to \`800\` nanometers, walks
+ through the whole sequence of colors the film can produce, then begins to
+ repeat as higher interference orders take over.
+
+ With no Thickness Map the film is uniformly Thickness Maximum thick, and the
+ minimum has no effect. Add a map and the two values become the ends of the
+ range that the map interpolates between.
+ `
+ }
+
+ RowLayout {
+ Label {
+ text: "Thickness Minimum (" + rootView.targetMaterial.iridescenceThicknessMinimum.toFixed(0) + " nm)"
+ Layout.fillWidth: true
+ }
+ Slider {
+ from: 0
+ to: 1200
+ value: rootView.targetMaterial.iridescenceThicknessMinimum
+ onValueChanged: rootView.targetMaterial.iridescenceThicknessMinimum = value
+ }
+ }
+
+ RowLayout {
+ Label {
+ text: "Thickness Maximum (" + rootView.targetMaterial.iridescenceThicknessMaximum.toFixed(0) + " nm)"
+ Layout.fillWidth: true
+ }
+ Slider {
+ from: 0
+ to: 1200
+ value: rootView.targetMaterial.iridescenceThicknessMaximum
+ onValueChanged: rootView.targetMaterial.iridescenceThicknessMaximum = value
+ }
+ }
+
+ MarkdownLabel {
+ text: `### Thickness Map
+ A single channel texture interpolating the film thickness between the minimum
+ and the maximum. The glTF extension packs thickness into the green channel,
+ which is why that is the default here.
+ `
+ }
+
+ ComboBox {
+ id: thicknessChannelComboBox
+ textRole: "text"
+ valueRole: "value"
+ implicitContentWidthPolicy: ComboBox.WidestText
+ onActivated: rootView.targetMaterial.iridescenceThicknessChannel = currentValue
+ Component.onCompleted: currentIndex = indexOfValue(rootView.targetMaterial.iridescenceThicknessChannel)
+ 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.iridescenceThicknessMap = targetTexture
+ }
+ }
+ }
+ }