# PA9 More Complexity

## 1 这可是弹坑啊：Occluded Areas

在之前的凹凸方案中使用了 bump 进行法线扰动。但是 bump 产生的凹凸仅支持直射光环境下的深度错觉，没有自阴影。这种缺陷在法线贴图显示有小孔、凹痕或裂缝时最为明显，它们无法表达出足够的深度。

为了解决这一问题，这一节准备了 occlusion map。可以将其理解为一个固定的 shadow map，是材质的一部分。

{% tabs %}
{% tab title="Main" %}

<pre class="language-hlsl"><code class="lang-hlsl">Shader "Code/PA9_MoreComplexity/OccludedAreas" {
	Properties {
		_Tint ("Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Albedo", 2D) = "white" {}

		[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
		_BumpScale ("Bump Scale", Float) = 1

		[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
		[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
		_Smoothness ("Smoothness", Range(0, 1)) = 0.1

<strong>		[NoScaleOffset] _OcclusionMap ("Occlusion", 2D) = "white" {}
</strong><strong>		_OcclusionStrength("Occlusion Strength", Range(0, 1)) = 1
</strong>
		[NoScaleOffset] _EmissionMap ("Emission", 2D) = "black" {}
		_Emission ("Emission", Color) = (0, 0, 0)

		_DetailTex ("Detail Albedo", 2D) = "gray" {}
		[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
		_DetailBumpScale ("Detail Bump Scale", Float) = 1
	}

	CGINCLUDE
	#define BINORMAL_PER_FRAGMENT
	ENDCG

	SubShader {
		Pass {
			Tags {
				"LightMode" = "ForwardBase"
			}
			CGPROGRAM
			#pragma target 3.0
			#pragma shader_feature _METALLIC_MAP
			#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
<strong>			#pragma shader_feature _OCCLUSION_MAP // 仅应用于 base pase
</strong>			#pragma shader_feature _EMISSION_MAP
			#pragma multi_compile _ SHADOWS_SCREEN
			#pragma multi_compile _ VERTEXLIGHT_ON
			#pragma vertex Vert
			#pragma fragment Frag
			#define FORWARD_BASE_PASS
			#include "OccludedAreas_Light.cginc"
			ENDCG
		}

		...
	}

	CustomEditor "OccludedAreas_GUI"
}
</code></pre>

{% endtab %}

{% tab title="Light" %}

<pre class="language-hlsl"><code class="lang-hlsl">#if !defined(MORECOMPLEXITY_OCCLUDEDAREAS_LIGHT)
#define MORECOMPLEXITY_OCCLUDEDAREAS_LIGHT

#include "UnityPBSLighting.cginc"
#include "AutoLight.cginc"

float4 _Tint;
sampler2D _MainTex, _DetailTex;
float4 _MainTex_ST, _DetailTex_ST;

sampler2D _NormalMap, _DetailNormalMap;
float _BumpScale, _DetailBumpScale;

sampler2D _MetallicMap;
float _Metallic;
float _Smoothness;

<strong>sampler2D _OcclusionMap;
</strong><strong>float _OcclusionStrength;
</strong>
sampler2D _EmissionMap;
float3 _Emission;

...

<strong>float GetOcclusion(Interpolators i) {
</strong><strong>	#if defined(_OCCLUSION_MAP)
</strong><strong>		//返回 1 和 map 之间的插值（系数由 slider _OcclusionStrength 设定）
</strong><strong>		return lerp(1, tex2D(_OcclusionMap, i.uv.xy).g, _OcclusionStrength);
</strong><strong>	#else
</strong><strong>		return 1;
</strong><strong>	#endif
</strong><strong>}
</strong>
UnityIndirect CreateIndirectLight(Interpolators i, float3 viewDir) {
	UnityIndirect indirectLight;
	indirectLight.diffuse = 0;
	indirectLight.specular = 0;

	#if defined(VERTEXLIGHT_ON)
		indirectLight.diffuse = i.vertexLightColor;
	#endif

	#if defined(FORWARD_BASE_PASS)
		
		...

<strong>		float occlusion = GetOcclusion(i);
</strong><strong>		indirectLight.diffuse *= occlusion;
</strong><strong>		indirectLight.specular *= occlusion;
</strong>	#endif

	return indirectLight;
}

...

#endif
</code></pre>

{% endtab %}

{% tab title="GUI" %}

<pre class="language-csharp"><code class="lang-csharp">using UnityEngine;
using UnityEditor;

public class OccludedAreas_GUI : ShaderGUI {
	enum SmoothnessSource {
		Uniform, Albedo, Metallic
	}
	static GUIContent staticLabel = new GUIContent();
	Material target;
	MaterialEditor editor;
	MaterialProperty[] properties;

	public override void OnGUI(
		MaterialEditor editor, MaterialProperty[] properties
	) {
		...
	}

	void DoMain() {
		GUILayout.Label("Main Maps", EditorStyles.boldLabel);
		MaterialProperty mainTex = FindProperty("_MainTex");
		editor.TexturePropertySingleLine(MakeLabel(mainTex, "Albedo(RGB)"), mainTex, FindProperty("_Tint"));
		DoMetallic();
		DoSmoothness();
		DoNormals();
<strong>		DoOcclusion();
</strong>		DoEmission();
		editor.TextureScaleOffsetProperty(mainTex);
	}
	
	...

<strong>	void DoOcclusion() {
</strong><strong>		MaterialProperty map = FindProperty("_OcclusionMap");
</strong><strong>		EditorGUI.BeginChangeCheck();
</strong><strong>		editor.TexturePropertySingleLine(
</strong><strong>			MakeLabel(map, "Occlusion (G)"), map,
</strong><strong>			map.textureValue ? FindProperty("_OcclusionStrength") : null
</strong><strong>		);
</strong><strong>		if (EditorGUI.EndChangeCheck()) {
</strong><strong>			SetKeyword("_OCCLUSION_MAP", map.textureValue);
</strong><strong>		}
</strong><strong>	}
</strong>
	...

}
</code></pre>

{% endtab %}
{% endtabs %}

<details>

<summary>为什么 Occlusion 选择使用 G 通道存储信息？</summary>

Occlusion map 默认单通道存储。

一般 A 通道用于存储透明度信息，而 RGB 三个通道中，因为人眼对绿色更敏感，所以在常用的纹理压缩格式中，绿色通道通常分配更多比特数，提供更高的存储精度。因此最终选择 G 通道。

</details>

<details>

<summary>为什么将 occlusion map 的影响应用在 indirect light 上？</summary>

也可以应用在 direct light 上，将其与 attenuation 相乘：

<pre class="language-hlsl"><code class="lang-hlsl">UnityLight CreateLight (Interpolators i) {
    ...
    UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos);
<strong>    attenuation *= GetOcclusion(i);
</strong>    light.color = _LightColor0.rgb * attenuation;
    light.ndotl = DotClamped(i.normal, light.dir);
    return light;
}
</code></pre>

这时可以观察到弹坑深了许多，但总体上并不明显。

因为 occlusion map 只基于表面形状而非特定光线（indirect / direct 都可），而同时应用于两个光线时，看起来又效果过强了。考虑到这个场景中很多光是间接光，所以最终选择仅应用在 indirect light 上。

</details>

## 2 Mask 技术它终于来了：Masking Details

实现一个 mask 效果，使得弹坑仅影响电路板，不会覆盖金属部分。

Unity standard shader 使用 alpha 通道存储 detail mask。

将 fragment shader 中的 albedo 提取为单独的函数，使其不仅仅将 albedo 和 detail 值简单相乘，而是将 GetDetailMask 函数返回值作为插值。

与此同时修改 InitializeFragmentNormal 函数。这里将法线向上方向（0，0，1）作为未修改的值。

{% tabs %}
{% tab title="Main" %}

<pre class="language-hlsl"><code class="lang-hlsl">Shader "Code/PA9_MoreComplexity/MaskingDetails" {

	Properties {
		
		...

<strong>		[NoScaleOffset] _DetailMask ("Detail Mask", 2D) = "white" {}
</strong>		_DetailTex ("Detail Albedo", 2D) = "gray" {}
		[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
		_DetailBumpScale ("Detail Bump Scale", Float) = 1
	}

	CGINCLUDE
	#define BINORMAL_PER_FRAGMENT
	ENDCG

	SubShader {
		Pass {
			Tags {
				"LightMode" = "ForwardBase"
			}
			CGPROGRAM
			#pragma target 3.0
			#pragma shader_feature _METALLIC_MAP
			#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
			#pragma shader_feature _OCCLUSION_MAP
			#pragma shader_feature _EMISSION_MAP
<strong>			#pragma shader_feature _DETAIL_MASK
</strong>			#pragma multi_compile _ SHADOWS_SCREEN
			#pragma multi_compile _ VERTEXLIGHT_ON
			#pragma vertex Vert
			#pragma fragment Frag
			#define FORWARD_BASE_PASS
			#include "MaskingDetails_Light.cginc"
			ENDCG
		}

		Pass {
			Tags {
				"LightMode" = "ForwardAdd"
			}
			Blend One One
			ZWrite Off
			CGPROGRAM
			#pragma target 3.0
			#pragma shader_feature _METALLIC_MAP
			#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
<strong>			#pragma shader_feature _DETAIL_MASK
</strong>			#pragma multi_compile_fwdadd_fullshadows
			#pragma vertex Vert
			#pragma fragment Frag
			#include "MaskingDetails_Light.cginc"
			ENDCG
		}
		
		...
	}

	CustomEditor "MaskingDetails_GUI"
}
</code></pre>

{% endtab %}

{% tab title="Light" %}

<pre class="language-hlsl"><code class="lang-hlsl">#if !defined(MORECOMPLEXITY_MASKINGDETAILS_LIGHT)
#define MORECOMPLEXITY_MASKINGDETAILS_LIGHT

#include "UnityPBSLighting.cginc"
#include "AutoLight.cginc"

float4 _Tint;
<strong>sampler2D _MainTex, _DetailTex, _DetailMask;
</strong>float4 _MainTex_ST, _DetailTex_ST;

...

<strong>float GetDetailMask(Interpolators i) {
</strong><strong>	#if defined (_DETAIL_MASK)
</strong><strong>		return tex2D(_DetailMask, i.uv.xy).a;
</strong><strong>	#else
</strong><strong>		return 1;
</strong><strong>	#endif
</strong><strong>}
</strong><strong>
</strong><strong>float3 GetAlbedo(Interpolators i) {
</strong><strong>	float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb;
</strong><strong>	float3 details = tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble;
</strong><strong>	albedo = lerp(albedo, albedo * details, GetDetailMask(i));
</strong><strong>	return albedo;
</strong><strong>}
</strong>
...

void InitializeFragmentNormal(inout Interpolators i) {
	float3 mainNormal =
		UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
	float3 detailNormal =
		UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
<strong>	detailNormal = lerp(float3(0, 0, 1), detailNormal, GetDetailMask(i));
</strong>	float3 tangentSpaceNormal = BlendNormals(mainNormal, detailNormal);

	#if defined(BINORMAL_PER_FRAGMENT)
		float3 binormal = CreateBinormal(i.normal, i.tangent.xyz, i.tangent.w);
	#else
		float3 binormal = i.binormal;
	#endif
	
	i.normal = normalize(
		tangentSpaceNormal.x * i.tangent +
		tangentSpaceNormal.y * binormal +
		tangentSpaceNormal.z * i.normal
	);
}

float4 Frag(Interpolators i) : SV_TARGET {
	InitializeFragmentNormal(i);

	float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);

	// float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb;
	// albedo *= tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble;
	
	float3 specularTint;
	float oneMinusReflectivity;
<strong>	<a data-footnote-ref href="#user-content-fn-1">float3 albedo</a> = DiffuseAndSpecularFromMetallic(
</strong><strong>		<a data-footnote-ref href="#user-content-fn-2">GetAlbedo(i)</a>, GetMetallic(i), specularTint, oneMinusReflectivity
</strong><strong>	);
</strong>
	float4 color = UNITY_BRDF_PBS(
		albedo, specularTint,
		oneMinusReflectivity, GetSmoothness(i),
		i.normal, viewDir,
		CreateLight(i), CreateIndirectLight(i, viewDir)
	);
	color.rgb += GetEmission(i);
	return color;
}

#endif
</code></pre>

{% endtab %}

{% tab title="GUI" %}

<pre class="language-csharp"><code class="lang-csharp">using UnityEngine;
using UnityEditor;

public class MaskingDetails_GUI: ShaderGUI {
	enum SmoothnessSource {
		Uniform, Albedo, Metallic
	}
	static GUIContent staticLabel = new GUIContent();
	Material target;
	MaterialEditor editor;
	MaterialProperty[] properties;

	void DoMain() {
		GUILayout.Label("Main Maps", EditorStyles.boldLabel);
		MaterialProperty mainTex = FindProperty("_MainTex");
		editor.TexturePropertySingleLine(MakeLabel(mainTex, "Albedo(RGB)"), mainTex, FindProperty("_Tint"));
		DoMetallic();
		DoSmoothness();
		DoNormals();
		DoOcclusion();
		DoEmission();
<strong>		DoDetailMask();
</strong>		editor.TextureScaleOffsetProperty(mainTex);
	}
	
	...

<strong>	void DoDetailMask() {
</strong><strong>		MaterialProperty mask = FindProperty("_DetailMask");
</strong><strong>		EditorGUI.BeginChangeCheck();
</strong><strong>		editor.TexturePropertySingleLine(
</strong><strong>			MakeLabel(mask, "Detail Mask (A)"), mask
</strong><strong>		);
</strong><strong>		if (EditorGUI.EndChangeCheck()) {
</strong><strong>			SetKeyword("_DETAIL_MASK", mask.textureValue);
</strong><strong>		}
</strong><strong>	}
</strong>
	...
}
</code></pre>

{% endtab %}
{% endtabs %}

## 3 更多操控：More Keywords

没啥好说的。

1. 动态管理关键字（Main + GUI 代码）；

{% tabs %}
{% tab title="Main" %}

<pre class="language-hlsl"><code class="lang-hlsl">Shader "Code/PA9_MoreComplexity/MoreKeywords" {

	Properties {
		...
	}

	CGINCLUDE
	#define BINORMAL_PER_FRAGMENT
	ENDCG

	SubShader {
		Pass {
			Tags {
				"LightMode" = "ForwardBase"
			}
			CGPROGRAM
			#pragma target 3.0
			#pragma shader_feature _METALLIC_MAP
			#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
<strong>			#pragma shader_feature _NORMAL_MAP
</strong>			#pragma shader_feature _OCCLUSION_MAP
			#pragma shader_feature _EMISSION_MAP
			#pragma shader_feature _DETAIL_MASK
<strong>			#pragma shader_feature _DETAIL_ALBEDO_MAP
</strong><strong>			#pragma shader_feature _DETAIL_NORMAL_MAP
</strong>			#pragma multi_compile _ SHADOWS_SCREEN
			#pragma multi_compile _ VERTEXLIGHT_ON
			#pragma vertex Vert
			#pragma fragment Frag
			#define FORWARD_BASE_PASS
			#include "MoreKeywords_Light.cginc"
			ENDCG
		}

		Pass {
			Tags {
				"LightMode" = "ForwardAdd"
			}
			Blend One One
			ZWrite Off
			CGPROGRAM
			#pragma target 3.0
			#pragma shader_feature _METALLIC_MAP
			#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
<strong>			#pragma shader_feature _NORMAL_MAP
</strong>			#pragma shader_feature _DETAIL_MASK
<strong>			#pragma shader_feature _DETAIL_ALBEDO_MAP
</strong><strong>			#pragma shader_feature _DETAIL_NORMAL_MAP
</strong>			#pragma multi_compile_fwdadd_fullshadows
			#pragma vertex Vert
			#pragma fragment Frag
			#include "MoreKeywords_Light.cginc"
			ENDCG
		}

		...
		
	}

	CustomEditor "MoreKeywords_GUI"
}
</code></pre>

{% endtab %}

{% tab title="Light" %}

<pre class="language-hlsl"><code class="lang-hlsl">#if !defined(MORECOMPLEXITY_MOREKEYWORDS_LIGHT)
#define MORECOMPLEXITY_MOREKEYWORDS_LIGHT

#include "UnityPBSLighting.cginc"
#include "AutoLight.cginc"

...

float3 GetAlbedo(Interpolators i) {
	float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb;
<strong>	#if defined (_DETAIL_ALBEDO_MAP)
</strong>		float3 details = tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble;
		albedo = lerp(albedo, albedo * details, GetDetailMask(i));
<strong>	#endif
</strong>	return albedo;
}

<strong>float3 GetTangentSpaceNormal(Interpolators i) {
</strong><strong>	float3 normal = float3(0, 0, 1);
</strong><strong>	#if defined(_NORMAL_MAP)
</strong><strong>		normal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
</strong><strong>	#endif
</strong><strong>	#if defined(_DETAIL_NORMAL_MAP)
</strong><strong>		float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
</strong><strong>		detailNormal = lerp(float3(0, 0, 1), detailNormal, GetDetailMask(i));
</strong><strong>		normal = BlendNormals(normal, detailNormal);
</strong><strong>	#endif
</strong><strong>	return normal;
</strong><strong>}
</strong>
...

void InitializeFragmentNormal(inout Interpolators i) {
	// float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
	// float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
	// detailNormal = lerp(float3(0, 0, 1), detailNormal, GetDetailMask(i));
	// float3 tangentSpaceNormal = BlendNormals(mainNormal, detailNormal);
<strong>	float3 tangentSpaceNormal = GetTangentSpaceNormal(i);
</strong>	#if defined(BINORMAL_PER_FRAGMENT)
		float3 binormal = CreateBinormal(i.normal, i.tangent.xyz, i.tangent.w);
	#else
		float3 binormal = i.binormal;
	#endif
	
	i.normal = normalize(
		tangentSpaceNormal.x * i.tangent +
		tangentSpaceNormal.y * binormal +
		tangentSpaceNormal.z * i.normal
	);
}

...

#endif
</code></pre>

{% endtab %}

{% tab title="GUI" %}

<pre class="language-csharp"><code class="lang-csharp">using UnityEngine;
using UnityEditor;

public class MoreKeywords_GUI : ShaderGUI {
	
	...

	void DoNormals() {
		MaterialProperty map = FindProperty("_NormalMap");
<strong>		EditorGUI.BeginChangeCheck();
</strong>		editor.TexturePropertySingleLine(MakeLabel(map), map, map.textureValue ? FindProperty("_BumpScale") : null);
<strong>		if (EditorGUI.EndChangeCheck()) {
</strong><strong>			SetKeyword("_NORMAL_MAP", map.textureValue);
</strong><strong>		}
</strong>	}
	
	...

	void DoSecondary() {
		GUILayout.Label("Secondary Maps", EditorStyles.boldLabel);
		MaterialProperty detailTex = FindProperty("_DetailTex");
<strong>		EditorGUI.BeginChangeCheck();
</strong>		editor.TexturePropertySingleLine(MakeLabel(detailTex, "Albedo(RGB) multiplied by 2"), detailTex);
<strong>		if (EditorGUI.EndChangeCheck()) {
</strong><strong>			SetKeyword("_DETAIL_ALBEDO_MAP", detailTex.textureValue);
</strong><strong>		}
</strong>		DoSecondaryNormals();
		editor.TextureScaleOffsetProperty(detailTex);
	}

	void DoSecondaryNormals() {
		MaterialProperty map = FindProperty("_DetailNormalMap");
<strong>		EditorGUI.BeginChangeCheck();
</strong>		editor.TexturePropertySingleLine(MakeLabel(map), map, map.textureValue ? FindProperty("_DetailBumpScale") : null);
<strong>		if (EditorGUI.EndChangeCheck()) {
</strong><strong>			SetKeyword("_DETAIL_NORMAL_MAP", map.textureValue);
</strong><strong>		}
</strong>	}

	...
}
</code></pre>

{% endtab %}
{% endtabs %}

## 4 一 shader 多用：Editing Multiple Materials

在使用一个 shader 编辑多个材质时，主要考虑解决两个冲突问题。

**关键字过少：“选不到” 的问题**

在编辑多个材质时，如果材质 GUI 仅设置了选中材质中的第一个材质的关键字（`editor.target`），其他材质的关键字可能无法正确更新。

因此在 SetKeyword 的函数了使用 foreach 循环，遍历以确保所有材质应用更新后的关键字。

**关键字过多：“连坐” 问题**

当选中的多个材质之间的属性不一致，可能会导致错误。比如第一个材质使用了 normal map，但第二个材质没有。如果通过 GUI 修改了 normal map 相关的属性，可能会错误地为第二个材质启用了 normal map 的关键字。

为解决这个问题，需要在修改属性之前记录贴图的当前引用值。此时仅在贴图值真正发生改变时，才更新关键字。

{% hint style="info" %}
这里仅以 `DoNormals` 为例。需将同样的方法用在 `DoMetallic`，`DoOcclusion`，`DoEmission` 和 `DoSecondaryNormals` 函数中。
{% endhint %}

<pre class="language-csharp"><code class="lang-csharp">void DoNormals() {
	MaterialProperty map = FindProperty("_NormalMap");
<strong>	Texture tex = map.textureValue;
</strong>	EditorGUI.BeginChangeCheck();
<strong>	editor.TexturePropertySingleLine(MakeLabel(map), map, <a data-footnote-ref href="#user-content-fn-3">tex</a> ? FindProperty("_BumpScale") : null);
</strong><strong>	if(EditorGUI.EndChangeCheck() &#x26;&#x26; tex != map.textureValue) {
</strong>		SetKeyword("_NORMAL_MAP", map.textureValue);
	}
}

...

void SetKeyword(string keyword, bool state) {
	if(state) {
<strong>		foreach(Material m in editor.targets) {
</strong><strong>			m.EnableKeyword(keyword);
</strong>		}
	} else {
<strong>		foreach(Material m in editor.targets) {
</strong><strong>			m.DisableKeyword(keyword);
</strong>		}
	}
}
</code></pre>

***

## APPENDIX

**Anki**

{% file src="/files/RnNTppH291SowBvIe2UP" %}

**Reference**

[Catlike Coding - Rendering 10: More Complexity](https://catlikecoding.com/unity/tutorials/rendering/part-10/)

[^1]: pre: albedo (此前 albedo 来自 \_MainTex 和 \_DetailTex 的采样，写在 Frag shader 内)

[^2]: pre: albedo

[^3]: pre: map.textureValue


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dgtototo.gitbook.io/rendering/rendering/pa9-more-complexity.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
