Compare commits
15 Commits
v1.69.0
...
ry/wgpuFix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ad90d139d | ||
|
|
cf694a3334 | ||
|
|
863379b5b4 | ||
|
|
6ce06b1b60 | ||
|
|
e21d4a5326 | ||
|
|
7fd9e728ae | ||
|
|
b66736b8cc | ||
|
|
ea8f6a3c92 | ||
|
|
adcdbb45f9 | ||
|
|
cff958587d | ||
|
|
9ab2326f15 | ||
|
|
8785acd352 | ||
|
|
57767b7f27 | ||
|
|
febcfc8f2d | ||
|
|
c04c387a89 |
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.68.4'
|
||||
implementation 'com.google.android.filament:filament-android:1.68.5'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
@@ -196,7 +196,7 @@ dependencies {
|
||||
</div>
|
||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.68.4'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.68.5'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
|
||||
@@ -504,9 +504,9 @@ D_{GGX}(h,\alpha) = \frac{\aa}{\pi ( (\NoH)^2 (\aa - 1) + 1)^2}
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
The GLSL implementation of the NDF, shown in <a href="#listing_speculard">listing 1</a>, is simple and efficient.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">D_GGX</span>(<span class="hljs-params"><span class="hljs-built_in">float</span> NoH, <span class="hljs-built_in">float</span> roughness</span>)</span> {</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> a = NoH * roughness;</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> k = roughness / (<span class="hljs-number">1.0</span> - NoH * NoH + a * a);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">D_GGX</span><span class="hljs-params">(<span class="hljs-type">float</span> NoH, <span class="hljs-type">float</span> roughness)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a = NoH * roughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> k = roughness / (<span class="hljs-number">1.0</span> - NoH * NoH + a * a);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> k * k * (<span class="hljs-number">1.0</span> / PI);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_speculard"> </a><b style="font-style:normal;">Listing 1:</b> Implementation of the specular D term in GLSL</div></center>
|
||||
<p>
|
||||
@@ -590,10 +590,10 @@ V(v,l,\alpha) = \frac{0.5}{\NoL \sqrt{(\NoV)^2 (1 - \aa) + \aa} + \NoV \sqrt{(\N
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
The GLSL implementation of the visibility term, shown in <a href="#listing_specularv">listing 3</a>, is a bit more expensive than we would like since it requires two <code>sqrt</code> operations.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">V_SmithGGXCorrelated</span><span class="hljs-params">(<span class="hljs-keyword">float</span> NoV, <span class="hljs-keyword">float</span> NoL, <span class="hljs-keyword">float</span> roughness)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> a2 = roughness * roughness;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>(NoV * NoV * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>(NoL * NoL * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">V_SmithGGXCorrelated</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> roughness)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a2 = roughness * roughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>(NoV * NoV * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>(NoL * NoL * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_specularv"> </a><b style="font-style:normal;">Listing 3:</b> Implementation of the specular V term in GLSL</div></center>
|
||||
<p>
|
||||
@@ -604,10 +604,10 @@ V(v,l,\alpha) = \frac{0.5}{\NoL (\NoV (1 - \alpha) + \alpha) + \NoV (\NoL (1 - \
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
This approximation is mathematically wrong but saves two square root operations and is good enough for real-time mobile applications, as shown in <a href="#listing_approximatedspecularv">listing 4</a>.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">V_SmithGGXCorrelatedFast</span>(<span class="hljs-params"><span class="hljs-built_in">float</span> NoV, <span class="hljs-built_in">float</span> NoL, <span class="hljs-built_in">float</span> roughness</span>)</span> {</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> a = roughness;</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> GGXV = NoL * (NoV * (<span class="hljs-number">1.0</span> - a) + a);</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> GGXL = NoV * (NoL * (<span class="hljs-number">1.0</span> - a) + a);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">V_SmithGGXCorrelatedFast</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> roughness)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a = roughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * (NoV * (<span class="hljs-number">1.0</span> - a) + a);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * (NoL * (<span class="hljs-number">1.0</span> - a) + a);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_approximatedspecularv"> </a><b style="font-style:normal;">Listing 4:</b> Implementation of the approximated specular V term in GLSL</div></center>
|
||||
<p>
|
||||
@@ -659,7 +659,7 @@ $$\begin{equation}
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
In practice, the diffuse reflectance \(\sigma\) is multiplied later, as shown in <a href="#listing_diffusebrdf">listing 8</a>.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">Fd_Lambert</span>(<span class="hljs-params"></span>)</span> {</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">Fd_Lambert</span>()</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / PI;</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
@@ -680,14 +680,14 @@ Where:
|
||||
$$\begin{equation}
|
||||
\fGrazing=0.5 + 2 \cdot \alpha cos^2(\theta_d)
|
||||
\end{equation}$$
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">F_Schlick</span><span class="hljs-params">(<span class="hljs-keyword">float</span> u, <span class="hljs-keyword">float</span> f0, <span class="hljs-keyword">float</span> f90)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> f0 + (f90 - f0) * <span class="hljs-built_in">pow</span>(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">F_Schlick</span><span class="hljs-params">(<span class="hljs-type">float</span> u, <span class="hljs-type">float</span> f0, <span class="hljs-type">float</span> f90)</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> f0 + (f90 - f0) * pow(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">Fd_Burley</span><span class="hljs-params">(<span class="hljs-keyword">float</span> NoV, <span class="hljs-keyword">float</span> NoL, <span class="hljs-keyword">float</span> LoH, <span class="hljs-keyword">float</span> roughness)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> f90 = <span class="hljs-number">0.5</span> + <span class="hljs-number">2.0</span> * roughness * LoH * LoH;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> lightScatter = F_Schlick(NoL, <span class="hljs-number">1.0</span>, f90);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> viewScatter = F_Schlick(NoV, <span class="hljs-number">1.0</span>, f90);</span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">Fd_Burley</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> LoH, <span class="hljs-type">float</span> roughness)</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">f90</span> <span class="hljs-operator">=</span> <span class="hljs-number">0.5</span> + <span class="hljs-number">2.0</span> * roughness * LoH * LoH;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">lightScatter</span> <span class="hljs-operator">=</span> F_Schlick(NoL, <span class="hljs-number">1.0</span>, f90);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">viewScatter</span> <span class="hljs-operator">=</span> F_Schlick(NoV, <span class="hljs-number">1.0</span>, f90);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> lightScatter * viewScatter * (<span class="hljs-number">1.0</span> / PI);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_diffusebrdf"> </a><b style="font-style:normal;">Listing 8:</b> Implementation of the diffuse Disney BRDF in GLSL</div></center>
|
||||
<p>
|
||||
@@ -704,47 +704,47 @@ We could allow artists/developers to choose the Disney diffuse BRDF depending on
|
||||
<strong class="asterisk">Diffuse term</strong>: a Lambertian diffuse model.
|
||||
</p><p>
|
||||
The full GLSL implementation of the standard model is shown in <a href="#listing_glslbrdf">listing 9</a>.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> D_GGX(<span class="hljs-type">float</span> NoH, <span class="hljs-type">float</span> a) {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> f = (NoH * a2 - NoH) * NoH + <span class="hljs-number">1.0</span>;</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">D_GGX</span><span class="hljs-params">(<span class="hljs-type">float</span> NoH, <span class="hljs-type">float</span> a)</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">f</span> <span class="hljs-operator">=</span> (NoH * a2 - NoH) * NoH + <span class="hljs-number">1.0</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> a2 / (PI * f * f);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-type">vec3</span> F_Schlick(<span class="hljs-type">float</span> u, <span class="hljs-type">vec3</span> f0) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> f0 + (<span class="hljs-type">vec3</span>(<span class="hljs-number">1.0</span>) - f0) * <span class="hljs-built_in">pow</span>(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span>
|
||||
<span class="line">vec3 <span class="hljs-title function_">F_Schlick</span><span class="hljs-params">(<span class="hljs-type">float</span> u, vec3 f0)</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> f0 + (vec3(<span class="hljs-number">1.0</span>) - f0) * pow(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-type">float</span> V_SmithGGXCorrelated(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a) {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>((-NoL * a2 + NoL) * NoL + a2);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>((-NoV * a2 + NoV) * NoV + a2);</span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">V_SmithGGXCorrelated</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a)</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXL</span> <span class="hljs-operator">=</span> NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXV</span> <span class="hljs-operator">=</span> NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-type">float</span> Fd_Lambert() {</span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">Fd_Lambert</span><span class="hljs-params">()</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / PI;</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-type">void</span> BRDF(...) {</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> h = <span class="hljs-built_in">normalize</span>(v + l);</span>
|
||||
<span class="line"><span class="hljs-keyword">void</span> <span class="hljs-title function_">BRDF</span><span class="hljs-params">(...)</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">h</span> <span class="hljs-operator">=</span> normalize(v + l);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> NoV = <span class="hljs-built_in">abs</span>(<span class="hljs-built_in">dot</span>(n, v)) + <span class="hljs-number">1e-5</span>;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> NoL = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(n, l), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> NoH = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(n, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> LoH = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(l, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoV</span> <span class="hljs-operator">=</span> abs(dot(n, v)) + <span class="hljs-number">1e-5</span>;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoL</span> <span class="hljs-operator">=</span> clamp(dot(n, l), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoH</span> <span class="hljs-operator">=</span> clamp(dot(n, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">LoH</span> <span class="hljs-operator">=</span> clamp(dot(l, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// perceptually linear roughness to roughness (see parameterization)</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> roughness = perceptualRoughness * perceptualRoughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">roughness</span> <span class="hljs-operator">=</span> perceptualRoughness * perceptualRoughness;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> D = D_GGX(NoH, roughness);</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> F = F_Schlick(LoH, f0);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> V = V_SmithGGXCorrelated(NoV, NoL, roughness);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">D</span> <span class="hljs-operator">=</span> D_GGX(NoH, roughness);</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">F</span> <span class="hljs-operator">=</span> F_Schlick(LoH, f0);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">V</span> <span class="hljs-operator">=</span> V_SmithGGXCorrelated(NoV, NoL, roughness);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// specular BRDF</span></span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> Fr = (D * V) * F;</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">Fr</span> <span class="hljs-operator">=</span> (D * V) * F;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// diffuse BRDF</span></span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> Fd = diffuseColor * Fd_Lambert();</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">Fd</span> <span class="hljs-operator">=</span> diffuseColor * Fd_Lambert();</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// apply lighting...</span></span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_glslbrdf"> </a><b style="font-style:normal;">Listing 9:</b> Evaluation of the BRDF in GLSL</div></center>
|
||||
@@ -965,7 +965,7 @@ $$\begin{equation}
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
<a href="#listing_fnormal">Listing 12</a> shows how \(\fNormal\) is computed for both dielectric and metallic materials. It shows that the color of the specular reflectance is derived from the base color in the metallic case.
|
||||
</p><pre class="listing tilde"><code><span class="line">vec3 f0 = 0.16 <span class="hljs-emphasis">* reflectance *</span> reflectance <span class="hljs-emphasis">* (1.0 - metallic) + baseColor *</span> metallic;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fnormal"> </a><b style="font-style:normal;">Listing 12:</b> Computing \(\fNormal\) for dielectric and metallic materials in GLSL</div></center>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-symbol">vec3</span> <span class="hljs-built_in">f0</span> = <span class="hljs-number">0</span>.<span class="hljs-number">16</span> * reflectance * reflectance * (<span class="hljs-number">1</span>.<span class="hljs-number">0</span> - metallic) + baseColor * metallic<span class="hljs-comment">;</span></span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fnormal"> </a><b style="font-style:normal;">Listing 12:</b> Computing \(\fNormal\) for dielectric and metallic materials in GLSL</div></center>
|
||||
<a class="target" name="roughnessremappingandclamping"> </a><a class="target" name="materialsystem/parameterization/remapping/roughnessremappingandclamping"> </a><a class="target" name="toc4.8.3.3"> </a><h4 id="roughness-remapping-and-clamping"><a class="header" href="#roughness-remapping-and-clamping">Roughness remapping and clamping</a></h4>
|
||||
<p>
|
||||
<p>The roughness set by the user, called <code>perceptualRoughness</code> here, is remapped to a perceptually linear range using the following formulation:</p>
|
||||
@@ -1054,7 +1054,7 @@ V(l,h) = \frac{1}{4(\LoH)^2}
|
||||
This masking-shadowing function is not physically based, as shown in [<a href="#citation-heitz14">Heitz14</a>], but its simplicity makes it desirable for real-time rendering.
|
||||
</p><p>
|
||||
In summary, our clear coat BRDF is a Cook-Torrance specular microfacet model, with a GGX normal distribution function, a Kelemen visibility function, and a Schlick Fresnel function. <a href="#listing_kelemen">Listing 13</a> shows how trivial the GLSL implementation is.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">V_Kelemen</span>(<span class="hljs-params"><span class="hljs-built_in">float</span> LoH</span>)</span> {</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">V_Kelemen</span><span class="hljs-params">(<span class="hljs-type">float</span> LoH)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.25</span> / (LoH * LoH);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_kelemen"> </a><b style="font-style:normal;">Listing 13:</b> Implementation of the Kelemen visibility term in GLSL</div></center>
|
||||
<p>
|
||||
@@ -1097,18 +1097,18 @@ The clear coat roughness parameter is remapped and clamped in a similar way to t
|
||||
<center><div class="image" style=""><a href="../images/material_clear_coat2.png" target="_blank"><img class="markdeep" src="../images/material_clear_coat2.png" /></a><center><span class="imagecaption"><a class="target" name="figure_clearcoatroughness"> </a><b style="font-style:normal;">Figure 26:</b> Clear coat roughness varying from 0.0 (left) to 1.0 (right) with metallic set to 1.0, roughness to 0.8 and clear coat to 1.0</span></center></div></center>
|
||||
</p><p>
|
||||
<a href="#listing_clearcoatbrdf">Listing 14</a> shows the GLSL implementation of the clear coat material model after remapping, parameterization and integration in the standard surface response.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">BRDF</span>(<span class="hljs-params">...</span>)</span> {</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">BRDF</span><span class="hljs-params">(...)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-comment">// compute Fd and Fr from standard model</span></span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// remapping and linearization of clear coat roughness</span></span>
|
||||
<span class="line"> clearCoatPerceptualRoughness = clamp(clearCoatPerceptualRoughness, <span class="hljs-number">0.089</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> clearCoatPerceptualRoughness = <span class="hljs-built_in">clamp</span>(clearCoatPerceptualRoughness, <span class="hljs-number">0.089</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> clearCoatRoughness = clearCoatPerceptualRoughness * clearCoatPerceptualRoughness;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// clear coat BRDF</span></span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> Dc = D_GGX(clearCoatRoughness, NoH);</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> Vc = V_Kelemen(clearCoatRoughness, LoH);</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> Fc = F_Schlick(<span class="hljs-number">0.04</span>, LoH) * clearCoat; <span class="hljs-comment">// clear coat strength</span></span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> Frc = (Dc * Vc) * Fc;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Dc = <span class="hljs-built_in">D_GGX</span>(clearCoatRoughness, NoH);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Vc = <span class="hljs-built_in">V_Kelemen</span>(clearCoatRoughness, LoH);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Fc = <span class="hljs-built_in">F_Schlick</span>(<span class="hljs-number">0.04</span>, LoH) * clearCoat; <span class="hljs-comment">// clear coat strength</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Frc = (Dc * Vc) * Fc;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// account for energy loss in the base layer</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> color * ((Fd + Fr * (<span class="hljs-number">1.0</span> - Fc)) * (<span class="hljs-number">1.0</span> - Fc) + Frc);</span>
|
||||
@@ -1294,14 +1294,14 @@ f_{r}(v,h,\alpha) = \frac{D_{velvet}(v,h,\alpha)}{4(\NoL + \NoV - (\NoL)(\NoV))}
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
The implementation of the velvet NDF is presented in <a href="#listing_clothbrdf">listing 17</a>, optimized to properly fit in half float formats and to avoid computing a costly cotangent, relying instead on trigonometric identities. Note that we removed the Fresnel component from this BRDF.
|
||||
</p><pre class="listing tilde"><code><span class="line">float D_Ashikhmin(float roughness, float NoH) {</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">D_Ashikhmin</span><span class="hljs-params">(<span class="hljs-type">float</span> roughness, <span class="hljs-type">float</span> NoH)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-comment">// Ashikhmin 2007, "Distribution-based BRDFs"</span></span>
|
||||
<span class="line"> float a<span class="hljs-number">2</span> = roughness * roughness;</span>
|
||||
<span class="line"> float <span class="hljs-keyword">cos</span><span class="hljs-number">2</span>h = NoH * NoH;</span>
|
||||
<span class="line"> float <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h = <span class="hljs-keyword">max</span>(<span class="hljs-number">1.0</span> - <span class="hljs-keyword">cos</span><span class="hljs-number">2</span>h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 > 0 in fp16</span></span>
|
||||
<span class="line"> float <span class="hljs-keyword">sin</span><span class="hljs-number">4</span>h = <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h * <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h;</span>
|
||||
<span class="line"> float cot<span class="hljs-number">2</span> = -<span class="hljs-keyword">cos</span><span class="hljs-number">2</span>h / (a<span class="hljs-number">2</span> * <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (PI * (<span class="hljs-number">4.0</span> * a<span class="hljs-number">2</span> + <span class="hljs-number">1.0</span>) * <span class="hljs-keyword">sin</span><span class="hljs-number">4</span>h) * (<span class="hljs-number">4.0</span> * exp(cot<span class="hljs-number">2</span>) + <span class="hljs-keyword">sin</span><span class="hljs-number">4</span>h);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a2 = roughness * roughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> cos2h = NoH * NoH;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> sin2h = <span class="hljs-built_in">max</span>(<span class="hljs-number">1.0</span> - cos2h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 > 0 in fp16</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> sin4h = sin2h * sin2h;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> cot2 = -cos2h / (a2 * sin2h);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (PI * (<span class="hljs-number">4.0</span> * a2 + <span class="hljs-number">1.0</span>) * sin4h) * (<span class="hljs-number">4.0</span> * <span class="hljs-built_in">exp</span>(cot2) + sin4h);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothbrdf"> </a><b style="font-style:normal;">Listing 17:</b> Implementation of Ashikhmin's velvet NDF in GLSL</div></center>
|
||||
<p>
|
||||
<p>In [<a href="#citation-estevez17">Estevez17</a>] Estevez and Kulla propose a different NDF (called the “Charlie” sheen) that is based on an exponentiated sinusoidal instead of an inverted Gaussian. This NDF is appealing for several reasons: its parameterization feels more natural and intuitive, it provides a softer appearance and, as shown in equation (\ref{charlieNDF}), its implementation is simpler:</p>
|
||||
@@ -1312,11 +1312,11 @@ D(m) = \frac{(2 + \frac{1}{\alpha}) sin(\theta)^{\frac{1}{\alpha}}}{2 \pi}
|
||||
</p><p>
|
||||
[<a href="#citation-estevez17">Estevez17</a>] also presents a new shadowing term that we omit here because of its cost. We instead rely on the visibility term from [<a href="#citation-neubelt13">Neubelt13</a>] (shown in equation \(\ref{clothSpecularBRDF}\) above).
|
||||
The implementation of this NDF is presented in <a href="#listing_clothcharliebrdf">listing 18</a>, optimized to properly fit in half float formats.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">D_Charlie</span><span class="hljs-params">(<span class="hljs-keyword">float</span> roughness, <span class="hljs-keyword">float</span> NoH)</span> </span>{</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">D_Charlie</span><span class="hljs-params">(<span class="hljs-type">float</span> roughness, <span class="hljs-type">float</span> NoH)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-comment">// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF"</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> invAlpha = <span class="hljs-number">1.0</span> / roughness;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> cos2h = NoH * NoH;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> sin2h = max(<span class="hljs-number">1.0</span> - cos2h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 > 0 in fp16</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> invAlpha = <span class="hljs-number">1.0</span> / roughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> cos2h = NoH * NoH;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> sin2h = <span class="hljs-built_in">max</span>(<span class="hljs-number">1.0</span> - cos2h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 > 0 in fp16</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> (<span class="hljs-number">2.0</span> + invAlpha) * <span class="hljs-built_in">pow</span>(sin2h, invAlpha * <span class="hljs-number">0.5</span>) / (<span class="hljs-number">2.0</span> * PI);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothcharliebrdf"> </a><b style="font-style:normal;">Listing 18:</b> Implementation of the “Charlie” NDF in GLSL</div></center>
|
||||
<a class="target" name="sheencolor"> </a><a class="target" name="materialsystem/clothmodel/clothspecularbrdf/sheencolor"> </a><a class="target" name="toc4.12.1.1"> </a><h4 id="sheen-color"><a class="header" href="#sheen-color">Sheen color</a></h4>
|
||||
@@ -1745,21 +1745,21 @@ The photometric attenuation function can be easily implemented in GLSL by adding
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_glslphotometricpunctuallight"> </a><b style="font-style:normal;">Listing 22:</b> Implementation of attenuation from photometric profiles in GLSL</div></center>
|
||||
<p>
|
||||
<p>The light intensity is computed CPU-side (<a href="#listing_photometriclightintensity">listing 23</a>) and depends on whether the photometric profile is used as a mask.</p>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-keyword">float</span> multiplier;</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> multiplier;</span>
|
||||
<span class="line"><span class="hljs-comment">// Photometric profile used as a mask</span></span>
|
||||
<span class="line"><span class="hljs-keyword">if</span> (photometricLight.isMasked()) {</span>
|
||||
<span class="line"><span class="hljs-keyword">if</span> (photometricLight.<span class="hljs-built_in">isMasked</span>()) {</span>
|
||||
<span class="line"> <span class="hljs-comment">// The desired intensity is set by the artist</span></span>
|
||||
<span class="line"> <span class="hljs-comment">// The integrated intensity comes from a Monte-Carlo</span></span>
|
||||
<span class="line"> <span class="hljs-comment">// integration over the unit sphere around the luminaire</span></span>
|
||||
<span class="line"> multiplier = photometricLight.getDesiredIntensity() /</span>
|
||||
<span class="line"> photometricLight.getIntegratedIntensity();</span>
|
||||
<span class="line"> multiplier = photometricLight.<span class="hljs-built_in">getDesiredIntensity</span>() /</span>
|
||||
<span class="line"> photometricLight.<span class="hljs-built_in">getIntegratedIntensity</span>();</span>
|
||||
<span class="line">} <span class="hljs-keyword">else</span> {</span>
|
||||
<span class="line"> <span class="hljs-comment">// Multiplier provided for convenience, set to 1.0 by default</span></span>
|
||||
<span class="line"> multiplier = photometricLight.getMultiplier();</span>
|
||||
<span class="line"> multiplier = photometricLight.<span class="hljs-built_in">getMultiplier</span>();</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// The max intensity in cd comes from the IES profile</span></span>
|
||||
<span class="line"><span class="hljs-keyword">float</span> lightIntensity = photometricLight.getMaxIntensity() * multiplier;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_photometriclightintensity"> </a><b style="font-style:normal;">Listing 23:</b> Computing the intensity of a photometric light on the CPU</div></center>
|
||||
<span class="line"><span class="hljs-type">float</span> lightIntensity = photometricLight.<span class="hljs-built_in">getMaxIntensity</span>() * multiplier;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_photometriclightintensity"> </a><b style="font-style:normal;">Listing 23:</b> Computing the intensity of a photometric light on the CPU</div></center>
|
||||
<p>
|
||||
<div class="endnote"><a class="target" name="endnote-xarrowintensity"> </a><sup>4</sup> The XArrow profile declares a luminous intensity of 1,750 lm but a Monte-Carlo integration shows an intensity of only 350 lm.
|
||||
</div>
|
||||
@@ -2058,19 +2058,19 @@ In practice only 4 or 9 coefficients (i.e.: 2 or 3 bands) are enough for \(\cosT
|
||||
<center><div class="image" style=""><a href="../images/ibl/ibl_irradiance_sh2.png" target="_blank"><img class="markdeep" src="../images/ibl/ibl_irradiance_sh2.png" style="max-width:100%;" /></a><center><span class="imagecaption"><a class="target" name="figure_iblsh2"> </a><b style="font-style:normal;">Figure 52:</b> 2 bands (4 coefficients)</span></center></div></center>
|
||||
</p><p>
|
||||
In practice we pre-convolve \(\Lt\) with \(\cosTheta\) and pre-scale these coefficients by the basis scaling factors \(K_l^m\) so that the reconstruction code is as simple as possible in the shader:
|
||||
</p><pre class="listing tilde"><code><span class="line">vec3 <span class="hljs-function"><span class="hljs-title">irradianceSH</span>(<span class="hljs-params">vec3 n</span>)</span> {</span>
|
||||
<span class="line"> <span class="hljs-comment">// uniform vec3 sphericalHarmonics[9]</span></span>
|
||||
<span class="line"> <span class="hljs-comment">// We can use only the first 2 bands for better performance</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span></span>
|
||||
<span class="line"> sphericalHarmonics[<span class="hljs-number">0</span>]</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">1</span>] * (n.y)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">2</span>] * (n.z)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">3</span>] * (n.x)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">4</span>] * (n.y * n.x)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">5</span>] * (n.y * n.z)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">6</span>] * (<span class="hljs-number">3.0</span> * n.z * n.z - <span class="hljs-number">1.0</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">7</span>] * (n.z * n.x)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">8</span>] * (n.x * n.x - n.y * n.y);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line">vec3 irradianceSH(vec3 n) {</span>
|
||||
<span class="line"> // uniform vec3 sphericalHarmonics<span class="hljs-selector-attr">[9]</span></span>
|
||||
<span class="line"> // We can <span class="hljs-selector-tag">use</span> only the first <span class="hljs-number">2</span> bands for better performance</span>
|
||||
<span class="line"> return</span>
|
||||
<span class="line"> sphericalHarmonics<span class="hljs-selector-attr">[0]</span></span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[1]</span> * (n<span class="hljs-selector-class">.y</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[2]</span> * (n<span class="hljs-selector-class">.z</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[3]</span> * (n<span class="hljs-selector-class">.x</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[4]</span> * (n<span class="hljs-selector-class">.y</span> * n<span class="hljs-selector-class">.x</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[5]</span> * (n<span class="hljs-selector-class">.y</span> * n<span class="hljs-selector-class">.z</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[6]</span> * (<span class="hljs-number">3.0</span> * n<span class="hljs-selector-class">.z</span> * n<span class="hljs-selector-class">.z</span> - <span class="hljs-number">1.0</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[7]</span> * (n<span class="hljs-selector-class">.z</span> * n<span class="hljs-selector-class">.x</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[8]</span> * (n<span class="hljs-selector-class">.x</span> * n<span class="hljs-selector-class">.x</span> - n<span class="hljs-selector-class">.y</span> * n<span class="hljs-selector-class">.y</span>);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_irradiancesh"> </a><b style="font-style:normal;">Listing 26:</b> GLSL code to reconstruct the irradiance from the pre-scaled SH</div></center>
|
||||
<p>
|
||||
<p>Note that with 2 bands, the computation above becomes a single (4 \times 4) matrix-by-vector multiply.</p>
|
||||
@@ -2443,7 +2443,7 @@ LD(n, \alpha) &= \frac{\sum_i^N V(l_i, n,
|
||||
$$
|
||||
</p><p>
|
||||
These two new \(DFG\) terms simply need to replace the ones used in the implementation shown in section <a href="#toc9.5">9.5</a>:
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-keyword">float</span> Fc = <span class="hljs-built_in">pow</span>(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0f</span>);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> Fc = <span class="hljs-built_in">pow</span>(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0f</span>);</span>
|
||||
<span class="line">r.x += Gv * Fc;</span>
|
||||
<span class="line">r.y += Gv;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_multiscatteriblpreintegration"> </a><b style="font-style:normal;">Listing 29:</b> C++ implementation of the \(L_{DFG}\) term for multiscattering</div></center>
|
||||
<p>
|
||||
@@ -2507,11 +2507,11 @@ using an environment made of colored vertical stripes (skybox hidden).</span></c
|
||||
<p>
|
||||
<p>When sampling the IBL, the clear coat layer is calculated as a second specular lobe. This specular lobe is oriented along the view direction since we cannot reasonably integrate over the hemisphere. <a href="#listing_clearcoatibl">Listing 31</a> demonstrates this approximation in practice. It also shows the energy conservation step. It is important to note that this second specular lobe is computed exactly the same way as the main specular lobe, using the same DFG approximation.</p>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-comment">// clearCoat_NoV == shading_NoV if the clear coat layer doesn't have its own normal map</span></span>
|
||||
<span class="line"><span class="hljs-built_in">float</span> Fc = F_Schlick(<span class="hljs-number">0.04</span>, <span class="hljs-number">1.0</span>, clearCoat_NoV) * clearCoat;</span>
|
||||
<span class="line"><span class="hljs-type">float</span> Fc = <span class="hljs-built_in">F_Schlick</span>(<span class="hljs-number">0.04</span>, <span class="hljs-number">1.0</span>, clearCoat_NoV) * clearCoat;</span>
|
||||
<span class="line"><span class="hljs-comment">// base layer attenuation for energy compensation</span></span>
|
||||
<span class="line">iblDiffuse *= <span class="hljs-number">1.0</span> - Fc;</span>
|
||||
<span class="line">iblSpecular *= sq(<span class="hljs-number">1.0</span> - Fc);</span>
|
||||
<span class="line">iblSpecular += specularIBL(r, clearCoatPerceptualRoughness) * Fc;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clearcoatibl"> </a><b style="font-style:normal;">Listing 31:</b> GLSL implementation of the clear coat specular lobe for image-based lighting</div></center>
|
||||
<span class="line">iblSpecular *= <span class="hljs-built_in">sq</span>(<span class="hljs-number">1.0</span> - Fc);</span>
|
||||
<span class="line">iblSpecular += <span class="hljs-built_in">specularIBL</span>(r, clearCoatPerceptualRoughness) * Fc;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clearcoatibl"> </a><b style="font-style:normal;">Listing 31:</b> GLSL implementation of the clear coat specular lobe for image-based lighting</div></center>
|
||||
<a class="target" name="anisotropy"> </a><a class="target" name="lighting/imagebasedlights/anisotropy"> </a><a class="target" name="toc5.3.6"> </a><h3 id="anisotropy"><a class="header" href="#anisotropy">Anisotropy </a></h3>
|
||||
<p>
|
||||
<p>[<a href="#citation-mcauley15">McAuley15</a>] describes a technique called “bent reflection vector”, based [<a href="#citation-revie12">Revie12</a>]. The bent reflection vector is a rough approximation of anisotropic lighting but the alternative is to use importance sampling. This approximation is sufficiently cheap to compute and provides good results, as shown in <a href="#figure_anisotropicibl1">figure 59</a> and <a href="#figure_anisotropicibl2">figure 60</a>.</p>
|
||||
@@ -2550,17 +2550,17 @@ The DG term is generated using uniform sampling as recommended in [<a href="#cit
|
||||
<center><div class="image" style=""><a href="../images/ibl/dfg_cloth.png" target="_blank"><img class="markdeep" src="../images/ibl/dfg_cloth.png" /></a><center><span class="imagecaption"><a class="target" name="figure_dfgclothlut"> </a><b style="font-style:normal;">Figure 62:</b> DFG LUT with a 3rd channel encoding the DG term of the cloth BRDF</span></center></div></center>
|
||||
</p><p>
|
||||
The remainder of the image-based lighting implementation follows the same steps as the implementation of regular lights, including the optional subsurface scattering term and its wrap diffuse component. Just as with the clear coat IBL implementation, we cannot integrate over the hemisphere and use the view direction as the dominant light direction to compute the wrap diffuse component.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-built_in">float</span> diffuse = Fd_Lambert() * ambientOcclusion;</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> defined(SHADING_MODEL_CLOTH)</span></span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span>
|
||||
<span class="line">diffuse *= saturate((NoV + <span class="hljs-number">0.5</span>) / <span class="hljs-number">2.25</span>);</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> diffuse = <span class="hljs-built_in">Fd_Lambert</span>() * ambientOcclusion;</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined(SHADING_MODEL_CLOTH)</span></span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span>
|
||||
<span class="line">diffuse *= <span class="hljs-built_in">saturate</span>((NoV + <span class="hljs-number">0.5</span>) / <span class="hljs-number">2.25</span>);</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span></span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span></span>
|
||||
<span class="line"></span>
|
||||
<span class="line">vec3 indirectDiffuse = irradianceIBL(n) * diffuse;</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> defined(SHADING_MODEL_CLOTH) && defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span>
|
||||
<span class="line">indirectDiffuse *= saturate(subsurfaceColor + NoV);</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></span>
|
||||
<span class="line">vec3 indirectDiffuse = <span class="hljs-built_in">irradianceIBL</span>(n) * diffuse;</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined(SHADING_MODEL_CLOTH) && defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span>
|
||||
<span class="line">indirectDiffuse *= <span class="hljs-built_in">saturate</span>(subsurfaceColor + NoV);</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span></span>
|
||||
<span class="line"></span>
|
||||
<span class="line">vec3 ibl = diffuseColor * indirectDiffuse + indirectSpecular * specularColor;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothapprox"> </a><b style="font-style:normal;">Listing 34:</b> GLSL implementation of the DFG approximation for the cloth NDF</div></center>
|
||||
<p>
|
||||
@@ -2982,20 +2982,20 @@ L_{max} &= 2^{EV_{100}} \times 1.2
|
||||
<span class="line"><span class="hljs-comment">// aperture in f-stops</span></span>
|
||||
<span class="line"><span class="hljs-comment">// shutterSpeed in seconds</span></span>
|
||||
<span class="line"><span class="hljs-comment">// sensitivity in ISO</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">exposureSettings</span><span class="hljs-params">(<span class="hljs-keyword">float</span> aperture, <span class="hljs-keyword">float</span> shutterSpeed, <span class="hljs-keyword">float</span> sensitivity)</span> </span>{</span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">exposureSettings</span><span class="hljs-params">(<span class="hljs-type">float</span> aperture, <span class="hljs-type">float</span> shutterSpeed, <span class="hljs-type">float</span> sensitivity)</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> log2((aperture * aperture) / shutterSpeed * <span class="hljs-number">100.0</span> / sensitivity);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// Computes the exposure normalization factor from</span></span>
|
||||
<span class="line"><span class="hljs-comment">// the camera's EV100</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">exposure</span><span class="hljs-params">(<span class="hljs-keyword">float</span> ev100)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (<span class="hljs-built_in">pow</span>(<span class="hljs-number">2.0</span>, ev100) * <span class="hljs-number">1.2</span>);</span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">exposure</span><span class="hljs-params">(<span class="hljs-type">float</span> ev100)</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (pow(<span class="hljs-number">2.0</span>, ev100) * <span class="hljs-number">1.2</span>);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-keyword">float</span> ev100 = exposureSettings(aperture, shutterSpeed, sensitivity);</span>
|
||||
<span class="line"><span class="hljs-keyword">float</span> exposure = exposure(ev100);</span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-variable">ev100</span> <span class="hljs-operator">=</span> exposureSettings(aperture, shutterSpeed, sensitivity);</span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-variable">exposure</span> <span class="hljs-operator">=</span> exposure(ev100);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line">vec4 color = evaluateLighting();</span>
|
||||
<span class="line"><span class="hljs-type">vec4</span> <span class="hljs-variable">color</span> <span class="hljs-operator">=</span> evaluateLighting();</span>
|
||||
<span class="line">color.rgb *= exposure;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fragmentexposure"> </a><b style="font-style:normal;">Listing 42:</b> Implementation of exposure in GLSL</div></center>
|
||||
<p>
|
||||
<p>In practice the exposure factor can be pre-computed on the CPU to save shader instructions.</p>
|
||||
@@ -3466,9 +3466,9 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
|
||||
<span class="line"><span class="hljs-comment">// Data source:</span></span>
|
||||
<span class="line"><span class="hljs-comment">// http://cvrl.ioo.ucl.ac.uk/cmfs.htm</span></span>
|
||||
<span class="line"><span class="hljs-comment">// http://cvrl.ioo.ucl.ac.uk/database/text/cmfs/ciexyz31.htm</span></span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_XYZ_START = <span class="hljs-number">360</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_XYZ_COUNT = <span class="hljs-number">471</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> float3 CIE_XYZ[CIE_XYZ_COUNT] = { ... };</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_XYZ_START = <span class="hljs-number">360</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_XYZ_COUNT = <span class="hljs-number">471</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> float3 CIE_XYZ[CIE_XYZ_COUNT] = { ... };</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// CIE Standard Illuminant D65 relative spectral power distribution,</span></span>
|
||||
<span class="line"><span class="hljs-comment">// from 300nm to 830, at 5nm intervals</span></span>
|
||||
@@ -3476,51 +3476,51 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
|
||||
<span class="line"><span class="hljs-comment">// Data source:</span></span>
|
||||
<span class="line"><span class="hljs-comment">// https://en.wikipedia.org/wiki/Illuminant_D65</span></span>
|
||||
<span class="line"><span class="hljs-comment">// https://cielab.xyz/pdf/CIE_sel_colorimetric_tables.xls</span></span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_INTERVAL = <span class="hljs-number">5</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_START = <span class="hljs-number">300</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_END = <span class="hljs-number">830</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_COUNT = <span class="hljs-number">107</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">float</span> CIE_D65[CIE_D65_COUNT] = { ... };</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_INTERVAL = <span class="hljs-number">5</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_START = <span class="hljs-number">300</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_END = <span class="hljs-number">830</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_COUNT = <span class="hljs-number">107</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">float</span> CIE_D65[CIE_D65_COUNT] = { ... };</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Sample</span> {</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> w = <span class="hljs-number">0.0f</span>; <span class="hljs-comment">// wavelength</span></span>
|
||||
<span class="line"> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">complex</span><<span class="hljs-keyword">float</span>> ior; <span class="hljs-comment">// complex IOR, n + ik</span></span>
|
||||
<span class="line"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Sample</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> w = <span class="hljs-number">0.0f</span>; <span class="hljs-comment">// wavelength</span></span>
|
||||
<span class="line"> std::complex<<span class="hljs-type">float</span>> ior; <span class="hljs-comment">// complex IOR, n + ik</span></span>
|
||||
<span class="line">};</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">float</span> <span class="hljs-title">illuminantD65</span><span class="hljs-params">(<span class="hljs-keyword">float</span> w)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> i0 = <span class="hljs-keyword">size_t</span>((w - CIE_D65_START) / CIE_D65_INTERVAL);</span>
|
||||
<span class="line"> uint2 indexBounds{i0, <span class="hljs-built_in">std</span>::min(i0 + <span class="hljs-number">1</span>, CIE_D65_END)};</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">float</span> <span class="hljs-title">illuminantD65</span><span class="hljs-params">(<span class="hljs-type">float</span> w)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> i0 = <span class="hljs-built_in">size_t</span>((w - CIE_D65_START) / CIE_D65_INTERVAL);</span>
|
||||
<span class="line"> uint2 indexBounds{i0, std::<span class="hljs-built_in">min</span>(i0 + <span class="hljs-number">1</span>, CIE_D65_END)};</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> float2 wavelengthBounds = CIE_D65_START + float2{indexBounds} * CIE_D65_INTERVAL;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> t = (w - wavelengthBounds.x) / (wavelengthBounds.y - wavelengthBounds.x);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> lerp(CIE_D65[indexBounds.x], CIE_D65[indexBounds.y], t);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> t = (w - wavelengthBounds.x) / (wavelengthBounds.y - wavelengthBounds.x);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-built_in">lerp</span>(CIE_D65[indexBounds.x], CIE_D65[indexBounds.y], t);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// For std::lower_bound</span></span>
|
||||
<span class="line"><span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span><(<span class="hljs-keyword">const</span> Sample& lhs, <span class="hljs-keyword">const</span> Sample& rhs) {</span>
|
||||
<span class="line"><span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span><(<span class="hljs-type">const</span> Sample& lhs, <span class="hljs-type">const</span> Sample& rhs) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> lhs.w < rhs.w;</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// The wavelength w must be between 360nm and 830nm</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">complex</span><<span class="hljs-keyword">float</span>> <span class="hljs-title">findSample</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span><sample>& samples, <span class="hljs-keyword">float</span> w)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> i1 = <span class="hljs-built_in">std</span>::lower_bound(</span>
|
||||
<span class="line"> samples.begin(), samples.end(), Sample{w, <span class="hljs-number">0.0f</span> + <span class="hljs-number">0.0</span><span class="hljs-keyword">if</span>});</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">static</span> std::complex<<span class="hljs-type">float</span>> <span class="hljs-title">findSample</span><span class="hljs-params">(<span class="hljs-type">const</span> std::vector<sample>& samples, <span class="hljs-type">float</span> w)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> i1 = std::<span class="hljs-built_in">lower_bound</span>(</span>
|
||||
<span class="line"> samples.<span class="hljs-built_in">begin</span>(), samples.<span class="hljs-built_in">end</span>(), Sample{w, <span class="hljs-number">0.0f</span> + <span class="hljs-number">0.0</span><span class="hljs-keyword">if</span>});</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> i0 = i1 - <span class="hljs-number">1</span>;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// Interpolate the complex IORs</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> t = (w - i0->w) / (i1->w - i0->w);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> n = lerp(i0->ior.real(), i1->ior.real(), t);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> k = lerp(i0->ior.imag(), i1->ior.imag(), t);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> t = (w - i0->w) / (i1->w - i0->w);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> n = <span class="hljs-built_in">lerp</span>(i0->ior.<span class="hljs-built_in">real</span>(), i1->ior.<span class="hljs-built_in">real</span>(), t);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> k = <span class="hljs-built_in">lerp</span>(i0->ior.<span class="hljs-built_in">imag</span>(), i1->ior.<span class="hljs-built_in">imag</span>(), t);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> { n, k };</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">float</span> <span class="hljs-title">fresnel</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">complex</span><<span class="hljs-keyword">float</span>>& sample)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> (((sample - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (<span class="hljs-built_in">std</span>::conj(sample) - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>))) /</span>
|
||||
<span class="line"> ((sample + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (<span class="hljs-built_in">std</span>::conj(sample) + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)))).real();</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">float</span> <span class="hljs-title">fresnel</span><span class="hljs-params">(<span class="hljs-type">const</span> std::complex<<span class="hljs-type">float</span>>& sample)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> (((sample - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (std::<span class="hljs-built_in">conj</span>(sample) - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>))) /</span>
|
||||
<span class="line"> ((sample + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (std::<span class="hljs-built_in">conj</span>(sample) + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)))).<span class="hljs-built_in">real</span>();</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> float3 <span class="hljs-title">XYZ_to_sRGB</span><span class="hljs-params">(<span class="hljs-keyword">const</span> float3& v)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">const</span> mat3f XYZ_sRGB{</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">static</span> float3 <span class="hljs-title">XYZ_to_sRGB</span><span class="hljs-params">(<span class="hljs-type">const</span> float3& v)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-type">const</span> mat3f XYZ_sRGB{</span>
|
||||
<span class="line"> <span class="hljs-number">3.2404542f</span>, <span class="hljs-number">-0.9692660f</span>, <span class="hljs-number">0.0556434f</span>,</span>
|
||||
<span class="line"> <span class="hljs-number">-1.5371385f</span>, <span class="hljs-number">1.8760108f</span>, <span class="hljs-number">-0.2040259f</span>,</span>
|
||||
<span class="line"> <span class="hljs-number">-0.4985314f</span>, <span class="hljs-number">0.0415560f</span>, <span class="hljs-number">1.0572252f</span></span>
|
||||
@@ -3529,21 +3529,21 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// Outputs a linear sRGB color</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> float3 <span class="hljs-title">computeColor</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span><sample>& samples)</span> </span>{</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">static</span> float3 <span class="hljs-title">computeColor</span><span class="hljs-params">(<span class="hljs-type">const</span> std::vector<sample>& samples)</span> </span>{</span>
|
||||
<span class="line"> float3 xyz{<span class="hljs-number">0.0f</span>};</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> y = <span class="hljs-number">0.0f</span>;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> y = <span class="hljs-number">0.0f</span>;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">size_t</span> i = <span class="hljs-number">0</span>; i < CIE_XYZ_COUNT; i++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">size_t</span> i = <span class="hljs-number">0</span>; i < CIE_XYZ_COUNT; i++) {</span>
|
||||
<span class="line"> <span class="hljs-comment">// Current wavelength</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> w = CIE_XYZ_START + i;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> w = CIE_XYZ_START + i;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// Find most appropriate CIE XYZ sample for the wavelength</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> sample = findSample(samples, w);</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> sample = <span class="hljs-built_in">findSample</span>(samples, w);</span>
|
||||
<span class="line"> <span class="hljs-comment">// Compute Fresnel reflectance at normal incidence</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> f0 = fresnel(sample);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> f0 = <span class="hljs-built_in">fresnel</span>(sample);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// We need to multiply by the spectral power distribution of the illuminant</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> d65 = illuminantD65(w);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> d65 = <span class="hljs-built_in">illuminantD65</span>(w);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> xyz += f0 * CIE_XYZ[i] * d65;</span>
|
||||
<span class="line"> y += CIE_XYZ[i].y * d65;</span>
|
||||
@@ -3552,10 +3552,10 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
|
||||
<span class="line"> <span class="hljs-comment">// Normalize so that 100% reflectance at every wavelength yields Y=1</span></span>
|
||||
<span class="line"> xyz /= y;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> float3 linear = XYZ_to_sRGB(xyz);</span>
|
||||
<span class="line"> float3 linear = <span class="hljs-built_in">XYZ_to_sRGB</span>(xyz);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// Normalize out-of-gamut values</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (any(greaterThan(linear, float3{<span class="hljs-number">1.0f</span>}))) linear *= <span class="hljs-number">1.0f</span> / max(linear);</span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">any</span>(<span class="hljs-built_in">greaterThan</span>(linear, float3{<span class="hljs-number">1.0f</span>}))) linear *= <span class="hljs-number">1.0f</span> / <span class="hljs-built_in">max</span>(linear);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> linear;</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_specularcolorimpl"> </a><b style="font-style:normal;">Listing 46:</b> C++ implementation to compute the base color of a metallic surface from spectral data</div></center>
|
||||
@@ -3735,47 +3735,47 @@ l &= \{ cos\phi sin\theta,sin\phi sin\theta,cos\theta \}
|
||||
<pre class="listing tilde"><code><span class="line">vec2f hammersley(uint i, <span class="hljs-built_in">float</span> numSamples) {</span>
|
||||
<span class="line"> uint bits = i;</span>
|
||||
<span class="line"> bits = (bits << <span class="hljs-string">16) | (bits >> 16</span>);</span>
|
||||
<span class="line"> bits = ((bits & 0x55555555) << <span class="hljs-string">1) | ((bits & 0xAAAAAAAA) >> 1</span>);</span>
|
||||
<span class="line"> bits = ((bits & 0x33333333) << <span class="hljs-string">2) | ((bits & 0xCCCCCCCC) >> 2</span>);</span>
|
||||
<span class="line"> bits = ((bits & 0x0F0F0F0F) << <span class="hljs-string">4) | ((bits & 0xF0F0F0F0) >> 4</span>);</span>
|
||||
<span class="line"> bits = ((bits & 0x00FF00FF) << <span class="hljs-string">8) | ((bits & 0xFF00FF00) >> 8</span>);</span>
|
||||
<span class="line"> <span class="hljs-built_in">return</span> vec2f(i / numSamples, bits / exp2(32));</span>
|
||||
<span class="line"> bits = ((bits & <span class="hljs-number">0</span>x55555555) << <span class="hljs-number">1</span>) | ((bits & <span class="hljs-number">0</span>xAAAAAAAA) >> <span class="hljs-number">1</span>);</span>
|
||||
<span class="line"> bits = ((bits & <span class="hljs-number">0</span>x33333333) << <span class="hljs-number">2</span>) | ((bits & <span class="hljs-number">0</span>xCCCCCCCC) >> <span class="hljs-number">2</span>);</span>
|
||||
<span class="line"> bits = ((bits & <span class="hljs-number">0</span>x0F0F0F0F) << <span class="hljs-number">4</span>) | ((bits & <span class="hljs-number">0</span>xF0F0F0F0) >> <span class="hljs-number">4</span>);</span>
|
||||
<span class="line"> bits = ((bits & <span class="hljs-number">0</span>x00FF00FF) << <span class="hljs-number">8</span>) | ((bits & <span class="hljs-number">0</span>xFF00FF00) >> <span class="hljs-number">8</span>);</span>
|
||||
<span class="line"> return vec2f(i / numSamples, bits / exp2(<span class="hljs-number">32</span>));</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde">C++ implementation of a Hammersley sequence generator</div></center>
|
||||
<a class="target" name="precomputinglforimage-basedlighting"> </a><a class="target" name="annex/precomputinglforimage-basedlighting"> </a><a class="target" name="toc9.5"> </a><h2 id="precomputing-l-for-image-based-lighting"><a class="header" href="#precomputing-l-for-image-based-lighting">Precomputing L for image-based lighting</a></h2>
|
||||
<p>
|
||||
<p>The term ( L_{DFG} ) is only dependent on ( \NoV ). Below, the normal is arbitrarily set to ( n=\left[0, 0, 1\right] ) and (v) is chosen to satisfy ( \NoV ). The vector ( h_i ) is the ( D_{GGX}(\alpha) ) important direction sample (i).</p>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> GDFG(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a) {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>((-NoL * a2 + NoL) * NoL + a2);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>((-NoV * a2 + NoV) * NoV + a2);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">GDFG</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a)</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXL</span> <span class="hljs-operator">=</span> NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXV</span> <span class="hljs-operator">=</span> NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> (<span class="hljs-number">2</span> * NoL) / (GGXV + GGXL);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line">float2 DFG(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> a) {</span>
|
||||
<span class="line">float2 <span class="hljs-title function_">DFG</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> a)</span> {</span>
|
||||
<span class="line"> float3 V;</span>
|
||||
<span class="line"> V.x = <span class="hljs-built_in">sqrt</span>(<span class="hljs-number">1.0</span>f - NoV*NoV);</span>
|
||||
<span class="line"> V.y = <span class="hljs-number">0.0</span>f;</span>
|
||||
<span class="line"> V.x = sqrt(<span class="hljs-number">1.0f</span> - NoV*NoV);</span>
|
||||
<span class="line"> V.y = <span class="hljs-number">0.0f</span>;</span>
|
||||
<span class="line"> V.z = NoV;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> float2 r = <span class="hljs-number">0.0</span>f;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">uint</span> i = <span class="hljs-number">0</span>; i < sampleCount; i++) {</span>
|
||||
<span class="line"> float2 Xi = hammersley(i, sampleCount);</span>
|
||||
<span class="line"> float3 H = importanceSampleGGX(Xi, a, N);</span>
|
||||
<span class="line"> float3 L = <span class="hljs-number">2.0</span>f * <span class="hljs-built_in">dot</span>(V, H) * H - V;</span>
|
||||
<span class="line"> <span class="hljs-type">float2</span> <span class="hljs-variable">r</span> <span class="hljs-operator">=</span> <span class="hljs-number">0.0f</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">uint</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i < sampleCount; i++) {</span>
|
||||
<span class="line"> <span class="hljs-type">float2</span> <span class="hljs-variable">Xi</span> <span class="hljs-operator">=</span> hammersley(i, sampleCount);</span>
|
||||
<span class="line"> <span class="hljs-type">float3</span> <span class="hljs-variable">H</span> <span class="hljs-operator">=</span> importanceSampleGGX(Xi, a, N);</span>
|
||||
<span class="line"> <span class="hljs-type">float3</span> <span class="hljs-variable">L</span> <span class="hljs-operator">=</span> <span class="hljs-number">2.0f</span> * dot(V, H) * H - V;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> VoH = saturate(<span class="hljs-built_in">dot</span>(V, H));</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> NoL = saturate(L.z);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> NoH = saturate(H.z);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">VoH</span> <span class="hljs-operator">=</span> saturate(dot(V, H));</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoL</span> <span class="hljs-operator">=</span> saturate(L.z);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoH</span> <span class="hljs-operator">=</span> saturate(H.z);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (NoL > <span class="hljs-number">0.0</span>f) {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> G = GDFG(NoV, NoL, a);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Gv = G * VoH / NoH;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Fc = <span class="hljs-built_in">pow</span>(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0</span>f);</span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (NoL > <span class="hljs-number">0.0f</span>) {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">G</span> <span class="hljs-operator">=</span> GDFG(NoV, NoL, a);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">Gv</span> <span class="hljs-operator">=</span> G * VoH / NoH;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">Fc</span> <span class="hljs-operator">=</span> pow(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0f</span>);</span>
|
||||
<span class="line"> r.x += Gv * (<span class="hljs-number">1</span> - Fc);</span>
|
||||
<span class="line"> r.y += Gv * Fc;</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> r * (<span class="hljs-number">1.0</span>f / sampleCount);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> r * (<span class="hljs-number">1.0f</span> / sampleCount);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde">C++ implementation of the \( L_{DFG} \) term</div></center>
|
||||
<a class="target" name="sphericalharmonics"> </a><a class="target" name="annex/sphericalharmonics"> </a><a class="target" name="toc9.6"> </a><h2 id="spherical-harmonics"><a class="header" href="#spherical-harmonics">Spherical Harmonics</a></h2>
|
||||
<p>
|
||||
@@ -3851,56 +3851,56 @@ sin(m \phi + \phi) &= sin(m \phi) cos(\phi) + cos(m \phi) sin(\phi) \Leftrightar
|
||||
\end{align*}$$
|
||||
</p><p>
|
||||
<a href="#listing_nonnormalizedshbasis">Listing 47</a> shows the C++ code to compute the non-normalized SH basis \(\frac{y^m_l(s)}{\sqrt{2} K^m_l}\):
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-keyword">size_t</span> <span class="hljs-title">SHindex</span><span class="hljs-params">(<span class="hljs-keyword">ssize_t</span> m, <span class="hljs-keyword">size_t</span> l)</span> </span>{</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">size_t</span> <span class="hljs-title">SHindex</span><span class="hljs-params">(<span class="hljs-type">ssize_t</span> m, <span class="hljs-type">size_t</span> l)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> l * (l + <span class="hljs-number">1</span>) + m;</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">computeShBasis</span><span class="hljs-params">(</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span>* <span class="hljs-keyword">const</span> SHb,</span>
|
||||
<span class="line"> <span class="hljs-keyword">size_t</span> numBands,</span>
|
||||
<span class="line"> <span class="hljs-keyword">const</span> vec3& s)</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">computeShBasis</span><span class="hljs-params">(</span>
|
||||
<span class="line"> <span class="hljs-type">double</span>* <span class="hljs-type">const</span> SHb,</span>
|
||||
<span class="line"> <span class="hljs-type">size_t</span> numBands,</span>
|
||||
<span class="line"> <span class="hljs-type">const</span> vec3& s)</span></span>
|
||||
<span class="line"></span>{</span>
|
||||
<span class="line"> <span class="hljs-comment">// handle m=0 separately, since it produces only one coefficient</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml_2 = <span class="hljs-number">0</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml_1 = <span class="hljs-number">1</span>;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml_2 = <span class="hljs-number">0</span>;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml_1 = <span class="hljs-number">1</span>;</span>
|
||||
<span class="line"> SHb[<span class="hljs-number">0</span>] = Pml_1;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> l = <span class="hljs-number">1</span>; l < numBands; l++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l - <span class="hljs-number">1</span>) * Pml_2) / l;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = <span class="hljs-number">1</span>; l < numBands; l++) {</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l - <span class="hljs-number">1</span>) * Pml_2) / l;</span>
|
||||
<span class="line"> Pml_2 = Pml_1;</span>
|
||||
<span class="line"> Pml_1 = Pml;</span>
|
||||
<span class="line"> SHb[SHindex(<span class="hljs-number">0</span>, l)] = Pml;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(<span class="hljs-number">0</span>, l)] = Pml;</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pmm = <span class="hljs-number">1</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> m = <span class="hljs-number">1</span>; m < numBands ; m++) {</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pmm = <span class="hljs-number">1</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> m = <span class="hljs-number">1</span>; m < numBands ; m++) {</span>
|
||||
<span class="line"> Pmm = (<span class="hljs-number">1</span> - <span class="hljs-number">2</span> * m) * Pmm;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml_2 = Pmm;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml_1 = (<span class="hljs-number">2</span> * m + <span class="hljs-number">1</span>)*Pmm*s.z;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml_2 = Pmm;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml_1 = (<span class="hljs-number">2</span> * m + <span class="hljs-number">1</span>)*Pmm*s.z;</span>
|
||||
<span class="line"> <span class="hljs-comment">// l == m</span></span>
|
||||
<span class="line"> SHb[SHindex(-m, m)] = Pml_2;</span>
|
||||
<span class="line"> SHb[SHindex( m, m)] = Pml_2;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, m)] = Pml_2;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, m)] = Pml_2;</span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (m + <span class="hljs-number">1</span> < numBands) {</span>
|
||||
<span class="line"> <span class="hljs-comment">// l == m+1</span></span>
|
||||
<span class="line"> SHb[SHindex(-m, m + <span class="hljs-number">1</span>)] = Pml_1;</span>
|
||||
<span class="line"> SHb[SHindex( m, m + <span class="hljs-number">1</span>)] = Pml_1;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> l = m + <span class="hljs-number">2</span>; l < numBands; l++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l + m - <span class="hljs-number">1</span>) * Pml_2)</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, m + <span class="hljs-number">1</span>)] = Pml_1;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, m + <span class="hljs-number">1</span>)] = Pml_1;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = m + <span class="hljs-number">2</span>; l < numBands; l++) {</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l + m - <span class="hljs-number">1</span>) * Pml_2)</span>
|
||||
<span class="line"> / (l - m);</span>
|
||||
<span class="line"> Pml_2 = Pml_1;</span>
|
||||
<span class="line"> Pml_1 = Pml;</span>
|
||||
<span class="line"> SHb[SHindex(-m, l)] = Pml;</span>
|
||||
<span class="line"> SHb[SHindex( m, l)] = Pml;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, l)] = Pml;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, l)] = Pml;</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Cm = s.x;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Sm = s.y;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> m = <span class="hljs-number">1</span>; m <= numBands ; m++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> l = m; l < numBands ; l++) {</span>
|
||||
<span class="line"> SHb[SHindex(-m, l)] *= Sm;</span>
|
||||
<span class="line"> SHb[SHindex( m, l)] *= Cm;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Cm = s.x;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Sm = s.y;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> m = <span class="hljs-number">1</span>; m <= numBands ; m++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = m; l < numBands ; l++) {</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, l)] *= Sm;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, l)] *= Cm;</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Cm1 = Cm * s.x - Sm * s.y;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Sm1 = Sm * s.x + Cm * s.y;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Cm1 = Cm * s.x - Sm * s.y;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Sm1 = Sm * s.x + Cm * s.y;</span>
|
||||
<span class="line"> Cm = Cm1;</span>
|
||||
<span class="line"> Sm = Sm1;</span>
|
||||
<span class="line"> }</span>
|
||||
@@ -3979,10 +3979,10 @@ $$\begin{equation}
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
Here is the C++ code to compute \(\hat{C}_l\):
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> n, <span class="hljs-keyword">size_t</span> d = <span class="hljs-number">1</span>)</span></span>;</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-type">size_t</span> n, <span class="hljs-type">size_t</span> d = <span class="hljs-number">1</span>)</span></span>;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// < cos(theta) > SH coefficients pre-multiplied by 1 / K(0,l)</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">computeTruncatedCosSh</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> l)</span> </span>{</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">computeTruncatedCosSh</span><span class="hljs-params">(<span class="hljs-type">size_t</span> l)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (l == <span class="hljs-number">0</span>) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> M_PI;</span>
|
||||
<span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (l == <span class="hljs-number">1</span>) {</span>
|
||||
@@ -3990,17 +3990,17 @@ Here is the C++ code to compute \(\hat{C}_l\):
|
||||
<span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (l & <span class="hljs-number">1</span>) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> <span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> l_2 = l / <span class="hljs-number">2</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> A0 = ((l_2 & <span class="hljs-number">1</span>) ? <span class="hljs-number">1.0</span> : <span class="hljs-number">-1.0</span>) / ((l + <span class="hljs-number">2</span>) * (l - <span class="hljs-number">1</span>));</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> A1 = factorial(l, l_2) / (factorial(l_2) * (<span class="hljs-number">1</span> << l));</span>
|
||||
<span class="line"> <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> l_2 = l / <span class="hljs-number">2</span>;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> A0 = ((l_2 & <span class="hljs-number">1</span>) ? <span class="hljs-number">1.0</span> : <span class="hljs-number">-1.0</span>) / ((l + <span class="hljs-number">2</span>) * (l - <span class="hljs-number">1</span>));</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> A1 = <span class="hljs-built_in">factorial</span>(l, l_2) / (<span class="hljs-built_in">factorial</span>(l_2) * (<span class="hljs-number">1</span> << l));</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * M_PI * A0 * A1;</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// returns n! / d!</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> n, <span class="hljs-keyword">size_t</span> d )</span> </span>{</span>
|
||||
<span class="line"> d = <span class="hljs-built_in">std</span>::max(<span class="hljs-keyword">size_t</span>(<span class="hljs-number">1</span>), d);</span>
|
||||
<span class="line"> n = <span class="hljs-built_in">std</span>::max(<span class="hljs-keyword">size_t</span>(<span class="hljs-number">1</span>), n);</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> r = <span class="hljs-number">1.0</span>;</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-type">size_t</span> n, <span class="hljs-type">size_t</span> d )</span> </span>{</span>
|
||||
<span class="line"> d = std::<span class="hljs-built_in">max</span>(<span class="hljs-built_in">size_t</span>(<span class="hljs-number">1</span>), d);</span>
|
||||
<span class="line"> n = std::<span class="hljs-built_in">max</span>(<span class="hljs-built_in">size_t</span>(<span class="hljs-number">1</span>), n);</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> r = <span class="hljs-number">1.0</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (n == d) {</span>
|
||||
<span class="line"> <span class="hljs-comment">// intentionally left blank</span></span>
|
||||
<span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (n > d) {</span>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
311
docs/wip/sky/SimulatedSkybox.js
Normal file
311
docs/wip/sky/SimulatedSkybox.js
Normal file
@@ -0,0 +1,311 @@
|
||||
|
||||
// SimulatedSkybox.js
|
||||
// Ported from samples/utils/SimulatedSkybox.cpp
|
||||
|
||||
class SimulatedSkybox {
|
||||
constructor(engine) {
|
||||
this.engine = engine;
|
||||
|
||||
// Default Parameters
|
||||
this.sunDirection = [0, 1, 0];
|
||||
this.sunIntensity = 100000.0;
|
||||
this.turbidity = 2.0;
|
||||
this.rayleigh = 1.0;
|
||||
this.mieCoefficient = 1.0;
|
||||
this.mieG = 0.8;
|
||||
this.ozone = 0.0;
|
||||
this.msFactors = [0.1, 0.5, 0.0];
|
||||
this.contrast = 1.0;
|
||||
this.nightColor = [0.0, 0.0003, 0.00075];
|
||||
this.shimmerControl = [0.0, 20.0, 0.1];
|
||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||
this.starControl = [1.0, 1.0]; // x=Density (0-1), y=Enabled (0-1)
|
||||
this.planetRadius = 6360.0;
|
||||
|
||||
// Sun Halo
|
||||
// x=cos(rad), y=limbDarkening, z=intensity, w=enabled
|
||||
this.sunHalo = [Math.cos(0.5 * Math.PI / 180.0), 0.5, 1.0, 1.0];
|
||||
|
||||
this.initEntity();
|
||||
}
|
||||
|
||||
async loadMaterial(url) {
|
||||
console.log("Loading material from:", url);
|
||||
const response = await fetch(url);
|
||||
const buffer = await response.arrayBuffer();
|
||||
this.material = this.engine.createMaterial(new Uint8Array(buffer));
|
||||
this.materialInstance = this.material.createInstance();
|
||||
|
||||
// Re-bind the entity with the loaded material
|
||||
const rcm = this.engine.getRenderableManager();
|
||||
const instance = rcm.getInstance(this.entity);
|
||||
rcm.setMaterialInstanceAt(instance, 0, this.materialInstance);
|
||||
|
||||
console.log("Material loaded and bound.");
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
initEntity() {
|
||||
const EntityManager = Filament.EntityManager;
|
||||
const RenderableManager = Filament.RenderableManager;
|
||||
const VertexBuffer = Filament.VertexBuffer;
|
||||
const IndexBuffer = Filament.IndexBuffer;
|
||||
const AttributeType = Filament.VertexBuffer$AttributeType;
|
||||
const VertexAttribute = Filament.VertexAttribute;
|
||||
const PrimitiveType = Filament.RenderableManager$PrimitiveType;
|
||||
const IndexType = Filament.IndexBuffer$IndexType;
|
||||
|
||||
this.entity = EntityManager.get().create();
|
||||
|
||||
// 3 vertices for full screen triangle
|
||||
// coords: -1,-1 to 3,-1 to -1,3
|
||||
const TRIANGLE_VERTICES = new Float32Array([
|
||||
-1.0, -1.0,
|
||||
3.0, -1.0,
|
||||
-1.0, 3.0
|
||||
]);
|
||||
|
||||
const TRIANGLE_INDICES = new Uint16Array([0, 1, 2]);
|
||||
|
||||
this.vb = VertexBuffer.Builder()
|
||||
.vertexCount(3)
|
||||
.bufferCount(1)
|
||||
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, 8)
|
||||
.build(this.engine);
|
||||
|
||||
this.vb.setBufferAt(this.engine, 0, TRIANGLE_VERTICES);
|
||||
|
||||
this.ib = IndexBuffer.Builder()
|
||||
.indexCount(3)
|
||||
.bufferType(IndexType.USHORT)
|
||||
.build(this.engine);
|
||||
|
||||
this.ib.setBuffer(this.engine, TRIANGLE_INDICES);
|
||||
|
||||
// We create a dummy material first or wait?
|
||||
// In JS we usually can't block. We'll rely on loadMaterial being called.
|
||||
// For now, we build the Renderable without material, then set it later.
|
||||
|
||||
RenderableManager.Builder(1)
|
||||
.geometry(0, PrimitiveType.TRIANGLES, this.vb, this.ib)
|
||||
.culling(false)
|
||||
.castShadows(false)
|
||||
.receiveShadows(false)
|
||||
.priority(7) // Render behind translucent objects? 7 is skybox priority typically.
|
||||
.build(this.engine, this.entity);
|
||||
}
|
||||
|
||||
setSunPosition(direction) {
|
||||
// normalize
|
||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
||||
if (len > 0) {
|
||||
this.sunDirection = [direction[0] / len, direction[1] / len, direction[2] / len];
|
||||
} else {
|
||||
this.sunDirection = [0, 1, 0];
|
||||
}
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setSunIntensity(intensity) {
|
||||
this.sunIntensity = Math.max(0.0, intensity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setTurbidity(turbidity) {
|
||||
this.turbidity = Math.max(0.0, turbidity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setRayleigh(rayleigh) {
|
||||
this.rayleigh = Math.max(0.0, rayleigh);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMieCoefficient(mie) {
|
||||
this.mieCoefficient = Math.max(0.0, mie);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMieG(g) {
|
||||
this.mieG = Math.max(0.0, g);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setOzone(strength) {
|
||||
this.ozone = Math.max(0.0, strength);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMultiScattering(r, m) {
|
||||
this.msFactors[0] = Math.max(0.0, Math.min(2.0, r));
|
||||
this.msFactors[1] = Math.max(0.0, Math.min(2.0, m));
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setHorizonGlow(strength) {
|
||||
this.msFactors[2] = Math.max(0.0, Math.min(1.0, strength));
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setContrast(contrast) {
|
||||
this.contrast = contrast;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setNightColor(color) {
|
||||
this.nightColor = color;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setSunRadius(degrees) {
|
||||
const rad = degrees * (Math.PI / 180.0);
|
||||
this.sunHalo[0] = Math.cos(rad);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setSunDiskIntensity(intensity) {
|
||||
this.sunHalo[2] = Math.max(0.0, intensity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setSunLimbDarkening(strength) {
|
||||
this.sunHalo[1] = Math.max(0.0, strength);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setSunDiskEnabled(enabled) {
|
||||
this.sunHalo[3] = enabled ? 1.0 : 0.0;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setShimmerControl(strength, frequency, maskHeight) {
|
||||
this.shimmerControl[0] = Math.max(0.0, strength);
|
||||
this.shimmerControl[1] = Math.max(0.0, frequency);
|
||||
this.shimmerControl[2] = Math.max(0.001, maskHeight);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setCloudControl(coverage, density, height, speed) {
|
||||
this.cloudControl[0] = Math.max(0.0, Math.min(1.0, coverage));
|
||||
this.cloudControl[1] = Math.max(0.0, density);
|
||||
this.cloudControl[2] = Math.max(1000.0, height);
|
||||
// JS speed adjustment logic matches C++: speed * (0.05 / 72.0)
|
||||
this.cloudControl[3] = speed * (0.05 / 72.0);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setCloudShapeEvolution(speed) {
|
||||
this.cloudControl2[0] = speed;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setCloudVolumetricLighting(enabled) {
|
||||
this.cloudControl2[1] = enabled ? 1.0 : 0.0;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setWaterControl(strength, speed, derivativeTrick, octaves) {
|
||||
this.waterControl[0] = Math.max(0.0, strength);
|
||||
this.waterControl[1] = Math.max(0.0, speed);
|
||||
this.waterControl[2] = derivativeTrick;
|
||||
this.waterControl[3] = Math.max(1.0, Math.min(8.0, octaves));
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setStarControl(density, enabled) {
|
||||
this.starControl[0] = Math.max(0.0, Math.min(1.0, density));
|
||||
this.starControl[1] = enabled ? 1.0 : 0.0;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
updateCoefficients() {
|
||||
if (!this.materialInstance) {
|
||||
console.warn("updateCoefficients called before material loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 1. Rayleigh Coefficients
|
||||
const F_PI = Math.PI;
|
||||
const lambda = [680e-9, 550e-9, 440e-9];
|
||||
const n = 1.0003;
|
||||
const N = 2.545e25;
|
||||
const term = (8.0 * Math.pow(F_PI, 3.0) * Math.pow(n * n - 1.0, 2.0)) / (3.0 * N);
|
||||
|
||||
const depthR = [
|
||||
term / Math.pow(lambda[0], 4.0),
|
||||
term / Math.pow(lambda[1], 4.0),
|
||||
term / Math.pow(lambda[2], 4.0)
|
||||
].map(v => v * 8000.0 * this.rayleigh);
|
||||
|
||||
// 2. Mie Coefficients
|
||||
const mieAlpha = 1.3;
|
||||
const mieBase = 2.0e-5 * this.turbidity;
|
||||
const depthM = [
|
||||
mieBase * Math.pow(550e-9 / lambda[0], mieAlpha),
|
||||
mieBase * Math.pow(550e-9 / lambda[1], mieAlpha),
|
||||
mieBase * Math.pow(550e-9 / lambda[2], mieAlpha)
|
||||
].map(v => v * 1200.0 * this.mieCoefficient);
|
||||
|
||||
// Fake Ozone
|
||||
const ozone = [0.0, this.ozone * 0.1, 0.0];
|
||||
|
||||
// Sun Fade (Horizon)
|
||||
const cutoffAngle = 96.0 * (F_PI / 180.0);
|
||||
const steepness = 1.5;
|
||||
const zenithFade = 1.0 - Math.exp(-(cutoffAngle / steepness));
|
||||
const zenithAngle = Math.acos(Math.max(-1.0, Math.min(1.0, this.sunDirection[1])));
|
||||
const sunFade = Math.max(0.0, 1.0 - Math.exp(-((cutoffAngle - zenithAngle) / steepness))) / zenithFade;
|
||||
|
||||
const physicalSunIntensity = this.sunIntensity * sunFade;
|
||||
|
||||
// Radiance Conversion for Sun Halo
|
||||
// Solid Angle = 2 * PI * (1 - cos(angularRadius))
|
||||
const solidAngle = 2.0 * F_PI * (1.0 - this.sunHalo[0]);
|
||||
const radianceConversion = 1.0 / Math.max(1e-9, solidAngle);
|
||||
const sunHaloUpload = [...this.sunHalo];
|
||||
sunHaloUpload[2] *= radianceConversion;
|
||||
|
||||
// Cloud Intersection
|
||||
const r = this.planetRadius;
|
||||
const h = this.cloudControl[2] * 0.001; // m -> km
|
||||
const intersectC = r * r - (r + h) * (r + h);
|
||||
const cloudUniform = [...this.cloudControl];
|
||||
cloudUniform[2] = intersectC;
|
||||
|
||||
// Shimmer Uniform
|
||||
const shimmerUniform = [...this.shimmerControl, r];
|
||||
|
||||
// Multi-Scattering Vector
|
||||
const isotropicPhase = 0.25;
|
||||
const msVector = depthR.map((v, i) => (v * this.msFactors[0] + depthM[i] * this.msFactors[1]) * isotropicPhase);
|
||||
|
||||
// Upload
|
||||
this.materialInstance.setFloat3Parameter('sunDirection', new Float32Array(this.sunDirection));
|
||||
this.materialInstance.setFloat3Parameter('depthR', new Float32Array(depthR));
|
||||
this.materialInstance.setFloat3Parameter('depthM', new Float32Array(depthM));
|
||||
this.materialInstance.setFloat3Parameter('ozone', new Float32Array(ozone));
|
||||
this.materialInstance.setFloat4Parameter('sunHalo', new Float32Array(sunHaloUpload));
|
||||
|
||||
this.materialInstance.setFloat4Parameter('multiScatParams', new Float32Array([...msVector, this.msFactors[2]]));
|
||||
|
||||
// Mie Phase
|
||||
const g2 = this.mieG * this.mieG;
|
||||
this.materialInstance.setFloat2Parameter('miePhaseParams', new Float32Array([1.0 + g2, -2.0 * this.mieG]));
|
||||
|
||||
this.materialInstance.setFloatParameter('contrast', this.contrast);
|
||||
|
||||
const nightColorScaled = this.nightColor.map(v => v * this.sunIntensity);
|
||||
this.materialInstance.setFloat3Parameter('nightColor', new Float32Array(nightColorScaled));
|
||||
|
||||
this.materialInstance.setFloat4Parameter('shimmerControl', new Float32Array(shimmerUniform));
|
||||
this.materialInstance.setFloat4Parameter('cloudControl', new Float32Array(cloudUniform));
|
||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||
this.materialInstance.setFloat2Parameter('starControl', new Float32Array(this.starControl));
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||
}
|
||||
}
|
||||
BIN
docs/wip/sky/assets/simulated_skybox.filamat
Normal file
BIN
docs/wip/sky/assets/simulated_skybox.filamat
Normal file
Binary file not shown.
10
docs/wip/sky/build_material.sh
Executable file
10
docs/wip/sky/build_material.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
# Result: /Users/mathias/sources/git/filament/out/cmake-release/tools/matc/matc
|
||||
MATC="../../../../out/cmake-release/tools/matc/matc"
|
||||
|
||||
# Navigate to script directory to ensure relative paths work
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
set -e
|
||||
|
||||
$MATC -a opengl -p mobile -o assets/simulated_skybox.filamat simulated_skybox.mat
|
||||
echo "Material recompiled to assets/simulated_skybox.filamat"
|
||||
1409
docs/wip/sky/filament.js
Normal file
1409
docs/wip/sky/filament.js
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/wip/sky/filament.wasm
Executable file
BIN
docs/wip/sky/filament.wasm
Executable file
Binary file not shown.
28
docs/wip/sky/gl-matrix-min.js
vendored
Normal file
28
docs/wip/sky/gl-matrix-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
39
docs/wip/sky/index.html
Normal file
39
docs/wip/sky/index.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Analytic Skybox</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||||
<link href="styles.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
canvas {
|
||||
touch-action: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas></canvas>
|
||||
<!-- Filament -->
|
||||
<script src="filament.js"></script>
|
||||
<script src="gl-matrix-min.js"></script>
|
||||
|
||||
<!-- UI -->
|
||||
<script src="lil-gui.js"></script>
|
||||
|
||||
<!-- App -->
|
||||
<script src="SimulatedSkybox.js?v=26"></script>
|
||||
<script src="main.js?v=26"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
8
docs/wip/sky/lil-gui.js
Normal file
8
docs/wip/sky/lil-gui.js
Normal file
File diff suppressed because one or more lines are too long
345
docs/wip/sky/main.js
Normal file
345
docs/wip/sky/main.js
Normal file
@@ -0,0 +1,345 @@
|
||||
|
||||
// main.js
|
||||
|
||||
Filament.init(['assets/simulated_skybox.filamat'], () => {
|
||||
window.app = new App(document.getElementsByTagName('canvas')[0]);
|
||||
});
|
||||
|
||||
class App {
|
||||
constructor(canvas) {
|
||||
this.canvas = canvas;
|
||||
const engine = this.engine = Filament.Engine.create(this.canvas);
|
||||
this.scene = engine.createScene();
|
||||
|
||||
this.skybox = new SimulatedSkybox(engine);
|
||||
this.skybox.entity = this.skybox.entity; // Ensuring access if needed
|
||||
this.scene.addEntity(this.skybox.entity);
|
||||
|
||||
// Load the material explicitly since we passed it to init but SimulatedSkybox needs to bind it
|
||||
// Actually SimulatedSkybox.loadMaterial fetches it.
|
||||
// Since we already loaded it in Filament.init, we can arguably just use it if we had a way to access the asset.
|
||||
// But Filament.init assets are for internal or easy access via assets object if configured?
|
||||
// Let's just let SimulatedSkybox fetch it again or use a blob if we wanted.
|
||||
// Simpler: Just let SimulatedSkybox fetch it.
|
||||
this.skybox.loadMaterial('assets/simulated_skybox.filamat').then(() => {
|
||||
this.initGUI();
|
||||
});
|
||||
|
||||
this.swapChain = engine.createSwapChain();
|
||||
this.renderer = engine.createRenderer();
|
||||
this.camera = engine.createCamera(Filament.EntityManager.get().create());
|
||||
|
||||
this.view = engine.createView();
|
||||
this.view.setCamera(this.camera);
|
||||
this.view.setScene(this.scene);
|
||||
// Color Grading
|
||||
const ColorGrading = Filament.ColorGrading;
|
||||
const ToneMapping = Filament.ColorGrading$ToneMapping;
|
||||
this.colorGrading = ColorGrading.Builder()
|
||||
.toneMapping(ToneMapping.ACES_LEGACY)
|
||||
.build(engine);
|
||||
this.view.setColorGrading(this.colorGrading);
|
||||
this.view.setPostProcessingEnabled(true); // Essential for tone mapping
|
||||
|
||||
// Bloom
|
||||
this.view.setBloomOptions({
|
||||
enabled: false,
|
||||
lenseFlare: false
|
||||
});
|
||||
|
||||
// Clear color is not really visible behind skybox, but black is standard
|
||||
this.renderer.setClearOptions({ clearColor: [0.0, 0.0, 0.0, 1.0], clear: true });
|
||||
|
||||
// Camera handling (Exposure)
|
||||
this.params = {
|
||||
aperture: 16.0,
|
||||
shutterSpeed: 125.0,
|
||||
iso: 100.0,
|
||||
sunTheta: Math.acos(0.0), // Default Height 0.0 (Horizon)
|
||||
sunPhi: 0.0,
|
||||
focalLength: 24.0, // mm
|
||||
sunIntensity: 100000.0 // Base intensity
|
||||
};
|
||||
|
||||
this.camState = {
|
||||
theta: Math.PI / 2, // Look at +X (Sun Position at Phi=0)
|
||||
phi: 0.0,
|
||||
dragging: false,
|
||||
lastX: 0,
|
||||
lastY: 0
|
||||
};
|
||||
|
||||
this.initControls(); // Initialize controls immediately
|
||||
|
||||
this.resize();
|
||||
window.addEventListener('resize', this.resize.bind(this));
|
||||
|
||||
this.render = this.render.bind(this);
|
||||
window.requestAnimationFrame(this.render);
|
||||
}
|
||||
|
||||
getExposure() {
|
||||
// Formula: 1.0 / ( 1.2 * (N^2 / t) * (S / 100) )
|
||||
// t = 1/shutterSpeed
|
||||
const N = this.params.aperture;
|
||||
const t = 1.0 / this.params.shutterSpeed;
|
||||
const S = this.params.iso;
|
||||
const ev100_linear = (N * N) / t * (100.0 / S);
|
||||
const exposure = 1.0 / (1.2 * ev100_linear);
|
||||
return exposure;
|
||||
}
|
||||
|
||||
updateCameraExposure() {
|
||||
this.camera.setExposure(this.params.aperture, 1.0 / this.params.shutterSpeed, this.params.iso);
|
||||
// Also update Sun Intensity because it needs to be pre-exposed
|
||||
this.updateSunIntensity();
|
||||
}
|
||||
|
||||
updateSunIntensity() {
|
||||
const exposure = this.getExposure();
|
||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||
this.skybox.setSunIntensity(preExposedIntensity);
|
||||
}
|
||||
|
||||
updateCameraProjection() {
|
||||
const width = this.canvas.width;
|
||||
const height = this.canvas.height;
|
||||
const aspect = width / height;
|
||||
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
||||
}
|
||||
|
||||
initGUI() {
|
||||
const gui = new lil.GUI({ title: "Analytic Skybox" });
|
||||
const self = this;
|
||||
const sky = this.skybox;
|
||||
|
||||
// Initialize local params from skybox defaults
|
||||
// Initialize local params from skybox defaults
|
||||
// REMOVED: Do not overwrite this.params from sky.sunDirection (Zenith)
|
||||
// const currentDir = sky.sunDirection;
|
||||
// this.params.sunTheta = ...
|
||||
|
||||
const updateSun = () => {
|
||||
const theta = this.params.sunTheta;
|
||||
const phi = this.params.sunPhi;
|
||||
const x = Math.sin(theta) * Math.cos(phi);
|
||||
const y = Math.cos(theta);
|
||||
const z = Math.sin(theta) * Math.sin(phi);
|
||||
sky.setSunPosition([x, y, z]);
|
||||
};
|
||||
|
||||
const sunFolder = gui.addFolder('Sun');
|
||||
// Helper for "Sun Height" cosine slider like C++
|
||||
const sunHeightParam = { height: Math.cos(this.params.sunTheta) };
|
||||
sunFolder.add(sunHeightParam, 'height', -0.2, 1.0).name('Height (Cos)').onChange(v => {
|
||||
this.params.sunTheta = Math.acos(v);
|
||||
updateSun();
|
||||
});
|
||||
sunFolder.add(this.params, 'sunPhi', 0.0, Math.PI * 2).name('Azimuth').onChange(updateSun);
|
||||
// Updated: Controls params.sunIntensity and triggers updateSunIntensity
|
||||
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).onChange(v => this.updateSunIntensity());
|
||||
|
||||
const sunDisk = sunFolder.addFolder('Disk');
|
||||
// We need local proxy for sunRadius due to conversion
|
||||
const diskParams = {
|
||||
radius: 1.2,
|
||||
enabled: true // Enable sun disk
|
||||
};
|
||||
sky.setSunDiskEnabled(true);
|
||||
sky.setSunRadius(1.2);
|
||||
sunDisk.add(diskParams, 'enabled').onChange(v => sky.setSunDiskEnabled(v));
|
||||
sunDisk.add(diskParams, 'radius', 0.0, 5.0).onChange(v => sky.setSunRadius(v));
|
||||
sunDisk.add(sky.sunHalo, 1, 0.0, 2.0).name('Limb Darkening').onChange(v => sky.setSunLimbDarkening(v));
|
||||
sunDisk.add(sky.sunHalo, 2, 0.0, 100.0).name('Intensity Boost').onChange(v => sky.setSunDiskIntensity(v));
|
||||
|
||||
const atmFolder = gui.addFolder('Atmosphere');
|
||||
atmFolder.add(sky, 'turbidity', 1.0, 10.0).onChange(v => sky.setTurbidity(v));
|
||||
atmFolder.add(sky, 'rayleigh', 0.0, 10.0).onChange(v => sky.setRayleigh(v));
|
||||
atmFolder.add(sky, 'mieCoefficient', 0.0, 10.0).onChange(v => sky.setMieCoefficient(v));
|
||||
// Set Ozone default to 0.25
|
||||
sky.setOzone(0.25);
|
||||
atmFolder.add(sky, 'ozone', 0.0, 1.0).onChange(v => sky.setOzone(v));
|
||||
atmFolder.add(sky, 'mieG', 0.0, 0.999).onChange(v => sky.setMieG(v));
|
||||
|
||||
const artFolder = gui.addFolder('Artistic');
|
||||
// Set Horizon Glow default to 1.0
|
||||
sky.setHorizonGlow(1.0);
|
||||
sky.msFactors[2] = 1.0;
|
||||
// Set Contrast default to 0.85
|
||||
sky.setContrast(0.85);
|
||||
|
||||
artFolder.add(sky.msFactors, 0, 0.0, 2.0).name('MS Rayleigh').onChange(v => sky.setMultiScattering(v, sky.msFactors[1]));
|
||||
artFolder.add(sky.msFactors, 1, 0.0, 2.0).name('MS Mie').onChange(v => sky.setMultiScattering(sky.msFactors[0], v));
|
||||
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
|
||||
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
|
||||
|
||||
artFolder.addColor(sky, 'nightColor').onChange(v => sky.setNightColor(v));
|
||||
|
||||
const shmFolder = artFolder.addFolder('Shimmer');
|
||||
// Set Shimmer Strength default to 0.0
|
||||
sky.setShimmerControl(0.0, sky.shimmerControl[1], sky.shimmerControl[2]);
|
||||
|
||||
shmFolder.add(sky.shimmerControl, 0, 0.0, 0.1).name('Strength').onChange(v => sky.setShimmerControl(v, sky.shimmerControl[1], sky.shimmerControl[2]));
|
||||
shmFolder.add(sky.shimmerControl, 1, 1.0, 100.0).name('Frequency').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], v, sky.shimmerControl[2]));
|
||||
shmFolder.add(sky.shimmerControl, 2, 0.01, 0.5).name('Mask Height').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], sky.shimmerControl[1], v));
|
||||
|
||||
const cloudFolder = gui.addFolder('Clouds');
|
||||
const cParams = {
|
||||
volumetrics: sky.cloudControl2[1] > 0.5,
|
||||
coverage: 0.4,
|
||||
density: 0.02,
|
||||
height: sky.cloudControl[2],
|
||||
speed: 50.0,
|
||||
evolution: 0.02
|
||||
};
|
||||
// Apply Cloud Defaults
|
||||
sky.setCloudControl(0.4, 0.02, cParams.height, 50.0);
|
||||
sky.setCloudShapeEvolution(0.02);
|
||||
|
||||
cloudFolder.add(cParams, 'volumetrics').onChange(v => sky.setCloudVolumetricLighting(v));
|
||||
cloudFolder.add(cParams, 'coverage', 0.0, 1.0).onChange(v => sky.setCloudControl(v, cParams.density, cParams.height, cParams.speed));
|
||||
cloudFolder.add(cParams, 'density', 0.0, 1.0).onChange(v => sky.setCloudControl(cParams.coverage, v, cParams.height, cParams.speed));
|
||||
cloudFolder.add(cParams, 'height', 2000.0, 20000.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, v, cParams.speed));
|
||||
// Reverse speed calc: w = speed * (0.05 / 72.0)
|
||||
cloudFolder.add(cParams, 'speed', 0.0, 200.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, cParams.height, v));
|
||||
cloudFolder.add(cParams, 'evolution', 0.0, 2.0).onChange(v => sky.setCloudShapeEvolution(v));
|
||||
|
||||
const waterFolder = gui.addFolder('Water');
|
||||
const wParams = {
|
||||
derivativeTrick: true,
|
||||
strength: 50.0,
|
||||
speed: 1.0,
|
||||
octaves: 4.0
|
||||
};
|
||||
// Initialize defaults
|
||||
sky.setWaterControl(50.0, 1.0, 1.0, 4.0); // 1.0 = Derivative Trick On, 4 octaves
|
||||
|
||||
const updateWater = () => {
|
||||
sky.setWaterControl(wParams.strength, wParams.speed, wParams.derivativeTrick ? 1.0 : 0.0, wParams.octaves);
|
||||
};
|
||||
|
||||
waterFolder.add(wParams, 'derivativeTrick').name('Derivative Trick').onChange(updateWater);
|
||||
waterFolder.add(wParams, 'strength', 10.0, 100.0).onChange(updateWater);
|
||||
waterFolder.add(wParams, 'speed', 0.0, 5.0).onChange(updateWater);
|
||||
waterFolder.add(wParams, 'octaves', 1, 8, 1).name('Octaves').onChange(updateWater);
|
||||
waterFolder.close();
|
||||
|
||||
const starFolder = gui.addFolder('Stars');
|
||||
const sParams = {
|
||||
enabled: true,
|
||||
density: 1.0
|
||||
};
|
||||
// Initialize defaults (Density 1.0, Enabled True)
|
||||
sky.setStarControl(1.0, true);
|
||||
|
||||
const updateStars = () => {
|
||||
sky.setStarControl(sParams.density, sParams.enabled);
|
||||
};
|
||||
|
||||
starFolder.add(sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||
starFolder.add(sParams, 'density', 0.0, 1.0).name('Density').onChange(updateStars);
|
||||
starFolder.close();
|
||||
|
||||
const camFolder = gui.addFolder('Camera');
|
||||
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
||||
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'shutterSpeed', 1.0, 1000.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
||||
|
||||
const bloomFolder = camFolder.addFolder('Bloom');
|
||||
const bParams = {
|
||||
enabled: false,
|
||||
lensFlare: false
|
||||
};
|
||||
|
||||
const updateBloom = () => {
|
||||
this.view.setBloomOptions({
|
||||
enabled: bParams.enabled,
|
||||
lensFlare: bParams.lensFlare
|
||||
});
|
||||
};
|
||||
|
||||
bloomFolder.add(bParams, 'enabled').onChange(updateBloom);
|
||||
bloomFolder.add(bParams, 'lensFlare').onChange(updateBloom);
|
||||
bloomFolder.close();
|
||||
|
||||
// Collapse folders by default
|
||||
sunDisk.close();
|
||||
atmFolder.close();
|
||||
artFolder.close();
|
||||
// shmFolder is inside artFolder, so it's hidden, but we can close it too if we want
|
||||
shmFolder.close();
|
||||
cloudFolder.close();
|
||||
// camFolder left open? User didn't specify, but "Artistic, shimmer and clouds" + "Disk, Atmosphere" were requested.
|
||||
// So Camera might stay open or close. Let's keep Camera open for now as it wasn't listed.
|
||||
|
||||
// Initial sync
|
||||
updateSun();
|
||||
this.updateCameraExposure(); // This will trigger updateSunIntensity too
|
||||
}
|
||||
|
||||
initControls() {
|
||||
|
||||
// listeners only
|
||||
this.canvas.addEventListener('mousedown', e => {
|
||||
this.camState.dragging = true;
|
||||
this.camState.lastX = e.clientX;
|
||||
this.camState.lastY = e.clientY;
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', () => {
|
||||
this.camState.dragging = false;
|
||||
});
|
||||
|
||||
window.addEventListener('mousemove', e => {
|
||||
if (!this.camState.dragging) return;
|
||||
const dx = e.clientX - this.camState.lastX;
|
||||
const dy = e.clientY - this.camState.lastY;
|
||||
this.camState.lastX = e.clientX;
|
||||
this.camState.lastY = e.clientY;
|
||||
|
||||
const sensitivity = 0.005;
|
||||
this.camState.theta -= dx * sensitivity;
|
||||
this.camState.phi += dy * sensitivity;
|
||||
// Clamp pitch to avoid flip [ -PI/2, PI/2 ]
|
||||
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
// Update Camera LookAt
|
||||
const r = 1.0;
|
||||
const theta = this.camState.theta;
|
||||
const phi = this.camState.phi;
|
||||
|
||||
// Convert spherical to cartesian
|
||||
// Y is UP. Z is Forward.
|
||||
// At phi=0, y=0. forward vector should correspond to theta.
|
||||
// Let's say theta=0 is -Z.
|
||||
const y = Math.sin(phi);
|
||||
const h = Math.cos(phi);
|
||||
const x = h * Math.sin(theta);
|
||||
const z = -h * Math.cos(theta);
|
||||
|
||||
const eye = [0, 0, 0];
|
||||
const center = [x, y, z];
|
||||
const up = [0, 1, 0];
|
||||
this.camera.lookAt(eye, center, up);
|
||||
|
||||
this.renderer.render(this.swapChain, this.view);
|
||||
window.requestAnimationFrame(this.render);
|
||||
}
|
||||
|
||||
resize() {
|
||||
const dpr = window.devicePixelRatio;
|
||||
const width = this.canvas.width = window.innerWidth * dpr;
|
||||
const height = this.canvas.height = window.innerHeight * dpr;
|
||||
this.view.setViewport([0, 0, width, height]);
|
||||
|
||||
const aspect = width / height;
|
||||
// near=0.1, far=5000.0
|
||||
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
||||
}
|
||||
}
|
||||
909
docs/wip/sky/simulated_skybox.mat
Normal file
909
docs/wip/sky/simulated_skybox.mat
Normal file
@@ -0,0 +1,909 @@
|
||||
material {
|
||||
name : SimulatedSkybox,
|
||||
parameters : [
|
||||
{
|
||||
type : float3,
|
||||
name : sunDirection
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : sunDirection2
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : depthR,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : depthM,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
name : miePhaseParams, // x=(1+g^2), y=(-2*g)
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : sunIntensity,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : contrast,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : nightColor,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : ozone,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : multiScatParams, // xyz=MultiScatteringColor, w=HorizonGlow
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : sunHalo, // x=Size, y=Limb, z=Intensity, w=Enabled
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : shimmerControl, // x=Strength, y=Frequency, z=MaskHeight, w=PlanetRadius
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : cloudControl, // x=Coverage, y=Density, z=QuadraticConst, w=WindSpeed
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : cloudControl2, // x=EvolutionSpeed
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : sunIntensity2,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : sunHalo2, // x=Size, y=Limb, z=Intensity, w=Enabled
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : waterControl, // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
name : starControl, // x=Density, y=Enabled
|
||||
precision : high
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
eyeDirection
|
||||
],
|
||||
vertexDomain : device,
|
||||
depthWrite : false,
|
||||
shadingModel : unlit,
|
||||
culling: none
|
||||
}
|
||||
|
||||
vertex {
|
||||
void materialVertex(inout MaterialVertexInputs material) {
|
||||
// This code is taken from computeWorldPosition and assumes the vertex domain is 'device'.
|
||||
highp vec4 p = getPosition();
|
||||
// GL convention to inverted DX convention
|
||||
p.z = p.z * -0.5 + 0.5;
|
||||
highp vec4 worldPosition = getWorldFromClipMatrix() * p;
|
||||
// Getting the true world position would require dividing by w, but since this is a skybox
|
||||
// at inifinity, this results in very large numbers for material.eyeDirection.
|
||||
// Since the eyeDirection is only used as a direction vector in the fragment shader, we can
|
||||
// skip that step to improve precision.
|
||||
material.eyeDirection.xyz = worldPosition.xyz;
|
||||
}
|
||||
}
|
||||
|
||||
fragment {
|
||||
// ------------------------------------------------------------------------
|
||||
// Analytic Rayleigh and Mie Scattering (Physics Based)
|
||||
// Derived from:
|
||||
// - Hoffman & Preetham (2002): "Real-time Light-Atmosphere Interactions"
|
||||
// - Henyey & Greenstein (1941): "Diffuse radiation in the galaxy" (Mie Phase)
|
||||
// - Kasten & Young (1989): "Revised optical air mass tables" (Air Mass)
|
||||
// - "Simulated Sky" / Three.js (Sky.js): Empirical adjustments for aesthetics
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
#define PI 3.14159265359
|
||||
|
||||
void dummy() {} // squash editor syntax highlighting bugs
|
||||
|
||||
// Rayleigh Phase Function: Scattering distribution for small particles (air molecules)
|
||||
// Lord Rayleigh (1871)
|
||||
// Normalized to integrate to 4*PI (Boosting brightness by factor PI vs standard 1-normalization)
|
||||
highp float rayleighPhase(highp float cosTheta) {
|
||||
const highp float THREE_SIXTEENTH = (3.0 / 16.0);
|
||||
return THREE_SIXTEENTH * (1.0 + cosTheta * cosTheta);
|
||||
}
|
||||
|
||||
// Henyey-Greenstein Phase Function (Mie)
|
||||
// Henyey & Greenstein (1941)
|
||||
// Controls the forward scattering peak (sun halo) via anisotropy parameter 'g'
|
||||
// Optimized: params.x = (1 + g^2), params.y = (-2 * g)
|
||||
highp float hgPhase(highp float cosTheta, highp vec2 params) {
|
||||
const highp float ONE_FOURTH = (1.0 / 4.0);
|
||||
// Recover (1 - g^2) => 2.0 - (1 + g^2)
|
||||
highp float oneMinusG2 = 2.0 - params.x;
|
||||
highp float inverse = 1.0 / pow(params.x + params.y * cosTheta, 1.5);
|
||||
return ONE_FOURTH * (oneMinusG2 * inverse);
|
||||
}
|
||||
|
||||
// --- Noise Functions for Clouds ---
|
||||
highp float hash13(highp vec3 p3) {
|
||||
p3 = fract(p3 * .1031);
|
||||
p3 += dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
|
||||
highp float noise(highp vec3 p) {
|
||||
highp vec3 i = floor(p);
|
||||
highp vec3 f = fract(p);
|
||||
// Cubic Hermite Interpolation
|
||||
highp vec3 u = f*f*(3.0-2.0*f);
|
||||
return mix(mix(mix(hash13(i + vec3(0,0,0)), hash13(i + vec3(1,0,0)), u.x),
|
||||
mix(hash13(i + vec3(0,1,0)), hash13(i + vec3(1,1,0)), u.x), u.y),
|
||||
mix(mix(hash13(i + vec3(0,0,1)), hash13(i + vec3(1,0,1)), u.x),
|
||||
mix(hash13(i + vec3(0,1,1)), hash13(i + vec3(1,1,1)), u.x), u.y), u.z);
|
||||
}
|
||||
|
||||
// Fractal Brownian Motion (4 Octaves)
|
||||
highp float fbm(highp vec3 p) {
|
||||
highp float total = 0.0;
|
||||
highp float amplitude = 0.5;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
total += noise(p) * amplitude;
|
||||
p *= 2.02; // Lacunarity
|
||||
p += 100.0; // Shift to avoid artifacts
|
||||
amplitude *= 0.5; // Gain
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
highp float fbm(highp vec3 p, int octaves) {
|
||||
highp float total = 0.0;
|
||||
highp float amplitude = 0.5;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (i >= octaves) break;
|
||||
total += noise(p) * amplitude;
|
||||
p *= 2.02;
|
||||
p += 100.0;
|
||||
amplitude *= 0.5;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Ray-Sphere Intersection
|
||||
// Returns distance to intersection or -1.0 if none.
|
||||
// Re = Planet Radius.
|
||||
// C = Re^2 - (Re + height)^2 (Precalculated on CPU for precision).
|
||||
highp float raySphereIntersect(highp vec3 rd, highp float Re, highp float C) {
|
||||
// Ray Origin is (0, Re, 0) relative to Planet Center (0, 0, 0)
|
||||
// We solve |(0, Re, 0) + t*rd|^2 = Rm^2
|
||||
// |O + tD|^2 = R^2
|
||||
// t^2 + 2t(O.D) + (O^2 - R^2) = 0
|
||||
// a=1, b=2(O.D), c = O^2 - R^2 = C
|
||||
// Reduced quadratic: t = -b' +/- sqrt(b'^2 - c) where b' = O.D
|
||||
|
||||
highp float b = Re * rd.y; // dot(vec3(0, Re, 0), rd)
|
||||
highp float disc = b*b - C;
|
||||
|
||||
if (disc < 0.0) return -1.0;
|
||||
|
||||
// t = -b + sqrt(disc)
|
||||
return -b + sqrt(disc);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Atmospheric Heat Shimmer (Mirage)
|
||||
// ------------------------------------------------------------------------
|
||||
// Simulates heat convection turbulence near the horizon (e.g., hot desert road effect).
|
||||
//
|
||||
// PHYSICS:
|
||||
// Heat rising from the ground creates pockets of varying air density (refractive index).
|
||||
// This bends light rays, causing a visual "shimmer" or displacement.
|
||||
//
|
||||
// IMPLEMENTATION:
|
||||
// - Perturbs the view vector `V.y` using interleaved sine waves.
|
||||
// - Uses World Space `V` so the noise is stable under camera rotation.
|
||||
// - Masked to only affect the horizon line.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V Normalized World View Vector (modified in place).
|
||||
// @param strength Max vertical displacement amplitude. (e.g. 0.002).
|
||||
// @param freq Ripple frequency/density. (e.g. 20.0).
|
||||
// @param maskHeight Horizon mask height (0.0 to 1.0). (e.g. 0.1).
|
||||
// ------------------------------------------------------------------------
|
||||
float applyHeatShimmer(inout highp vec3 V, highp float strength, highp float freq, highp float maskHeight) {
|
||||
if (strength <= 0.0) return 0.0;
|
||||
|
||||
// Mask: Strongest at horizon (0.0), fades out by maskHeight.
|
||||
highp float mask = 1.0 - smoothstep(0.0, maskHeight, abs(V.y));
|
||||
|
||||
if (mask > 0.0) {
|
||||
// Use FBM for organic turbulence (rising heat waves)
|
||||
highp float time = getUserTime().x;
|
||||
// Animate upward (y) and slightly drift (x)
|
||||
highp vec3 p = vec3(V.x * freq, V.y * freq + time * 2.0, time * 0.5);
|
||||
|
||||
// We use a cheap noise or FBM. Since we have FBM:
|
||||
// Use fewer octaves for performance if possible, but 4 is fine.
|
||||
highp float distortion = fbm(p);
|
||||
|
||||
// Remap noise from [0, 1] to [-1, 1] for perturbation
|
||||
distortion = distortion * 2.0 - 1.0;
|
||||
|
||||
// Apply vertical perturbation
|
||||
V.y += distortion * strength * mask * 0.1;
|
||||
V = normalize(V);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Analytic Sky Model (Rayleigh + Mie + Ozone)
|
||||
// ------------------------------------------------------------------------
|
||||
// Computes the scattering and transmittance of the atmosphere along the view ray.
|
||||
//
|
||||
// PHYSICS:
|
||||
// - Rayleigh: Scattering by air molecules (Blue sky). High frequency (lambda^-4).
|
||||
// - Mie: Scattering by aerosols/dust (White haze). Low frequency (lambda^-1.3).
|
||||
// - Ozone: Absorption layer (Pink sunset). Absorbs green light.
|
||||
// - Optical Mass: Approximation of path length through spherical atmosphere.
|
||||
//
|
||||
// OUTPUTS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunIntensity Sun Illuminance (Lux).
|
||||
// @param depthR Rayleigh Optical Depth (Precalculated).
|
||||
// @param depthM Mie Optical Depth (Precalculated).
|
||||
// @param ozone Ozone Absorption (Precalculated).
|
||||
// @param msFactors Multi-Scattering factors (Rayleigh, Mie, Glow).
|
||||
// @param mieG Mie Phase Anisotropy.
|
||||
// @param outTransmittance Output: Atmospheric Transmittance (0..1) along V.
|
||||
// @return Output: In-Scattered Radiance (The sky color).
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getAtmosphere(highp vec3 V, highp vec3 L, highp float sunIntensity,
|
||||
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
|
||||
highp vec4 multiScatParams, highp vec2 mieParams,
|
||||
out highp vec3 outTransmittance) {
|
||||
|
||||
highp float cosTheta = dot(V, L);
|
||||
|
||||
// 1. Phase Functions
|
||||
// "Golden Hour" Hack (Three.js Sky.js):
|
||||
// Remapping cosTheta from [-1, 1] to [0, 1] breaks the symmetry of Rayleigh scattering.
|
||||
highp float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5);
|
||||
highp float mPhase = hgPhase(cosTheta, mieParams);
|
||||
|
||||
// 2. Optical Depth (Air Mass)
|
||||
// Kasten and Young (1989) - Relative Air Mass Model
|
||||
highp float zenithCos = clamp(V.y, 0.0, 1.0);
|
||||
highp float zenithAngle = acos(zenithCos);
|
||||
highp float zenithAngleDeg = zenithAngle * (180.0 / PI);
|
||||
highp float opticalMass = 1.0 / (zenithCos + 0.15 * pow(93.885 - zenithAngleDeg, -1.253));
|
||||
|
||||
// 3. Extinction & Transmittance
|
||||
highp vec3 totalExtinction = depthR + depthM + ozone;
|
||||
highp vec3 extinction = totalExtinction * opticalMass;
|
||||
outTransmittance = exp(-extinction);
|
||||
|
||||
// 4. In-Scattering
|
||||
// Approximate Multi-Scattering (Isotropic Fill) precomputed in C++.
|
||||
highp vec3 multiScattering = multiScatParams.xyz;
|
||||
|
||||
highp vec3 scatteringTerm = (depthR * rPhase) + (depthM * mPhase) + multiScattering;
|
||||
highp vec3 extinctionTerm = max(vec3(1e-6), totalExtinction);
|
||||
|
||||
// Equilibrium Radiance (Source Function)
|
||||
highp vec3 inScattering = sunIntensity * (scatteringTerm / extinctionTerm);
|
||||
|
||||
// Single-Scattering Integral: L = L_inf * (1 - exp(-opticalDepth))
|
||||
highp vec3 sunLight = inScattering * (1.0 - outTransmittance);
|
||||
|
||||
// 5. Horizon "Glow" Mix (Artistic Hack)
|
||||
// multiScatParams.w contains the Horizon Glow Strength
|
||||
// Uses Sun Elevation (L.y) to only activate during golden hour/twilight.
|
||||
mediump float horizonMix = saturate(pow(1.0 - L.y, 5.0)) * multiScatParams.w;
|
||||
highp vec3 horizonGlow = sqrt(inScattering * outTransmittance);
|
||||
sunLight *= mix(vec3(1.0), horizonGlow, horizonMix);
|
||||
|
||||
return sunLight;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Secondary Sun Scattering (Simplified)
|
||||
// ------------------------------------------------------------------------
|
||||
// Computes in-scattering for a second light source, reusing precomputed optical depths.
|
||||
// Skips multi-scattering (ambient) for performance, providing only direct beams/glow.
|
||||
//
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param depthR Rayleigh Optical Depth.
|
||||
// @param depthM Mie Optical Depth.
|
||||
// @param ozone Ozone Absorption.
|
||||
// @param mieParams Mie Phase Params.
|
||||
// @param transmittance Precomputed Atmospheric Transmittance.
|
||||
// @return In-Scattered Radiance.
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getSecondarySunScattering(highp vec3 V, highp vec3 L, highp float sunIntensity,
|
||||
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
|
||||
highp vec2 miePhaseParams, highp vec3 transmittance) {
|
||||
highp float cosTheta = dot(V, L);
|
||||
|
||||
// Phase Functions
|
||||
highp float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5);
|
||||
highp float mPhase = hgPhase(cosTheta, miePhaseParams);
|
||||
|
||||
// Scattering
|
||||
highp vec3 scatteringTerm = (depthR * rPhase) + (depthM * mPhase);
|
||||
highp vec3 totalExtinction = depthR + depthM + ozone;
|
||||
highp vec3 extinctionTerm = max(vec3(1e-6), totalExtinction);
|
||||
|
||||
highp vec3 inScattering = sunIntensity * (scatteringTerm / extinctionTerm);
|
||||
return inScattering * (1.0 - transmittance);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Physical Sun Disk
|
||||
// ------------------------------------------------------------------------
|
||||
// Renders the Solar Photosphere with limb darkening.
|
||||
//
|
||||
// PHYSICS:
|
||||
// - The sun is not a point light; it has an angular size (~0.53 deg).
|
||||
// - Limb Darkening: The sun is darker at the edges (limbs) because we see cooler outer layers.
|
||||
// - Drawn "Behind" the atmosphere, so it is attenuated by Transmittance.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunParams x=CosRadius, y=LimbDarkening, z=IntensityBoost, w=Enabled.
|
||||
// @param sunIntensity Peak Sun Illuminance (Lux).
|
||||
// @param transmittance Atmospheric Transmittance (0..1).
|
||||
// @return Radiance of the sun disk (if visible and enabled).
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getSunDisk(highp vec3 V, highp vec3 L, highp vec4 sunParams,
|
||||
highp float sunIntensity, highp vec3 transmittance) {
|
||||
|
||||
highp float sunCosRadius = sunParams.x;
|
||||
highp float limbDarkening = sunParams.y;
|
||||
highp float sunDiskIntensity = sunParams.z;
|
||||
bool sunEnabled = sunParams.w > 0.5;
|
||||
|
||||
highp float cosTheta = dot(V, L);
|
||||
|
||||
// Robust edge detection for small angles using (1 - cos)
|
||||
highp float dist = 1.0 - cosTheta;
|
||||
highp float diskRadius = max(1e-6, 1.0 - sunCosRadius);
|
||||
|
||||
// AA Edge: smoothstep from radius to radius+epsilon
|
||||
// We invert it because we want 1.0 inside (dist < radius)
|
||||
highp float sunDiskProfile = 1.0 - smoothstep(diskRadius, diskRadius + 0.00002, dist);
|
||||
|
||||
if (sunEnabled && sunDiskProfile > 0.0) {
|
||||
// Limb Darkening approximation: mu = sqrt(1 - (r/R)^2)
|
||||
// dist/diskRadius is approx (r/R)^2 for small angles
|
||||
highp float relativeDist = min(1.0, dist / diskRadius);
|
||||
highp float mu = sqrt(1.0 - relativeDist);
|
||||
|
||||
// Avoid pow(0, 0) which causes NaNs
|
||||
highp float limbFactor = (limbDarkening < 1e-4) ? 1.0 : pow(mu, limbDarkening);
|
||||
|
||||
// Direct Sun Light (Radiance)
|
||||
// SunIntensity * Transmittance -> Physical Sun Color
|
||||
// SunDiskIntensity -> Artistic Boost to punch through Mie halo
|
||||
return sunIntensity * transmittance * limbFactor * sunDiskIntensity * sunDiskProfile;
|
||||
}
|
||||
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Cirrus Clouds
|
||||
// ------------------------------------------------------------------------
|
||||
// Renders a thin layer of high-altitude clouds (Cirrus) using 3D Noise.
|
||||
//
|
||||
// IMPLEMENTATION:
|
||||
// - Modeled as a spherical shell at a specific altitude.
|
||||
// - Ray-Sphere intersection determines UV layout and distance.
|
||||
// - Animated using 3D FBM (Fractal Brownian Motion) for shape evolution + Wind drift.
|
||||
// - Lighting includes Silver Lining (HG Phase) and Atmospheric Extinction.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param background Current Sky Color (to be blended with).
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param control x=Coverage, y=Density, z=QuadraticConst(C), w=WindSpeed.
|
||||
// @param control2 x=EvolutionSpeed.
|
||||
// @param geometry w=PlanetRadius (Re).
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param transmittance Atmospheric Transmittance (Cloud Color Tint).
|
||||
// @return Sky color composed with clouds.
|
||||
// ------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Cirrus Clouds
|
||||
// ------------------------------------------------------------------------
|
||||
// Renders a thin layer of high-altitude clouds (Cirrus) using 3D Noise.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V .
|
||||
// @param L .
|
||||
// @param control .
|
||||
// @param control2 .
|
||||
// @param geometry .
|
||||
// @param sunIntensity .
|
||||
// @param transmittance.
|
||||
// @param outDensity Output: Cloud Density (0..1).
|
||||
// @return Cloud Lit Color (pre-multiplied by density? No, just lit color).
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getCloudLayer(highp vec3 V, highp vec3 L,
|
||||
highp vec4 control, highp vec4 control2, highp vec4 geometry,
|
||||
highp float sunIntensity, highp vec3 transmittance,
|
||||
out highp float outDensity) {
|
||||
|
||||
outDensity = 0.0;
|
||||
highp float cloudCoverage = control.x;
|
||||
|
||||
// Clip clouds below the horizon (Earth occlusion)
|
||||
// Simple check V.y > 0.0 is sufficient for skybox provided camera is near ground.
|
||||
if (cloudCoverage > 0.0 && V.y > 0.0) {
|
||||
highp float Re = geometry.w;
|
||||
highp float intersectC = control.z;
|
||||
highp float distToCloud = raySphereIntersect(V, Re, intersectC);
|
||||
|
||||
if (distToCloud > 0.0) {
|
||||
highp vec3 p = V * distToCloud;
|
||||
highp float speed = control.w;
|
||||
highp float morphSpeed = control2.x;
|
||||
highp float time = getUserTime().x;
|
||||
|
||||
// UV Mapping (Planar projected onto sphere cap is sufficient for skybox)
|
||||
// Scale factor 0.05 km^-1
|
||||
highp vec2 uv = (p.xz * 0.05) + vec2(time * speed * 2.0, 0.0);
|
||||
|
||||
// 3D Noise for Morphing
|
||||
highp float noiseVal = fbm(vec3(uv, time * morphSpeed));
|
||||
|
||||
// Remap noise based on coverage.
|
||||
// Coverage 0.5 -> threshold 0.5. Coverage 1.0 -> threshold 0.0.
|
||||
highp float threshold = 1.0 - cloudCoverage;
|
||||
highp float cloudDensity = smoothstep(threshold, threshold + 0.3, noiseVal);
|
||||
|
||||
if (cloudDensity > 0.0) {
|
||||
cloudDensity *= control.y; // Global Density Scalar
|
||||
cloudDensity = clamp(cloudDensity, 0.0, 1.0);
|
||||
outDensity = cloudDensity;
|
||||
|
||||
// Cloud Lighting
|
||||
// Silver Lining: Strong forward scattering (Fixed g=0.9 for clouds)
|
||||
highp float cosTheta = dot(V, L);
|
||||
// We need separate params for cloud silver lining (g=0.9).
|
||||
// 1 + 0.9^2 = 1.81. -2*0.9 = -1.8.
|
||||
|
||||
// Attenuation (Beer's Law)
|
||||
// Thick clouds block light.
|
||||
// 20.0 is an artistic extinction coefficient.
|
||||
highp float extinction = exp(-cloudDensity * 20.0);
|
||||
|
||||
highp float silver = hgPhase(cosTheta, vec2(1.81, -1.8)) * 40.0 * extinction;
|
||||
|
||||
// Ambient/Diffuse term.
|
||||
// We allow some ambient light to pass through even thick clouds (0.05 min)
|
||||
// so they don't look like black holes.
|
||||
highp float ambient = 0.1 + 0.4 * extinction;
|
||||
|
||||
// Diffuse term (Sun Color) + Silver Lining
|
||||
highp vec3 cloudLight = sunIntensity * transmittance * (ambient + silver);
|
||||
|
||||
// Mix based on density
|
||||
highp float volumetric = control2.y;
|
||||
highp float shading = 1.0;
|
||||
|
||||
if (volumetric > 0.5) {
|
||||
// Gradient Lighting (Fake Volumetric Bump)
|
||||
highp float gradX = dFdx(cloudDensity);
|
||||
highp float gradY = dFdy(cloudDensity);
|
||||
// Smaller Z = Steeper Bumps.
|
||||
// dFdx(density) is typically small (e.g. 0.001).
|
||||
// We want Normal to have significant X/Y component.
|
||||
highp vec3 N = normalize(vec3(-gradX, -gradY, 0.001));
|
||||
|
||||
// Screen Space Sun Direction
|
||||
highp vec3 sRight = normalize(dFdx(V));
|
||||
highp vec3 sUp = normalize(dFdy(V));
|
||||
highp vec3 L_screen = vec3(dot(L, sRight), dot(L, sUp), 0.5);
|
||||
L_screen = normalize(L_screen);
|
||||
|
||||
shading = dot(N, L_screen);
|
||||
// Increase contrast: Darker shadows
|
||||
// dot is [-1, 1]. Map to [0.3, 1.0]
|
||||
shading = mix(0.3, 1.0, shading * 0.5 + 0.5);
|
||||
|
||||
// Darken thick parts (Beer's Law approximation)
|
||||
// Aggressively darken center of clouds
|
||||
shading *= (1.0 - cloudDensity * 0.7);
|
||||
}
|
||||
|
||||
return cloudLight * shading;
|
||||
}
|
||||
}
|
||||
}
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Dynamic Tone Mapping
|
||||
// ------------------------------------------------------------------------
|
||||
// Applies a contrast curve that varies with sun elevation.
|
||||
//
|
||||
// PROBLEM:
|
||||
// Default linear/gamma tone mapping can make sunsets look washing out.
|
||||
// Real eyes accept much higher dynamic range at twilight.
|
||||
//
|
||||
// SOLUTION:
|
||||
// - Zenith (Noon): Linear gamma (Exponent 1.0). Physically accurate.
|
||||
// - Horizon (Sunset): High contrast (Exponent > 1.0). Crushes shadows, boosts color.
|
||||
//
|
||||
// @param color Input HDR color.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param contrast Maximum contrast exponent (at horizon). e.g. 1.5.
|
||||
// @return Tone mapped color.
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 applyDynamicToneMapping(highp vec3 color, highp vec3 L, highp float contrast) {
|
||||
float c = saturate(L.y);
|
||||
// Exponent blends from 'contrast' (at L.y=0) to 1.0 (at L.y=1)
|
||||
float exponent = mix(contrast, 1.0, sqrt(c));
|
||||
return pow(max(vec3(0.0), color), vec3(exponent));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Water Surface
|
||||
// ------------------------------------------------------------------------
|
||||
// Simulates an infinite ocean plane at y=0 using screen-space derivatives for normals.
|
||||
//
|
||||
// FEATURES:
|
||||
// - Projected grid for infinite surface.
|
||||
// - Screen-space wave normal reconstruction (no geometry required).
|
||||
// - Fresnel reflection of Atmosphere, Sun, and Clouds.
|
||||
// - Specular highlights (Blinn-Phong).
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param depthR Rayleigh Optical Depth.
|
||||
// @param depthM Mie Optical Depth.
|
||||
// @param ozone Ozone Absorption.
|
||||
// @param multiScatParams Multi-Scattering Params.
|
||||
// @param miePhaseParams Mie Phase Params.
|
||||
// @param sunHalo Sun Halo Params.
|
||||
// @param cloudControl Cloud Control Params.
|
||||
// @param cloudControl2 Cloud Evolution Params.
|
||||
// @param shimmerControl Shimmer Control (w component used as PlanetRadius for clouds).
|
||||
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
|
||||
// @return Water surface color.
|
||||
// ------------------------------------------------------------------------
|
||||
// 3D Noise for Stars
|
||||
highp float hash31(highp vec3 p) {
|
||||
p = fract(p * 0.1031);
|
||||
p += dot(p, p.yzx + 33.33);
|
||||
return fract((p.x + p.y) * p.z);
|
||||
}
|
||||
|
||||
highp float getStars(highp vec3 V, highp float density) {
|
||||
// Simple procedural stars
|
||||
// We use view vector direction to tile the sky
|
||||
// Higher frequency = smaller stars
|
||||
highp float frequency = 300.0;
|
||||
highp vec3 p = floor(V * frequency);
|
||||
|
||||
highp float h = hash31(p);
|
||||
|
||||
// Threshold for stars (very sparse)
|
||||
// param density: 0.0 (none) to 1.0 (max)
|
||||
// Default threshold was 0.995 (0.5% stars)
|
||||
// We map density 0.0 -> 1.0 threshold (no stars)
|
||||
// density 1.0 -> 0.990 threshold (1.0% stars)
|
||||
highp float threshold = 1.0 - (0.001 + density * 0.009);
|
||||
|
||||
highp float star = 0.0;
|
||||
if (h > threshold) {
|
||||
// Random brightness
|
||||
highp float brightness = (h - threshold) / (1.0 - threshold);
|
||||
star = brightness * 15.0; // Reduced from 50.0 to 15.0
|
||||
}
|
||||
return star;
|
||||
}
|
||||
|
||||
// New helper to handle Star Compositing (Fade, Rotation, Occlusion)
|
||||
highp vec3 getStarLayer(highp vec3 V, highp vec3 L, highp float cloudDensity, highp vec3 transmittance, highp vec2 starControl) {
|
||||
// starControl.x = Density, .y = Enabled
|
||||
if (starControl.y < 0.5) return vec3(0.0);
|
||||
|
||||
// 1. Fade by Sun Elevation
|
||||
// Start appearing sooner (when sun is still slightly up), but stay dim.
|
||||
// 0.10 (5.7 deg up) -> 0.0
|
||||
// -0.20 (11.5 deg down) -> 1.0
|
||||
highp float starFade = 1.0 - smoothstep(-0.20, 0.10, L.y);
|
||||
starFade *= starFade;
|
||||
|
||||
if (starFade <= 0.0) return vec3(0.0);
|
||||
|
||||
// 2. Rotate to break grid alignment
|
||||
highp vec3 rotV = vec3(
|
||||
dot(V, vec3(0.6, 0.8, 0.0)),
|
||||
dot(V, vec3(-0.8, 0.6, 0.0)),
|
||||
V.z
|
||||
);
|
||||
|
||||
highp float starVal = getStars(rotV, starControl.x);
|
||||
if (starVal <= 0.0) return vec3(0.0);
|
||||
|
||||
// 3. Cloud Occlusion (Aggressive)
|
||||
highp float cloudOcclusion = 1.0 - smoothstep(0.0, 1.0, pow(cloudDensity, 0.1));
|
||||
|
||||
return vec3(starVal) * transmittance * starFade * cloudOcclusion * 0.1;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Water Surface
|
||||
// ------------------------------------------------------------------------
|
||||
// Simulates an infinite ocean plane at y=0 using screen-space derivatives for normals.
|
||||
//
|
||||
// FEATURES:
|
||||
// - Projected grid for infinite surface.
|
||||
// - Screen-space wave normal reconstruction (no geometry required).
|
||||
// - Fresnel reflection of Atmosphere, Sun, and Clouds.
|
||||
// - Specular highlights (Blinn-Phong).
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param depthR Rayleigh Optical Depth.
|
||||
// @param depthM Mie Optical Depth.
|
||||
// @param ozone Ozone Absorption.
|
||||
// @param multiScatParams Multi-Scattering Params.
|
||||
// @param miePhaseParams Mie Phase Params.
|
||||
// @param sunHalo Sun Halo Params.
|
||||
// @param cloudControl Cloud Control Params.
|
||||
// @param cloudControl2 Cloud Evolution Params.
|
||||
// @param shimmerControl Shimmer Control (w component used as PlanetRadius for clouds).
|
||||
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
|
||||
// @return Water surface color.
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getWaterColor(highp vec3 V, highp vec3 L,
|
||||
highp float sunIntensity,
|
||||
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
|
||||
highp vec4 multiScatParams, highp vec2 miePhaseParams,
|
||||
highp vec4 sunHalo,
|
||||
highp vec4 cloudControl, highp vec4 cloudControl2,
|
||||
highp vec4 shimmerControl, highp vec4 waterControl) {
|
||||
|
||||
// Project to plane y=0
|
||||
highp float t = -10.0 / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
||||
highp vec2 uv = V.xz * t * 0.05;
|
||||
|
||||
highp float time = getUserTime().x;
|
||||
highp float speed = waterControl.y;
|
||||
uv += vec2(time * 0.5 * speed, time * 0.2 * speed);
|
||||
|
||||
// Wave Normal
|
||||
// Use screen-space derivatives to compute world-space normal perturbation
|
||||
// Wave Normal
|
||||
// Use screen-space derivatives to compute world-space normal perturbation
|
||||
int octaves = int(max(1.0, waterControl.w));
|
||||
highp float h = fbm(vec3(uv, time * 0.1 * speed), octaves);
|
||||
|
||||
// Reconstruct screen-space basis in world space
|
||||
// highp vec3 sRight = normalize(dFdx(V)); // Moved inside block
|
||||
// highp vec3 sUp = normalize(dFdy(V)); // Moved inside block
|
||||
|
||||
// Perturb normal based on height gradient
|
||||
// If h increases in screen-X direction, normal tilts against sRight.
|
||||
// Fade out perturbation near horizon (V.y -> 0) to reduce aliasing
|
||||
highp float horizonFade = smoothstep(0.0, 0.5, abs(V.y));
|
||||
highp float strength = waterControl.x;
|
||||
|
||||
highp vec3 N_perturb;
|
||||
|
||||
// Derivative Trick Toggle
|
||||
if (waterControl.z > 0.5) {
|
||||
// Screen-Space Derivatives (Fast, 1 tap)
|
||||
// Reconstruct screen-space basis in world space
|
||||
// If h increases in screen-X direction, normal tilts against sRight.
|
||||
highp vec3 sRight = normalize(dFdx(V));
|
||||
highp vec3 sUp = normalize(dFdy(V));
|
||||
N_perturb = (sRight * dFdx(h) + sUp * dFdy(h)) * strength * horizonFade;
|
||||
} else {
|
||||
// Finite Difference (Standard, 3 taps)
|
||||
// More expensive but analytically correct in world space (independent of view resolution/derivatives)
|
||||
float eps = 0.02; // Epsilon for gradient
|
||||
vec3 p = vec3(uv, time * 0.1 * speed);
|
||||
float hx = fbm(p + vec3(eps, 0.0, 0.0), octaves);
|
||||
float hy = fbm(p + vec3(0.0, eps, 0.0), octaves);
|
||||
|
||||
// Gradient
|
||||
float dx = (hx - h) / eps;
|
||||
float dy = (hy - h) / eps;
|
||||
|
||||
// Construct World Space Perturbation
|
||||
// Gradient (dx, dy) acts on XZ plane.
|
||||
// Normal = normalize(-dx, 1, -dy).
|
||||
// We want N_perturb to SUBTRACT from (0,1,0).
|
||||
// N_water = normalize(Up - Perturb).
|
||||
// So Perturb = (dx, 0, dy).
|
||||
// Note: Strength needs to be calibrated to match derivative trick roughly, or just raw.
|
||||
// Derivative trick Strength was ~50.0.
|
||||
// Here dx/dy are raw noise slopes.
|
||||
// Reduced to 0.002 to match visual range of derivative trick and prevent black artifacts.
|
||||
N_perturb = vec3(dx, 0.0, dy) * (strength * 0.002) * horizonFade;
|
||||
}
|
||||
|
||||
highp vec3 N_water = normalize(vec3(0.0, 1.0, 0.0) - N_perturb);
|
||||
|
||||
// Reflection
|
||||
highp vec3 R = reflect(V, N_water);
|
||||
|
||||
highp vec3 transRefl;
|
||||
highp vec3 reflection = getAtmosphere(R, L, sunIntensity,
|
||||
depthR, depthM,
|
||||
ozone, multiScatParams,
|
||||
miePhaseParams,
|
||||
transRefl);
|
||||
|
||||
// Clouds in reflection
|
||||
highp float reflCloudDensity;
|
||||
highp vec3 reflCloudLayer = getCloudLayer(R, L, materialParams.cloudControl, materialParams.cloudControl2,
|
||||
materialParams.shimmerControl, materialParams.sunIntensity, transRefl,
|
||||
reflCloudDensity);
|
||||
|
||||
// Add Stars to Reflection
|
||||
// Use helper with Reflection Vector and Reflection Cloud Density
|
||||
// Horizon Mask: Fade out star reflections that are deep in the water (high R.y)
|
||||
// Restricted to very close to horizon (0.0 to 0.1) as requested.
|
||||
highp float rHorizonMask = 1.0 - smoothstep(0.0, 0.1, R.y);
|
||||
|
||||
if (rHorizonMask > 0.0) {
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, transRefl, materialParams.starControl) * rHorizonMask;
|
||||
}
|
||||
|
||||
// Add Sun Disk to reflection (Occluded)
|
||||
highp float reflSunAccess = 1.0 - smoothstep(0.0, 0.7, reflCloudDensity * 1.5);
|
||||
reflection += getSunDisk(R, L, sunHalo, sunIntensity, transRefl) * reflSunAccess;
|
||||
|
||||
// Apply clouds to reflection
|
||||
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
||||
|
||||
// Fresnel
|
||||
highp float F0 = 0.02; // Water
|
||||
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
||||
highp float F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
|
||||
|
||||
highp vec3 deepColor = vec3(0.0, 0.005, 0.02); // Deep blue/black
|
||||
|
||||
highp vec3 waterColor = mix(deepColor, reflection, F);
|
||||
|
||||
return waterColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
|
||||
highp vec3 V = normalize(variable_eyeDirection.xyz);
|
||||
highp vec3 L = normalize(materialParams.sunDirection);
|
||||
|
||||
// 1. Heat Shimmer
|
||||
// Fade out as sun rises (Strongest at horizon, zero at 30 degrees up)
|
||||
highp float sunFade = 1.0 - smoothstep(0.0, 0.5, abs(L.y));
|
||||
highp float shimmerIntensity = applyHeatShimmer(V, materialParams.shimmerControl.x * sunFade,
|
||||
materialParams.shimmerControl.y,
|
||||
materialParams.shimmerControl.z);
|
||||
|
||||
// 2. Atmospheric Scattering
|
||||
highp vec3 transmittance;
|
||||
highp vec3 inScatter1 = getAtmosphere(V, L, materialParams.sunIntensity,
|
||||
materialParams.depthR, materialParams.depthM,
|
||||
materialParams.ozone, materialParams.multiScatParams,
|
||||
materialParams.miePhaseParams,
|
||||
transmittance);
|
||||
|
||||
|
||||
// Sun 2 (Optional)
|
||||
// We reuse the same Transmittance (view dependent) and Phase params.
|
||||
// We do NOT add extra Multi-Scattering (Ambient) for the second sun to save cost/complexity.
|
||||
// It contributes Direct In-Scattering (Beams/Glow) only.
|
||||
highp vec3 inScatter2 = vec3(0.0);
|
||||
if (materialParams.sunHalo2.w > 0.5) {
|
||||
highp vec3 L2 = normalize(materialParams.sunDirection2);
|
||||
inScatter2 = getSecondarySunScattering(V, L2,
|
||||
materialParams.sunIntensity2,
|
||||
materialParams.depthR,
|
||||
materialParams.depthM,
|
||||
materialParams.ozone,
|
||||
materialParams.miePhaseParams,
|
||||
transmittance);
|
||||
}
|
||||
|
||||
highp vec3 finalColor = inScatter1 + inScatter2;
|
||||
|
||||
// 5. Procedural Clouds
|
||||
highp float cloudDensity;
|
||||
highp vec3 cloudLayer = getCloudLayer(V, L,
|
||||
materialParams.cloudControl,
|
||||
materialParams.cloudControl2,
|
||||
materialParams.shimmerControl, // reusing w=PlanetRadius
|
||||
materialParams.sunIntensity,
|
||||
transmittance,
|
||||
cloudDensity);
|
||||
|
||||
// Add Stars
|
||||
// Stars are at infinity.
|
||||
// Use helper function.
|
||||
finalColor += getStarLayer(V, L, cloudDensity, transmittance, materialParams.starControl);
|
||||
|
||||
// 3. Sun Disks - Occluded by clouds
|
||||
// Sun Access is (1.0 - cloudDensity) but arguably non-linear for sharp disk
|
||||
highp float sunAccess = 1.0 - smoothstep(0.0, 0.7, cloudDensity * 1.5);
|
||||
|
||||
finalColor += getSunDisk(V, L, materialParams.sunHalo,
|
||||
materialParams.sunIntensity, transmittance) * sunAccess;
|
||||
|
||||
if (materialParams.sunHalo2.w > 0.5) {
|
||||
highp vec3 L2 = normalize(materialParams.sunDirection2);
|
||||
// Note: Ideally we should compute cloud density for L2 direction if clouds are 3D...
|
||||
// But here we use V direction clouds (view-based).
|
||||
// Since clouds are in front of everything, this is correct for view-based occlusion.
|
||||
finalColor += getSunDisk(V, L2, materialParams.sunHalo2,
|
||||
materialParams.sunIntensity2, transmittance) * sunAccess;
|
||||
}
|
||||
|
||||
// 4. Night Sky Offset
|
||||
finalColor += materialParams.nightColor;
|
||||
|
||||
// 5. Apply Clouds
|
||||
finalColor = mix(finalColor, cloudLayer, cloudDensity);
|
||||
|
||||
// 6. Dynamic Tone Mapping
|
||||
finalColor = applyDynamicToneMapping(finalColor, L, materialParams.contrast);
|
||||
|
||||
if (V.y < 0.0) {
|
||||
finalColor = getWaterColor(V, L, materialParams.sunIntensity,
|
||||
materialParams.depthR, materialParams.depthM,
|
||||
materialParams.ozone, materialParams.multiScatParams,
|
||||
materialParams.miePhaseParams,
|
||||
materialParams.sunHalo,
|
||||
materialParams.cloudControl, materialParams.cloudControl2,
|
||||
materialParams.shimmerControl,
|
||||
materialParams.waterControl);
|
||||
finalColor = applyDynamicToneMapping(finalColor, L, materialParams.contrast);
|
||||
}
|
||||
|
||||
material.baseColor = vec4(finalColor, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
docs/wip/sky/styles.css
Normal file
9
docs/wip/sky/styles.css
Normal file
@@ -0,0 +1,9 @@
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
canvas {
|
||||
touch-action: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
311
docs_src/src_raw/wip/sky/SimulatedSkybox.js
Normal file
311
docs_src/src_raw/wip/sky/SimulatedSkybox.js
Normal file
@@ -0,0 +1,311 @@
|
||||
|
||||
// SimulatedSkybox.js
|
||||
// Ported from samples/utils/SimulatedSkybox.cpp
|
||||
|
||||
class SimulatedSkybox {
|
||||
constructor(engine) {
|
||||
this.engine = engine;
|
||||
|
||||
// Default Parameters
|
||||
this.sunDirection = [0, 1, 0];
|
||||
this.sunIntensity = 100000.0;
|
||||
this.turbidity = 2.0;
|
||||
this.rayleigh = 1.0;
|
||||
this.mieCoefficient = 1.0;
|
||||
this.mieG = 0.8;
|
||||
this.ozone = 0.0;
|
||||
this.msFactors = [0.1, 0.5, 0.0];
|
||||
this.contrast = 1.0;
|
||||
this.nightColor = [0.0, 0.0003, 0.00075];
|
||||
this.shimmerControl = [0.0, 20.0, 0.1];
|
||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||
this.starControl = [1.0, 1.0]; // x=Density (0-1), y=Enabled (0-1)
|
||||
this.planetRadius = 6360.0;
|
||||
|
||||
// Sun Halo
|
||||
// x=cos(rad), y=limbDarkening, z=intensity, w=enabled
|
||||
this.sunHalo = [Math.cos(0.5 * Math.PI / 180.0), 0.5, 1.0, 1.0];
|
||||
|
||||
this.initEntity();
|
||||
}
|
||||
|
||||
async loadMaterial(url) {
|
||||
console.log("Loading material from:", url);
|
||||
const response = await fetch(url);
|
||||
const buffer = await response.arrayBuffer();
|
||||
this.material = this.engine.createMaterial(new Uint8Array(buffer));
|
||||
this.materialInstance = this.material.createInstance();
|
||||
|
||||
// Re-bind the entity with the loaded material
|
||||
const rcm = this.engine.getRenderableManager();
|
||||
const instance = rcm.getInstance(this.entity);
|
||||
rcm.setMaterialInstanceAt(instance, 0, this.materialInstance);
|
||||
|
||||
console.log("Material loaded and bound.");
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
initEntity() {
|
||||
const EntityManager = Filament.EntityManager;
|
||||
const RenderableManager = Filament.RenderableManager;
|
||||
const VertexBuffer = Filament.VertexBuffer;
|
||||
const IndexBuffer = Filament.IndexBuffer;
|
||||
const AttributeType = Filament.VertexBuffer$AttributeType;
|
||||
const VertexAttribute = Filament.VertexAttribute;
|
||||
const PrimitiveType = Filament.RenderableManager$PrimitiveType;
|
||||
const IndexType = Filament.IndexBuffer$IndexType;
|
||||
|
||||
this.entity = EntityManager.get().create();
|
||||
|
||||
// 3 vertices for full screen triangle
|
||||
// coords: -1,-1 to 3,-1 to -1,3
|
||||
const TRIANGLE_VERTICES = new Float32Array([
|
||||
-1.0, -1.0,
|
||||
3.0, -1.0,
|
||||
-1.0, 3.0
|
||||
]);
|
||||
|
||||
const TRIANGLE_INDICES = new Uint16Array([0, 1, 2]);
|
||||
|
||||
this.vb = VertexBuffer.Builder()
|
||||
.vertexCount(3)
|
||||
.bufferCount(1)
|
||||
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, 8)
|
||||
.build(this.engine);
|
||||
|
||||
this.vb.setBufferAt(this.engine, 0, TRIANGLE_VERTICES);
|
||||
|
||||
this.ib = IndexBuffer.Builder()
|
||||
.indexCount(3)
|
||||
.bufferType(IndexType.USHORT)
|
||||
.build(this.engine);
|
||||
|
||||
this.ib.setBuffer(this.engine, TRIANGLE_INDICES);
|
||||
|
||||
// We create a dummy material first or wait?
|
||||
// In JS we usually can't block. We'll rely on loadMaterial being called.
|
||||
// For now, we build the Renderable without material, then set it later.
|
||||
|
||||
RenderableManager.Builder(1)
|
||||
.geometry(0, PrimitiveType.TRIANGLES, this.vb, this.ib)
|
||||
.culling(false)
|
||||
.castShadows(false)
|
||||
.receiveShadows(false)
|
||||
.priority(7) // Render behind translucent objects? 7 is skybox priority typically.
|
||||
.build(this.engine, this.entity);
|
||||
}
|
||||
|
||||
setSunPosition(direction) {
|
||||
// normalize
|
||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
||||
if (len > 0) {
|
||||
this.sunDirection = [direction[0] / len, direction[1] / len, direction[2] / len];
|
||||
} else {
|
||||
this.sunDirection = [0, 1, 0];
|
||||
}
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setSunIntensity(intensity) {
|
||||
this.sunIntensity = Math.max(0.0, intensity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setTurbidity(turbidity) {
|
||||
this.turbidity = Math.max(0.0, turbidity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setRayleigh(rayleigh) {
|
||||
this.rayleigh = Math.max(0.0, rayleigh);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMieCoefficient(mie) {
|
||||
this.mieCoefficient = Math.max(0.0, mie);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMieG(g) {
|
||||
this.mieG = Math.max(0.0, g);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setOzone(strength) {
|
||||
this.ozone = Math.max(0.0, strength);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMultiScattering(r, m) {
|
||||
this.msFactors[0] = Math.max(0.0, Math.min(2.0, r));
|
||||
this.msFactors[1] = Math.max(0.0, Math.min(2.0, m));
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setHorizonGlow(strength) {
|
||||
this.msFactors[2] = Math.max(0.0, Math.min(1.0, strength));
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setContrast(contrast) {
|
||||
this.contrast = contrast;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setNightColor(color) {
|
||||
this.nightColor = color;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setSunRadius(degrees) {
|
||||
const rad = degrees * (Math.PI / 180.0);
|
||||
this.sunHalo[0] = Math.cos(rad);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setSunDiskIntensity(intensity) {
|
||||
this.sunHalo[2] = Math.max(0.0, intensity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setSunLimbDarkening(strength) {
|
||||
this.sunHalo[1] = Math.max(0.0, strength);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setSunDiskEnabled(enabled) {
|
||||
this.sunHalo[3] = enabled ? 1.0 : 0.0;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setShimmerControl(strength, frequency, maskHeight) {
|
||||
this.shimmerControl[0] = Math.max(0.0, strength);
|
||||
this.shimmerControl[1] = Math.max(0.0, frequency);
|
||||
this.shimmerControl[2] = Math.max(0.001, maskHeight);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setCloudControl(coverage, density, height, speed) {
|
||||
this.cloudControl[0] = Math.max(0.0, Math.min(1.0, coverage));
|
||||
this.cloudControl[1] = Math.max(0.0, density);
|
||||
this.cloudControl[2] = Math.max(1000.0, height);
|
||||
// JS speed adjustment logic matches C++: speed * (0.05 / 72.0)
|
||||
this.cloudControl[3] = speed * (0.05 / 72.0);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setCloudShapeEvolution(speed) {
|
||||
this.cloudControl2[0] = speed;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setCloudVolumetricLighting(enabled) {
|
||||
this.cloudControl2[1] = enabled ? 1.0 : 0.0;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setWaterControl(strength, speed, derivativeTrick, octaves) {
|
||||
this.waterControl[0] = Math.max(0.0, strength);
|
||||
this.waterControl[1] = Math.max(0.0, speed);
|
||||
this.waterControl[2] = derivativeTrick;
|
||||
this.waterControl[3] = Math.max(1.0, Math.min(8.0, octaves));
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setStarControl(density, enabled) {
|
||||
this.starControl[0] = Math.max(0.0, Math.min(1.0, density));
|
||||
this.starControl[1] = enabled ? 1.0 : 0.0;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
updateCoefficients() {
|
||||
if (!this.materialInstance) {
|
||||
console.warn("updateCoefficients called before material loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 1. Rayleigh Coefficients
|
||||
const F_PI = Math.PI;
|
||||
const lambda = [680e-9, 550e-9, 440e-9];
|
||||
const n = 1.0003;
|
||||
const N = 2.545e25;
|
||||
const term = (8.0 * Math.pow(F_PI, 3.0) * Math.pow(n * n - 1.0, 2.0)) / (3.0 * N);
|
||||
|
||||
const depthR = [
|
||||
term / Math.pow(lambda[0], 4.0),
|
||||
term / Math.pow(lambda[1], 4.0),
|
||||
term / Math.pow(lambda[2], 4.0)
|
||||
].map(v => v * 8000.0 * this.rayleigh);
|
||||
|
||||
// 2. Mie Coefficients
|
||||
const mieAlpha = 1.3;
|
||||
const mieBase = 2.0e-5 * this.turbidity;
|
||||
const depthM = [
|
||||
mieBase * Math.pow(550e-9 / lambda[0], mieAlpha),
|
||||
mieBase * Math.pow(550e-9 / lambda[1], mieAlpha),
|
||||
mieBase * Math.pow(550e-9 / lambda[2], mieAlpha)
|
||||
].map(v => v * 1200.0 * this.mieCoefficient);
|
||||
|
||||
// Fake Ozone
|
||||
const ozone = [0.0, this.ozone * 0.1, 0.0];
|
||||
|
||||
// Sun Fade (Horizon)
|
||||
const cutoffAngle = 96.0 * (F_PI / 180.0);
|
||||
const steepness = 1.5;
|
||||
const zenithFade = 1.0 - Math.exp(-(cutoffAngle / steepness));
|
||||
const zenithAngle = Math.acos(Math.max(-1.0, Math.min(1.0, this.sunDirection[1])));
|
||||
const sunFade = Math.max(0.0, 1.0 - Math.exp(-((cutoffAngle - zenithAngle) / steepness))) / zenithFade;
|
||||
|
||||
const physicalSunIntensity = this.sunIntensity * sunFade;
|
||||
|
||||
// Radiance Conversion for Sun Halo
|
||||
// Solid Angle = 2 * PI * (1 - cos(angularRadius))
|
||||
const solidAngle = 2.0 * F_PI * (1.0 - this.sunHalo[0]);
|
||||
const radianceConversion = 1.0 / Math.max(1e-9, solidAngle);
|
||||
const sunHaloUpload = [...this.sunHalo];
|
||||
sunHaloUpload[2] *= radianceConversion;
|
||||
|
||||
// Cloud Intersection
|
||||
const r = this.planetRadius;
|
||||
const h = this.cloudControl[2] * 0.001; // m -> km
|
||||
const intersectC = r * r - (r + h) * (r + h);
|
||||
const cloudUniform = [...this.cloudControl];
|
||||
cloudUniform[2] = intersectC;
|
||||
|
||||
// Shimmer Uniform
|
||||
const shimmerUniform = [...this.shimmerControl, r];
|
||||
|
||||
// Multi-Scattering Vector
|
||||
const isotropicPhase = 0.25;
|
||||
const msVector = depthR.map((v, i) => (v * this.msFactors[0] + depthM[i] * this.msFactors[1]) * isotropicPhase);
|
||||
|
||||
// Upload
|
||||
this.materialInstance.setFloat3Parameter('sunDirection', new Float32Array(this.sunDirection));
|
||||
this.materialInstance.setFloat3Parameter('depthR', new Float32Array(depthR));
|
||||
this.materialInstance.setFloat3Parameter('depthM', new Float32Array(depthM));
|
||||
this.materialInstance.setFloat3Parameter('ozone', new Float32Array(ozone));
|
||||
this.materialInstance.setFloat4Parameter('sunHalo', new Float32Array(sunHaloUpload));
|
||||
|
||||
this.materialInstance.setFloat4Parameter('multiScatParams', new Float32Array([...msVector, this.msFactors[2]]));
|
||||
|
||||
// Mie Phase
|
||||
const g2 = this.mieG * this.mieG;
|
||||
this.materialInstance.setFloat2Parameter('miePhaseParams', new Float32Array([1.0 + g2, -2.0 * this.mieG]));
|
||||
|
||||
this.materialInstance.setFloatParameter('contrast', this.contrast);
|
||||
|
||||
const nightColorScaled = this.nightColor.map(v => v * this.sunIntensity);
|
||||
this.materialInstance.setFloat3Parameter('nightColor', new Float32Array(nightColorScaled));
|
||||
|
||||
this.materialInstance.setFloat4Parameter('shimmerControl', new Float32Array(shimmerUniform));
|
||||
this.materialInstance.setFloat4Parameter('cloudControl', new Float32Array(cloudUniform));
|
||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||
this.materialInstance.setFloat2Parameter('starControl', new Float32Array(this.starControl));
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||
}
|
||||
}
|
||||
BIN
docs_src/src_raw/wip/sky/assets/simulated_skybox.filamat
Normal file
BIN
docs_src/src_raw/wip/sky/assets/simulated_skybox.filamat
Normal file
Binary file not shown.
10
docs_src/src_raw/wip/sky/build_material.sh
Executable file
10
docs_src/src_raw/wip/sky/build_material.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
# Result: /Users/mathias/sources/git/filament/out/cmake-release/tools/matc/matc
|
||||
MATC="../../../../out/cmake-release/tools/matc/matc"
|
||||
|
||||
# Navigate to script directory to ensure relative paths work
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
set -e
|
||||
|
||||
$MATC -a opengl -p mobile -o assets/simulated_skybox.filamat simulated_skybox.mat
|
||||
echo "Material recompiled to assets/simulated_skybox.filamat"
|
||||
1409
docs_src/src_raw/wip/sky/filament.js
Normal file
1409
docs_src/src_raw/wip/sky/filament.js
Normal file
File diff suppressed because one or more lines are too long
BIN
docs_src/src_raw/wip/sky/filament.wasm
Executable file
BIN
docs_src/src_raw/wip/sky/filament.wasm
Executable file
Binary file not shown.
28
docs_src/src_raw/wip/sky/gl-matrix-min.js
vendored
Normal file
28
docs_src/src_raw/wip/sky/gl-matrix-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
39
docs_src/src_raw/wip/sky/index.html
Normal file
39
docs_src/src_raw/wip/sky/index.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Analytic Skybox</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||||
<link href="styles.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
canvas {
|
||||
touch-action: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas></canvas>
|
||||
<!-- Filament -->
|
||||
<script src="filament.js"></script>
|
||||
<script src="gl-matrix-min.js"></script>
|
||||
|
||||
<!-- UI -->
|
||||
<script src="lil-gui.js"></script>
|
||||
|
||||
<!-- App -->
|
||||
<script src="SimulatedSkybox.js?v=26"></script>
|
||||
<script src="main.js?v=26"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
8
docs_src/src_raw/wip/sky/lil-gui.js
Normal file
8
docs_src/src_raw/wip/sky/lil-gui.js
Normal file
File diff suppressed because one or more lines are too long
345
docs_src/src_raw/wip/sky/main.js
Normal file
345
docs_src/src_raw/wip/sky/main.js
Normal file
@@ -0,0 +1,345 @@
|
||||
|
||||
// main.js
|
||||
|
||||
Filament.init(['assets/simulated_skybox.filamat'], () => {
|
||||
window.app = new App(document.getElementsByTagName('canvas')[0]);
|
||||
});
|
||||
|
||||
class App {
|
||||
constructor(canvas) {
|
||||
this.canvas = canvas;
|
||||
const engine = this.engine = Filament.Engine.create(this.canvas);
|
||||
this.scene = engine.createScene();
|
||||
|
||||
this.skybox = new SimulatedSkybox(engine);
|
||||
this.skybox.entity = this.skybox.entity; // Ensuring access if needed
|
||||
this.scene.addEntity(this.skybox.entity);
|
||||
|
||||
// Load the material explicitly since we passed it to init but SimulatedSkybox needs to bind it
|
||||
// Actually SimulatedSkybox.loadMaterial fetches it.
|
||||
// Since we already loaded it in Filament.init, we can arguably just use it if we had a way to access the asset.
|
||||
// But Filament.init assets are for internal or easy access via assets object if configured?
|
||||
// Let's just let SimulatedSkybox fetch it again or use a blob if we wanted.
|
||||
// Simpler: Just let SimulatedSkybox fetch it.
|
||||
this.skybox.loadMaterial('assets/simulated_skybox.filamat').then(() => {
|
||||
this.initGUI();
|
||||
});
|
||||
|
||||
this.swapChain = engine.createSwapChain();
|
||||
this.renderer = engine.createRenderer();
|
||||
this.camera = engine.createCamera(Filament.EntityManager.get().create());
|
||||
|
||||
this.view = engine.createView();
|
||||
this.view.setCamera(this.camera);
|
||||
this.view.setScene(this.scene);
|
||||
// Color Grading
|
||||
const ColorGrading = Filament.ColorGrading;
|
||||
const ToneMapping = Filament.ColorGrading$ToneMapping;
|
||||
this.colorGrading = ColorGrading.Builder()
|
||||
.toneMapping(ToneMapping.ACES_LEGACY)
|
||||
.build(engine);
|
||||
this.view.setColorGrading(this.colorGrading);
|
||||
this.view.setPostProcessingEnabled(true); // Essential for tone mapping
|
||||
|
||||
// Bloom
|
||||
this.view.setBloomOptions({
|
||||
enabled: false,
|
||||
lenseFlare: false
|
||||
});
|
||||
|
||||
// Clear color is not really visible behind skybox, but black is standard
|
||||
this.renderer.setClearOptions({ clearColor: [0.0, 0.0, 0.0, 1.0], clear: true });
|
||||
|
||||
// Camera handling (Exposure)
|
||||
this.params = {
|
||||
aperture: 16.0,
|
||||
shutterSpeed: 125.0,
|
||||
iso: 100.0,
|
||||
sunTheta: Math.acos(0.0), // Default Height 0.0 (Horizon)
|
||||
sunPhi: 0.0,
|
||||
focalLength: 24.0, // mm
|
||||
sunIntensity: 100000.0 // Base intensity
|
||||
};
|
||||
|
||||
this.camState = {
|
||||
theta: Math.PI / 2, // Look at +X (Sun Position at Phi=0)
|
||||
phi: 0.0,
|
||||
dragging: false,
|
||||
lastX: 0,
|
||||
lastY: 0
|
||||
};
|
||||
|
||||
this.initControls(); // Initialize controls immediately
|
||||
|
||||
this.resize();
|
||||
window.addEventListener('resize', this.resize.bind(this));
|
||||
|
||||
this.render = this.render.bind(this);
|
||||
window.requestAnimationFrame(this.render);
|
||||
}
|
||||
|
||||
getExposure() {
|
||||
// Formula: 1.0 / ( 1.2 * (N^2 / t) * (S / 100) )
|
||||
// t = 1/shutterSpeed
|
||||
const N = this.params.aperture;
|
||||
const t = 1.0 / this.params.shutterSpeed;
|
||||
const S = this.params.iso;
|
||||
const ev100_linear = (N * N) / t * (100.0 / S);
|
||||
const exposure = 1.0 / (1.2 * ev100_linear);
|
||||
return exposure;
|
||||
}
|
||||
|
||||
updateCameraExposure() {
|
||||
this.camera.setExposure(this.params.aperture, 1.0 / this.params.shutterSpeed, this.params.iso);
|
||||
// Also update Sun Intensity because it needs to be pre-exposed
|
||||
this.updateSunIntensity();
|
||||
}
|
||||
|
||||
updateSunIntensity() {
|
||||
const exposure = this.getExposure();
|
||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||
this.skybox.setSunIntensity(preExposedIntensity);
|
||||
}
|
||||
|
||||
updateCameraProjection() {
|
||||
const width = this.canvas.width;
|
||||
const height = this.canvas.height;
|
||||
const aspect = width / height;
|
||||
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
||||
}
|
||||
|
||||
initGUI() {
|
||||
const gui = new lil.GUI({ title: "Analytic Skybox" });
|
||||
const self = this;
|
||||
const sky = this.skybox;
|
||||
|
||||
// Initialize local params from skybox defaults
|
||||
// Initialize local params from skybox defaults
|
||||
// REMOVED: Do not overwrite this.params from sky.sunDirection (Zenith)
|
||||
// const currentDir = sky.sunDirection;
|
||||
// this.params.sunTheta = ...
|
||||
|
||||
const updateSun = () => {
|
||||
const theta = this.params.sunTheta;
|
||||
const phi = this.params.sunPhi;
|
||||
const x = Math.sin(theta) * Math.cos(phi);
|
||||
const y = Math.cos(theta);
|
||||
const z = Math.sin(theta) * Math.sin(phi);
|
||||
sky.setSunPosition([x, y, z]);
|
||||
};
|
||||
|
||||
const sunFolder = gui.addFolder('Sun');
|
||||
// Helper for "Sun Height" cosine slider like C++
|
||||
const sunHeightParam = { height: Math.cos(this.params.sunTheta) };
|
||||
sunFolder.add(sunHeightParam, 'height', -0.2, 1.0).name('Height (Cos)').onChange(v => {
|
||||
this.params.sunTheta = Math.acos(v);
|
||||
updateSun();
|
||||
});
|
||||
sunFolder.add(this.params, 'sunPhi', 0.0, Math.PI * 2).name('Azimuth').onChange(updateSun);
|
||||
// Updated: Controls params.sunIntensity and triggers updateSunIntensity
|
||||
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).onChange(v => this.updateSunIntensity());
|
||||
|
||||
const sunDisk = sunFolder.addFolder('Disk');
|
||||
// We need local proxy for sunRadius due to conversion
|
||||
const diskParams = {
|
||||
radius: 1.2,
|
||||
enabled: true // Enable sun disk
|
||||
};
|
||||
sky.setSunDiskEnabled(true);
|
||||
sky.setSunRadius(1.2);
|
||||
sunDisk.add(diskParams, 'enabled').onChange(v => sky.setSunDiskEnabled(v));
|
||||
sunDisk.add(diskParams, 'radius', 0.0, 5.0).onChange(v => sky.setSunRadius(v));
|
||||
sunDisk.add(sky.sunHalo, 1, 0.0, 2.0).name('Limb Darkening').onChange(v => sky.setSunLimbDarkening(v));
|
||||
sunDisk.add(sky.sunHalo, 2, 0.0, 100.0).name('Intensity Boost').onChange(v => sky.setSunDiskIntensity(v));
|
||||
|
||||
const atmFolder = gui.addFolder('Atmosphere');
|
||||
atmFolder.add(sky, 'turbidity', 1.0, 10.0).onChange(v => sky.setTurbidity(v));
|
||||
atmFolder.add(sky, 'rayleigh', 0.0, 10.0).onChange(v => sky.setRayleigh(v));
|
||||
atmFolder.add(sky, 'mieCoefficient', 0.0, 10.0).onChange(v => sky.setMieCoefficient(v));
|
||||
// Set Ozone default to 0.25
|
||||
sky.setOzone(0.25);
|
||||
atmFolder.add(sky, 'ozone', 0.0, 1.0).onChange(v => sky.setOzone(v));
|
||||
atmFolder.add(sky, 'mieG', 0.0, 0.999).onChange(v => sky.setMieG(v));
|
||||
|
||||
const artFolder = gui.addFolder('Artistic');
|
||||
// Set Horizon Glow default to 1.0
|
||||
sky.setHorizonGlow(1.0);
|
||||
sky.msFactors[2] = 1.0;
|
||||
// Set Contrast default to 0.85
|
||||
sky.setContrast(0.85);
|
||||
|
||||
artFolder.add(sky.msFactors, 0, 0.0, 2.0).name('MS Rayleigh').onChange(v => sky.setMultiScattering(v, sky.msFactors[1]));
|
||||
artFolder.add(sky.msFactors, 1, 0.0, 2.0).name('MS Mie').onChange(v => sky.setMultiScattering(sky.msFactors[0], v));
|
||||
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
|
||||
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
|
||||
|
||||
artFolder.addColor(sky, 'nightColor').onChange(v => sky.setNightColor(v));
|
||||
|
||||
const shmFolder = artFolder.addFolder('Shimmer');
|
||||
// Set Shimmer Strength default to 0.0
|
||||
sky.setShimmerControl(0.0, sky.shimmerControl[1], sky.shimmerControl[2]);
|
||||
|
||||
shmFolder.add(sky.shimmerControl, 0, 0.0, 0.1).name('Strength').onChange(v => sky.setShimmerControl(v, sky.shimmerControl[1], sky.shimmerControl[2]));
|
||||
shmFolder.add(sky.shimmerControl, 1, 1.0, 100.0).name('Frequency').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], v, sky.shimmerControl[2]));
|
||||
shmFolder.add(sky.shimmerControl, 2, 0.01, 0.5).name('Mask Height').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], sky.shimmerControl[1], v));
|
||||
|
||||
const cloudFolder = gui.addFolder('Clouds');
|
||||
const cParams = {
|
||||
volumetrics: sky.cloudControl2[1] > 0.5,
|
||||
coverage: 0.4,
|
||||
density: 0.02,
|
||||
height: sky.cloudControl[2],
|
||||
speed: 50.0,
|
||||
evolution: 0.02
|
||||
};
|
||||
// Apply Cloud Defaults
|
||||
sky.setCloudControl(0.4, 0.02, cParams.height, 50.0);
|
||||
sky.setCloudShapeEvolution(0.02);
|
||||
|
||||
cloudFolder.add(cParams, 'volumetrics').onChange(v => sky.setCloudVolumetricLighting(v));
|
||||
cloudFolder.add(cParams, 'coverage', 0.0, 1.0).onChange(v => sky.setCloudControl(v, cParams.density, cParams.height, cParams.speed));
|
||||
cloudFolder.add(cParams, 'density', 0.0, 1.0).onChange(v => sky.setCloudControl(cParams.coverage, v, cParams.height, cParams.speed));
|
||||
cloudFolder.add(cParams, 'height', 2000.0, 20000.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, v, cParams.speed));
|
||||
// Reverse speed calc: w = speed * (0.05 / 72.0)
|
||||
cloudFolder.add(cParams, 'speed', 0.0, 200.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, cParams.height, v));
|
||||
cloudFolder.add(cParams, 'evolution', 0.0, 2.0).onChange(v => sky.setCloudShapeEvolution(v));
|
||||
|
||||
const waterFolder = gui.addFolder('Water');
|
||||
const wParams = {
|
||||
derivativeTrick: true,
|
||||
strength: 50.0,
|
||||
speed: 1.0,
|
||||
octaves: 4.0
|
||||
};
|
||||
// Initialize defaults
|
||||
sky.setWaterControl(50.0, 1.0, 1.0, 4.0); // 1.0 = Derivative Trick On, 4 octaves
|
||||
|
||||
const updateWater = () => {
|
||||
sky.setWaterControl(wParams.strength, wParams.speed, wParams.derivativeTrick ? 1.0 : 0.0, wParams.octaves);
|
||||
};
|
||||
|
||||
waterFolder.add(wParams, 'derivativeTrick').name('Derivative Trick').onChange(updateWater);
|
||||
waterFolder.add(wParams, 'strength', 10.0, 100.0).onChange(updateWater);
|
||||
waterFolder.add(wParams, 'speed', 0.0, 5.0).onChange(updateWater);
|
||||
waterFolder.add(wParams, 'octaves', 1, 8, 1).name('Octaves').onChange(updateWater);
|
||||
waterFolder.close();
|
||||
|
||||
const starFolder = gui.addFolder('Stars');
|
||||
const sParams = {
|
||||
enabled: true,
|
||||
density: 1.0
|
||||
};
|
||||
// Initialize defaults (Density 1.0, Enabled True)
|
||||
sky.setStarControl(1.0, true);
|
||||
|
||||
const updateStars = () => {
|
||||
sky.setStarControl(sParams.density, sParams.enabled);
|
||||
};
|
||||
|
||||
starFolder.add(sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||
starFolder.add(sParams, 'density', 0.0, 1.0).name('Density').onChange(updateStars);
|
||||
starFolder.close();
|
||||
|
||||
const camFolder = gui.addFolder('Camera');
|
||||
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
||||
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'shutterSpeed', 1.0, 1000.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
||||
|
||||
const bloomFolder = camFolder.addFolder('Bloom');
|
||||
const bParams = {
|
||||
enabled: false,
|
||||
lensFlare: false
|
||||
};
|
||||
|
||||
const updateBloom = () => {
|
||||
this.view.setBloomOptions({
|
||||
enabled: bParams.enabled,
|
||||
lensFlare: bParams.lensFlare
|
||||
});
|
||||
};
|
||||
|
||||
bloomFolder.add(bParams, 'enabled').onChange(updateBloom);
|
||||
bloomFolder.add(bParams, 'lensFlare').onChange(updateBloom);
|
||||
bloomFolder.close();
|
||||
|
||||
// Collapse folders by default
|
||||
sunDisk.close();
|
||||
atmFolder.close();
|
||||
artFolder.close();
|
||||
// shmFolder is inside artFolder, so it's hidden, but we can close it too if we want
|
||||
shmFolder.close();
|
||||
cloudFolder.close();
|
||||
// camFolder left open? User didn't specify, but "Artistic, shimmer and clouds" + "Disk, Atmosphere" were requested.
|
||||
// So Camera might stay open or close. Let's keep Camera open for now as it wasn't listed.
|
||||
|
||||
// Initial sync
|
||||
updateSun();
|
||||
this.updateCameraExposure(); // This will trigger updateSunIntensity too
|
||||
}
|
||||
|
||||
initControls() {
|
||||
|
||||
// listeners only
|
||||
this.canvas.addEventListener('mousedown', e => {
|
||||
this.camState.dragging = true;
|
||||
this.camState.lastX = e.clientX;
|
||||
this.camState.lastY = e.clientY;
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', () => {
|
||||
this.camState.dragging = false;
|
||||
});
|
||||
|
||||
window.addEventListener('mousemove', e => {
|
||||
if (!this.camState.dragging) return;
|
||||
const dx = e.clientX - this.camState.lastX;
|
||||
const dy = e.clientY - this.camState.lastY;
|
||||
this.camState.lastX = e.clientX;
|
||||
this.camState.lastY = e.clientY;
|
||||
|
||||
const sensitivity = 0.005;
|
||||
this.camState.theta -= dx * sensitivity;
|
||||
this.camState.phi += dy * sensitivity;
|
||||
// Clamp pitch to avoid flip [ -PI/2, PI/2 ]
|
||||
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
// Update Camera LookAt
|
||||
const r = 1.0;
|
||||
const theta = this.camState.theta;
|
||||
const phi = this.camState.phi;
|
||||
|
||||
// Convert spherical to cartesian
|
||||
// Y is UP. Z is Forward.
|
||||
// At phi=0, y=0. forward vector should correspond to theta.
|
||||
// Let's say theta=0 is -Z.
|
||||
const y = Math.sin(phi);
|
||||
const h = Math.cos(phi);
|
||||
const x = h * Math.sin(theta);
|
||||
const z = -h * Math.cos(theta);
|
||||
|
||||
const eye = [0, 0, 0];
|
||||
const center = [x, y, z];
|
||||
const up = [0, 1, 0];
|
||||
this.camera.lookAt(eye, center, up);
|
||||
|
||||
this.renderer.render(this.swapChain, this.view);
|
||||
window.requestAnimationFrame(this.render);
|
||||
}
|
||||
|
||||
resize() {
|
||||
const dpr = window.devicePixelRatio;
|
||||
const width = this.canvas.width = window.innerWidth * dpr;
|
||||
const height = this.canvas.height = window.innerHeight * dpr;
|
||||
this.view.setViewport([0, 0, width, height]);
|
||||
|
||||
const aspect = width / height;
|
||||
// near=0.1, far=5000.0
|
||||
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
||||
}
|
||||
}
|
||||
909
docs_src/src_raw/wip/sky/simulated_skybox.mat
Normal file
909
docs_src/src_raw/wip/sky/simulated_skybox.mat
Normal file
@@ -0,0 +1,909 @@
|
||||
material {
|
||||
name : SimulatedSkybox,
|
||||
parameters : [
|
||||
{
|
||||
type : float3,
|
||||
name : sunDirection
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : sunDirection2
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : depthR,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : depthM,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
name : miePhaseParams, // x=(1+g^2), y=(-2*g)
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : sunIntensity,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : contrast,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : nightColor,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : ozone,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : multiScatParams, // xyz=MultiScatteringColor, w=HorizonGlow
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : sunHalo, // x=Size, y=Limb, z=Intensity, w=Enabled
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : shimmerControl, // x=Strength, y=Frequency, z=MaskHeight, w=PlanetRadius
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : cloudControl, // x=Coverage, y=Density, z=QuadraticConst, w=WindSpeed
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : cloudControl2, // x=EvolutionSpeed
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : sunIntensity2,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : sunHalo2, // x=Size, y=Limb, z=Intensity, w=Enabled
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : waterControl, // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
name : starControl, // x=Density, y=Enabled
|
||||
precision : high
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
eyeDirection
|
||||
],
|
||||
vertexDomain : device,
|
||||
depthWrite : false,
|
||||
shadingModel : unlit,
|
||||
culling: none
|
||||
}
|
||||
|
||||
vertex {
|
||||
void materialVertex(inout MaterialVertexInputs material) {
|
||||
// This code is taken from computeWorldPosition and assumes the vertex domain is 'device'.
|
||||
highp vec4 p = getPosition();
|
||||
// GL convention to inverted DX convention
|
||||
p.z = p.z * -0.5 + 0.5;
|
||||
highp vec4 worldPosition = getWorldFromClipMatrix() * p;
|
||||
// Getting the true world position would require dividing by w, but since this is a skybox
|
||||
// at inifinity, this results in very large numbers for material.eyeDirection.
|
||||
// Since the eyeDirection is only used as a direction vector in the fragment shader, we can
|
||||
// skip that step to improve precision.
|
||||
material.eyeDirection.xyz = worldPosition.xyz;
|
||||
}
|
||||
}
|
||||
|
||||
fragment {
|
||||
// ------------------------------------------------------------------------
|
||||
// Analytic Rayleigh and Mie Scattering (Physics Based)
|
||||
// Derived from:
|
||||
// - Hoffman & Preetham (2002): "Real-time Light-Atmosphere Interactions"
|
||||
// - Henyey & Greenstein (1941): "Diffuse radiation in the galaxy" (Mie Phase)
|
||||
// - Kasten & Young (1989): "Revised optical air mass tables" (Air Mass)
|
||||
// - "Simulated Sky" / Three.js (Sky.js): Empirical adjustments for aesthetics
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
#define PI 3.14159265359
|
||||
|
||||
void dummy() {} // squash editor syntax highlighting bugs
|
||||
|
||||
// Rayleigh Phase Function: Scattering distribution for small particles (air molecules)
|
||||
// Lord Rayleigh (1871)
|
||||
// Normalized to integrate to 4*PI (Boosting brightness by factor PI vs standard 1-normalization)
|
||||
highp float rayleighPhase(highp float cosTheta) {
|
||||
const highp float THREE_SIXTEENTH = (3.0 / 16.0);
|
||||
return THREE_SIXTEENTH * (1.0 + cosTheta * cosTheta);
|
||||
}
|
||||
|
||||
// Henyey-Greenstein Phase Function (Mie)
|
||||
// Henyey & Greenstein (1941)
|
||||
// Controls the forward scattering peak (sun halo) via anisotropy parameter 'g'
|
||||
// Optimized: params.x = (1 + g^2), params.y = (-2 * g)
|
||||
highp float hgPhase(highp float cosTheta, highp vec2 params) {
|
||||
const highp float ONE_FOURTH = (1.0 / 4.0);
|
||||
// Recover (1 - g^2) => 2.0 - (1 + g^2)
|
||||
highp float oneMinusG2 = 2.0 - params.x;
|
||||
highp float inverse = 1.0 / pow(params.x + params.y * cosTheta, 1.5);
|
||||
return ONE_FOURTH * (oneMinusG2 * inverse);
|
||||
}
|
||||
|
||||
// --- Noise Functions for Clouds ---
|
||||
highp float hash13(highp vec3 p3) {
|
||||
p3 = fract(p3 * .1031);
|
||||
p3 += dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
|
||||
highp float noise(highp vec3 p) {
|
||||
highp vec3 i = floor(p);
|
||||
highp vec3 f = fract(p);
|
||||
// Cubic Hermite Interpolation
|
||||
highp vec3 u = f*f*(3.0-2.0*f);
|
||||
return mix(mix(mix(hash13(i + vec3(0,0,0)), hash13(i + vec3(1,0,0)), u.x),
|
||||
mix(hash13(i + vec3(0,1,0)), hash13(i + vec3(1,1,0)), u.x), u.y),
|
||||
mix(mix(hash13(i + vec3(0,0,1)), hash13(i + vec3(1,0,1)), u.x),
|
||||
mix(hash13(i + vec3(0,1,1)), hash13(i + vec3(1,1,1)), u.x), u.y), u.z);
|
||||
}
|
||||
|
||||
// Fractal Brownian Motion (4 Octaves)
|
||||
highp float fbm(highp vec3 p) {
|
||||
highp float total = 0.0;
|
||||
highp float amplitude = 0.5;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
total += noise(p) * amplitude;
|
||||
p *= 2.02; // Lacunarity
|
||||
p += 100.0; // Shift to avoid artifacts
|
||||
amplitude *= 0.5; // Gain
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
highp float fbm(highp vec3 p, int octaves) {
|
||||
highp float total = 0.0;
|
||||
highp float amplitude = 0.5;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (i >= octaves) break;
|
||||
total += noise(p) * amplitude;
|
||||
p *= 2.02;
|
||||
p += 100.0;
|
||||
amplitude *= 0.5;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Ray-Sphere Intersection
|
||||
// Returns distance to intersection or -1.0 if none.
|
||||
// Re = Planet Radius.
|
||||
// C = Re^2 - (Re + height)^2 (Precalculated on CPU for precision).
|
||||
highp float raySphereIntersect(highp vec3 rd, highp float Re, highp float C) {
|
||||
// Ray Origin is (0, Re, 0) relative to Planet Center (0, 0, 0)
|
||||
// We solve |(0, Re, 0) + t*rd|^2 = Rm^2
|
||||
// |O + tD|^2 = R^2
|
||||
// t^2 + 2t(O.D) + (O^2 - R^2) = 0
|
||||
// a=1, b=2(O.D), c = O^2 - R^2 = C
|
||||
// Reduced quadratic: t = -b' +/- sqrt(b'^2 - c) where b' = O.D
|
||||
|
||||
highp float b = Re * rd.y; // dot(vec3(0, Re, 0), rd)
|
||||
highp float disc = b*b - C;
|
||||
|
||||
if (disc < 0.0) return -1.0;
|
||||
|
||||
// t = -b + sqrt(disc)
|
||||
return -b + sqrt(disc);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Atmospheric Heat Shimmer (Mirage)
|
||||
// ------------------------------------------------------------------------
|
||||
// Simulates heat convection turbulence near the horizon (e.g., hot desert road effect).
|
||||
//
|
||||
// PHYSICS:
|
||||
// Heat rising from the ground creates pockets of varying air density (refractive index).
|
||||
// This bends light rays, causing a visual "shimmer" or displacement.
|
||||
//
|
||||
// IMPLEMENTATION:
|
||||
// - Perturbs the view vector `V.y` using interleaved sine waves.
|
||||
// - Uses World Space `V` so the noise is stable under camera rotation.
|
||||
// - Masked to only affect the horizon line.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V Normalized World View Vector (modified in place).
|
||||
// @param strength Max vertical displacement amplitude. (e.g. 0.002).
|
||||
// @param freq Ripple frequency/density. (e.g. 20.0).
|
||||
// @param maskHeight Horizon mask height (0.0 to 1.0). (e.g. 0.1).
|
||||
// ------------------------------------------------------------------------
|
||||
float applyHeatShimmer(inout highp vec3 V, highp float strength, highp float freq, highp float maskHeight) {
|
||||
if (strength <= 0.0) return 0.0;
|
||||
|
||||
// Mask: Strongest at horizon (0.0), fades out by maskHeight.
|
||||
highp float mask = 1.0 - smoothstep(0.0, maskHeight, abs(V.y));
|
||||
|
||||
if (mask > 0.0) {
|
||||
// Use FBM for organic turbulence (rising heat waves)
|
||||
highp float time = getUserTime().x;
|
||||
// Animate upward (y) and slightly drift (x)
|
||||
highp vec3 p = vec3(V.x * freq, V.y * freq + time * 2.0, time * 0.5);
|
||||
|
||||
// We use a cheap noise or FBM. Since we have FBM:
|
||||
// Use fewer octaves for performance if possible, but 4 is fine.
|
||||
highp float distortion = fbm(p);
|
||||
|
||||
// Remap noise from [0, 1] to [-1, 1] for perturbation
|
||||
distortion = distortion * 2.0 - 1.0;
|
||||
|
||||
// Apply vertical perturbation
|
||||
V.y += distortion * strength * mask * 0.1;
|
||||
V = normalize(V);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Analytic Sky Model (Rayleigh + Mie + Ozone)
|
||||
// ------------------------------------------------------------------------
|
||||
// Computes the scattering and transmittance of the atmosphere along the view ray.
|
||||
//
|
||||
// PHYSICS:
|
||||
// - Rayleigh: Scattering by air molecules (Blue sky). High frequency (lambda^-4).
|
||||
// - Mie: Scattering by aerosols/dust (White haze). Low frequency (lambda^-1.3).
|
||||
// - Ozone: Absorption layer (Pink sunset). Absorbs green light.
|
||||
// - Optical Mass: Approximation of path length through spherical atmosphere.
|
||||
//
|
||||
// OUTPUTS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunIntensity Sun Illuminance (Lux).
|
||||
// @param depthR Rayleigh Optical Depth (Precalculated).
|
||||
// @param depthM Mie Optical Depth (Precalculated).
|
||||
// @param ozone Ozone Absorption (Precalculated).
|
||||
// @param msFactors Multi-Scattering factors (Rayleigh, Mie, Glow).
|
||||
// @param mieG Mie Phase Anisotropy.
|
||||
// @param outTransmittance Output: Atmospheric Transmittance (0..1) along V.
|
||||
// @return Output: In-Scattered Radiance (The sky color).
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getAtmosphere(highp vec3 V, highp vec3 L, highp float sunIntensity,
|
||||
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
|
||||
highp vec4 multiScatParams, highp vec2 mieParams,
|
||||
out highp vec3 outTransmittance) {
|
||||
|
||||
highp float cosTheta = dot(V, L);
|
||||
|
||||
// 1. Phase Functions
|
||||
// "Golden Hour" Hack (Three.js Sky.js):
|
||||
// Remapping cosTheta from [-1, 1] to [0, 1] breaks the symmetry of Rayleigh scattering.
|
||||
highp float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5);
|
||||
highp float mPhase = hgPhase(cosTheta, mieParams);
|
||||
|
||||
// 2. Optical Depth (Air Mass)
|
||||
// Kasten and Young (1989) - Relative Air Mass Model
|
||||
highp float zenithCos = clamp(V.y, 0.0, 1.0);
|
||||
highp float zenithAngle = acos(zenithCos);
|
||||
highp float zenithAngleDeg = zenithAngle * (180.0 / PI);
|
||||
highp float opticalMass = 1.0 / (zenithCos + 0.15 * pow(93.885 - zenithAngleDeg, -1.253));
|
||||
|
||||
// 3. Extinction & Transmittance
|
||||
highp vec3 totalExtinction = depthR + depthM + ozone;
|
||||
highp vec3 extinction = totalExtinction * opticalMass;
|
||||
outTransmittance = exp(-extinction);
|
||||
|
||||
// 4. In-Scattering
|
||||
// Approximate Multi-Scattering (Isotropic Fill) precomputed in C++.
|
||||
highp vec3 multiScattering = multiScatParams.xyz;
|
||||
|
||||
highp vec3 scatteringTerm = (depthR * rPhase) + (depthM * mPhase) + multiScattering;
|
||||
highp vec3 extinctionTerm = max(vec3(1e-6), totalExtinction);
|
||||
|
||||
// Equilibrium Radiance (Source Function)
|
||||
highp vec3 inScattering = sunIntensity * (scatteringTerm / extinctionTerm);
|
||||
|
||||
// Single-Scattering Integral: L = L_inf * (1 - exp(-opticalDepth))
|
||||
highp vec3 sunLight = inScattering * (1.0 - outTransmittance);
|
||||
|
||||
// 5. Horizon "Glow" Mix (Artistic Hack)
|
||||
// multiScatParams.w contains the Horizon Glow Strength
|
||||
// Uses Sun Elevation (L.y) to only activate during golden hour/twilight.
|
||||
mediump float horizonMix = saturate(pow(1.0 - L.y, 5.0)) * multiScatParams.w;
|
||||
highp vec3 horizonGlow = sqrt(inScattering * outTransmittance);
|
||||
sunLight *= mix(vec3(1.0), horizonGlow, horizonMix);
|
||||
|
||||
return sunLight;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Secondary Sun Scattering (Simplified)
|
||||
// ------------------------------------------------------------------------
|
||||
// Computes in-scattering for a second light source, reusing precomputed optical depths.
|
||||
// Skips multi-scattering (ambient) for performance, providing only direct beams/glow.
|
||||
//
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param depthR Rayleigh Optical Depth.
|
||||
// @param depthM Mie Optical Depth.
|
||||
// @param ozone Ozone Absorption.
|
||||
// @param mieParams Mie Phase Params.
|
||||
// @param transmittance Precomputed Atmospheric Transmittance.
|
||||
// @return In-Scattered Radiance.
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getSecondarySunScattering(highp vec3 V, highp vec3 L, highp float sunIntensity,
|
||||
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
|
||||
highp vec2 miePhaseParams, highp vec3 transmittance) {
|
||||
highp float cosTheta = dot(V, L);
|
||||
|
||||
// Phase Functions
|
||||
highp float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5);
|
||||
highp float mPhase = hgPhase(cosTheta, miePhaseParams);
|
||||
|
||||
// Scattering
|
||||
highp vec3 scatteringTerm = (depthR * rPhase) + (depthM * mPhase);
|
||||
highp vec3 totalExtinction = depthR + depthM + ozone;
|
||||
highp vec3 extinctionTerm = max(vec3(1e-6), totalExtinction);
|
||||
|
||||
highp vec3 inScattering = sunIntensity * (scatteringTerm / extinctionTerm);
|
||||
return inScattering * (1.0 - transmittance);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Physical Sun Disk
|
||||
// ------------------------------------------------------------------------
|
||||
// Renders the Solar Photosphere with limb darkening.
|
||||
//
|
||||
// PHYSICS:
|
||||
// - The sun is not a point light; it has an angular size (~0.53 deg).
|
||||
// - Limb Darkening: The sun is darker at the edges (limbs) because we see cooler outer layers.
|
||||
// - Drawn "Behind" the atmosphere, so it is attenuated by Transmittance.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunParams x=CosRadius, y=LimbDarkening, z=IntensityBoost, w=Enabled.
|
||||
// @param sunIntensity Peak Sun Illuminance (Lux).
|
||||
// @param transmittance Atmospheric Transmittance (0..1).
|
||||
// @return Radiance of the sun disk (if visible and enabled).
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getSunDisk(highp vec3 V, highp vec3 L, highp vec4 sunParams,
|
||||
highp float sunIntensity, highp vec3 transmittance) {
|
||||
|
||||
highp float sunCosRadius = sunParams.x;
|
||||
highp float limbDarkening = sunParams.y;
|
||||
highp float sunDiskIntensity = sunParams.z;
|
||||
bool sunEnabled = sunParams.w > 0.5;
|
||||
|
||||
highp float cosTheta = dot(V, L);
|
||||
|
||||
// Robust edge detection for small angles using (1 - cos)
|
||||
highp float dist = 1.0 - cosTheta;
|
||||
highp float diskRadius = max(1e-6, 1.0 - sunCosRadius);
|
||||
|
||||
// AA Edge: smoothstep from radius to radius+epsilon
|
||||
// We invert it because we want 1.0 inside (dist < radius)
|
||||
highp float sunDiskProfile = 1.0 - smoothstep(diskRadius, diskRadius + 0.00002, dist);
|
||||
|
||||
if (sunEnabled && sunDiskProfile > 0.0) {
|
||||
// Limb Darkening approximation: mu = sqrt(1 - (r/R)^2)
|
||||
// dist/diskRadius is approx (r/R)^2 for small angles
|
||||
highp float relativeDist = min(1.0, dist / diskRadius);
|
||||
highp float mu = sqrt(1.0 - relativeDist);
|
||||
|
||||
// Avoid pow(0, 0) which causes NaNs
|
||||
highp float limbFactor = (limbDarkening < 1e-4) ? 1.0 : pow(mu, limbDarkening);
|
||||
|
||||
// Direct Sun Light (Radiance)
|
||||
// SunIntensity * Transmittance -> Physical Sun Color
|
||||
// SunDiskIntensity -> Artistic Boost to punch through Mie halo
|
||||
return sunIntensity * transmittance * limbFactor * sunDiskIntensity * sunDiskProfile;
|
||||
}
|
||||
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Cirrus Clouds
|
||||
// ------------------------------------------------------------------------
|
||||
// Renders a thin layer of high-altitude clouds (Cirrus) using 3D Noise.
|
||||
//
|
||||
// IMPLEMENTATION:
|
||||
// - Modeled as a spherical shell at a specific altitude.
|
||||
// - Ray-Sphere intersection determines UV layout and distance.
|
||||
// - Animated using 3D FBM (Fractal Brownian Motion) for shape evolution + Wind drift.
|
||||
// - Lighting includes Silver Lining (HG Phase) and Atmospheric Extinction.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param background Current Sky Color (to be blended with).
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param control x=Coverage, y=Density, z=QuadraticConst(C), w=WindSpeed.
|
||||
// @param control2 x=EvolutionSpeed.
|
||||
// @param geometry w=PlanetRadius (Re).
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param transmittance Atmospheric Transmittance (Cloud Color Tint).
|
||||
// @return Sky color composed with clouds.
|
||||
// ------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Cirrus Clouds
|
||||
// ------------------------------------------------------------------------
|
||||
// Renders a thin layer of high-altitude clouds (Cirrus) using 3D Noise.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V .
|
||||
// @param L .
|
||||
// @param control .
|
||||
// @param control2 .
|
||||
// @param geometry .
|
||||
// @param sunIntensity .
|
||||
// @param transmittance.
|
||||
// @param outDensity Output: Cloud Density (0..1).
|
||||
// @return Cloud Lit Color (pre-multiplied by density? No, just lit color).
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getCloudLayer(highp vec3 V, highp vec3 L,
|
||||
highp vec4 control, highp vec4 control2, highp vec4 geometry,
|
||||
highp float sunIntensity, highp vec3 transmittance,
|
||||
out highp float outDensity) {
|
||||
|
||||
outDensity = 0.0;
|
||||
highp float cloudCoverage = control.x;
|
||||
|
||||
// Clip clouds below the horizon (Earth occlusion)
|
||||
// Simple check V.y > 0.0 is sufficient for skybox provided camera is near ground.
|
||||
if (cloudCoverage > 0.0 && V.y > 0.0) {
|
||||
highp float Re = geometry.w;
|
||||
highp float intersectC = control.z;
|
||||
highp float distToCloud = raySphereIntersect(V, Re, intersectC);
|
||||
|
||||
if (distToCloud > 0.0) {
|
||||
highp vec3 p = V * distToCloud;
|
||||
highp float speed = control.w;
|
||||
highp float morphSpeed = control2.x;
|
||||
highp float time = getUserTime().x;
|
||||
|
||||
// UV Mapping (Planar projected onto sphere cap is sufficient for skybox)
|
||||
// Scale factor 0.05 km^-1
|
||||
highp vec2 uv = (p.xz * 0.05) + vec2(time * speed * 2.0, 0.0);
|
||||
|
||||
// 3D Noise for Morphing
|
||||
highp float noiseVal = fbm(vec3(uv, time * morphSpeed));
|
||||
|
||||
// Remap noise based on coverage.
|
||||
// Coverage 0.5 -> threshold 0.5. Coverage 1.0 -> threshold 0.0.
|
||||
highp float threshold = 1.0 - cloudCoverage;
|
||||
highp float cloudDensity = smoothstep(threshold, threshold + 0.3, noiseVal);
|
||||
|
||||
if (cloudDensity > 0.0) {
|
||||
cloudDensity *= control.y; // Global Density Scalar
|
||||
cloudDensity = clamp(cloudDensity, 0.0, 1.0);
|
||||
outDensity = cloudDensity;
|
||||
|
||||
// Cloud Lighting
|
||||
// Silver Lining: Strong forward scattering (Fixed g=0.9 for clouds)
|
||||
highp float cosTheta = dot(V, L);
|
||||
// We need separate params for cloud silver lining (g=0.9).
|
||||
// 1 + 0.9^2 = 1.81. -2*0.9 = -1.8.
|
||||
|
||||
// Attenuation (Beer's Law)
|
||||
// Thick clouds block light.
|
||||
// 20.0 is an artistic extinction coefficient.
|
||||
highp float extinction = exp(-cloudDensity * 20.0);
|
||||
|
||||
highp float silver = hgPhase(cosTheta, vec2(1.81, -1.8)) * 40.0 * extinction;
|
||||
|
||||
// Ambient/Diffuse term.
|
||||
// We allow some ambient light to pass through even thick clouds (0.05 min)
|
||||
// so they don't look like black holes.
|
||||
highp float ambient = 0.1 + 0.4 * extinction;
|
||||
|
||||
// Diffuse term (Sun Color) + Silver Lining
|
||||
highp vec3 cloudLight = sunIntensity * transmittance * (ambient + silver);
|
||||
|
||||
// Mix based on density
|
||||
highp float volumetric = control2.y;
|
||||
highp float shading = 1.0;
|
||||
|
||||
if (volumetric > 0.5) {
|
||||
// Gradient Lighting (Fake Volumetric Bump)
|
||||
highp float gradX = dFdx(cloudDensity);
|
||||
highp float gradY = dFdy(cloudDensity);
|
||||
// Smaller Z = Steeper Bumps.
|
||||
// dFdx(density) is typically small (e.g. 0.001).
|
||||
// We want Normal to have significant X/Y component.
|
||||
highp vec3 N = normalize(vec3(-gradX, -gradY, 0.001));
|
||||
|
||||
// Screen Space Sun Direction
|
||||
highp vec3 sRight = normalize(dFdx(V));
|
||||
highp vec3 sUp = normalize(dFdy(V));
|
||||
highp vec3 L_screen = vec3(dot(L, sRight), dot(L, sUp), 0.5);
|
||||
L_screen = normalize(L_screen);
|
||||
|
||||
shading = dot(N, L_screen);
|
||||
// Increase contrast: Darker shadows
|
||||
// dot is [-1, 1]. Map to [0.3, 1.0]
|
||||
shading = mix(0.3, 1.0, shading * 0.5 + 0.5);
|
||||
|
||||
// Darken thick parts (Beer's Law approximation)
|
||||
// Aggressively darken center of clouds
|
||||
shading *= (1.0 - cloudDensity * 0.7);
|
||||
}
|
||||
|
||||
return cloudLight * shading;
|
||||
}
|
||||
}
|
||||
}
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Dynamic Tone Mapping
|
||||
// ------------------------------------------------------------------------
|
||||
// Applies a contrast curve that varies with sun elevation.
|
||||
//
|
||||
// PROBLEM:
|
||||
// Default linear/gamma tone mapping can make sunsets look washing out.
|
||||
// Real eyes accept much higher dynamic range at twilight.
|
||||
//
|
||||
// SOLUTION:
|
||||
// - Zenith (Noon): Linear gamma (Exponent 1.0). Physically accurate.
|
||||
// - Horizon (Sunset): High contrast (Exponent > 1.0). Crushes shadows, boosts color.
|
||||
//
|
||||
// @param color Input HDR color.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param contrast Maximum contrast exponent (at horizon). e.g. 1.5.
|
||||
// @return Tone mapped color.
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 applyDynamicToneMapping(highp vec3 color, highp vec3 L, highp float contrast) {
|
||||
float c = saturate(L.y);
|
||||
// Exponent blends from 'contrast' (at L.y=0) to 1.0 (at L.y=1)
|
||||
float exponent = mix(contrast, 1.0, sqrt(c));
|
||||
return pow(max(vec3(0.0), color), vec3(exponent));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Water Surface
|
||||
// ------------------------------------------------------------------------
|
||||
// Simulates an infinite ocean plane at y=0 using screen-space derivatives for normals.
|
||||
//
|
||||
// FEATURES:
|
||||
// - Projected grid for infinite surface.
|
||||
// - Screen-space wave normal reconstruction (no geometry required).
|
||||
// - Fresnel reflection of Atmosphere, Sun, and Clouds.
|
||||
// - Specular highlights (Blinn-Phong).
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param depthR Rayleigh Optical Depth.
|
||||
// @param depthM Mie Optical Depth.
|
||||
// @param ozone Ozone Absorption.
|
||||
// @param multiScatParams Multi-Scattering Params.
|
||||
// @param miePhaseParams Mie Phase Params.
|
||||
// @param sunHalo Sun Halo Params.
|
||||
// @param cloudControl Cloud Control Params.
|
||||
// @param cloudControl2 Cloud Evolution Params.
|
||||
// @param shimmerControl Shimmer Control (w component used as PlanetRadius for clouds).
|
||||
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
|
||||
// @return Water surface color.
|
||||
// ------------------------------------------------------------------------
|
||||
// 3D Noise for Stars
|
||||
highp float hash31(highp vec3 p) {
|
||||
p = fract(p * 0.1031);
|
||||
p += dot(p, p.yzx + 33.33);
|
||||
return fract((p.x + p.y) * p.z);
|
||||
}
|
||||
|
||||
highp float getStars(highp vec3 V, highp float density) {
|
||||
// Simple procedural stars
|
||||
// We use view vector direction to tile the sky
|
||||
// Higher frequency = smaller stars
|
||||
highp float frequency = 300.0;
|
||||
highp vec3 p = floor(V * frequency);
|
||||
|
||||
highp float h = hash31(p);
|
||||
|
||||
// Threshold for stars (very sparse)
|
||||
// param density: 0.0 (none) to 1.0 (max)
|
||||
// Default threshold was 0.995 (0.5% stars)
|
||||
// We map density 0.0 -> 1.0 threshold (no stars)
|
||||
// density 1.0 -> 0.990 threshold (1.0% stars)
|
||||
highp float threshold = 1.0 - (0.001 + density * 0.009);
|
||||
|
||||
highp float star = 0.0;
|
||||
if (h > threshold) {
|
||||
// Random brightness
|
||||
highp float brightness = (h - threshold) / (1.0 - threshold);
|
||||
star = brightness * 15.0; // Reduced from 50.0 to 15.0
|
||||
}
|
||||
return star;
|
||||
}
|
||||
|
||||
// New helper to handle Star Compositing (Fade, Rotation, Occlusion)
|
||||
highp vec3 getStarLayer(highp vec3 V, highp vec3 L, highp float cloudDensity, highp vec3 transmittance, highp vec2 starControl) {
|
||||
// starControl.x = Density, .y = Enabled
|
||||
if (starControl.y < 0.5) return vec3(0.0);
|
||||
|
||||
// 1. Fade by Sun Elevation
|
||||
// Start appearing sooner (when sun is still slightly up), but stay dim.
|
||||
// 0.10 (5.7 deg up) -> 0.0
|
||||
// -0.20 (11.5 deg down) -> 1.0
|
||||
highp float starFade = 1.0 - smoothstep(-0.20, 0.10, L.y);
|
||||
starFade *= starFade;
|
||||
|
||||
if (starFade <= 0.0) return vec3(0.0);
|
||||
|
||||
// 2. Rotate to break grid alignment
|
||||
highp vec3 rotV = vec3(
|
||||
dot(V, vec3(0.6, 0.8, 0.0)),
|
||||
dot(V, vec3(-0.8, 0.6, 0.0)),
|
||||
V.z
|
||||
);
|
||||
|
||||
highp float starVal = getStars(rotV, starControl.x);
|
||||
if (starVal <= 0.0) return vec3(0.0);
|
||||
|
||||
// 3. Cloud Occlusion (Aggressive)
|
||||
highp float cloudOcclusion = 1.0 - smoothstep(0.0, 1.0, pow(cloudDensity, 0.1));
|
||||
|
||||
return vec3(starVal) * transmittance * starFade * cloudOcclusion * 0.1;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Water Surface
|
||||
// ------------------------------------------------------------------------
|
||||
// Simulates an infinite ocean plane at y=0 using screen-space derivatives for normals.
|
||||
//
|
||||
// FEATURES:
|
||||
// - Projected grid for infinite surface.
|
||||
// - Screen-space wave normal reconstruction (no geometry required).
|
||||
// - Fresnel reflection of Atmosphere, Sun, and Clouds.
|
||||
// - Specular highlights (Blinn-Phong).
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param depthR Rayleigh Optical Depth.
|
||||
// @param depthM Mie Optical Depth.
|
||||
// @param ozone Ozone Absorption.
|
||||
// @param multiScatParams Multi-Scattering Params.
|
||||
// @param miePhaseParams Mie Phase Params.
|
||||
// @param sunHalo Sun Halo Params.
|
||||
// @param cloudControl Cloud Control Params.
|
||||
// @param cloudControl2 Cloud Evolution Params.
|
||||
// @param shimmerControl Shimmer Control (w component used as PlanetRadius for clouds).
|
||||
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
|
||||
// @return Water surface color.
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getWaterColor(highp vec3 V, highp vec3 L,
|
||||
highp float sunIntensity,
|
||||
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
|
||||
highp vec4 multiScatParams, highp vec2 miePhaseParams,
|
||||
highp vec4 sunHalo,
|
||||
highp vec4 cloudControl, highp vec4 cloudControl2,
|
||||
highp vec4 shimmerControl, highp vec4 waterControl) {
|
||||
|
||||
// Project to plane y=0
|
||||
highp float t = -10.0 / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
||||
highp vec2 uv = V.xz * t * 0.05;
|
||||
|
||||
highp float time = getUserTime().x;
|
||||
highp float speed = waterControl.y;
|
||||
uv += vec2(time * 0.5 * speed, time * 0.2 * speed);
|
||||
|
||||
// Wave Normal
|
||||
// Use screen-space derivatives to compute world-space normal perturbation
|
||||
// Wave Normal
|
||||
// Use screen-space derivatives to compute world-space normal perturbation
|
||||
int octaves = int(max(1.0, waterControl.w));
|
||||
highp float h = fbm(vec3(uv, time * 0.1 * speed), octaves);
|
||||
|
||||
// Reconstruct screen-space basis in world space
|
||||
// highp vec3 sRight = normalize(dFdx(V)); // Moved inside block
|
||||
// highp vec3 sUp = normalize(dFdy(V)); // Moved inside block
|
||||
|
||||
// Perturb normal based on height gradient
|
||||
// If h increases in screen-X direction, normal tilts against sRight.
|
||||
// Fade out perturbation near horizon (V.y -> 0) to reduce aliasing
|
||||
highp float horizonFade = smoothstep(0.0, 0.5, abs(V.y));
|
||||
highp float strength = waterControl.x;
|
||||
|
||||
highp vec3 N_perturb;
|
||||
|
||||
// Derivative Trick Toggle
|
||||
if (waterControl.z > 0.5) {
|
||||
// Screen-Space Derivatives (Fast, 1 tap)
|
||||
// Reconstruct screen-space basis in world space
|
||||
// If h increases in screen-X direction, normal tilts against sRight.
|
||||
highp vec3 sRight = normalize(dFdx(V));
|
||||
highp vec3 sUp = normalize(dFdy(V));
|
||||
N_perturb = (sRight * dFdx(h) + sUp * dFdy(h)) * strength * horizonFade;
|
||||
} else {
|
||||
// Finite Difference (Standard, 3 taps)
|
||||
// More expensive but analytically correct in world space (independent of view resolution/derivatives)
|
||||
float eps = 0.02; // Epsilon for gradient
|
||||
vec3 p = vec3(uv, time * 0.1 * speed);
|
||||
float hx = fbm(p + vec3(eps, 0.0, 0.0), octaves);
|
||||
float hy = fbm(p + vec3(0.0, eps, 0.0), octaves);
|
||||
|
||||
// Gradient
|
||||
float dx = (hx - h) / eps;
|
||||
float dy = (hy - h) / eps;
|
||||
|
||||
// Construct World Space Perturbation
|
||||
// Gradient (dx, dy) acts on XZ plane.
|
||||
// Normal = normalize(-dx, 1, -dy).
|
||||
// We want N_perturb to SUBTRACT from (0,1,0).
|
||||
// N_water = normalize(Up - Perturb).
|
||||
// So Perturb = (dx, 0, dy).
|
||||
// Note: Strength needs to be calibrated to match derivative trick roughly, or just raw.
|
||||
// Derivative trick Strength was ~50.0.
|
||||
// Here dx/dy are raw noise slopes.
|
||||
// Reduced to 0.002 to match visual range of derivative trick and prevent black artifacts.
|
||||
N_perturb = vec3(dx, 0.0, dy) * (strength * 0.002) * horizonFade;
|
||||
}
|
||||
|
||||
highp vec3 N_water = normalize(vec3(0.0, 1.0, 0.0) - N_perturb);
|
||||
|
||||
// Reflection
|
||||
highp vec3 R = reflect(V, N_water);
|
||||
|
||||
highp vec3 transRefl;
|
||||
highp vec3 reflection = getAtmosphere(R, L, sunIntensity,
|
||||
depthR, depthM,
|
||||
ozone, multiScatParams,
|
||||
miePhaseParams,
|
||||
transRefl);
|
||||
|
||||
// Clouds in reflection
|
||||
highp float reflCloudDensity;
|
||||
highp vec3 reflCloudLayer = getCloudLayer(R, L, materialParams.cloudControl, materialParams.cloudControl2,
|
||||
materialParams.shimmerControl, materialParams.sunIntensity, transRefl,
|
||||
reflCloudDensity);
|
||||
|
||||
// Add Stars to Reflection
|
||||
// Use helper with Reflection Vector and Reflection Cloud Density
|
||||
// Horizon Mask: Fade out star reflections that are deep in the water (high R.y)
|
||||
// Restricted to very close to horizon (0.0 to 0.1) as requested.
|
||||
highp float rHorizonMask = 1.0 - smoothstep(0.0, 0.1, R.y);
|
||||
|
||||
if (rHorizonMask > 0.0) {
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, transRefl, materialParams.starControl) * rHorizonMask;
|
||||
}
|
||||
|
||||
// Add Sun Disk to reflection (Occluded)
|
||||
highp float reflSunAccess = 1.0 - smoothstep(0.0, 0.7, reflCloudDensity * 1.5);
|
||||
reflection += getSunDisk(R, L, sunHalo, sunIntensity, transRefl) * reflSunAccess;
|
||||
|
||||
// Apply clouds to reflection
|
||||
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
||||
|
||||
// Fresnel
|
||||
highp float F0 = 0.02; // Water
|
||||
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
||||
highp float F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
|
||||
|
||||
highp vec3 deepColor = vec3(0.0, 0.005, 0.02); // Deep blue/black
|
||||
|
||||
highp vec3 waterColor = mix(deepColor, reflection, F);
|
||||
|
||||
return waterColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
|
||||
highp vec3 V = normalize(variable_eyeDirection.xyz);
|
||||
highp vec3 L = normalize(materialParams.sunDirection);
|
||||
|
||||
// 1. Heat Shimmer
|
||||
// Fade out as sun rises (Strongest at horizon, zero at 30 degrees up)
|
||||
highp float sunFade = 1.0 - smoothstep(0.0, 0.5, abs(L.y));
|
||||
highp float shimmerIntensity = applyHeatShimmer(V, materialParams.shimmerControl.x * sunFade,
|
||||
materialParams.shimmerControl.y,
|
||||
materialParams.shimmerControl.z);
|
||||
|
||||
// 2. Atmospheric Scattering
|
||||
highp vec3 transmittance;
|
||||
highp vec3 inScatter1 = getAtmosphere(V, L, materialParams.sunIntensity,
|
||||
materialParams.depthR, materialParams.depthM,
|
||||
materialParams.ozone, materialParams.multiScatParams,
|
||||
materialParams.miePhaseParams,
|
||||
transmittance);
|
||||
|
||||
|
||||
// Sun 2 (Optional)
|
||||
// We reuse the same Transmittance (view dependent) and Phase params.
|
||||
// We do NOT add extra Multi-Scattering (Ambient) for the second sun to save cost/complexity.
|
||||
// It contributes Direct In-Scattering (Beams/Glow) only.
|
||||
highp vec3 inScatter2 = vec3(0.0);
|
||||
if (materialParams.sunHalo2.w > 0.5) {
|
||||
highp vec3 L2 = normalize(materialParams.sunDirection2);
|
||||
inScatter2 = getSecondarySunScattering(V, L2,
|
||||
materialParams.sunIntensity2,
|
||||
materialParams.depthR,
|
||||
materialParams.depthM,
|
||||
materialParams.ozone,
|
||||
materialParams.miePhaseParams,
|
||||
transmittance);
|
||||
}
|
||||
|
||||
highp vec3 finalColor = inScatter1 + inScatter2;
|
||||
|
||||
// 5. Procedural Clouds
|
||||
highp float cloudDensity;
|
||||
highp vec3 cloudLayer = getCloudLayer(V, L,
|
||||
materialParams.cloudControl,
|
||||
materialParams.cloudControl2,
|
||||
materialParams.shimmerControl, // reusing w=PlanetRadius
|
||||
materialParams.sunIntensity,
|
||||
transmittance,
|
||||
cloudDensity);
|
||||
|
||||
// Add Stars
|
||||
// Stars are at infinity.
|
||||
// Use helper function.
|
||||
finalColor += getStarLayer(V, L, cloudDensity, transmittance, materialParams.starControl);
|
||||
|
||||
// 3. Sun Disks - Occluded by clouds
|
||||
// Sun Access is (1.0 - cloudDensity) but arguably non-linear for sharp disk
|
||||
highp float sunAccess = 1.0 - smoothstep(0.0, 0.7, cloudDensity * 1.5);
|
||||
|
||||
finalColor += getSunDisk(V, L, materialParams.sunHalo,
|
||||
materialParams.sunIntensity, transmittance) * sunAccess;
|
||||
|
||||
if (materialParams.sunHalo2.w > 0.5) {
|
||||
highp vec3 L2 = normalize(materialParams.sunDirection2);
|
||||
// Note: Ideally we should compute cloud density for L2 direction if clouds are 3D...
|
||||
// But here we use V direction clouds (view-based).
|
||||
// Since clouds are in front of everything, this is correct for view-based occlusion.
|
||||
finalColor += getSunDisk(V, L2, materialParams.sunHalo2,
|
||||
materialParams.sunIntensity2, transmittance) * sunAccess;
|
||||
}
|
||||
|
||||
// 4. Night Sky Offset
|
||||
finalColor += materialParams.nightColor;
|
||||
|
||||
// 5. Apply Clouds
|
||||
finalColor = mix(finalColor, cloudLayer, cloudDensity);
|
||||
|
||||
// 6. Dynamic Tone Mapping
|
||||
finalColor = applyDynamicToneMapping(finalColor, L, materialParams.contrast);
|
||||
|
||||
if (V.y < 0.0) {
|
||||
finalColor = getWaterColor(V, L, materialParams.sunIntensity,
|
||||
materialParams.depthR, materialParams.depthM,
|
||||
materialParams.ozone, materialParams.multiScatParams,
|
||||
materialParams.miePhaseParams,
|
||||
materialParams.sunHalo,
|
||||
materialParams.cloudControl, materialParams.cloudControl2,
|
||||
materialParams.shimmerControl,
|
||||
materialParams.waterControl);
|
||||
finalColor = applyDynamicToneMapping(finalColor, L, materialParams.contrast);
|
||||
}
|
||||
|
||||
material.baseColor = vec4(finalColor, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
docs_src/src_raw/wip/sky/styles.css
Normal file
9
docs_src/src_raw/wip/sky/styles.css
Normal file
@@ -0,0 +1,9 @@
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
canvas {
|
||||
touch-action: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -180,10 +180,6 @@ private:
|
||||
|
||||
int32_t setProducerThrottlingEnabled(EGLNativeWindowType nativeWindow, bool enabled) const;
|
||||
|
||||
struct InitializeJvmForPerformanceManagerIfNeeded {
|
||||
InitializeJvmForPerformanceManagerIfNeeded();
|
||||
};
|
||||
|
||||
struct ExternalTextureAndroid : public ExternalTexture {
|
||||
EGLImageKHR eglImage = EGL_NO_IMAGE;
|
||||
};
|
||||
@@ -191,7 +187,6 @@ private:
|
||||
int mOSVersion;
|
||||
ExternalStreamManagerAndroid& mExternalStreamManager;
|
||||
AndroidDetails& mAndroidDetails;
|
||||
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
|
||||
utils::PerformanceHintManager mPerformanceHintManager;
|
||||
utils::PerformanceHintManager::Session mPerformanceHintSession;
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
|
||||
@@ -1089,9 +1089,16 @@ UTILS_NOINLINE
|
||||
|
||||
/* static */ std::string_view process_countBits(OpenGLContext& context) noexcept {
|
||||
using namespace std::literals;
|
||||
// bitCount is available in GL 4.0 and GLES 3.1.
|
||||
if (context.isAtLeastGL<4, 0>() || context.isAtLeastGLES<3, 1>()) {
|
||||
return ""sv;
|
||||
}
|
||||
|
||||
// GLES 2.0 does not support bitwise operations or unsigned integers.
|
||||
if (context.isES2()) {
|
||||
return ""sv;
|
||||
}
|
||||
|
||||
return R"(
|
||||
// https://graphics.stanford.edu/%7Eseander/bithacks.html
|
||||
int bitCount(highp uint value) {
|
||||
|
||||
@@ -119,18 +119,6 @@ struct PlatformEGLAndroid::AndroidDetails {
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
|
||||
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need
|
||||
// to attach this thread to the JVM before we initialize PerformanceHintManager.
|
||||
// This should be done in PerformanceHintManager(), but libutils doesn't have access to
|
||||
// VirtualMachineEnv.
|
||||
if (PerformanceHintManager::isSupported()) {
|
||||
(void)VirtualMachineEnv::get().getEnvironment();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
|
||||
: mExternalStreamManager(ExternalStreamManagerAndroid::create()),
|
||||
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
|
||||
@@ -145,6 +133,7 @@ PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::terminate() noexcept {
|
||||
mPerformanceHintManager.terminate();
|
||||
mAndroidDetails.androidFrameCallback.terminate();
|
||||
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
|
||||
PlatformEGL::terminate();
|
||||
@@ -223,6 +212,14 @@ void PlatformEGLAndroid::preCommit() noexcept {
|
||||
Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
||||
const DriverConfig& driverConfig) {
|
||||
|
||||
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need
|
||||
// to attach this thread to the JVM before we initialize PerformanceHintManager.
|
||||
if (PerformanceHintManager::isSupported()) {
|
||||
(void)VirtualMachineEnv::get().getEnvironment();
|
||||
}
|
||||
|
||||
mPerformanceHintManager.init();
|
||||
|
||||
// the refresh rate default value doesn't matter, we change it later
|
||||
int32_t const tid = gettid();
|
||||
mPerformanceHintSession = PerformanceHintManager::Session{
|
||||
|
||||
@@ -2553,6 +2553,11 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
VkPipelineLayout const pipelineLayout = mPipelineLayoutCache.getLayout(vklayouts, program);
|
||||
if (pipelineLayout != mPipelineState.pipelineLayout) {
|
||||
bindPipelineImpl(bundle.pipelineState, pipelineLayout, bundle.descriptorSetMask);
|
||||
// Any push constants received while the layout was null were not written out. They were
|
||||
// added to a list of pending constants, so we'll flush them now.
|
||||
// Further push constant updates after the pipeline is bound will immediately set
|
||||
// the constants because the layout is non-null (see VulkanProgram::writePushConstant).
|
||||
program->flushPushConstants(pipelineLayout);
|
||||
}
|
||||
mPipelineState.bindInDraw.first = false;
|
||||
}
|
||||
|
||||
@@ -387,6 +387,15 @@ VulkanProgram::~VulkanProgram() {
|
||||
delete mInfo;
|
||||
}
|
||||
|
||||
void VulkanProgram::flushPushConstants(VkPipelineLayout layout) {
|
||||
// At this point, we really ought to have a VkPipelineLayout.
|
||||
assert_invariant(layout != VK_NULL_HANDLE);
|
||||
for (const auto& c : mQueuedPushConstants) {
|
||||
mInfo->pushConstantDescription.write(c.cmdbuf, layout, c.stage, c.index, c.value);
|
||||
}
|
||||
mQueuedPushConstants.clear();
|
||||
}
|
||||
|
||||
// Creates a special "default" render target (i.e. associated with the swap chain)
|
||||
VulkanRenderTarget::VulkanRenderTarget()
|
||||
: HwRenderTarget(0, 0),
|
||||
|
||||
@@ -262,6 +262,14 @@ struct VulkanProgram : public HwProgram, fvkmemory::Resource {
|
||||
VulkanProgram(VkDevice device, Program const& builder) noexcept;
|
||||
~VulkanProgram();
|
||||
|
||||
/**
|
||||
* Writes out any queued push constants using the provided VkPipelineLayout.
|
||||
*
|
||||
* @param layout The layout that is to be used along with these push constants,
|
||||
* in the next draw call.
|
||||
*/
|
||||
void flushPushConstants(VkPipelineLayout layout);
|
||||
|
||||
inline VkShaderModule getVertexShader() const {
|
||||
return mInfo->shaders[0];
|
||||
}
|
||||
@@ -278,7 +286,15 @@ struct VulkanProgram : public HwProgram, fvkmemory::Resource {
|
||||
|
||||
inline void writePushConstant(VkCommandBuffer cmdbuf, VkPipelineLayout layout,
|
||||
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
|
||||
mInfo->pushConstantDescription.write(cmdbuf, layout, stage, index, value);
|
||||
// It's possible that we don't have the layout yet. When external samplers are used, bindPipeline()
|
||||
// in VulkanDriver returns early, without binding a layout. If that happens, the layout is not
|
||||
// set until draw time. Any push constants that are written during that time should be saved for
|
||||
// later, and flushed when the layout is set.
|
||||
if (layout != VK_NULL_HANDLE) {
|
||||
mInfo->pushConstantDescription.write(cmdbuf, layout, stage, index, value);
|
||||
} else {
|
||||
mQueuedPushConstants.push_back({cmdbuf, stage, index, value});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle compute shaders.
|
||||
@@ -295,8 +311,16 @@ private:
|
||||
PushConstantDescription pushConstantDescription;
|
||||
};
|
||||
|
||||
struct PushConstantInfo {
|
||||
VkCommandBuffer cmdbuf;
|
||||
backend::ShaderStage stage;
|
||||
uint8_t index;
|
||||
backend::PushConstantVariant value;
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
VkDevice mDevice = VK_NULL_HANDLE;
|
||||
std::vector<PushConstantInfo> mQueuedPushConstants;
|
||||
};
|
||||
|
||||
// The render target bundles together a set of attachments, each of which can have one of the
|
||||
|
||||
@@ -1472,6 +1472,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
|
||||
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
|
||||
assert_invariant(srcTarget);
|
||||
|
||||
uint32_t srcMipLevel = 0;
|
||||
wgpu::Texture srcTexture{ nullptr };
|
||||
if (srcTarget->isDefaultRenderTarget()) {
|
||||
assert_invariant(mSwapChain);
|
||||
@@ -1482,6 +1483,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
|
||||
// TODO we are currently assuming the first attachment is the desired texture.
|
||||
if (colorAttachmentInfos[0].handle) {
|
||||
auto texture = handleCast<WebGPUTexture>(colorAttachmentInfos[0].handle);
|
||||
srcMipLevel = colorAttachmentInfos[0].level;
|
||||
if (texture) {
|
||||
srcTexture = texture->getTexture();
|
||||
}
|
||||
@@ -1494,15 +1496,20 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
|
||||
return;
|
||||
}
|
||||
const uint32_t srcWidth {srcTexture.GetWidth()};
|
||||
const uint32_t srcHeight{srcTexture.GetHeight()};
|
||||
const uint32_t srcHeight {srcTexture.GetHeight()};
|
||||
const uint32_t srcMipLevelCount {srcTexture.GetMipLevelCount()};
|
||||
|
||||
constexpr uint32_t minValidTextureSize = 1;
|
||||
const uint32_t mipLeveledSrcWidth = std::max(minValidTextureSize, srcWidth >> srcMipLevel);
|
||||
const uint32_t mipLeveledSrcHeight = std::max(minValidTextureSize, srcHeight >> srcMipLevel);
|
||||
|
||||
// Clamp read region to texture bounds
|
||||
if (UTILS_UNLIKELY(x >= srcWidth || y >= srcHeight)) {
|
||||
if (UTILS_UNLIKELY(x >= mipLeveledSrcWidth || y >= mipLeveledSrcHeight)) {
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
}
|
||||
auto actualWidth{ std::min(width, srcWidth - x) };
|
||||
auto actualHeight{ std::min(height, srcHeight - y)};
|
||||
auto actualWidth{ std::min(width, mipLeveledSrcWidth - x) };
|
||||
auto actualHeight{ std::min(height, mipLeveledSrcHeight - y)};
|
||||
if (UTILS_UNLIKELY(actualWidth == 0 || actualHeight == 0)) {
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
@@ -1526,6 +1533,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
|
||||
// If the source format is different from the destination (e.g. BGRA vs RGBA),
|
||||
// we need to perform a conversion using an intermediate blit.
|
||||
if (conversionNecessary(srcFormat, dstFormat, pixelBufferDescriptor.type)) {
|
||||
// TODO: check if the blit process here is correct when mipmap level > 0
|
||||
const wgpu::TextureDescriptor stagingDescriptor{
|
||||
.label = "readpixels_staging_texture",
|
||||
.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment,
|
||||
@@ -1536,7 +1544,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
|
||||
.depthOrArrayLayers = 1,
|
||||
},
|
||||
.format = dstFormat,
|
||||
.mipLevelCount = 1,
|
||||
.mipLevelCount = srcMipLevelCount,
|
||||
.sampleCount = srcTexture.GetSampleCount(),
|
||||
};
|
||||
stagingTexture = mDevice.CreateTexture(&stagingDescriptor);
|
||||
@@ -1586,12 +1594,11 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
|
||||
|
||||
// WebGPU's texture coordinates for copies are top-left, but Filament's y-coordinate is
|
||||
// bottom-left. We must flip the y-coordinate relative to the texture we are reading from.
|
||||
const uint32_t textureHeight{ textureToReadFrom.GetHeight() };
|
||||
const uint32_t flippedY{ textureHeight - readY - actualHeight };
|
||||
const uint32_t flippedY{ mipLeveledSrcHeight - readY - actualHeight };
|
||||
|
||||
const wgpu::TexelCopyTextureInfo source{
|
||||
.texture = textureToReadFrom, // Read from the original or intermediate texture
|
||||
.mipLevel = 0,
|
||||
.mipLevel = srcMipLevel,
|
||||
.origin = {.x = readX, .y = flippedY, .z = 0,},
|
||||
};
|
||||
const wgpu::TexelCopyBufferInfo destination{
|
||||
|
||||
@@ -83,8 +83,10 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
Handle<HwTexture> srcTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
|
||||
Handle<HwTexture> depthTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::DEPTH16, 1, 512, 512, 1, TextureUsage::DEPTH_ATTACHMENT));
|
||||
|
||||
Handle<HwTexture> depthTexture =
|
||||
addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::DEPTH16, 1,
|
||||
512, 512, 1, TextureUsage::DEPTH_ATTACHMENT | TextureUsage::SAMPLEABLE));
|
||||
|
||||
// Render into the bottom-left quarter of the texture.
|
||||
Viewport srcRect = {
|
||||
|
||||
@@ -940,31 +940,32 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
|
||||
|
||||
// Maximum space occupied in the CircularBuffer by a single `Command`. This must be
|
||||
// reevaluated when the inner loop below adds DriverApi commands or when we change the
|
||||
// CommandStream protocol. Currently, the maximum is 248 bytes.
|
||||
// CommandStream protocol. Currently, the maximum is 272 bytes.
|
||||
// The batch size is calculated by adding the size of all commands that can possibly be
|
||||
// emitted per draw call:
|
||||
constexpr size_t maxCommandSizeInBytes =
|
||||
sizeof(COMMAND_TYPE(scissor)) +
|
||||
sizeof(COMMAND_TYPE(bindDescriptorSet)) +
|
||||
sizeof(COMMAND_TYPE(bindDescriptorSet)) +
|
||||
sizeof(COMMAND_TYPE(bindDescriptorSet)) + backend::CustomCommand::align(sizeof(NoopCommand) + 4) +
|
||||
sizeof(COMMAND_TYPE(bindPipeline)) +
|
||||
sizeof(COMMAND_TYPE(bindRenderPrimitive)) +
|
||||
sizeof(COMMAND_TYPE(bindDescriptorSet)) + backend::CustomCommand::align(sizeof(NoopCommand) + 8) +
|
||||
sizeof(COMMAND_TYPE(setPushConstant)) +
|
||||
sizeof(COMMAND_TYPE(draw2));
|
||||
|
||||
// Add 20% margin for safety
|
||||
constexpr size_t safeCommandSizeInBytes = maxCommandSizeInBytes + (maxCommandSizeInBytes / 5);
|
||||
|
||||
// Number of Commands that can be issued and guaranteed to fit in the current
|
||||
// CircularBuffer allocation. In practice, we'll have tons of headroom especially if
|
||||
// skinning and morphing aren't used. With a 2 MiB buffer (the default) a batch is
|
||||
// 6553 commands (i.e. draw calls).
|
||||
size_t const batchCommandCount = capacity / maxCommandSizeInBytes;
|
||||
// CircularBuffer allocation. With a 2 MiB buffer (the default) a batch is
|
||||
// 6432 commands (i.e. draw calls).
|
||||
size_t const batchCommandCount = capacity / safeCommandSizeInBytes;
|
||||
while(first != last) {
|
||||
Command const* const batchLast = std::min(first + batchCommandCount, last);
|
||||
|
||||
// actual number of commands we need to write (can be smaller than batchCommandCount)
|
||||
size_t const commandCount = batchLast - first;
|
||||
size_t const commandSizeInBytes = commandCount * maxCommandSizeInBytes;
|
||||
size_t const commandSizeInBytes = commandCount * safeCommandSizeInBytes;
|
||||
|
||||
// check we have enough capacity to write these commandCount commands, if not,
|
||||
// request a new CircularBuffer allocation of `capacity` bytes.
|
||||
|
||||
@@ -522,8 +522,8 @@ fgviewer::FrameGraphInfo FrameGraph::getFrameGraphInfo(const char *viewName) con
|
||||
continue;
|
||||
writes.push_back(resourceNode->resourceHandle.index);
|
||||
}
|
||||
passes.emplace_back(utils::CString(pass->getName()),
|
||||
std::move(reads), std::move(writes));
|
||||
passes.emplace_back(pass->getId(), utils::CString(pass->getName()),
|
||||
std::move(reads), std::move(writes), pass->getRenderTargetInfo());
|
||||
}
|
||||
|
||||
std::unordered_map<fgviewer::ResourceId, fgviewer::FrameGraphInfo::Resource> resources;
|
||||
|
||||
@@ -64,6 +64,8 @@ public:
|
||||
|
||||
bool isInitialized() const noexcept { return index != UNINITIALIZED; }
|
||||
|
||||
uint16_t getIndex() const noexcept { return index; }
|
||||
|
||||
operator bool() const noexcept { return isInitialized(); }
|
||||
|
||||
void clear() noexcept { index = UNINITIALIZED; version = 0; }
|
||||
|
||||
@@ -39,6 +39,11 @@ struct FrameGraphRenderPass {
|
||||
FrameGraphId<FrameGraphTexture> stencil;
|
||||
|
||||
FrameGraphId<FrameGraphTexture>& operator[](size_t index) noexcept {
|
||||
return const_cast<FrameGraphId<FrameGraphTexture>&>(
|
||||
static_cast<const Attachments*>(this)->operator[](index));
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> const& operator[](size_t index) const noexcept {
|
||||
assert_invariant(index < ATTACHMENT_COUNT);
|
||||
if (index < backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT) {
|
||||
return color[index];
|
||||
|
||||
@@ -175,7 +175,8 @@ void RenderPassNode::resolve() noexcept {
|
||||
rt.backend.params.flags.discardStart |= target;
|
||||
}
|
||||
VirtualResource* pResource = mFrameGraph.getResource(rt.descriptor.attachments[i]);
|
||||
Resource<FrameGraphTexture>* pTextureResource = static_cast<Resource<FrameGraphTexture>*>(pResource);
|
||||
Resource<FrameGraphTexture>* pTextureResource =
|
||||
static_cast<Resource<FrameGraphTexture>*>(pResource);
|
||||
|
||||
pImportedRenderTarget = pImportedRenderTarget ?
|
||||
pImportedRenderTarget : pResource->asImportedRenderTarget();
|
||||
@@ -332,6 +333,52 @@ utils::CString RenderPassNode::graphvizify() const noexcept {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if FILAMENT_ENABLE_FGVIEWER
|
||||
using RenderTargetInfo = fgviewer::FrameGraphInfo::Pass::RenderTargetInfo;
|
||||
using AttachmentInfo = fgviewer::FrameGraphInfo::Pass::AttachmentInfo;
|
||||
std::vector<RenderTargetInfo> RenderPassNode::getRenderTargetInfo() const noexcept {
|
||||
using namespace backend;
|
||||
std::vector<RenderTargetInfo> info;
|
||||
info.reserve(mRenderTargetData.size());
|
||||
|
||||
for (auto const& rt: mRenderTargetData) {
|
||||
RenderTargetInfo rtInfo;
|
||||
|
||||
auto extractAttachmentInfo = [&](TargetBufferFlags flags,
|
||||
std::vector<AttachmentInfo>& list) {
|
||||
for (size_t i = 0; i < RenderPassData::ATTACHMENT_COUNT; ++i) {
|
||||
TargetBufferFlags mask = getTargetBufferFlagsAt(i);
|
||||
if (any(flags & mask)) {
|
||||
FrameGraphHandle handle = rt.descriptor.attachments[i];
|
||||
if (handle) {
|
||||
const char* name = nullptr;
|
||||
if (i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT) name = "color";
|
||||
else if (i == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT)
|
||||
name = "depth";
|
||||
else if (i == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 1)
|
||||
name = "stencil";
|
||||
|
||||
utils::CString slotName(name);
|
||||
if (i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT) {
|
||||
slotName += utils::to_string(i);
|
||||
}
|
||||
|
||||
list.push_back({ slotName, handle.getIndex() });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extractAttachmentInfo(rt.backend.params.flags.discardStart, rtInfo.discardStart);
|
||||
extractAttachmentInfo(rt.backend.params.flags.discardEnd, rtInfo.discardEnd);
|
||||
extractAttachmentInfo(rt.backend.params.flags.clear, rtInfo.clear);
|
||||
|
||||
info.push_back(std::move(rtInfo));
|
||||
}
|
||||
return info;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
PresentPassNode::PresentPassNode(FrameGraph& fg) noexcept
|
||||
|
||||
@@ -23,9 +23,12 @@
|
||||
#include "fg/FrameGraphRenderPass.h"
|
||||
|
||||
#include "backend/DriverApiForward.h"
|
||||
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#if FILAMENT_ENABLE_FGVIEWER
|
||||
#include <fgviewer/FrameGraphInfo.h>
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
@@ -60,6 +63,12 @@ public:
|
||||
virtual void resolve() noexcept = 0;
|
||||
utils::CString graphvizifyEdgeColor() const noexcept override;
|
||||
|
||||
#if FILAMENT_ENABLE_FGVIEWER
|
||||
virtual std::vector<fgviewer::FrameGraphInfo::Pass::RenderTargetInfo>
|
||||
getRenderTargetInfo() const noexcept {
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
Vector<VirtualResource*> devirtualize; // resources we need to create before executing
|
||||
Vector<VirtualResource*> destroy; // resources we need to destroy after executing
|
||||
};
|
||||
@@ -93,6 +102,7 @@ public:
|
||||
utils::StaticString name, FrameGraphRenderPass::Descriptor const& descriptor);
|
||||
|
||||
RenderPassData const* getRenderPassData(uint32_t id) const noexcept;
|
||||
size_t getRenderTargetCount() const noexcept { return mRenderTargetData.size(); }
|
||||
|
||||
private:
|
||||
// virtuals from DependencyGraph::Node
|
||||
@@ -100,6 +110,10 @@ private:
|
||||
utils::CString graphvizify() const noexcept override;
|
||||
void execute(FrameGraphResources const& resources, backend::DriverApi& driver) noexcept override;
|
||||
void resolve() noexcept override;
|
||||
#if FILAMENT_ENABLE_FGVIEWER
|
||||
std::vector<fgviewer::FrameGraphInfo::Pass::RenderTargetInfo>
|
||||
getRenderTargetInfo() const noexcept override;
|
||||
#endif
|
||||
|
||||
// constants
|
||||
const char* const mName = nullptr;
|
||||
|
||||
@@ -39,14 +39,29 @@ public:
|
||||
bool operator==(const FrameGraphInfo& rhs) const;
|
||||
|
||||
struct Pass {
|
||||
Pass(utils::CString name, std::vector<ResourceId> reads,
|
||||
std::vector<ResourceId> writes);
|
||||
struct AttachmentInfo {
|
||||
utils::CString name;
|
||||
ResourceId id;
|
||||
bool operator==(const AttachmentInfo& rhs) const;
|
||||
};
|
||||
|
||||
struct RenderTargetInfo {
|
||||
std::vector<AttachmentInfo> discardStart;
|
||||
std::vector<AttachmentInfo> discardEnd;
|
||||
std::vector<AttachmentInfo> clear;
|
||||
bool operator==(const RenderTargetInfo& rhs) const;
|
||||
};
|
||||
|
||||
Pass(uint32_t id, utils::CString name, std::vector<ResourceId> reads,
|
||||
std::vector<ResourceId> writes, std::vector<RenderTargetInfo> renderTargets = {});
|
||||
|
||||
bool operator==(const Pass& rhs) const;
|
||||
|
||||
uint32_t id;
|
||||
utils::CString name;
|
||||
std::vector<ResourceId> reads;
|
||||
std::vector<ResourceId> writes;
|
||||
std::vector<RenderTargetInfo> renderTargets;
|
||||
};
|
||||
|
||||
struct Resource {
|
||||
|
||||
@@ -37,13 +37,25 @@ bool FrameGraphInfo::operator==(const FrameGraphInfo& rhs) const {
|
||||
&& resources == rhs.resources;
|
||||
}
|
||||
|
||||
FrameGraphInfo::Pass::Pass(utils::CString name, std::vector<ResourceId> reads,
|
||||
std::vector<ResourceId> writes): name(std::move(name)),
|
||||
FrameGraphInfo::Pass::Pass(uint32_t id, utils::CString name, std::vector<ResourceId> reads,
|
||||
std::vector<ResourceId> writes, std::vector<RenderTargetInfo> renderTargets):
|
||||
id(id),
|
||||
name(std::move(name)),
|
||||
reads(std::move(reads)),
|
||||
writes(std::move(writes)) {}
|
||||
writes(std::move(writes)),
|
||||
renderTargets(std::move(renderTargets)) {}
|
||||
|
||||
bool FrameGraphInfo::Pass::operator==(const Pass& rhs) const {
|
||||
return name == rhs.name && reads == rhs.reads && writes == rhs.writes;
|
||||
return id == rhs.id && name == rhs.name && reads == rhs.reads && writes == rhs.writes &&
|
||||
renderTargets == rhs.renderTargets;
|
||||
}
|
||||
|
||||
bool FrameGraphInfo::Pass::AttachmentInfo::operator==(const AttachmentInfo& rhs) const {
|
||||
return name == rhs.name && id == rhs.id;
|
||||
}
|
||||
|
||||
bool FrameGraphInfo::Pass::RenderTargetInfo::operator==(const RenderTargetInfo& rhs) const {
|
||||
return discardStart == rhs.discardStart && discardEnd == rhs.discardEnd && clear == rhs.clear;
|
||||
}
|
||||
|
||||
FrameGraphInfo::Resource::Resource(ResourceId id, utils::CString name,
|
||||
|
||||
@@ -57,12 +57,49 @@ void writeResourceIds(std::ostream& os, const std::vector<ResourceId>& resources
|
||||
}
|
||||
}
|
||||
|
||||
void writeRenderTargetInfo(std::ostream& os,
|
||||
const std::vector<FrameGraphInfo::Pass::RenderTargetInfo>& renderTargets) {
|
||||
os << " \"renderTargets\": [\n";
|
||||
for (size_t i = 0; i < renderTargets.size(); ++i) {
|
||||
const auto& rt = renderTargets[i];
|
||||
os << " {\n";
|
||||
|
||||
auto writeAttachmentInfo =
|
||||
[&](const char* label,
|
||||
const std::vector<FrameGraphInfo::Pass::AttachmentInfo>& attachments) {
|
||||
os << " \"" << label << "\": [\n";
|
||||
for (size_t k = 0; k < attachments.size(); ++k) {
|
||||
const auto& att = attachments[k];
|
||||
os << " { \"name\": ";
|
||||
writeJSONString(os, att.name.c_str());
|
||||
os << ", \"id\": " << att.id << " }";
|
||||
if (k + 1 < attachments.size()) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " ]";
|
||||
};
|
||||
|
||||
writeAttachmentInfo("discardStart", rt.discardStart);
|
||||
os << ",\n";
|
||||
writeAttachmentInfo("discardEnd", rt.discardEnd);
|
||||
os << ",\n";
|
||||
writeAttachmentInfo("clear", rt.clear);
|
||||
os << "\n";
|
||||
|
||||
os << " }";
|
||||
if (i + 1 < renderTargets.size()) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " ]\n";
|
||||
}
|
||||
|
||||
void writePasses(std::ostream& os, const FrameGraphInfo& frameGraph) {
|
||||
os << " \"passes\": [\n";
|
||||
auto& passes = frameGraph.getPasses();
|
||||
for (size_t i = 0; i < passes.size(); ++i) {
|
||||
const FrameGraphInfo::Pass& pass = passes[i];
|
||||
os << " {\n";
|
||||
os << " \"id\": " << pass.id << ",\n";
|
||||
os << " \"name\": ";
|
||||
writeJSONString(os, pass.name.c_str());
|
||||
os << ",\n";
|
||||
@@ -75,7 +112,10 @@ void writePasses(std::ostream& os, const FrameGraphInfo& frameGraph) {
|
||||
const std::vector<ResourceId>& writes = pass.writes;
|
||||
os << " \"writes\": [";
|
||||
writeResourceIds(os, writes);
|
||||
os << "]\n";
|
||||
os << "],\n";
|
||||
|
||||
writeRenderTargetInfo(os, pass.renderTargets);
|
||||
os << "\n";
|
||||
|
||||
os << " }";
|
||||
if (i + 1 < passes.size()) os << ",";
|
||||
|
||||
@@ -28,11 +28,13 @@ const REGULAR_FONT_SIZE = 12;
|
||||
// Constants for color operations
|
||||
const READ_COLOR = '#aee1a6';
|
||||
const WRITE_COLOR = '#df8e8e';
|
||||
const NO_ACCESS_COLOR = '#d8dde754';
|
||||
const NO_ACCESS_COLOR = '#bcbcbc';
|
||||
const READ_WRITE_COLOR = '#ffeb99';
|
||||
const DEFAULT_COLOR = '#ffffff';
|
||||
const SUBRESOURCE_COLOR = '#d3d3d3';
|
||||
|
||||
const SELECTED_ROW_COLUMN_COLOR = "rgba(180,210,240,.35)";
|
||||
|
||||
// Constants for view mode toggle
|
||||
const VIEW_TOGGLE_INACTIVE_COLOR = '#292929';
|
||||
const VIEW_TOGGLE_UNSELECTED_COLOR = '#3f4cbe';
|
||||
@@ -200,6 +202,13 @@ class FrameGraphSidePanel extends LitElement {
|
||||
background-color: ${this.connected ? VIEW_TOGGLE_SELECTED_COLOR:VIEW_TOGGLE_INACTIVE_COLOR};
|
||||
font-weight: bold;
|
||||
}
|
||||
.resource-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.render-target-resource {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -208,6 +217,7 @@ class FrameGraphSidePanel extends LitElement {
|
||||
connected: {type: Boolean, attribute: 'connected'},
|
||||
selectedFrameGraph: {type: String, attribute: 'selected-framegraph'},
|
||||
selectedResourceId: {type: Number, attribute: 'selected-resource'},
|
||||
selectedPassIndex: {type: Number, attribute: 'selected-pass'},
|
||||
viewMode: {type: String, attribute: 'view-mode'},
|
||||
|
||||
database: {type: Object, state: true},
|
||||
@@ -222,6 +232,7 @@ class FrameGraphSidePanel extends LitElement {
|
||||
this.database = {};
|
||||
this.viewMode = VIEW_MODE_TABLE;
|
||||
this.selectedFrameGraph = '';
|
||||
this.selectedPassIndex = -1;
|
||||
}
|
||||
|
||||
updated(props) {
|
||||
@@ -274,6 +285,32 @@ class FrameGraphSidePanel extends LitElement {
|
||||
return null;
|
||||
}
|
||||
|
||||
_findCurrentPass() {
|
||||
if (!this.selectedFrameGraph || this.selectedPassIndex === -1 ||
|
||||
this.selectedPassIndex === undefined) {
|
||||
return null;
|
||||
}
|
||||
const passes = this.database[this.selectedFrameGraph]?.passes;
|
||||
return passes ? passes[this.selectedPassIndex] : null;
|
||||
}
|
||||
|
||||
_handleResourceLinkClick(ev, id) {
|
||||
ev.stopPropagation();
|
||||
this.dispatchEvent(new CustomEvent('select-resource', {
|
||||
detail: id,
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
}
|
||||
|
||||
_formatResourceId(id) {
|
||||
const resources = this.database[this.selectedFrameGraph]?.resources;
|
||||
const resource = resources ? resources[id] : null;
|
||||
const label = resource ? `${resource.name} [${id}]` : `[${id}]`;
|
||||
return html`<span class="resource-link" style="cursor: pointer;"
|
||||
@click="${(e) => this._handleResourceLinkClick(e, id)}">${label}</span>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
const renderFrameGraphs = (title) => {
|
||||
if (!this.framegraphs.length)
|
||||
@@ -316,10 +353,8 @@ class FrameGraphSidePanel extends LitElement {
|
||||
return html`
|
||||
<menu-section title="${title}">
|
||||
<div class="resource-content">
|
||||
<div><b>Name:</b> ${currentResource.name}</div>
|
||||
</div>
|
||||
<div class="resource-content">
|
||||
<div><b>ID:</b> ${currentResource.id}</div>
|
||||
<div><b>Name:</b> ${currentResource.name}
|
||||
<b>[ID=${currentResource.id}]</b></div>
|
||||
</div>
|
||||
${currentResource.properties.length > 0 ? html`
|
||||
<div class="resource-content">
|
||||
@@ -335,12 +370,76 @@ class FrameGraphSidePanel extends LitElement {
|
||||
`;
|
||||
};
|
||||
|
||||
const renderPassDetails = (title) => {
|
||||
const currentPass = this._findCurrentPass();
|
||||
if (!currentPass) return nothing;
|
||||
|
||||
const renderRenderTarget = (rt, index) => {
|
||||
const resourceFlags = {}; // id -> { name, flags: [] }
|
||||
|
||||
const processList = (list, flagName) => {
|
||||
if (!list) return;
|
||||
for (const att of list) {
|
||||
if (!resourceFlags[att.id]) {
|
||||
resourceFlags[att.id] = { name: att.name, id: att.id, flags: [] };
|
||||
}
|
||||
resourceFlags[att.id].flags.push(flagName);
|
||||
}
|
||||
};
|
||||
|
||||
processList(rt.discardStart, "Discard Start");
|
||||
processList(rt.discardEnd, "Discard End");
|
||||
processList(rt.clear, "Clear");
|
||||
|
||||
const resources = Object.values(resourceFlags);
|
||||
if (resources.length === 0) return nothing;
|
||||
|
||||
return html`
|
||||
<div class="resource-content render-target-resource"
|
||||
style="margin-top: 5px; border-top: 1px dashed #6f6f6f;">
|
||||
<div>(${index})</div>
|
||||
<ul style="margin:0;padding-inline-start:25px">
|
||||
${resources.map(res => html`
|
||||
<li>
|
||||
${this._formatResourceId(res.id)}: ${res.flags.join(", ")}
|
||||
</li>
|
||||
`)}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
const joinResources = (ids) => {
|
||||
return ids.map((id, i) => html`
|
||||
<li>${this._formatResourceId(id)}</li>`);
|
||||
};
|
||||
|
||||
return html`
|
||||
<menu-section title="${title}">
|
||||
<div class="resource-content">
|
||||
<div><b>Name:</b> ${currentPass.name} <b>[ID=${currentPass.id}]</b></div>
|
||||
</div>
|
||||
${currentPass.reads.length > 0 ? html`
|
||||
<div class="resource-content">
|
||||
<div><b>Reads:</b> </div>
|
||||
<ul style="margin: 4px 0;">
|
||||
${joinResources(currentPass.reads)}
|
||||
</ul>
|
||||
</div>` : nothing}
|
||||
${currentPass.renderTargets && currentPass.renderTargets.length > 0 ?
|
||||
html`<div><b>Writes (Render Targets):</b></div>
|
||||
${currentPass.renderTargets.map((rt, i) => renderRenderTarget(rt, i))}` : nothing}
|
||||
</menu-section>
|
||||
`;
|
||||
};
|
||||
|
||||
return html`
|
||||
<style>${this.dynamicStyle()}</style>
|
||||
<div class="container">
|
||||
<div class="title">fgviewer</div>
|
||||
${renderFrameGraphs("Views") ?? nothing}
|
||||
${renderResourceDetails("Resource Details") ?? nothing}
|
||||
${renderPassDetails("Pass Details") ?? nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -439,6 +538,7 @@ class FrameGraphTable extends LitElement {
|
||||
}
|
||||
.selected {
|
||||
text-decoration: underline;
|
||||
font-weight: bold;
|
||||
}
|
||||
.sticky-col {
|
||||
text-align: right;
|
||||
@@ -457,6 +557,7 @@ class FrameGraphTable extends LitElement {
|
||||
return {
|
||||
frameGraphData: {type: Object, state: true}, // Expecting a JSON frame graph structure
|
||||
selectedResourceId: {type: Number, attribute: 'selected-resource'},
|
||||
selectedPassIndex: {type: Number, attribute: 'selected-pass'},
|
||||
tooltipText: {type: String, state: true},
|
||||
tooltipVisible: {type: Boolean, state: true},
|
||||
tooltipX: {type: Number, state: true},
|
||||
@@ -468,6 +569,7 @@ class FrameGraphTable extends LitElement {
|
||||
super();
|
||||
this.frameGraphData = null;
|
||||
this.selectedResourceId = -1;
|
||||
this.selectedPassIndex = -1;
|
||||
this.expandedResourceSet = new Set();
|
||||
this.subresourceToParent = {};
|
||||
this._hoverTimeout = null;
|
||||
@@ -494,7 +596,7 @@ class FrameGraphTable extends LitElement {
|
||||
}
|
||||
|
||||
_getRowGridTemplateColumnsStyle(numColumns) {
|
||||
let out = '165px ';
|
||||
let out = '185px ';
|
||||
const oneCol = '35px ';
|
||||
for (let i = 0; i < numColumns; i++) {
|
||||
out += oneCol;
|
||||
@@ -517,15 +619,23 @@ class FrameGraphTable extends LitElement {
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
if (props.has('selectedResourceId')) {
|
||||
const parentId = this.subresourceToParent[this.selectedResourceId];
|
||||
if (parentId && !this.expandedResourceSet.has(parentId)) {
|
||||
this.expandedResourceSet.add(parentId);
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getCellColor(type) {
|
||||
_getCellColor(type, defaultColor) {
|
||||
return {
|
||||
[RESOURCE_USAGE_TYPE_READ]: READ_COLOR,
|
||||
[RESOURCE_USAGE_TYPE_WRITE]: WRITE_COLOR,
|
||||
[RESOURCE_USAGE_TYPE_NO_ACCESS]: NO_ACCESS_COLOR,
|
||||
[RESOURCE_USAGE_TYPE_READ_WRITE]: READ_WRITE_COLOR
|
||||
}[type] || DEFAULT_COLOR;
|
||||
}[type] || defaultColor;
|
||||
}
|
||||
|
||||
_toggleCollapse(resourceId) {
|
||||
@@ -549,9 +659,10 @@ class FrameGraphTable extends LitElement {
|
||||
_renderResourceUsage(allPasses, resourceIds, defaultColor, lastRow) {
|
||||
const passLen = allPasses.length;
|
||||
return allPasses.map((passData, index) => {
|
||||
const isSelectedPass = index === this.selectedPassIndex;
|
||||
const isRead = resourceIds.some(resourceId => passData?.reads.includes(resourceId));
|
||||
const isWrite = resourceIds.some(resourceId => passData?.writes.includes(resourceId));
|
||||
let type = null;
|
||||
let restype = null;
|
||||
|
||||
const hasUsed = (passData) => {
|
||||
return resourceIds.some(resourceId =>
|
||||
@@ -561,24 +672,25 @@ class FrameGraphTable extends LitElement {
|
||||
const hasBeenUsedBefore = allPasses.slice(0, index).some(hasUsed);
|
||||
const willBeUsedLater = allPasses.slice(index + 1).some(hasUsed);
|
||||
|
||||
if (isRead && isWrite) type = RESOURCE_USAGE_TYPE_READ_WRITE;
|
||||
else if (isRead) type = RESOURCE_USAGE_TYPE_READ;
|
||||
else if (isWrite) type = RESOURCE_USAGE_TYPE_WRITE;
|
||||
else if (hasBeenUsedBefore && willBeUsedLater) type = RESOURCE_USAGE_TYPE_NO_ACCESS;
|
||||
if (isRead && isWrite) restype = RESOURCE_USAGE_TYPE_READ_WRITE;
|
||||
else if (isRead) restype = RESOURCE_USAGE_TYPE_READ;
|
||||
else if (isWrite) restype = RESOURCE_USAGE_TYPE_WRITE;
|
||||
else if (hasBeenUsedBefore && willBeUsedLater) restype = RESOURCE_USAGE_TYPE_NO_ACCESS;
|
||||
|
||||
const lastPass = index == passLen - 1;
|
||||
const borderStyle = '1px solid #a5a5a5';
|
||||
const bgColor = this._getCellColor(type, defaultColor);
|
||||
const bgColor = isSelectedPass ? this._getCellColor(restype, SELECTED_ROW_COLUMN_COLOR)
|
||||
: this._getCellColor(restype, defaultColor);
|
||||
const style = `background-color:${bgColor};` +
|
||||
`border-left:${borderStyle};border-top:${borderStyle};` +
|
||||
(lastPass ? `border-right:${borderStyle};` : '') +
|
||||
(lastRow ? `border-bottom:${borderStyle};` : '');
|
||||
`border-left:${borderStyle};border-top:${borderStyle};` +
|
||||
(lastPass ? `border-right:${borderStyle};` : '') +
|
||||
(lastRow ? `border-bottom:${borderStyle};` : '');
|
||||
|
||||
let tooltip = '';
|
||||
if (type === RESOURCE_USAGE_TYPE_READ) tooltip = 'Read';
|
||||
else if (type === RESOURCE_USAGE_TYPE_WRITE) tooltip = 'Write';
|
||||
else if (type === RESOURCE_USAGE_TYPE_READ_WRITE) tooltip = 'Read-Write';
|
||||
else if (type === RESOURCE_USAGE_TYPE_NO_ACCESS) tooltip = 'No Access';
|
||||
if (restype === RESOURCE_USAGE_TYPE_READ) tooltip = 'Read';
|
||||
else if (restype === RESOURCE_USAGE_TYPE_WRITE) tooltip = 'Write';
|
||||
else if (restype === RESOURCE_USAGE_TYPE_READ_WRITE) tooltip = 'Read-Write';
|
||||
else if (restype === RESOURCE_USAGE_TYPE_NO_ACCESS) tooltip = 'No Access';
|
||||
|
||||
return html`
|
||||
<div class="grid-cell" style="${style}"
|
||||
@@ -635,9 +747,11 @@ class FrameGraphTable extends LitElement {
|
||||
groupColorStyle = "border-right:5px solid " + COLORS[colorIndex];
|
||||
}
|
||||
|
||||
const isSelectedResource = resource.id === this.selectedResourceId;
|
||||
const onClickResource = () => this._handleResourceClick(resource.id);
|
||||
const selectedStyle = resource.id === this.selectedResourceId ? "selected" : "";
|
||||
const selectedStyle = isSelectedResource ? "selected" : "";
|
||||
const rowStyle = this._getRowGridTemplateColumnsStyle(allPasses.length);
|
||||
const defaultColor = isSelectedResource ? SELECTED_ROW_COLUMN_COLOR : DEFAULT_COLOR;
|
||||
return html`
|
||||
<div class="grid-row" id="resource-${resource.id}" style="${rowStyle}">
|
||||
<div class="grid-cell sticky-col resource ${selectedStyle}" style="${groupColorStyle}"
|
||||
@@ -652,11 +766,19 @@ class FrameGraphTable extends LitElement {
|
||||
${resource.name}
|
||||
${hasSubresources && !isExpanded ? html`(${subresourceIds.length})` : nothing}
|
||||
</div>
|
||||
${this._renderResourceUsage(allPasses, resourceIds, DEFAULT_COLOR, lastRow)}
|
||||
${this._renderResourceUsage(allPasses, resourceIds, defaultColor, lastRow)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_handlePassClick(index) {
|
||||
this.dispatchEvent(new CustomEvent('select-pass', {
|
||||
detail: index,
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.frameGraphData?.passes || !this.frameGraphData?.resources)
|
||||
return nothing;
|
||||
@@ -670,8 +792,14 @@ class FrameGraphTable extends LitElement {
|
||||
<div class="grid-row" style="${rowStyle}">
|
||||
<div class="grid-cell" style="${colStyle}">
|
||||
</div>
|
||||
${allPasses.map(pass => html`<div class="grid-cell" style="border:1px solid rgba(0,0,0,0)">
|
||||
<div class="pass-cell">${pass.name}</div></div>`)}
|
||||
${allPasses.map((pass, index) => {
|
||||
const isSelected = index === this.selectedPassIndex;
|
||||
const gclass = isSelected ? "grid-cell selected" : "grid-cell";
|
||||
const pclass = isSelected ? "pass-cell selected" : "pass-cell";
|
||||
return html`<div class="${gclass}" style="border:1px solid rgba(0,0,0,0); cursor: pointer;"
|
||||
@click="${() => this._handlePassClick(index)}">
|
||||
<div class="${pclass}">${pass.name}</div></div>`;
|
||||
})}
|
||||
</div>
|
||||
${this._renderResourceRows(resources, allPasses)}
|
||||
</div>
|
||||
@@ -819,6 +947,7 @@ class FrameGraphViewer extends LitElement {
|
||||
this.database = {};
|
||||
this.selectedFrameGraph = null;
|
||||
this.selectedResourceId = -1;
|
||||
this.selectedPassIndex = -1;
|
||||
this.viewMode = VIEW_MODE_TABLE;
|
||||
this.init();
|
||||
|
||||
@@ -830,7 +959,13 @@ class FrameGraphViewer extends LitElement {
|
||||
|
||||
this.addEventListener('select-resource',
|
||||
(ev) => {
|
||||
this.selectedResourceId = ev.detail;
|
||||
this.selectedResourceId = this.selectedResourceId === ev.detail ? -1 : ev.detail;
|
||||
}
|
||||
);
|
||||
|
||||
this.addEventListener('select-pass',
|
||||
(ev) => {
|
||||
this.selectedPassIndex = this.selectedPassIndex === ev.detail ? -1 : ev.detail;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -847,6 +982,7 @@ class FrameGraphViewer extends LitElement {
|
||||
database: {type: Object, state: true},
|
||||
selectedFrameGraph: {type: String, state: true},
|
||||
selectedResourceId: {type: Number, state: true},
|
||||
selectedPassIndex: {type: Number, state: true},
|
||||
viewMode: {type: String, state: true},
|
||||
}
|
||||
}
|
||||
@@ -867,6 +1003,7 @@ class FrameGraphViewer extends LitElement {
|
||||
?connected="${this.connected}"
|
||||
selected-framegraph="${this.selectedFrameGraph}"
|
||||
selected-resource="${this.selectedResourceId}"
|
||||
selected-pass="${this.selectedPassIndex}"
|
||||
view-mode="${this.viewMode}">
|
||||
</framegraph-sidepanel>
|
||||
|
||||
@@ -874,7 +1011,8 @@ class FrameGraphViewer extends LitElement {
|
||||
style="display: ${this.viewMode === VIEW_MODE_TABLE ? 'block' : 'none'};"
|
||||
?connected="${this.connected}"
|
||||
selected-framegraph="${this.selectedFrameGraph}"
|
||||
selected-resource="${this.selectedResourceId}">
|
||||
selected-resource="${this.selectedResourceId}"
|
||||
selected-pass="${this.selectedPassIndex}">
|
||||
</framegraph-table>
|
||||
|
||||
<graphviz-view id="graphviz"
|
||||
|
||||
@@ -55,10 +55,15 @@ public:
|
||||
int reportActualWorkDuration(int64_t actualDurationNanos) noexcept;
|
||||
};
|
||||
|
||||
// caveat: This must be called on a Java thread
|
||||
PerformanceHintManager() noexcept;
|
||||
~PerformanceHintManager() noexcept;
|
||||
|
||||
// caveat: This must be called on a Java thread
|
||||
void init();
|
||||
|
||||
// caveat: This must be called on the same thread as init
|
||||
void terminate();
|
||||
|
||||
// Whether PerformanceHintManager APIs are supported (which doesn't mean PerformanceHintManager
|
||||
// itself is, use isValid()).
|
||||
static bool isSupported() noexcept;
|
||||
|
||||
@@ -39,13 +39,18 @@ struct PerformanceHintManager::SessionDetails {
|
||||
APerformanceHintSession* mSession = nullptr;
|
||||
};
|
||||
|
||||
PerformanceHintManager::PerformanceHintManager() noexcept {
|
||||
PerformanceHintManager::PerformanceHintManager() noexcept = default;
|
||||
|
||||
PerformanceHintManager::~PerformanceHintManager() noexcept = default;
|
||||
|
||||
void PerformanceHintManager::init() {
|
||||
if (__builtin_available(android __ANDROID_API_T__, *)) {
|
||||
mImpl->mManager = APerformanceHint_getManager();
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceHintManager::~PerformanceHintManager() noexcept = default;
|
||||
void PerformanceHintManager::terminate() {
|
||||
}
|
||||
|
||||
bool PerformanceHintManager::isSupported() noexcept {
|
||||
if (__builtin_available(android __ANDROID_API_T__, *)) {
|
||||
@@ -111,7 +116,7 @@ int PerformanceHintManager::Session::reportActualWorkDuration(
|
||||
int64_t actualDurationNanos) noexcept {
|
||||
if (__builtin_available(android __ANDROID_API_T__, *)) {
|
||||
if (UTILS_LIKELY(mImpl->mSession)) {
|
||||
return APerformanceHint_updateTargetWorkDuration(mImpl->mSession, actualDurationNanos);
|
||||
return APerformanceHint_reportActualWorkDuration(mImpl->mSession, actualDurationNanos);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
||||
Reference in New Issue
Block a user