<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Rendering on zhangdoa</title><link>https://zhangdoa.pages.dev/categories/rendering/</link><description>Recent Posts in Rendering on zhangdoa</description><generator>Hugo 0.162.0</generator><language>en</language><managingEditor>zhangandhang@gmail.com (Hang Zhang)</managingEditor><webMaster>zhangandhang@gmail.com (Hang Zhang)</webMaster><lastBuildDate>Wed, 27 May 2026 23:05:47 +0200</lastBuildDate><atom:link href="https://zhangdoa.pages.dev/categories/rendering/index.xml" rel="self" type="application/rss+xml"/><item><title>Rendering analysis - Cyberpunk 2077</title><link>https://zhangdoa.pages.dev/rendering-analysis-cyberpunk-2077/</link><pubDate>Sat, 12 Dec 2020 10:30:00 +0000</pubDate><author>zhangandhang@gmail.com (Hang Zhang)</author><dc:creator>Hang Zhang</dc:creator><guid>https://zhangdoa.pages.dev/rendering-analysis-cyberpunk-2077/</guid><description>&lt;p&gt;(&lt;strong&gt;Spoilers-free post!&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;Quite a lot of people I know inside or outside this industry are playing the long-waited &amp;ldquo;gargantuan maelstrom&amp;rdquo; now. After a couple few hours immersed around the stunning view (and bugs of course) in the Night City, I was wondering how one frame is rendered in Cyberpunk 2077 (every graphic programmer&amp;rsquo;s instincts!). So I opened RenderDoc and PIX, luckily REDengine didn&amp;rsquo;t behave unfriendly like some other triple-that (yes Watch Dogs 2 I mean you), I got a frame capture without any problems. (Disclaimer: I&amp;rsquo;m pretty sure later others like &lt;a href="https://alain.xyz/blog" target="_blank" rel="external noopener noreferrer nofollow"&gt;Alain Galvan&lt;/a&gt;, &lt;a href="https://aschrein.github.io" target="_blank" rel="external noopener noreferrer nofollow"&gt;Anton Schreiner&lt;/a&gt;, &lt;a href="http://www.adriancourreges.com/" target="_blank" rel="external noopener noreferrer nofollow"&gt;Adrian Courrèges&lt;/a&gt; or even guys from CDPR would bring us a more detailed and accurate demonstration about how things works, I&amp;rsquo;m just chilling out around and welcome for any discussions and corrections! &lt;i class="fas fa-coffee"&gt;&lt;/i&gt;)&lt;/p&gt;</description><content:encoded>&lt;p&gt;(&lt;strong&gt;Spoilers-free post!&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;Quite a lot of people I know inside or outside this industry are playing the long-waited &amp;ldquo;gargantuan maelstrom&amp;rdquo; now. After a couple few hours immersed around the stunning view (and bugs of course) in the Night City, I was wondering how one frame is rendered in Cyberpunk 2077 (every graphic programmer&amp;rsquo;s instincts!). So I opened RenderDoc and PIX, luckily REDengine didn&amp;rsquo;t behave unfriendly like some other triple-that (yes Watch Dogs 2 I mean you), I got a frame capture without any problems. (Disclaimer: I&amp;rsquo;m pretty sure later others like &lt;a href="https://alain.xyz/blog" target="_blank" rel="external noopener noreferrer nofollow"&gt;Alain Galvan&lt;/a&gt;, &lt;a href="https://aschrein.github.io" target="_blank" rel="external noopener noreferrer nofollow"&gt;Anton Schreiner&lt;/a&gt;, &lt;a href="http://www.adriancourreges.com/" target="_blank" rel="external noopener noreferrer nofollow"&gt;Adrian Courrèges&lt;/a&gt; or even guys from CDPR would bring us a more detailed and accurate demonstration about how things works, I&amp;rsquo;m just chilling out around and welcome for any discussions and corrections! &lt;i class="fas fa-coffee"&gt;&lt;/i&gt;)&lt;/p&gt;
&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m running the game in Ultra configs in 1080p without ray-tracing and DLSS enabled, for the sake of my years &amp;ldquo;old&amp;rdquo; GTX1070&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Two captured final results, one is around the main character&amp;rsquo;s apartment at day (let&amp;rsquo;s name it main street) and another one is around some river bank at night (Nobody dislikes eye candy!):
&lt;img src="https://zhangdoa.pages.dev/attachments/99_FinalResult_MainStreet.png" alt="99_FinalResult_MainStreet"&gt;
&lt;img src="https://zhangdoa.pages.dev/attachments/99_FinalResult_RiverBank.png" alt="99_FinalResult_RiverBank"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Some pictures&amp;rsquo; color is manually clamped to easier visualize&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It&amp;rsquo;s a DirectX 12-only game on Windows (if they started the development around 2013 or slightly earlier, I&amp;rsquo;m curious that how much effort it costs to upgrade the engine)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are 2 Descriptor Heaps allocated just, one for half million CBV-SRV-UAVs and another for 2k~ Samplers &lt;i class="fas fa-thumbs-up"&gt;&lt;/i&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/00_DescriptorHeap_CSU.png" alt="00_DescriptorHeap_CSU"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/00_DescriptorHeap_Sampler.png" alt="00_DescriptorHeap_Sampler"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;There is one Graphic queue, one Compute queue, and one Copy queue, the GPU work balance is quite even &lt;i class="fas fa-thumbs-up"&gt;&lt;/i&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ID3D12GraphicsCommandList::CopyBufferRegion&lt;/code&gt; is frequently called between passes to copy RT results&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Quite a lot &lt;code&gt;D3D12_RESOURCE_FLAGS::ALLOW_UNORDERED_ACCESS&lt;/code&gt; removal recommendation was thrown out by PIX, not sure if it&amp;rsquo;s really just my capture occasionally didn&amp;rsquo;t use some resources or, they do need some optimizations (I had experiences that unordered access was quite hurting sometimes) &lt;i class="fas fa-question"&gt;&lt;/i&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Frame Timeline: Intensive at some early geometry stages, then deferred works kick in
&lt;img src="https://zhangdoa.pages.dev/attachments/00_FrameTimeline.png" alt="00_FrameTimeline"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pre-geometry-passes"&gt;Pre-geometry passes&lt;/h2&gt;
&lt;h3 id="billboard-and-gui"&gt;Billboard and GUI&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The entire frame starts by preparing the resources for the in-game billboards and screens&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;First few copy operations executed on 3 textures: The destination textures&amp;rsquo; format is &lt;code&gt;DXGI_FORMAT_R8_UNORM&lt;/code&gt;, the first is 480x272 and another two are at half size, the source texture should be a mega texture which is the destination of lots later copy operations. (Question: what&amp;rsquo;s the purpose for these copies? In the night scene it doesn&amp;rsquo;t have these operations, is it related to streaming?)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;One of the three very first copied textures:
&lt;img src="https://zhangdoa.pages.dev/attachments/01_Degree_0.png" alt="01_Degree_0"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next pass they are used as the shader input:
&lt;img src="https://zhangdoa.pages.dev/attachments/01_Full.png" alt="01_Full"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Billboards are drawn to one texture where all mips tightly packaged together&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Billboards:
&lt;img src="https://zhangdoa.pages.dev/attachments/02_Billboard.png" alt="02_Billboard"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then the elements for the cyberish-GUI are drawn and also copied to the mega texture&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Part of the GUI elements:
&lt;img src="https://zhangdoa.pages.dev/attachments/02_GUI.png" alt="02_GUI"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="sky-visibilitytop-view-shadowmini-map"&gt;Sky visibility/top-view shadow/mini map&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The RT of the next pass is a single &lt;code&gt;D16_UNORM&lt;/code&gt; 2k texture, around 400~ instanced draw calls executed in the captured scene (not sure how this RT later used)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Shadowmap from top:
&lt;img src="https://zhangdoa.pages.dev/attachments/03_Shadowmap_Top.png" alt="03_Shadowmap_Top"&gt;&lt;/p&gt;
&lt;h3 id="unknown-compute-pass-01"&gt;Unknown compute pass 01&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;There are 2 dispatches over a 64x64x64 &lt;code&gt;R16G16B16A16_FLOAT&lt;/code&gt; 3D texture in this pass (looks like voxelized normal/position?). The thread group count at the first time is 2x2x64, the second time it&amp;rsquo;s 16x16x16 (the RT is used later multiple times in terrain pass, ocean wave pass, depth-pre pass, and base material pass, definitely something crucial for geometry information)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Some slices of the 3D Normal:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;04_3DNormal_Slice&amp;quot;: /attachments/04_3DNormal_Slice.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="unknown-compute-pass-02"&gt;Unknown compute pass 02&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;3 dispatches made up by 63x1x1, 3x1x1 then again 63x1x1 thread groups (in the night scene capture it&amp;rsquo;s 38x1x1, 3x1x1 and 38x1x1), couple-few &lt;code&gt;ByteAddressBuffer&lt;/code&gt; and &lt;code&gt;StructuredBuffer&lt;/code&gt; are bound as UAV (all of them are used later in the base material pass)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="terrain"&gt;Terrain&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1 &lt;code&gt;R8G8B8A8_UNORM&lt;/code&gt; 2 slices 2D texture array and 1 &lt;code&gt;R16_UINT&lt;/code&gt; 2D texture are drawn in 1k resolution (looks like they are chunks of terrain)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2 draw calls and RTs:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;05_Terrain&amp;quot;: /attachments/05_Terrain.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="unknown-color-pass-01"&gt;Unknown color pass 01&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2 draw calls issued (only at the main street capture) but nothing is drawn due to the clip (and the meshes are barely comprehensible, looks like they are 2 levels of a LOD-ed mesh), RT is 1 256x256 &lt;code&gt;R16_UINT&lt;/code&gt; 2D texture&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ocean-wave"&gt;Ocean wave&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The top-view ocean map mask and a generated wave noise texture are bound as input, RT format also changes to &lt;code&gt;R16_UNORM&lt;/code&gt;, no tessellation shader stage, some regular grid plate meshes are used to directly draw different chunks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ocean wave noise in &lt;code&gt;R16G16B16A16_FLOAT&lt;/code&gt; and mask in &lt;code&gt;BC7_SRGB&lt;/code&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;06_OceanMask&amp;quot;: /attachments/06_OceanMask.mp4&lt;/code&gt;:&lt;/p&gt;
&lt;h2 id="geometry-passes"&gt;Geometry passes&lt;/h2&gt;
&lt;h3 id="depth-pre-pass"&gt;Depth-pre pass&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The RT is in &lt;code&gt;D32_S8_TYPELESS&lt;/code&gt; format, surely it would help to optimize later pixel-heavy works&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Depth-pre pass result:
&lt;img src="https://zhangdoa.pages.dev/attachments/07_Depth_Pre.png" alt="07_Depth_Pre"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="base-material-passes"&gt;Base material passes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;All vertex buffers uploaded to GPU memory are packed as SOA:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// VB0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;nshort4&lt;/span&gt; &lt;span class="n"&gt;POSITION0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// VB1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;half2&lt;/span&gt; &lt;span class="n"&gt;TEXCOORD0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// VB2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;xint&lt;/span&gt; &lt;span class="n"&gt;NORMAL0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;xint&lt;/span&gt; &lt;span class="n"&gt;TANGENT0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// VB3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;unbyte4&lt;/span&gt; &lt;span class="n"&gt;COLOR0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;half2&lt;/span&gt; &lt;span class="n"&gt;TEXCOORD1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// VB7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;INSTANCE_TRANSFORM0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;INSTANCE_TRANSFORM1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;INSTANCE_TRANSFORM2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In pixel shader sometimes an &lt;code&gt;R16G16B16A16_UNORM&lt;/code&gt; 64x64 noise texture is bound as input, only RG channels contain data, similar to what we&amp;rsquo;d use in SSAO for random normal rotation or offset&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;All material textures are accessed through the descriptor table&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RT0 in &lt;code&gt;R10G10B10A2_UNORM&lt;/code&gt; is the albedo, alpha channel is used as object mask, animated meshes are marked as &lt;code&gt;0x03&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RT1 in &lt;code&gt;R10G10B10A2_UNORM&lt;/code&gt; is world-space normal packaged by offset $0.5x + 0.5$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RT2 in &lt;code&gt;R8G8B8A8_UNORM&lt;/code&gt; is metallic-roughness and other attributes, animated meshes are not rendered here, the alpha channel should be the transparent or emissive property for emissive material objects&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RT3 in &lt;code&gt;R16G16_FLOAT&lt;/code&gt; is Screen-space motion vector:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Depth-stencil is &lt;code&gt;D32_FLOAT_S8X24_UINT&lt;/code&gt; format, stencil bit &lt;code&gt;0x15&lt;/code&gt; is used to mark character body meshes, &lt;code&gt;0x35&lt;/code&gt; for face, &lt;code&gt;0x95&lt;/code&gt; for hair and &lt;code&gt;0xA0&lt;/code&gt; is for trees, brushes, and other foliage&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drawing order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Static Mesh&lt;/li&gt;
&lt;li&gt;Ground and emissive objects&lt;/li&gt;
&lt;li&gt;Animated objects and destructible (?)&lt;/li&gt;
&lt;li&gt;Foliage&lt;/li&gt;
&lt;li&gt;Decals&lt;/li&gt;
&lt;li&gt;Mask for fur&lt;/li&gt;
&lt;li&gt;Fur&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;How the base material passes are drawn on on of the main street&amp;quot;: /attachments/08_BaseMaterialPass_MainStreet.mp4&lt;/code&gt;
&lt;code&gt;video: title: &amp;quot;How the base material passes are drawn near a river at night&amp;quot;: /attachments/08_BaseMaterialPass_RiverBank.mp4&lt;/code&gt;
&lt;code&gt;video: title: &amp;quot;The MRT&amp;quot;: /attachments/08_BaseMaterialPass_RiverBank_MRT.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="ds-convert-passes"&gt;DS convert passes&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;D32S8_TYPELESS&lt;/code&gt; Depth-stencil buffer is converted to &lt;code&gt;R32_TYPELESS&lt;/code&gt; in full-screen size and then downsampled to half size as &lt;code&gt;R16_FLOAT&lt;/code&gt; and &lt;code&gt;R8_UINT&lt;/code&gt; in 3 passes, for easier pixel sampling later.&lt;/p&gt;
&lt;h3 id="motion-stencil-pass"&gt;Motion-stencil pass&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;All moving objects and foliage and furs are marked with some different bits in this pass regarding with last frame screen-space positions, and then further extended for few pixels (Exactly the same solution as &lt;a href="http://advances.realtimerendering.com/s2016/s16_Ke.pptx" target="_blank" rel="external noopener noreferrer nofollow"&gt;Temporal Antialiasing in Uncharted 4&lt;/a&gt;: Better anti-ghost!)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Motion-stencil pass:
&lt;img src="https://zhangdoa.pages.dev/attachments/10_MotionStencil.png" alt="10_MotionStencil"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mask-and-lut-passes"&gt;Mask and LUT passes&lt;/h2&gt;
&lt;h3 id="reflection-mask"&gt;Reflection mask&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Parts of some objects are drawn onto an &lt;code&gt;R8_UNORM&lt;/code&gt; RT and used later in SSR and TAA passes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/11_Reflection.png" alt="11_Reflection"&gt;&lt;/p&gt;
&lt;h3 id="compute-pass-to-noise-normal"&gt;Compute pass to noise normal&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The RT is used for sun shadow, AO(?), direct sunlight, SSR and indirect skylight passes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/12_Noised_Normal.png" alt="12_Noised_Normal"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Noise sample:
&lt;img src="https://zhangdoa.pages.dev/attachments/12_Noised_Normal_Noise_Sample.png" alt="12_Noised_Normal_Noise_Sample"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="some-passes-to-mask-the-sky-out"&gt;Some passes to mask the sky out&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;All of them are in quite low resolution and mipmaped, the mipmap calculation executes multiple times&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;13_SkyMaskMipmap&amp;quot;: /attachments/13_SkyMaskMipmap.mp4&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;And finally, there is a graphic pass to draw a quite accurate sky mask on &lt;code&gt;R32_FLOAT&lt;/code&gt;:
&lt;img src="https://zhangdoa.pages.dev/attachments/13_SkyMaskAccurate.png" alt="13_SkyMaskAccurate"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="hbao-"&gt;(HB)AO (?)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;All in 960x540, looks like some sort of AO-required normal data:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;14_AO&amp;quot;: /attachments/14_AO.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="color-grading-lut"&gt;Color-grading LUT&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Color-grading LUT:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;15_ColorGradingLUT&amp;quot;: /attachments/15_ColorGradingLUT.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="ocean-wave-noise"&gt;Ocean wave noise&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Around 22~ 32x32x1 compute works are dispatched to generate noise for water, which is used in previous ocean wave pass&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="light-and-shadow-passes"&gt;Light and shadow passes&lt;/h2&gt;
&lt;h3 id="csm-passes-for-direct-light"&gt;CSM passes for direct light&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A 2k &lt;code&gt;R16_TYPELESS&lt;/code&gt; texture is used first to render a few simple meshes (some kinds of mesh proxy?), but the RT is never used&lt;/li&gt;
&lt;li&gt;Then 4 classic CSM cascades are rendered and stored as 4 slices in a 4k &lt;code&gt;R16_TYPELESS&lt;/code&gt; 2D texture array, no geometry shader RT index is involved, it just executes draw calls 4 times on each cascade&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;16_CSM&amp;quot;: /attachments/16_CSM.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="omni-shadow-maps-for-pointarea-lights"&gt;Omni shadow maps for point/area lights&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Every omni shadow map is first rendered as &lt;code&gt;R32_TYPELESS&lt;/code&gt; in 1k, then converted to 10 slices &lt;code&gt;R16G16UNORM&lt;/code&gt; (should be VSM?)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/17_OmniShadowmap.png" alt="17_OmniShadowmap"&gt;&lt;/p&gt;
&lt;h3 id="cloud-distribution"&gt;Cloud distribution&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/18_Cloud.png" alt="18_Cloud"&gt;&lt;/p&gt;
&lt;h3 id="sky-cubemaps"&gt;Sky cubemaps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;7 mipmap levels in &lt;code&gt;R11G11B10_FLOAT&lt;/code&gt; format:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;18_SkyCubemap&amp;quot;: /attachments/18_SkyCubemap.mp4&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And a stereographic projected sky radiance is also generated:
&lt;img src="https://zhangdoa.pages.dev/attachments/18_SkyProjected.png" alt="18_SkyProjected"&gt;&lt;/p&gt;
&lt;h3 id="coat-mask-"&gt;Coat mask (?)&lt;/h3&gt;
&lt;h3 id="clustered-light-index-"&gt;Clustered light index (?)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Some compute-only passes generate a few 3D textures that contain only indices:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;20_ClusteredIndices&amp;quot;: /attachments/20_ClusteredIndices.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="direct-light-shadow"&gt;Direct light shadow&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;In full screen resolution, it&amp;rsquo;s coming from sun at the day and moon at the night
&lt;img src="https://zhangdoa.pages.dev/attachments/21_SunShadow.png" alt="21_SunShadow"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="local-lights-mask-"&gt;Local lights mask (?)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;In a 120x68 60x34 resolution
&lt;img src="https://zhangdoa.pages.dev/attachments/22_LocalLightMask.png" alt="22_LocalLightMask"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="unconfirmed-compute-dispatches-to-update-some-structuredbuffers"&gt;Unconfirmed compute dispatches to update some StructuredBuffers&lt;/h3&gt;
&lt;h3 id="environment-radiance-capture"&gt;Environment Radiance capture&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;First, cubemap version is generated
&lt;code&gt;video: title: &amp;quot;23_EnvironmentCaptureCubemap&amp;quot;: /attachments/23_EnvironmentCaptureCubemap.mp4&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then all of them are converted to 2D dual-paraboloid textures in 512x256, each one contains 6 mips, and there are 32 captures stored as slices in one 2D texture array, 6~ texture arrays in the main street current scene
&lt;code&gt;video: title: &amp;quot;23_EnvironmentDualParaboloid&amp;quot;: /attachments/23_EnvironmentDualParaboloid.mp4&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="a-lot-gi-related--compute-passes"&gt;A lot GI-related (?) compute passes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;64x64x64 texture that is &lt;code&gt;R32G32_UINT&lt;/code&gt; format, 2 channels contain index-like data&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Slice 19 for example
&lt;img src="https://zhangdoa.pages.dev/attachments/24_GIProbeIndices_Slice_19.png" alt="24_GIProbeIndices_Slice_19"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="landscape-maps"&gt;Landscape maps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A few layers drawn here, from normal to a kind of heatmap in 1k resolution&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/25_LandscapeMap_Light.png" alt="25_LandscapeMap_Light"&gt;
&lt;img src="https://zhangdoa.pages.dev/attachments/25_LandscapeMap_Heat.png" alt="25_LandscapeMap_Heat"&gt;&lt;/p&gt;
&lt;h3 id="local-volumetric-fog"&gt;Local volumetric fog&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2 mips from 240x136 to 120x68, each one has 128 slices and mip 0 is from main camera view, it should be some kind of ray-marching:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;26_LocalVolumetricFog&amp;quot;: /attachments/26_LocalVolumetricFog.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="head-mask"&gt;Head mask&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The most hilarious one, should be used later to render detailed facial expressions
&lt;img src="https://zhangdoa.pages.dev/attachments/27_HeadMask.png" alt="27_HeadMask"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="direct-and-emissive-lights"&gt;Direct and emissive lights&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Tiled-lighting, each tile is 16x16 by size and all executed as indirect compute command:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;28_DirectAndEmissiveLight&amp;quot;: /attachments/28_DirectAndEmissiveLight.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="sky"&gt;Sky&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Only masked sky region is drawn&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ssr"&gt;SSR&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A low resolution depth mask is drawn first:
&lt;img src="https://zhangdoa.pages.dev/attachments/30_SSRTile.png" alt="30_SSRTile"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then the full-screen size SSR is drawn in &lt;code&gt;R11B11G10_FLOAT&lt;/code&gt; with another &lt;code&gt;R8_UNORM&lt;/code&gt; RT for reflectivity. The previously noised world-space normal and motion stencil RT is used as inputs, also the last frame&amp;rsquo;s downscaled TAA output is used for sampling:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;31_SSR&amp;quot;: /attachments/31_SSR.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="indirect-light"&gt;Indirect light&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Could see some artifacts (Light leaking because of VCT?):
&lt;img src="https://zhangdoa.pages.dev/attachments/32_IndirectLight.png" alt="32_IndirectLight"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="all-lights-composition-pass"&gt;All lights composition pass&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2 RTs, specular reflections are stored on RT1:
&lt;img src="https://zhangdoa.pages.dev/attachments/33_LightCompositionPass_RT0.png" alt="33_LightCompositionPass_RT0"&gt;
&lt;img src="https://zhangdoa.pages.dev/attachments/33_LightCompositionPass_RT1.png" alt="33_LightCompositionPass_RT1"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="transparent-lut"&gt;Transparent LUT&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Compute shader is involved a few times to generate some LUTs for later transparent and holographic objects:
&lt;img src="https://zhangdoa.pages.dev/attachments/34_TransparentLUT_1.png" alt="34_TransparentLUT_1"&gt;
&lt;img src="https://zhangdoa.pages.dev/attachments/34_TransparentLUT_2.png" alt="34_TransparentLUT_2"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="skin-and-eyes"&gt;Skin and eyes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Skin irradiance is rendered in screen space:
&lt;img src="https://zhangdoa.pages.dev/attachments/35_SkinIrradiance.png" alt="35_SkinIrradiance"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then eyes are rendered:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;36_Eyes&amp;quot;: /attachments/36_Eyes.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="aogi-shadow"&gt;AO(?)/GI shadow&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Add additional shadows to pixels that should not be lighted too much:
&lt;code&gt;video: title: &amp;quot;37_AO&amp;quot;: /attachments/37_AO.mp4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="volumetric-fog"&gt;Volumetric fog&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Just a simple image composition pass:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;38_VolumetricFog&amp;quot;: /attachments/38_VolumetricFog.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="water-reflection"&gt;Water reflection&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Few passes generate the water reflection, including all related masks and noises:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;39_Water&amp;quot;: /attachments/39_Water.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="post-processing-passes"&gt;Post-processing passes&lt;/h2&gt;
&lt;h3 id="taa"&gt;TAA&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Next it&amp;rsquo;s a full-screen no-suprise TAA pass:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;40_TAA&amp;quot;: /attachments/40_TAA.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="blur"&gt;Blur&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;First, the TAA-ed result is downscaled to 1/2, 1/4, and 1/8 size in 1 compute pass and stored in 3 mips, then each of the mips is blurred horizontally and vertically&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;41_Blur&amp;quot;: /attachments/41_Blur.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="unknown-transparent"&gt;Unknown transparent&lt;/h3&gt;
&lt;h3 id="cloud"&gt;Cloud&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The distribution noise of the cloud is pre-generated:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;42_Cloud&amp;quot;: /attachments/42_Cloud.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="holo-and-transparent-objects"&gt;Holo and transparent objects&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;All futurism holographic objects are drawn next (a significant number of small triangle count meshes), including water surface and some local light scattering:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;43_Transparent&amp;quot;: /attachments/43_Transparent.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="post-taa"&gt;Post-TAA&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2 downscaled images are generated again in 1/2 and 1/4 sizes with all transparent objects on them, and finally, it&amp;rsquo;s converted back to full-screen size and gets sharpened&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;44_PostTAA&amp;quot;: /attachments/44_PostTAA.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="hdr-lut"&gt;HDR LUT(?)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A 256x256 &lt;code&gt;R16_FLOAT&lt;/code&gt;, a 256x64 &lt;code&gt;R16G16B16A16_FLOAT&lt;/code&gt; and a 256x1 &lt;code&gt;R16G16B16A16_FLOAT&lt;/code&gt; images are generated by 3 compute dispatches, and finally are used as part of the inputs for the next compute pass to generate a structured buffer, which is used widely when rendering sky cubemaps and reflection probes
&lt;img src="https://zhangdoa.pages.dev/attachments/45_HDRLUT_01.png" alt="45_HDRLUT_01"&gt;
&lt;img src="https://zhangdoa.pages.dev/attachments/45_HDRLUT_02.png" alt="45_HDRLUT_02"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bloom"&gt;Bloom&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The full-screen images are half-sized 6 times and get bloomed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;video: title: &amp;quot;46_Bloom&amp;quot;: /attachments/46_Bloom.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="camera-lens-effects"&gt;Camera lens effects&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Rendered on a half-screen size RT
&lt;img src="https://zhangdoa.pages.dev/attachments/47_LensFlare.png" alt="47_LensFlare"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="color-grading"&gt;Color grading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The color-graded image is then converted to LDR
&lt;img src="https://zhangdoa.pages.dev/attachments/48_ColorGrading.png" alt="48_ColorGrading"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="gamma-correction"&gt;Gamma correction&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The Gamma correction is executed in a weird resolution 456x256
&lt;img src="https://zhangdoa.pages.dev/attachments/49_GammaCorrection.png" alt="49_GammaCorrection"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="gui-elements"&gt;GUI elements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;All GUI elements are drawn on a full HD RT and the corresponding mipmaps are generated, and then a few compute passes add bloom to them
&lt;img src="https://zhangdoa.pages.dev/attachments/50_GUI.png" alt="50_GUI"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="film-grain"&gt;Film Grain&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The noise LUT is generated on-the-fly
&lt;img src="https://zhangdoa.pages.dev/attachments/51_FilmGrain.png" alt="51_FilmGrain"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="swap-chain-image"&gt;Swap chain image&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Finally, the result of the all above is presented to the screen
&lt;img src="https://zhangdoa.pages.dev/attachments/99_FinalResult_MainStreet.png" alt="99_FinalResult_MainStreet"&gt;
&lt;img src="https://zhangdoa.pages.dev/attachments/99_FinalResult_RiverBank.png" alt="99_FinalResult_RiverBank"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="undrawnable-conclusions"&gt;Undrawnable conclusions&lt;/h2&gt;
&lt;p&gt;Well, what else I could say, 2 decades have passed already in the 21st century, Cyberpunk 2077 basically is the summing of the modern game rendering techniques which progressed during the years. Even without the enabling of ray-tracing, the overall appearance of the graphics are magnificent, it just bursts my euphoria when I&amp;rsquo;m pacing around Night City.
Despite all the controversies over the release and gameplay glitches which mainly caused by not robust enough physics system and overestimated streaming implementation, the game&amp;rsquo;s rendering is quite well-crafted, but still, I expected to see more state-of-the-art architectural practices such as a heavier GPU-driven rendering pipeline (which&amp;rsquo;s been more and more popular since 2016-2017). How&amp;rsquo;s your opinion about it? And what could change if we come back 5 years later to have a review? Maybe we&amp;rsquo;d laugh at the &amp;ldquo;outdated&amp;rdquo; techniques in Cyberpunk 2077, but I&amp;rsquo;m sure we&amp;rsquo;ll still keep admitting that real-time rendering is always full of exciting directions to explore!&lt;/p&gt;</content:encoded><category>Rendering</category></item><item><title>Physically Based Rendering - Lighting</title><link>https://zhangdoa.pages.dev/physically-based-rendering-lighting/</link><pubDate>Sat, 07 Dec 2019 18:25:00 +0000</pubDate><author>zhangandhang@gmail.com (Hang Zhang)</author><dc:creator>Hang Zhang</dc:creator><guid>https://zhangdoa.pages.dev/physically-based-rendering-lighting/</guid><description>&lt;p&gt;As soon as we modeled the surface&amp;rsquo;s physical properties that covered a certain range of material in real life, we would need to emit light onto them, in order to finally get the outcome radiance from the surface. If you take a look back at the rendering equation, the outcome radiance is just an integral of the income radiance over the semi-sphere around the normal. This gives us a fundamental assumption that with a brutal algorithm such as path tracing we could get a numerical solution for the problem. We could just model the geometry shape of the light source, then emit single light through all the possible directions and evaluate whether they hit a surface, until the overall energy diminished to a certain threshold. But in real-time rendering, the computational source currently we had in hand won&amp;rsquo;t be sufficient for such cost of the algorithm. We need to find the analytical replacement of the integral, or at least some cheaper numerical integrals.&lt;/p&gt;</description><content:encoded>&lt;p&gt;As soon as we modeled the surface&amp;rsquo;s physical properties that covered a certain range of material in real life, we would need to emit light onto them, in order to finally get the outcome radiance from the surface. If you take a look back at the rendering equation, the outcome radiance is just an integral of the income radiance over the semi-sphere around the normal. This gives us a fundamental assumption that with a brutal algorithm such as path tracing we could get a numerical solution for the problem. We could just model the geometry shape of the light source, then emit single light through all the possible directions and evaluate whether they hit a surface, until the overall energy diminished to a certain threshold. But in real-time rendering, the computational source currently we had in hand won&amp;rsquo;t be sufficient for such cost of the algorithm. We need to find the analytical replacement of the integral, or at least some cheaper numerical integrals.&lt;/p&gt;
&lt;h2 id="remapping-of-reality"&gt;Remapping of reality&lt;/h2&gt;
&lt;p&gt;Before we dive into the detail of the analytical representation of different light shapes, I&amp;rsquo;d introduce another type of unit system to measure energy and related quantities. If you remembered what I introduced in a previous post about radiometry, they are identically related. Thus the photometry it is, basically it&amp;rsquo;s a weighted version of radiometry, but with the respect to the nature of human eyes&amp;rsquo; sensitivity. Because when we deal with the light in real life, typically the light sources we would get are bulbs, candles, flashlights, and our old friends, sun, we perceive the electromagnetic wave from them by the cell on our retina. And because of the non-linearly of human nature (of course!), our eyes have different sensitivities among different light wavelengths, so finally people invented the photometry quantities to better measure the light we actually &amp;ldquo;see&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Here are the common photometry quantities we would occur in real-time rendering:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Quantity&lt;/th&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;Unit&lt;/th&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;Notes&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Name&lt;/td&gt;
					&lt;td&gt;Symbol&lt;/td&gt;
					&lt;td&gt;Name&lt;/td&gt;
					&lt;td&gt;Symbol&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Luminous energy&lt;/td&gt;
					&lt;td&gt;${Q_v}$&lt;/td&gt;
					&lt;td&gt;lumen second&lt;/td&gt;
					&lt;td&gt;${lm·s}$&lt;/td&gt;
					&lt;td&gt;Energy of electromagnetic radiation with respect of human eyes&amp;rsquo; sensitivity&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Luminous flux&lt;/td&gt;
					&lt;td&gt;${\Phi_v}$&lt;/td&gt;
					&lt;td&gt;lumen&lt;/td&gt;
					&lt;td&gt;${lm}$&lt;/td&gt;
					&lt;td&gt;also called Luminous power&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Luminous intensity&lt;/td&gt;
					&lt;td&gt;${I_v}$&lt;/td&gt;
					&lt;td&gt;candela&lt;/td&gt;
					&lt;td&gt;${\frac{lm}{sr}}$ or ${cd}$&lt;/td&gt;
					&lt;td&gt;steradian is similar to angle in 2D space&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Illuminance Flux density&lt;/td&gt;
					&lt;td&gt;${M_v}$ or ${E_v}$&lt;/td&gt;
					&lt;td&gt;lux&lt;/td&gt;
					&lt;td&gt;${\frac{lm}{m^2}}$ or ${lx}$&lt;/td&gt;
					&lt;td&gt;&lt;em&gt;Il&lt;/em&gt;-luminance, means the luminance received by a surface&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Luminance&lt;/td&gt;
					&lt;td&gt;${L_v}$&lt;/td&gt;
					&lt;td&gt;nit&lt;/td&gt;
					&lt;td&gt;${\frac{lm}{m^2·sr}}$ or ${\frac{cd}{m^2}}$ or ${nt}$&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;As you could see, it&amp;rsquo;s basically another version of quantity measurement for the electromagnetic radiation, but with more emphasis on the visible light range. Because the two types of retina cell - rods and cones have quite different responses for different intensity and wavelengths of lights, the actual mapping between radiometry to photometry requires to be done in two different versions. The one related to cones cell typically has a luminance range from ${10 nt}$ to ${10^8 nt}$ and we call it photopic vision in biology, and another one related to rods cell is called scotopic vision with the luminance range from ${10^{-3} nt}$ to ${10^{-6} nt}$. The cones cell could receive the chromatic of the world and the rods cell receives the silhouette of the object. But you may wonder, how much is one $nt$? The SI defines the fundamental unit of the light measurement by ${cd}$, and then the other units are its deduced units. And with the standard definition, one candela means &amp;ldquo;A $540.0154×10^{12}Hz$ monochromatic light emits $1/683$ Watt per steradian&amp;rdquo;, then we could say how much a nit it is by adding &amp;ldquo;per square meter&amp;rdquo; to it. The reason to choose such a reference wavelength is that one of the three types of cone cells is most sensitive to it, and if you took a look at the visible light spectrum you&amp;rsquo;d see it&amp;rsquo;s a greenish color. The magic number 1/683 came from a historical compatibility requirement to transit the old unit system to the modern one.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;Radiometry deals purely with physical quantities, without taking account of human
perception. A related field, &lt;em&gt;photometry&lt;/em&gt;, is like radiometry, except that it weights everything by the sensitivity of the human eye. The results of radiometric computations
are converted to photometric units by multiplying by the &lt;em&gt;CIE photometric curve&lt;/em&gt;, bell-shaped curve centered around 555 nm that represents the eye’s response to various wavelengths of light&amp;hellip;&amp;rdquo; - pg. 271, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://upload.wikimedia.org/wikipedia/commons/7/72/CIE_1931_Luminosity.png" alt="The CIE 1931 photopic luminosity function" width="50%" height="50%"&gt;&lt;img src="https://upload.wikimedia.org/wikipedia/commons/7/7e/CIE_1951_scotopic_luminosity_function.svg" alt="The CIE 1951 scotopic luminosity function" width="50%" height="50%"&gt;&lt;/p&gt;
&lt;p&gt;So with a certain wavelength $\lambda$, the mapping from radiometry to photometry is expressed like ${I_v(\lambda)}=683.002\overline{y}(\lambda)I_e(\lambda)$, or we could integrate over the entire visible light wavelength range to get ${I_v}=683.002\int_{380}^{780}\overline{y}(\lambda)I_e(\lambda)d\lambda$. The $\overline{y}(\lambda)$ here is the luminosity function, it&amp;rsquo;s a bell-shaped distribution function and has different values in photopic and scotopic. The photopic luminosity function would reach 1 in 555nm or 540 THz (the definition frequency of candela) and the scotopic luminosity function is 1 in around 510nm, they demonstrate the eyes&amp;rsquo; perception ratio of different wavelengths. And we could furthermore deduce two quantities called luminous efficacy and luminous efficiency from the mapping function. Luminous efficacy $\eta$ is defined as ${\eta}=683.002\frac{\int_{380}^{780}\overline{y}(\lambda)I_e(\lambda)d\lambda}{\int_{380}^{780}I_e(\lambda)d\lambda}$ and the unit is ${\frac{lm}{W}}$, it represents the light source&amp;rsquo;s ability to emit visible light; And if we normalized the luminous efficacy with ${\frac{1W}{683lm}}$ then we would get luminous efficiency, which is simply defined as $\frac{\int_{380}^{780}\overline{y}(\lambda)I_e(\lambda)d\lambda}{\int_{380}^{780}I_e(\lambda)d\lambda}$. With the help of them we could easily evaluate whether a light source is wasting or saving energy, or to directly convert from the radiometric quantities to photometric version. And we could get that an ideal 540 THz black body would have a 683 ${\frac{lm}{W}}$ luminous efficacy or 100% luminous efficiency.&lt;/p&gt;
&lt;p&gt;Since what we&amp;rsquo;ve been talking so far is all about the real-time rendering, we would use the discrete RGB space instead of a continuous spectrum, and then we could simplify the continuous luminosity function to a discrete version. And we could then give the user a few parameters like the radiometric quantities and the luminous efficiency to adjust the light source. But when we build a physically-based rendering pipeline we&amp;rsquo;d often go to find references in real life, and almost all the light sources like those bulbs sold in supermarket would print their photometric quantities on the package, it would be more convenient to use photometric quantities in the light system for the final users.&lt;/p&gt;
&lt;h2 id="integration-matters"&gt;Integration matters&lt;/h2&gt;
&lt;p&gt;The more crucial part of a light system is how to model the geometry appearance of the light source. When dealing with a non-physically based rendering pipelines, there are often 3 types of analytical light we&amp;rsquo;d like to use, directional light, point light, and spot light. I&amp;rsquo;d not discuss any of them here because all of them are not modeling around the actual light source geometry but rather around the appearance of the lighting result, another reason is that there are tons of information online about how to implement them and I&amp;rsquo;d better not repeat again. I&amp;rsquo;d indicate them as punctual light sources later since the actual shape of them is infinitesimal. The physically correct light sources all have volume, or if we ignore one of the 3 dimensions with the respect of the nature of projection they still have the area. I&amp;rsquo;d indicate them as area light source later on. The typical analytical area light source has shapes like sphere, disk, tube and rectangular, or with a trivial shape which requires some advanced approximations.&lt;/p&gt;
&lt;p&gt;If we took a look once more at the reflectance equation, let&amp;rsquo;s write a slightly different version $L_o=\int\limits_{\Omega+} f(v,l) V(l) L_i \langle n·l \rangle dl$ instead, the $V(l)$ here is a Heaviside function to indicate whether a light source is visible along a certain direction, and it describes the shape and the shadow of the light. As long as the shape of the light source is non-trivial, it&amp;rsquo;s impossible to find a general analytical solution for such hemisphere integral. The punctual light sources all assume the shape is infinitesimal so the integral would be simplified by an integral of cosine over hemisphere and finally we&amp;rsquo;d get $L_o=\pi f(v,l) \langle n·l \rangle$, that&amp;rsquo;s the reason why we could write &lt;code&gt;Lo = rho * NDotL&lt;/code&gt; when using the simple Lambert diffuse BTDF $f(v,l) = \frac{\rho}{\pi}$, the $\pi$ is just perfectly canceled. For area light we cannot have such one-line-to-rule-them-all enjoyment, there are some practical solutions so far, that all of them would solve the problem to a certain level which is acceptable enough. I&amp;rsquo;d introduce them one by one below.&lt;/p&gt;
&lt;h3 id="form-factor"&gt;Form Factor&lt;/h3&gt;
&lt;p&gt;The first approach is that we could still try to solve the integral directly. We can&amp;rsquo;t numerically solve it in runtime, but if we convert it to the integral over the light source&amp;rsquo;s area instead, then it would bring some certainties because we would have known the shape or the actual area of the light source when shading the object. The rewritten version of the reflectance equation is $L_o=\int\limits_{A} f(v,l) L_i \langle n·l \rangle \frac{\langle n_a·-l \rangle}{r^2} dA$, here $n_a$ is the normal of $dA$, $r$ is the distance from the lighting point to $dA$. Because the effective area along the $dl$ is proportional to the normal orientation of $dA$ and the distance from the shading point to the $dA$, so here we introduced a $\frac{\langle n_a·-l \rangle}{r^2}$ factor.&lt;/p&gt;
&lt;p&gt;And then we could move the BSDF out of the integral by assuming it&amp;rsquo;s not interleaved with light direction so much, for example in a Lambert diffuse BTDF case. Then what we&amp;rsquo;d solve is $L_o = f(v,l) \int\limits_{\Omega+} L_i \langle n·l \rangle dl$, or $L_o = f(v,l) E(n)$ that we could treat all the income luminance as the illuminance. The analytic method to integrate illuminance has been already developed in problem domain of energy transformation, such like radio transfer or heat transfer, since fundamentally they are all the same type of problems about how to calculate the energy transferred from one object to another, or specifically speaking in our case, from a surface to another one.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;The view factor from a general surface A1 to another general surface A2 is given by: $\displaystyle F_{1\rightarrow 2}={\frac {1}{A_{1}}}\int &lt;em&gt;{A&lt;/em&gt;{1}}\int &lt;em&gt;{A&lt;/em&gt;{2}}{\frac {\cos \theta &lt;em&gt;{1}\cos \theta &lt;em&gt;{2}}{\pi s^{2}}},{d}A&lt;/em&gt;{2},{d}A&lt;/em&gt;{1}$&amp;hellip;&amp;rdquo; [Wiki1]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The actual analytic formulae are more complicated but well-documented in [Mar14] and [HSM10], I&amp;rsquo;d only give each link of the commonly used geometry shapes below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sphere: &amp;ldquo;Patch to a sphere - Titled&amp;rdquo; in pg. 10 in [Mar14], and B-43 in [HSM10]&lt;/li&gt;
&lt;li&gt;Disk: &amp;ldquo;Parallel configurations - Patch to disc&amp;rdquo; in pg. 22 in [Mar14], and B-13 in [HSM10]&lt;/li&gt;
&lt;li&gt;Rectangular: &amp;ldquo;Parallel configurations - Patch to rectangular plate&amp;rdquo; in pg. 22 and &amp;ldquo;Perpendicular -configurations - Patch to rectangular plate&amp;rdquo; in pg. 26 in [Mar14], and B-5 in [HSM10]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The solution in [HSM10] covers more general cases than [Mar14]. The overall runtime cost if we&amp;rsquo;d implement them naively is not so acceptable for real products, but they bring us the perfect analytic forms, we could use them wisely in products with some optimizations and simplifications, or just leave them as a ground truth faster than the classic path tracing.&lt;/p&gt;
&lt;p&gt;A code example for sphere light that modified from [LR14]:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NR_SPHERE_LIGHTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;lightRadius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sphereLightCBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;luminousFlux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lightRadius&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;unormalizedL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sphereLightCBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyz&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;posWS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unormalizedL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;LdotH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;sqrDist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unormalizedL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unormalizedL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Beta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;acos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;H2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqrDist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;H2&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;lightRadius&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;tan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Beta&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;illuminance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Beta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;				&lt;span class="n"&gt;illuminance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Beta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;				&lt;span class="n"&gt;illuminance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;					&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Beta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;acos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Beta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;					&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;atan&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Beta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you could see here, the form factor approach is suitable for our low-frequency signals - the diffuse part of the BSDF, since it is modeled basically around the phenomenon of energy bounce between diffuse surfaces. But still we need to find a solution for the specular part, and if you want to use some more advanced BTDF than the Lambert model which involves more about the complex subsurface scattering phenomenon, then the form factor solution is not accurate enough.&lt;/p&gt;
&lt;h3 id="representative-point"&gt;Representative Point&lt;/h3&gt;
&lt;p&gt;Another approach is that we won&amp;rsquo;t solve any of the integral at all, instead, we would try to substitute the problem with our old method, use punctual light to approximate area light. If we still focused on the lighting result, area light is similar to a bunch of point lights. And then we could find a point on the surface of the light source that contributes the most to the lighting result, and use it as the location of a point light to continue our shading process. This method is often called Representative Point or Most Representative Point (abbr. MRP), which has been successfully used in lots of PBR pipelines like [Kar13] and [LR14].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;These approaches resemble the idea of &lt;em&gt;importance sampling&lt;/em&gt; in Monte Carlo integral, where we numerically compute the value of a definite integral by averaging samples over the integral domain. In order
to do so more efficiently, we can try to prioritize samples that have a large contribution to the overall average&amp;hellip;.&amp;rdquo; - pg. 385, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When using the MRP method, we could both apply it to diffuse and specular part of BSDF, while it depends on the actual requirement of the products. One of the general algorithms has been developed by [Dro14], it uses the halfway vector method to find MRP for diffuse and cone projection to find MRP for specular. For the diffuse part, the halfway vector could be expressed as $\vec{h} = \frac{\vec{pp_0} + \vec{pp_1}}{||\vec{pp_0} + \vec{pp_1}||}$, $p$ is the shading point, $p_0$ is the intersection point of a ray from $p$ along the reflection vector $\vec{r}$ of the view direction, and $p_1$ is the intersection point of a ray from $p$ along negative direction of the light plane normal $\vec{n&amp;rsquo;}$. While the demonstration here may sound not complex, in a real scenario with an actual light shape, we often need to adjust the halfway vector method&amp;rsquo;s MRP result onto the surface of the light source, since it won&amp;rsquo;t always fall inside the light area. In another word, we need to find the closest point of the diffuse MRP in the light area if it&amp;rsquo;s outside.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1575531089636.drawio.svg" alt="MRP for diffuse"&gt;&lt;/p&gt;
&lt;p&gt;For the specular part, the calculation became more complicated with the respect of the probability distribution functions (abbr. PDF) in the BRDF, we need to generate a cone along the reflection direction based on the PDF and use the geometry center of the intersection area between the cone and the light surface as the MRP. Furthermore, we could pre-compute lookup tables for a specific BSDF and light texture combination we chose, by eliminating some variables we could limit the LUT to 3D tables and free us from runtime calculation. And after we use the MRP to calculate the illuminance we should weight it by the intersection area, to preserve the energy conservation. All the details were well demonstrated in [Dro14], I&amp;rsquo;d recommend reading if it&amp;rsquo;s available for you.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1575532829804.drawio.svg" alt="MRP for specular"&gt;&lt;/p&gt;
&lt;p&gt;Another way to calculate specular MRP is that we could find the point on the light surface with the smallest distance to the reflection ray of view direction, which is demonstrated in [Kar13]. For example, in a sphere light case we could calculate $p_{cr} = (l · r)r − l$ then $p_{cs} = l + p_{cr} · min(1,\frac{radius}{||p_{cr}||})$, while $p_{cr}$ is the point on the reflection ray closest to the sphere center, and $p_{cs}$ is the point on the sphere surface closest to $p_{cr}$.&lt;/p&gt;
&lt;h3 id="linearly-transformed-cosines"&gt;Linearly Transformed Cosines&lt;/h3&gt;
&lt;p&gt;The LTC approach tries to solve the integral from another point of view, or more precisely speaking, another space. The reflectance equation involves several variables, and we&amp;rsquo;d evaluate each direction of the incoming light with a BSDF function, and the complexity is $O(n^2)$ due to the variance of the $dl$ and the BSDF. But if we could control one of them to be some sort of constant, then we would optimize the complexity to $O(n)$. Thanks to the popular usage of parametric BSDF nowadays, we could pre-compute lots of them into a multi-dimensional lookup table and just sample from it in runtime to save our precious frame time. But unfortunately, when we implement such a solution and try to fit it into the rendering pipeline, we&amp;rsquo;d still need to integrate the illuminance over a semispherical region, with an uncertain shape and orientations of the light surface. But if we consider the solving procedure of the reflectance equation in a linear algebra point of view, then the integral of the incoming light in a &amp;ldquo;world&amp;rdquo; spherical space could be solved as the integral of the transformed incoming light in a &amp;ldquo;local&amp;rdquo; spherical space. If we could choose a suitable transformation to ensure the linearity then the above hypothesis would become a provable truth. And if this transformation could eliminate the shape factor or the orientation factor of the light then we would be one step closer to our expectation of $O(n)$. Luckily the BSDF is naturally a spherical distribution function, the only problem left is how to find the transformation for a BSDF and find a close form of the integral in that BSDF space, after all, if we can&amp;rsquo;t solve the reflectance equation easier then all of these works would be nonsense.&lt;/p&gt;
&lt;p&gt;The most commonly used distribution function of BSDF today is the GGX approximation for the Cook-Torrance microfacet model, the isotropic version of it only depends on the view direction and the surface roughness $\alpha$, even we add the anisotropicity later on, the number of the variables is still only 3. But even 3 variables the integral is still too expensive to calculate in runtime, we have to continue eliminating variables and transforming the light source until we finally find a space cheaper enough to evaluate the integral in real-time. That&amp;rsquo;s how the LTC came from, we would use a clamped cosine spherical distribution function as the base space since the integral over it is both cheap and analytic, then find a transformation matrix to approximately transform it to a specific BSDF, then use the inverse of that matrix to transform the vertices of the light source into the base space, and finally calculate the integral by utilising the analytic form of line integral.&lt;/p&gt;
&lt;p&gt;The theoretical details are well demonstrated in [Hei16] and the reference C++ source code has been provided on &lt;a href="https://github.com/selfshadow/ltc_code" target="_blank" rel="external noopener noreferrer nofollow"&gt;Github&lt;/a&gt;, but without too much explanation about what it is doing. Actually, if you have ever implemented any BSDF LUT, the approach behind is similar, we just generate the value pair of all possible parameter combination ahead of runtime, and store them in an easy-to-fetch data structure. What we want to get here is a table of transformation matrices that, we could transform our specific BSDF distribution function to the clamped cosine one bidirectionally. Or mathematically speaking, we need to find the $M$ in $D_{BSDF}(\omega) = M * D_o(\omega)$, where $D_o$ would be the original clamped cosine distribution function we chose, with a form of $D_o(\omega_o = (x, y, z)) = \frac{1}{\pi}max(0, z)$ that $\omega_o$ is the normalized direction vector. As you could see we would employ a 3D vector as usual, so the matrix should be a 3x3 matrix.&lt;/p&gt;
&lt;p&gt;The next step would be how to calculate the $M$ for our specific BSDF, for example, what the [Hei16] did for GGX distribution. They use &lt;a href="https://en.m.wikipedia.org/wiki/Nelder%E2%80%93Mead_method#One_possible_variation_of_the_NM_algorithm" target="_blank" rel="external noopener noreferrer nofollow"&gt;Nelder–Mead method&lt;/a&gt; to numerically search the $M$, with a cost/error function implemented by simply comparing the actual BSDF value and LTC fitted value. And because in the paper they only fitted isotropic version of GGX, finally the $M$ only has 5 effective elements on each diagonal.&lt;/p&gt;
&lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo fence="true"&gt;[&lt;/mo&gt;&lt;mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"&gt;&lt;mtr&gt;&lt;mtd&gt;&lt;mstyle scriptlevel="0" displaystyle="false"&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;mtd&gt;&lt;mstyle scriptlevel="0" displaystyle="false"&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;mtd&gt;&lt;mstyle scriptlevel="0" displaystyle="false"&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd&gt;&lt;mstyle scriptlevel="0" displaystyle="false"&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;mtd&gt;&lt;mstyle scriptlevel="0" displaystyle="false"&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;mtd&gt;&lt;mstyle scriptlevel="0" displaystyle="false"&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd&gt;&lt;mstyle scriptlevel="0" displaystyle="false"&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;mtd&gt;&lt;mstyle scriptlevel="0" displaystyle="false"&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;mtd&gt;&lt;mstyle scriptlevel="0" displaystyle="false"&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;/mtable&gt;&lt;mo fence="true"&gt;]&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;
\begin{bmatrix}
a &amp;amp; 0 &amp;amp; b\\
0 &amp;amp; c &amp;amp; 0\\
d &amp;amp; 0 &amp;amp; e\\
\end{bmatrix}
&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:3.6em;vertical-align:-1.55em;"&gt;&lt;/span&gt;&lt;span class="minner"&gt;&lt;span class="mopen"&gt;&lt;span class="delimsizing mult"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:2.05em;"&gt;&lt;span style="top:-4.05em;"&gt;&lt;span class="pstrut" style="height:5.6em;"&gt;&lt;/span&gt;&lt;span style="width:0.667em;height:3.600em;"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewBox="0 0 667 3600"&gt;&lt;path d="M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84
H403z M403 1759 V0 H319 V1759 v0 v1759 h84z"/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:1.55em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mtable"&gt;&lt;span class="col-align-c"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:2.05em;"&gt;&lt;span style="top:-4.21em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;a&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.01em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-1.81em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:1.55em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="arraycolsep" style="width:0.5em;"&gt;&lt;/span&gt;&lt;span class="arraycolsep" style="width:0.5em;"&gt;&lt;/span&gt;&lt;span class="col-align-c"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:2.05em;"&gt;&lt;span style="top:-4.21em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.01em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-1.81em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:1.55em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="arraycolsep" style="width:0.5em;"&gt;&lt;/span&gt;&lt;span class="arraycolsep" style="width:0.5em;"&gt;&lt;/span&gt;&lt;span class="col-align-c"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:2.05em;"&gt;&lt;span style="top:-4.21em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.01em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-1.81em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;e&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:1.55em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="delimsizing mult"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:2.05em;"&gt;&lt;span style="top:-4.05em;"&gt;&lt;span class="pstrut" style="height:5.6em;"&gt;&lt;/span&gt;&lt;span style="width:0.667em;height:3.600em;"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="3.600em" viewBox="0 0 667 3600"&gt;&lt;path d="M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v0 v1759 h84z"/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:1.55em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;p&gt;And with normalization by one of the elements there would be only 4 elements left, which could just be stored into a 4 channels texture and linear sampled in runtime. In the paper, they use $e$ as the normalizer, but later in [Hill16], they admit that it would bring some serious numerical error, and somehow I found that they use $c$ later again in the repo&amp;rsquo;s history, that should be a measured decision maybe. Also in [Hill16], they mentioned multiple problems when implementing LTC solutions, from how to deal with polygon clipping effectively to how to ensure the robust of inverse trigonometric functions, highly recommend to read.&lt;/p&gt;
&lt;p&gt;After you get the LUT for $M$, then could just use it in runtime for the shading. You just need to write some shader codes to transform the light vertices to LTC space and do a spherical line integral by $\frac{1}{2\pi}\sum_{i=1}^{n}acos(v_i · v_j)\frac{v_i \times v_j}{||v_i \times v_j||} · n$. But this numerical integral is not practical for light shapes such as single line, capsule, sphere and disk, since you can&amp;rsquo;t find a small but smooth enough vertices number for ellipse shapes. Moreover, it&amp;rsquo;s the natural solution for convex polygons like rectangular light. Luckily later they provided the corresponding integral form for other shapes in [Hei17], where the final appearance and performance are quite plausible for real products in contrast with other solutions. I&amp;rsquo;d recommend again to find details in their publications.&lt;/p&gt;
&lt;p&gt;To be continued.&lt;/p&gt;
&lt;p&gt;Bibliography：&lt;/p&gt;
&lt;p&gt;[Wiki1] &lt;a href="https://en.wikipedia.org/wiki/View_factor" target="_blank" rel="external noopener noreferrer nofollow"&gt;https://en.wikipedia.org/wiki/View_factor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[Mar14] &lt;a href="http://webserver.dmt.upm.es/~isidoro/tc3/Radiation%20View%20factors.pdf" target="_blank" rel="external noopener noreferrer nofollow"&gt;http://webserver.dmt.upm.es/~isidoro/tc3/Radiation%20View%20factors.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[HSM10] &lt;a href="http://www.thermalradiation.net/tablecon.html" target="_blank" rel="external noopener noreferrer nofollow"&gt;http://www.thermalradiation.net/tablecon.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[Kar13] B. Karis. “Real Shading in Unreal Engine 4”. In: Physically Based Shading in Theory and Practice, ACM SIGGRAPH 2013 Courses. SIGGRAPH ’13. Anaheim, California: ACM, 2013, 22:1–22:8. isbn: 978-1-4503-2339-0. doi: 10.1145/2504435.2504457. url: &lt;a href="http://selfshadow.com/publications/s2013-shading-course/" target="_blank" rel="external noopener noreferrer nofollow"&gt;http://selfshadow.com/publications/s2013-shading-course/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[LR14] S. Lagarde and C. de Rousiers. “Moving Frostbite to PBR”. In: Physically Based Shading in Theory and Practice, ACM SIGGRAPH 2014 Courses. SIGGRAPH ’14. Vancouver, Canada: ACM, 2014, 23:1–23:8. isbn: 978-1-4503-2962-0. doi: 10.1145/2614028.2615431. url: &lt;a href="http://www.frostbite.com/2014/11/moving-frostbite-to-pbr/" target="_blank" rel="external noopener noreferrer nofollow"&gt;http://www.frostbite.com/2014/11/moving-frostbite-to-pbr/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[Dro14] M. Drobot. &amp;ldquo;Physically Based Area Lights&amp;rdquo;. In: GPU Pro 5 Advanced Rendering Techniques. ISBN: 978-1-4822-0864-1. url: &lt;a href="https://www.crcpress.com/GPU-Pro-5-Advanced-Rendering-Techniques/Engel/p/book/9781482208634" target="_blank" rel="external noopener noreferrer nofollow"&gt;https://www.crcpress.com/GPU-Pro-5-Advanced-Rendering-Techniques/Engel/p/book/9781482208634&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[Hei16] E.Heitz, J.Dupuy, S.Hill and D.Neubelt. &amp;ldquo;Real-time polygonal-light shading with linearly transformed cosines&amp;rdquo;. In: ACM Transactions on Graphics (TOG) Volume 35 Issue 4, July 2016 Article No. 41.	url: &lt;a href="https://eheitzresearch.wordpress.com/415-2/" target="_blank" rel="external noopener noreferrer nofollow"&gt;https://eheitzresearch.wordpress.com/415-2/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[Hill16] S.Hill and E.Heitz. &amp;ldquo;Real-Time Area Lighting: a Journey from Research to Production&amp;rdquo;. In Advances in Real-Time Rendering in Games, ACM SIGGRAPH 2016 Courses. SIGGRAPH ’16. Anaheim, California: ACM, 2016, url: &lt;a href="https://blog.selfshadow.com/publications/s2016-advances/" target="_blank" rel="external noopener noreferrer nofollow"&gt;https://blog.selfshadow.com/publications/s2016-advances/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[Hei17] E.Heitz and S.Hill. &amp;ldquo;Real-Time Line- and Disk-Light Shading&amp;rdquo;. In Physically Based Shading in Theory and Practice, ACM SIGGRAPH 2017 Courses. SIGGRAPH ’17. Los Angeles, California: ACM, 2017, url: &lt;a href="https://blog.selfshadow.com/publications/s2017-shading-course/heitz/s2017_pbs_ltc_lines_disks.pdf" target="_blank" rel="external noopener noreferrer nofollow"&gt;https://blog.selfshadow.com/publications/s2017-shading-course/heitz/s2017_pbs_ltc_lines_disks.pdf&lt;/a&gt;&lt;/p&gt;</content:encoded><category>Rendering</category></item><item><title>Walking through the heap properties in DirectX 12</title><link>https://zhangdoa.pages.dev/walking-through-the-heap-properties-in-directx-12/</link><pubDate>Wed, 18 Sep 2019 14:00:00 +0000</pubDate><author>zhangandhang@gmail.com (Hang Zhang)</author><dc:creator>Hang Zhang</dc:creator><guid>https://zhangdoa.pages.dev/walking-through-the-heap-properties-in-directx-12/</guid><description>&lt;h2 id="indifferent-to-the-difference"&gt;Indifferent to the difference?&lt;/h2&gt;
&lt;p&gt;Back to the old times, you never worried about how the physical memory would be allocated when you&amp;rsquo;re dealing with OpenGL and DirectX 11, GPU and the video memory were hidden behind the driver so well that you might even not realize they were there. Nowadays we get Vulkan and DirectX 12 (of course Metal, but&amp;hellip;nevermind), that the &amp;ldquo;zero driver-overhead&amp;rdquo; slogan (not &amp;ldquo;Make XXX Great Again&amp;rdquo; sadly) become the reality on desktop platforms. And &amp;ldquo;ohh I don&amp;rsquo;t know that we need to manually handle the synchronization&amp;rdquo; or &amp;ldquo;ohh why I can&amp;rsquo;t directly bind that resources&amp;rdquo; and so on and on. Of course, the new generation (already not new actually) graphic API is not for casual usages, your hands get dirtier and your head gets more drizzling, while there are still a bunch of debugging layer warning and error messages keeping pop up. Long story in short, if you want something pretty and &lt;em&gt;simple&lt;/em&gt;, turn around and rush to &lt;em&gt;modern&lt;/em&gt; OpenGL (4.3+) or DirectX 11 and happy coding; if you want something pretty and &lt;em&gt;fast&lt;/em&gt;, then stay with me a while and let&amp;rsquo;s see what&amp;rsquo;s going on with the new D3D12 memory model.&lt;/p&gt;</description><content:encoded>&lt;h2 id="indifferent-to-the-difference"&gt;Indifferent to the difference?&lt;/h2&gt;
&lt;p&gt;Back to the old times, you never worried about how the physical memory would be allocated when you&amp;rsquo;re dealing with OpenGL and DirectX 11, GPU and the video memory were hidden behind the driver so well that you might even not realize they were there. Nowadays we get Vulkan and DirectX 12 (of course Metal, but&amp;hellip;nevermind), that the &amp;ldquo;zero driver-overhead&amp;rdquo; slogan (not &amp;ldquo;Make XXX Great Again&amp;rdquo; sadly) become the reality on desktop platforms. And &amp;ldquo;ohh I don&amp;rsquo;t know that we need to manually handle the synchronization&amp;rdquo; or &amp;ldquo;ohh why I can&amp;rsquo;t directly bind that resources&amp;rdquo; and so on and on. Of course, the new generation (already not new actually) graphic API is not for casual usages, your hands get dirtier and your head gets more drizzling, while there are still a bunch of debugging layer warning and error messages keeping pop up. Long story in short, if you want something pretty and &lt;em&gt;simple&lt;/em&gt;, turn around and rush to &lt;em&gt;modern&lt;/em&gt; OpenGL (4.3+) or DirectX 11 and happy coding; if you want something pretty and &lt;em&gt;fast&lt;/em&gt;, then stay with me a while and let&amp;rsquo;s see what&amp;rsquo;s going on with the new D3D12 memory model.&lt;/p&gt;
&lt;p&gt;The fundamental CPU-GPU communication architecture is quite similar around different machines, you have a CPU chip, you have a GPU chip, you have some memory chips, gotcha! The typical PC with a dedicated graphics card would have 2 memory chips, one we often referred as the main memory and another one as the dedicated graphics card memory, or more commonly used (not so strict) name convention are RAM and VRAM for them. Other architectures like those game consoles, the main memory, and the video card memory would be the same physical one, we name such kind of memory accessing model as UMA - &lt;strong&gt;U&lt;/strong&gt;niform &lt;strong&gt;M&lt;/strong&gt;emory &lt;strong&gt;A&lt;/strong&gt;ccess. Also, the functional microchips of CPU and GPU would be put together or closer in some certain designs (for example PS4) to get optimized communication performance. You should remember that you just paid once for your DDR4 16GB fancy &amp;ldquo;memory&amp;rdquo; when you&amp;rsquo;re crafting your state-of-the-art PC right? They are the &amp;ldquo;main&amp;rdquo; RAM for the general-purpose, like loading your OS after power-up or put the elements of your &lt;code&gt;std::vector&amp;lt;T&amp;gt;&lt;/code&gt; inside. But if you also purchased an AMD or NVIDIA dedicated graphics card, you might notice the printed instructions on the package box that there are other couple-few Gibibytes some sort of memory on it. That&amp;rsquo;s the VRAM memory where the raw texture and mesh data would stay when you are playing CS:GO and swearing random Ruglish in front of your screen.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1568382287724.drawio.svg" alt="An over-simplified PC"&gt;&lt;/p&gt;
&lt;p&gt;So, if you want to render the nice SWAT soldier mesh in CS::GO, you need to load it from your disk into the VRAM and then ask GPU to schedule some parallel rasterization work to draw it. But unfortunately, you can&amp;rsquo;t access the VRAM directly in your C++ code due to the physical design of the hardware. You could reference a main memory virtual address by semantics like &lt;code&gt;Foo* bar = nullptr&lt;/code&gt; in C++, because it would be finally compiled into some machine instructions like &lt;code&gt;movq, $(0x108), $0&lt;/code&gt; (it should be binary instruction data actually, for the sake of human-readability here I use assembly language instead) that your CPU could execute. But generally speaking, you can&amp;rsquo;t expect the same programming experience on GPU, since it is designed for highly parallel computational tasks thus you can&amp;rsquo;t refer to some fine-grin &lt;em&gt;global&lt;/em&gt; memory addresses directly (there are always some exceptions, but let&amp;rsquo;s stay foolish at present). The start offset of a bunch of raw VRAM data should be available for you in order to create a &lt;em&gt;context&lt;/em&gt; for GPU to prepare and execute works. If you were familiar with OpenGL or D3D11 then you had already used interfaces such as &lt;code&gt;glBindTextures&lt;/code&gt; or &lt;code&gt;ID3D11DeviceContext::PSSetShaderResources&lt;/code&gt;. These 2 APIs expose the VRAM memory not explicitly to developers, instead, you would get some indirect objects in runtimes like an integer handle in OpenGL or a COM object pointer in D3D11.&lt;/p&gt;
&lt;h2 id="a-step-closer"&gt;A step closer&lt;/h2&gt;
&lt;p&gt;GPU is a heavily history-influenced peripheral product, as time goes by its ability becomes more and more general and flexible. As you might know the appearing of &lt;em&gt;Unified Scalar Shader Architecture&lt;/em&gt; and &lt;em&gt;Highly Data Parallel Stream Processing&lt;/em&gt; made GPU become compatible for almost every kinds of parallel computation works, the only thing lying between the developer and the hardware is the API. The old generation of graphics APIs like OpenGL or DirectX 11 were designed with emphasis, that they&amp;rsquo;d better lead developers to a direction that they&amp;rsquo;d spend more time with the specific computer graphics related tasks they want to hand on with, rather than too much low-level hardware related details. But the experience told us, more abstraction, more overhead. So when the clock ticking around 2015 that the latest generation of graphics API was released to the mass developer like me, a brand new or I&amp;rsquo;d rather to say &amp;ldquo;retro&amp;rdquo; design philosophy appearing among them, no more pre-defined &amp;ldquo;texture&amp;rdquo; or &amp;ldquo;mesh&amp;rdquo; or &amp;ldquo;constant buffer&amp;rdquo; object models, instead we get some new but lower-level objects such as &amp;ldquo;resources&amp;rdquo; or &amp;ldquo;buffers&amp;rdquo; or &amp;ldquo;command&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Honestly speaking, it&amp;rsquo;s a little bit painful to transit the programming mindset from OpenGL/D3D11 era to Vulkan/D3D12. It&amp;rsquo;s quite like a 3-years-old kid who used to ride his cute tiny bike with auxiliary wheels now need to drive a 6-shifts manual gear 4WD car. Previously you call a &lt;code&gt;glGen*&lt;/code&gt; or &lt;code&gt;ID3D11Device::Create*&lt;/code&gt; interfaces you would get the resource handles in no means more than few milliseconds. Now you even can&amp;rsquo;t &amp;ldquo;invoke&amp;rdquo; functions to let GPU do these works! But wait, could we actually ask GPU to allocate a VRAM range for us and put some nice AK-47 textures inside before? Just the graphic cards vendor&amp;rsquo;s implementation handled the underlying dirty business for us, all the synchronization of CPU-GPU communication, all the VRAM allocation and management, all the resources binding details, we had even not taken a glimpse about them before! But it&amp;rsquo;s not as bad as I exaggerated, you just have to take care the additional steps which you don&amp;rsquo;t obligate to do previously, and if you succeeded you&amp;rsquo;d not only get more code in your repo but also a tremendous performance boost in your applications.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s forget about the API problems for a couple few minutes and take a look back at the hardware architecture to better understand the physical structure that triggered the API &amp;ldquo;revolution&amp;rdquo;. The actual memory management relies on the hardware memory &lt;em&gt;bus&lt;/em&gt; (it&amp;rsquo;s part of the I/O &lt;em&gt;bridge&lt;/em&gt;) and the MMU - &lt;strong&gt;M&lt;/strong&gt;emory &lt;strong&gt;M&lt;/strong&gt;anagement &lt;strong&gt;U&lt;/strong&gt;nit, they work together to transfer data between the processor and different external adapters to RAM, and mapping physical memory address to a virtual one. So when you want to load some data from your HDD to RAM, the data would travel through the I/O bridge to CPU and then after some parsing processes it would be stored into RAM. If you had a performance-focused attitude when writing codes, you may wonder is there any optimizations for usage cases like simply loading an executable binary file to RAM, which doesn&amp;rsquo;t require any additional processing to the data itself. And yes, we had DMA - &lt;strong&gt;D&lt;/strong&gt;irect &lt;strong&gt;M&lt;/strong&gt;emory &lt;strong&gt;A&lt;/strong&gt;ccess! With DMA the data doesn&amp;rsquo;t need to travel through CPU anymore and instead, it would be loaded directly from the HDD to RAM.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1568620011534.drawio.svg" alt="A closer look at the Processor-Memory communication model"&gt;&lt;/p&gt;
&lt;p&gt;As we could imagine, CPU and GPU could have individual RAMs and MMUs and Memory Buses, thus they could execute and load-store data into their RAMs individually. That&amp;rsquo;s perfect, two kingdoms live peacefully with each other. But the problems emerge as soon as they start to communicate, the data needs to be transferred from the CPU side to GPU side or vice versa, and we need to build a &amp;ldquo;highway&amp;rdquo; for it. One of the &amp;ldquo;highway&amp;rdquo; hardware communication protocol that widely used today is PCI-E, I&amp;rsquo;d omit the detail instructions and focus on what we&amp;rsquo;d care about here. It&amp;rsquo;s basically another bus-like design and provides the functionality that we could transfer data in between different adapters, such as a dedicated graphics card and main memory. With its help, we could almost freely (sadly highway still need payment, it&amp;rsquo;s not a freeway yet) write something utilizing CPU and GPU together now.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1568621748534.drawio.svg" alt="Too many bridges (omit MMU and Memory Bus for simplification)!"&gt;&lt;/p&gt;
&lt;p&gt;The bridges are a little bit too many, isn&amp;rsquo;t it? If you remembered that I&amp;rsquo;ve briefly introduced a memory architecture called UMA before, it basically just looks like we merging RAM and VRAM together. Since its design requires the chip and memory manufacturers to produce such products, and until now I&amp;rsquo;ve never seen one in the customer hardware market, we can&amp;rsquo;t craft it by ourselves. But still, if you had an Xbox One or PS4 you&amp;rsquo;ve enjoyed the benefit of UMA.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1568624046705.drawio.svg" alt="UMA, Umami"&gt;&lt;/p&gt;
&lt;h2 id="heap-creation"&gt;Heap creation&lt;/h2&gt;
&lt;p&gt;So now it&amp;rsquo;s time to open your favorite IDE and &lt;code&gt;#include&lt;/code&gt; some headers. In D3D12, all the resources would resident inside some explicitly specified memory pools, and the responsibility to manage the memory pool belongs to the developer now. This is the how the interface&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;HRESULT&lt;/span&gt; &lt;span class="n"&gt;ID3D12Device&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CreateHeap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;D3D12_HEAP_DESC&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;REFIID&lt;/span&gt; &lt;span class="n"&gt;riid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;ppvHeap&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;comes. If you&amp;rsquo;re familiar with D3D11 or other Windows APIs in COM model you could easily understand the function signature style. It is made by the combination of a reference to a description structure instance, a COM object class&amp;rsquo;s GUID and a pointer to store the created object instance&amp;rsquo;s address. The return value of the function is the execution result.&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s take a look at the description structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;D3D12_HEAP_DESC&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;UINT64&lt;/span&gt; &lt;span class="n"&gt;SizeInBytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_PROPERTIES&lt;/span&gt; &lt;span class="n"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;UINT64&lt;/span&gt; &lt;span class="n"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAGS&lt;/span&gt; &lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;D3D12_HEAP_DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It apparently follows the consistent code style of D3D12 API, and here we get another property structure to fulfill in:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;D3D12_HEAP_PROPERTIES&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_TYPE&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_CPU_PAGE_PROPERTY&lt;/span&gt; &lt;span class="n"&gt;CPUPageProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_MEMORY_POOL&lt;/span&gt; &lt;span class="n"&gt;MemoryPoolPreference&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;UINT&lt;/span&gt; &lt;span class="n"&gt;CreationNodeMask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;UINT&lt;/span&gt; &lt;span class="n"&gt;VisibleNodeMask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;D3D12_HEAP_PROPERTIES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This structure would inform the device which kind of the physical memory should the heap refer to. Since the documentation of D3D12 is comprehensible enough, I&amp;rsquo;d rather not talk about too many things which have been listed there. When &lt;code&gt;D3D12_HEAP_TYPE Type&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;D3D12_HEAP_TYPE_CUSTOM&lt;/code&gt;, then the &lt;code&gt;D3D12_CPU_PAGE_PROPERTY CPUPageProperty&lt;/code&gt; should be always &lt;code&gt;D3D12_CPU_PAGE_PROPERTY_UNKNOWN&lt;/code&gt;, because the CPU accessibility of the heap has already been indicated by the &lt;code&gt;D3D12_HEAP_TYPE&lt;/code&gt; so you shouldn&amp;rsquo;t repeat the information; Similar reason, &lt;code&gt;D3D12_MEMORY_POOL MemoryPoolPreference&lt;/code&gt; should always be &lt;code&gt;D3D12_MEMORY_POOL_UNKNOWN&lt;/code&gt; when &lt;code&gt;D3D12_HEAP_TYPE Type&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;D3D12_HEAP_TYPE_CUSTOM&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In UMA architecture, there is only one physical memory pool which is both shared by CPU and GPU, the most common case is that you got an Xbox One and start to write some D3D12 games on it. In such case only &lt;code&gt;D3D12_MEMORY_POOL_L0&lt;/code&gt; is available and thus we don&amp;rsquo;t need to take care of it at all.&lt;/p&gt;
&lt;p&gt;The most of the desktop PC with a dedicated graphics card are NUMA memory architecture (although recent years there are something like AMD&amp;rsquo;s hUMA appeared and gone), in such case &lt;code&gt;D3D12_MEMORY_POOL_L0&lt;/code&gt; is the RAM and &lt;code&gt;D3D12_MEMORY_POOL_L1&lt;/code&gt; is the VRAM.&lt;/p&gt;
&lt;p&gt;So now if we set the heap type to &lt;code&gt;D3D12_HEAP_TYPE_CUSTOM&lt;/code&gt;, then we could have a more flexible control over the heap configuration. I&amp;rsquo;ll list a chart below that how different combination of &lt;code&gt;D3D12_CPU_PAGE_PROPERTY&lt;/code&gt; and &lt;code&gt;D3D12_MEMORY_POOL&lt;/code&gt; would finally look like on NUMA architectures.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;NOT_AVAILABLE&lt;/th&gt;
					&lt;th&gt;WRITE_COMBINE&lt;/th&gt;
					&lt;th&gt;WRITE_BACK&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;L0&lt;/td&gt;
					&lt;td&gt;Similar as &lt;code&gt;D3D12_HEAP_TYPE_DEFAULT&lt;/code&gt;, a GPU access-only RAM (but a little bit non-sense configuration for common usage cases)&lt;/td&gt;
					&lt;td&gt;Similar as &lt;code&gt;D3D12_HEAP_TYPE_UPLOAD&lt;/code&gt;, it is uncached for CPU read operation so the reading result won&amp;rsquo;t always stay coherent but write operation is faster because now the memory ordering is trivial and irrelevant, perfect for GPU to read&lt;/td&gt;
					&lt;td&gt;Similar as &lt;code&gt;D3D12_HEAP_TYPE_READBACK&lt;/code&gt;, all the GPU write operation would be cached and CPU read operation would get a coherent and consistent result&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;L1&lt;/td&gt;
					&lt;td&gt;Similar as &lt;code&gt;D3D12_HEAP_TYPE_DEFAULT&lt;/code&gt;, a GPU access-only VRAM&lt;/td&gt;
					&lt;td&gt;Invalid, CPU can&amp;rsquo;t access VRAM directly&lt;/td&gt;
					&lt;td&gt;Invalid, CPU can&amp;rsquo;t access VRAM directly&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It looks like that we don&amp;rsquo;t need a custom heap property structure on NUMA architectures (or single engine/single adapter case), all possible heap types have been already provided by the pre-defined types, there is not too much space for us to maneuver in order to get some advanced optimization. But if your application wants any better customization for all the possible hardware that it would run on, then using custom heap properties is still worth enough to investigate.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1568936661204.drawio.svg" alt="The Processor-Memory model in D3D12"&gt;&lt;/p&gt;
&lt;p&gt;And finally, we had a misc flag mask to indicate the detailed usage of the heap:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;D3D12_HEAP_FLAGS&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_NONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_SHARED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_DENY_BUFFERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_ALLOW_DISPLAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_HARDWARE_PROTECTED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Depends on the specific &lt;code&gt;D3D12_RESOURCE_HEAP_TIER&lt;/code&gt; that different hardware support, some certain &lt;code&gt;D3D12_HEAP_FLAGS&lt;/code&gt; are not allowed to use alone or combine together. The furthermore detail is well documented on the official website so I&amp;rsquo;ll not discuss them here. Because some of the enums are just the alias to the others, the actual possible heap flags are less than how many it is defined, and I&amp;rsquo;ll list a chart below to demonstrate different usage cases and the corresponding flags.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;Tier1&lt;/th&gt;
					&lt;th&gt;Tier2&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;All resource types&lt;/td&gt;
					&lt;td&gt;Not supported&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES&lt;/code&gt; or &lt;code&gt;D3D12_HEAP_FLAG_NONE&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Buffer only&lt;/td&gt;
					&lt;td&gt;`D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES&lt;/td&gt;
					&lt;td&gt;D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES`&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Non-RT/DS texture only&lt;/td&gt;
					&lt;td&gt;`D3D12_HEAP_FLAG_DENY_BUFFERS&lt;/td&gt;
					&lt;td&gt;D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES`&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;RT/DS texture only&lt;/td&gt;
					&lt;td&gt;`D3D12_HEAP_FLAG_DENY_BUFFERS&lt;/td&gt;
					&lt;td&gt;D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES`&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Swap-chain surface only&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;D3D12_HEAP_FLAG_ALLOW_DISPLAY&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Same as Tier1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Shared heap (multi-process)&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;D3D12_HEAP_FLAG_SHARED&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Same as Tier1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Shared heap (multi-adapter)&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Same as Tier1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Memory write tracking&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Same as Tier1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Atomic primitive&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Same as Tier1&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;As you can see above, the only meaningful difference between Tier1 and Tier2 here is that Tier2 support a &lt;code&gt;D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES&lt;/code&gt; flag thus we could put all the common resources into one heap. It again depends on what specific task you would like to finish, sometimes you want an all-in-one heap, sometimes it&amp;rsquo;s better to separate them into different heaps by the usage cases.&lt;/p&gt;
&lt;h1 id="resource-creation"&gt;Resource creation&lt;/h1&gt;
&lt;p&gt;After you created a heap successfully, you could start to create resources inside it now. There are 3 different ways to create a resource:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create resource which has only virtual address inside the already created heap, it requires us to map to the physical address manually later. &lt;code&gt;ID3D12Device::CreateReservedResource&lt;/code&gt; is the interface for such a task;&lt;/li&gt;
&lt;li&gt;Create resource which has both virtual address and mapped physical address inside the already created heap, the most commonly-used resources are this type. &lt;code&gt;ID3D12Device::CreatePlacedResource&lt;/code&gt; is the interface for such a task;&lt;/li&gt;
&lt;li&gt;Create placed-resource and an implicate heap at the same time. &lt;code&gt;ID3D12Device::CreateCommittedResource&lt;/code&gt; is the interface for such a task.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you don&amp;rsquo;t want to manually manage the heap memory at all, then you could choose to use committed-resource with some sacrifices to the performance, but naturally it&amp;rsquo;s not a good idea to stick with committed-resource heavily in the product code (unless you&amp;rsquo;re lazy like me who don&amp;rsquo;t want to write more code in show-case projects). The more mature choice is using placed-resources since we&amp;rsquo;ve already could create heaps, the only thing left that you have to do now is designing a heap memory management module with some efficient strategies. You could just use as many design patterns and architectures from the experience when you&amp;rsquo;re implementing the main RAM heap memory management system (still &lt;code&gt;malloc()&lt;/code&gt; inside 16ms? No way!). A ring buffer or a double buffer for Upload heap or some linked-list for Default heap or whatever, there are no limitations for the imagination, just analysis your application requirement and figure out a suitable solution (but don&amp;rsquo;t write a messy GC system for it:). There shouldn&amp;rsquo;t be too many choices since in the most D3D12 applications like a game, the most of the resources are CPU write-once and others are dynamic buffers which won&amp;rsquo;t occupy too much space but update frequently.&lt;/p&gt;
&lt;p&gt;The more advanced situation which rely on a tremendous memory size, such like mega-texture (maybe you need a 64x64 $km^2$ terrain albedo texture?) or sparse-tree volume textures (maybe you need a voxel-cone-traced irradiance volume?), which would index over the physical VRAM address easily or the actual texture size is beyond the maximum hardware support. In such cases a dynamic virtual memory address mapping technique is necessary. Developers intended to implement a software cache solution for this problem in the past because the APIs didn&amp;rsquo;t provide any reliable functionalities at that time (before D3D11.2 and OpenGL 4.4 which started to support tiled/sparse textures). The reserved-resources in D3D12 are the fresh new one-for-all solution today, it inherited the design of the tiled-resources architecture in D3D11 but also provided more flexibilities. But still, it depends on the hardware support when you wonder how to fit your elegant and complex SVOGI volume texture into the VRAM, it&amp;rsquo;s better to query &lt;code&gt;D3D12_TILED_RESOURCES_TIER&lt;/code&gt; and see if the target hardware support tiled-resource or not at first.&lt;/p&gt;</content:encoded><category>Rendering</category></item><item><title>So many descriptors in Vulkan</title><link>https://zhangdoa.pages.dev/so-many-descriptors-in-vulkan/</link><pubDate>Sun, 21 Apr 2019 10:53:00 +0000</pubDate><author>zhangandhang@gmail.com (Hang Zhang)</author><dc:creator>Hang Zhang</dc:creator><guid>https://zhangdoa.pages.dev/so-many-descriptors-in-vulkan/</guid><description>&lt;h2 id="brainwash-is-always-somewhere"&gt;Brainwash is always somewhere&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s really a mess when I started to port my engine to Vulkan, there are too many new data types that mapped to those came-from-nowhere concepts, which I don&amp;rsquo;t need to take care about previously. But luckily those concepts are well designed and once after you understand what they are, every pain you occurred would just disappear.&lt;/p&gt;
&lt;p&gt;The new generation graphics APIs all transport the responsibility of CPU-GPU communication to the user more or less explicitly, now if you want to ask GPU to do something for you, there won&amp;rsquo;t be any already defined API, which you just need to feed in some data from your CPU and memory then all the others would be handled by &amp;ldquo;somebody&amp;rdquo;.&lt;/p&gt;</description><content:encoded>&lt;h2 id="brainwash-is-always-somewhere"&gt;Brainwash is always somewhere&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s really a mess when I started to port my engine to Vulkan, there are too many new data types that mapped to those came-from-nowhere concepts, which I don&amp;rsquo;t need to take care about previously. But luckily those concepts are well designed and once after you understand what they are, every pain you occurred would just disappear.&lt;/p&gt;
&lt;p&gt;The new generation graphics APIs all transport the responsibility of CPU-GPU communication to the user more or less explicitly, now if you want to ask GPU to do something for you, there won&amp;rsquo;t be any already defined API, which you just need to feed in some data from your CPU and memory then all the others would be handled by &amp;ldquo;somebody&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say, GPU knows absolutely nothing what you want to do as always, and that &amp;ldquo;(ex)-somebody&amp;rdquo;, previously it&amp;rsquo;s your graphics card vendor&amp;rsquo;s implementation of those graphics API, they did the &amp;ldquo;trivial&amp;rdquo; underlying pipeline works for you. They all have gone now, you have to take care of all what it did for you before. Sounds like a fairly bad break-up!&lt;/p&gt;
&lt;p&gt;But what would you benefit from a break-up? (almost) Freedom. Now GPU is more like a general computing server, which exposure itself through the new generation of lower level APIs. As the client, we need to submit the computing work with a detailed enough work &lt;strong&gt;description&lt;/strong&gt;, and then keep feed in data and commands following with the description which we signed with GPU before. But in practice, the most work I did like lots of people who found this article is rendering, and for this purpose, these new APIs were designed still with lots of rendering related specific concepts (because GPU is still &amp;ldquo;Graphic&amp;rdquo; Process Unit today:)).&lt;/p&gt;
&lt;p&gt;But without considering the details, the whole bunch of things is easy to understand. What we need to do is just fit ourselves into the new CPU-GPU communication model. Create a work description, submit work, repeat, that&amp;rsquo;s all. Now it&amp;rsquo;s time to write the code, and you may want to say &amp;ldquo;whaaaaat&amp;rdquo; when you type &amp;ldquo;vk&amp;rdquo; inside your IDE if it has some kind of code autocomplete features. Yep, too many data types!&lt;/p&gt;
&lt;h2 id="ce-este"&gt;Ce este?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;A descriptor is an opaque data structure representing a shader resource such as a buffer, buffer view, image view, sampler, or combined image sampler. Descriptors are organised into descriptor sets, which are bound during command recording for use in subsequent draw commands&amp;hellip;&amp;rdquo; -13. Resource Descriptors, Vulkan® 1.1.106 - A Specification (with all published extensions)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you asked me what is the most beneficial thing I got from the journey of writing a game engine, I would answer, &amp;ldquo;Don&amp;rsquo;t Panic&amp;rdquo;. One headache thing when I play with Vulkan is the &lt;strong&gt;descriptor&lt;/strong&gt;s, I can&amp;rsquo;t catch the meaning of it at the very beginning, because with an OpenGL mindset there is no corresponding concept.&lt;/p&gt;
&lt;p&gt;But if you think in a fresh point of view, it&amp;rsquo;s really not a nonsense existence because we have to tell GPU the work description, like where are the resources, how shader will access them and in which kind of &lt;em&gt;&lt;strong&gt;view&lt;/strong&gt;&lt;/em&gt; since data are just some bytes inside the GPU memory. So, it&amp;rsquo;s really better to build a new mindset closer to the GPU pipeline.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;Descriptors are grouped together into descriptor set objects. A descriptor set object is an opaque object that contains storage for a set of descriptors, where the types and number of descriptors is defined by a descriptor set layout. The layout object may be used to define the association of each descriptor binding with memory or other hardware resources. The layout is used both for determining the resources that need to be associated with the descriptor set, and determining the interface between shader stages and shader resources&amp;hellip; -13.2. Descriptor Sets, Vulkan® 1.1.106 - A Specification (with all published extensions)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Actually, there isn&amp;rsquo;t a &amp;ldquo;descriptor&amp;rdquo; data type that we could interact with directly on the CPU side, as the specification said, it&amp;rsquo;s opaque. The workflow of creating the descriptors is designed as to create a combination of a &lt;strong&gt;set&lt;/strong&gt;(&lt;em&gt;VkDescriptorSet&lt;/em&gt;) handle, a buffer or image bound &lt;strong&gt;info&lt;/strong&gt;(&lt;em&gt;VkDescriptorBufferInfo/VkDescriptorImageInfo&lt;/em&gt;) and a write or copy &lt;strong&gt;operation&lt;/strong&gt;(&lt;em&gt;VkWriteDescriptorSet/VkCopyDescriptorSet&lt;/em&gt;). You acquire one &lt;strong&gt;set&lt;/strong&gt; instance from a &lt;strong&gt;pool&lt;/strong&gt;(&lt;em&gt;vkAllocateDescriptorSets&lt;/em&gt;), and all of the &lt;strong&gt;set&lt;/strong&gt; and &lt;strong&gt;pool&lt;/strong&gt; have their own characteristics or usage hints which you would specific before creating them. These characteristics are typically configured with the &lt;strong&gt;layout&lt;/strong&gt;(&lt;em&gt;VkDescriptorSetLayout&lt;/em&gt;) and the &lt;strong&gt;create info&lt;/strong&gt;(&lt;em&gt;VkDescriptorSetAllocateInfo&lt;/em&gt; and &lt;em&gt;VkDescriptorPoolCreateInfo&lt;/em&gt;). After you create the &lt;strong&gt;set&lt;/strong&gt; you have to provide the &lt;strong&gt;info&lt;/strong&gt; about what buffer or image it would bind to, and finally, update this information by a write or copy &lt;strong&gt;operation&lt;/strong&gt;(&lt;em&gt;vkUpdateDescriptorSets&lt;/em&gt;). The cons of a plain code example are it&amp;rsquo;s not so intuitive about the relation between data structure and functions, so I made a little flow graph to demonstrate.&lt;/p&gt;
&lt;script type="module"&gt;
 import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
 mermaid.initialize({ startOnLoad: false, securityLevel: 'loose' });
 mermaid.run();
 &lt;/script&gt;
&lt;pre class="mermaid"&gt;graph TD
	subgraph "create descriptor pool"
 VkDescriptorPoolSize-.-&gt;VkDescriptorPoolCreateInfo
 VkDescriptorPoolCreateInfo==vkCreateDescriptorPool==&gt;pool((VkDescriptorPool))
	end
	subgraph "create descriptor set layout"
 VkDescriptorSetLayoutBinding-.-&gt;VkDescriptorSetLayoutCreateInfo
	VkDescriptorSetLayoutCreateInfo==vkCreateDescriptorSetLayout==&gt;layout(VkDescriptorSetLayout)
 end
 subgraph "create descriptor set"
	layout-.-&gt;VkDescriptorSetAllocateInfo
	pool-.-&gt;VkDescriptorSetAllocateInfo
	VkDescriptorSetAllocateInfo==vkAllocateDescriptorSets==&gt;set(VkDescriptorSet)
 end
	subgraph "UBO"
	ubo(VkBuffer)-.-&gt;VkDescriptorBufferInfo
	end
	subgraph "Sampler"
	sampler(VkSampler)-.-&gt;VkDescriptorImageInfo
	end
	subgraph "update descriptor set"
	set-.-&gt;writeCopySets(VkWriteDescriptorSet/VkCopyDescriptorSet)
	VkDescriptorBufferInfo-.-&gt;writeCopySets
	VkDescriptorImageInfo-.-&gt;writeCopySets
	writeCopySets--vkUpdateDescriptorSets--&gt;device(VkDevice)
 end
	subgraph "create pipeline layout"
	layout-.-&gt;VkPipelineLayoutCreateInfo
	VkPipelineLayoutCreateInfo==vkCreatePipelineLayout==&gt;pipelineLayout(VkPipelineLayout)
	end
	subgraph "create pipeline"
	pipelineLayout-.-&gt;VkGraphicsPipelineCreateInfo
	VkGraphicsPipelineCreateInfo==vkCreateGraphicsPipelines==&gt;pipeline(VkPipeline)
	end
&lt;/pre&gt;
&lt;p&gt;All the thick line indicate the real object instance is created by a function invocation, while the dotted line means the dependency between the info. I omitted the other dependencies of creating a VkPipeline since they are not related to the topic I&amp;rsquo;m talking about.&lt;/p&gt;
&lt;p&gt;Actually now you may start to feel Vulkan has a really clean architecture model, indeed it is. We now have a far more flexible possibility that we could have as many descriptors in many different descriptor sets in many descriptor pools with many different combinations of configurations. One thing that breaks a little bit of the name convention is the &lt;code&gt;VkWriteDescriptorSet&lt;/code&gt; and &lt;code&gt;VkCopyDescriptorSet&lt;/code&gt;, they should and only could be created by user directly and submit later rather than acquire from the vKDevice (I was thinking why there isn&amp;rsquo;t a &lt;code&gt;VkWriteDescriptorSetCreateInfo&lt;/code&gt; kind stuff but it would be too redundant to create a &lt;code&gt;VkWriteDescriptorSet&lt;/code&gt;, because after all, it&amp;rsquo;s about an operation around the descriptor, or maybe better call it &lt;code&gt;VkDescriptorSetWriteOp&lt;/code&gt;?).&lt;/p&gt;
&lt;h2 id="some-usage-cases"&gt;Some usage cases&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s have a look at some code examples, which are all coming from some real scenarios I occurred before.&lt;/p&gt;
&lt;h3 id="single-ubo-data-accessed-per-shader-stage"&gt;Single UBO data accessed per shader stage&lt;/h3&gt;
&lt;p&gt;I have a UBO for main camera related data like the projection matrix, only update once per frame, the GLSL code like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row_major&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;uniform&lt;/span&gt; &lt;span class="n"&gt;cameraUBO&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;mat4&lt;/span&gt; &lt;span class="n"&gt;uni_p_camera_original&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then it&amp;rsquo;s better to answer some questions before creating the descriptor-related data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How many descriptors will we have inside the pool? Only one.&lt;/li&gt;
&lt;li&gt;Which kind of resource type it will be used for? For uniform buffer.&lt;/li&gt;
&lt;li&gt;How many different type and number of descriptors will be allocated from this pool? Only one type and only descriptor will it hold.&lt;/li&gt;
&lt;li&gt;How many sets it could hold at all? Only one.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorPoolSize&lt;/span&gt; &lt;span class="n"&gt;l_poolSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorPoolCreateInfo&lt;/span&gt; &lt;span class="n"&gt;l_poolInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poolSizeCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pPoolSizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l_poolSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxSets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;VkDescriptorPool&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorPool&lt;/span&gt; &lt;span class="n"&gt;l_pool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;vkCreateDescriptorPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_poolInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_pool&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Where it will be bound to with the shader? Binding point 0.&lt;/li&gt;
&lt;li&gt;How many descriptors will be bound? Only one.&lt;/li&gt;
&lt;li&gt;Which shader stage could access it? Vertex shader.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorSetLayoutBinding&lt;/span&gt; &lt;span class="n"&gt;l_setLayoutBinding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_setLayoutBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_setLayoutBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_setLayoutBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_setLayoutBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pImmutableSamplers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_setLayoutBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stageFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_SHADER_STAGE_VERTEX_BIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorSetLayoutCreateInfo&lt;/span&gt; &lt;span class="n"&gt;l_layoutCreateInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_layoutCreateInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_layoutCreateInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bindingCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_layoutCreateInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pBindings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l_setLayoutBinding&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;VkDescriptorSetLayout&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorSetLayout&lt;/span&gt; &lt;span class="n"&gt;l_setLayout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;vkCreateDescriptorSetLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_layoutCreateInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_setLayout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;VkDescriptorSet&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorSetAllocateInfo&lt;/span&gt; &lt;span class="n"&gt;l_allocInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_allocInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_allocInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorPool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l_pool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_allocInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorSetCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_allocInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pSetLayouts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_setLayout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorSet&lt;/span&gt; &lt;span class="n"&gt;l_set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;vkAllocateDescriptorSets&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;m_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_allocInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Which resource it will be bound to? A UBO.&lt;/li&gt;
&lt;li&gt;Where to bind? Binding point 0.&lt;/li&gt;
&lt;li&gt;Which &lt;code&gt;VkDescriptorSet&lt;/code&gt; that the write operation targeted at? The one we just created.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorBufferInfo&lt;/span&gt; &lt;span class="n"&gt;l_bufferInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_bufferInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m_cameraUBO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// created from somewhere else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_bufferInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_bufferInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CameraGPUData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkWriteDescriptorSet&lt;/span&gt; &lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dstBinding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dstSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l_set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dstArrayElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pBufferInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_bufferInfo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;vkUpdateDescriptorSets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="n"&gt;m_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// VkDevice handle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// only 1 VkWriteDescriptorSet
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then when I need to submit command, the only thing left is a call to &lt;code&gt;vkCmdBindDescriptorSets&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;vkCmdBindDescriptorSets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;m_commandBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;VK_PIPELINE_BIND_POINT_GRAPHICS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;m_pipelineLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the first set is set 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// and one set only
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_descriptorSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="array-ubo-data-accessed-per-shader-stage"&gt;Array UBO data accessed per shader stage&lt;/h3&gt;
&lt;p&gt;My punctual light data is inside an array which contains all the information and will be updated to GPU once per frame, but I&amp;rsquo;ll iterate through the array for a deferred style light pass inside the fragment shader. The GLSL code like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define MAX_POINT_LIGHT 64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// w component of luminance is attenuationRadius&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;pointLight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;vec4&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;vec4&lt;/span&gt; &lt;span class="n"&gt;luminance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;//float attenuationRadius;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;uniform&lt;/span&gt; &lt;span class="n"&gt;pointLightUBO&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;pointLight&lt;/span&gt; &lt;span class="n"&gt;uni_pointLights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MAX_POINT_LIGHT&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The only things change in C++ code is the binding point and the buffer range, since it&amp;rsquo;s an array.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define MAX_POINT_LIGHT 64
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorSetLayoutBinding&lt;/span&gt; &lt;span class="n"&gt;l_setLayoutBinding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_setLayoutBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorBufferInfo&lt;/span&gt; &lt;span class="n"&gt;l_bufferInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_bufferInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m_pointLightUBO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// created from somewhere else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_bufferInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_bufferInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PointLightGPUData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;MAX_POINT_LIGHT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// the total size of the UBO array
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkWriteDescriptorSet&lt;/span&gt; &lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_writeDescriptorSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dstBinding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The command submit is exactly the same as the previous example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;vkCmdBindDescriptorSets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;m_commandBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;VK_PIPELINE_BIND_POINT_GRAPHICS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;m_pipelineLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the first set is set 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// and one set only
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_descriptorSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="array-ubo-data-accessed-per-draw-call-object"&gt;Array UBO data accessed per draw call object&lt;/h3&gt;
&lt;p&gt;The local-to-world space transformation matrix needs to be updated per object, and for this what we could use is the Dynamic Uniform Buffer (forget your glUpdateUniform* things!), I&amp;rsquo;ll update a UBO array per frame which contain all the drawable meshes information, and will use an offset to access the corresponding part per draw call later.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row_major&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;uniform&lt;/span&gt; &lt;span class="n"&gt;meshUBO&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;mat4&lt;/span&gt; &lt;span class="n"&gt;uni_m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now in C++ code, I need to specify a different descriptor type, also a different binding point because I bind other resources at binding point 0, but it&amp;rsquo;s trivial.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorPoolSize&lt;/span&gt; &lt;span class="n"&gt;l_poolSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorSetLayoutBinding&lt;/span&gt; &lt;span class="n"&gt;l_layoutBinding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_layoutBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_layoutBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorBufferInfo&lt;/span&gt; &lt;span class="n"&gt;l_bufferInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_bufferInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m_meshUBO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_bufferInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_bufferInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MeshGPUData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since it would be accessed in a dynamic favor, the buffer range is per block rather than the whole UBO data array.&lt;/p&gt;
&lt;p&gt;And when bind the DescriptorSet, we need to specify the dynamic offset, I use such an implementation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;l_blockSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MeshGPUData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;total_meshes_this_frame&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;l_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l_blockSize&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;vkCmdBindDescriptorSets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;m_commandBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;VK_PIPELINE_BIND_POINT_GRAPHICS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;m_pipelineLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the first set is set 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// and one set only
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_descriptorSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Now we have one dymanic offset
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_offset&lt;/span&gt; &lt;span class="c1"&gt;// the offset value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;// draw call
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="multiple-array-ubo-data-accessed-per-draw-call-object"&gt;Multiple array UBO data accessed per draw call object&lt;/h3&gt;
&lt;p&gt;This is the combination of the previous situations, the mesh UBO and material UBO is related to each draw call, but the camera UBO is one frame one update, but still, we could achieve this.&lt;/p&gt;
&lt;p&gt;The Vertex shader looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row_major&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;uniform&lt;/span&gt; &lt;span class="n"&gt;cameraUBO&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;mat4&lt;/span&gt; &lt;span class="n"&gt;uni_p_camera_original&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row_major&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;uniform&lt;/span&gt; &lt;span class="n"&gt;meshUBO&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;mat4&lt;/span&gt; &lt;span class="n"&gt;uni_m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While the fragment shader looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;uniform&lt;/span&gt; &lt;span class="n"&gt;materialUBO&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;vec4&lt;/span&gt; &lt;span class="n"&gt;uni_albedo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;vec4&lt;/span&gt; &lt;span class="n"&gt;uni_MRAT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the C++ code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How many descriptors will we have inside the pool? Now 3.&lt;/li&gt;
&lt;li&gt;Which kind of resource type it will be used for? For normal uniform buffer and dynamic uniform buffer.&lt;/li&gt;
&lt;li&gt;How many different type and number of descriptors will be allocated from this pool? 2 types 3 descriptors.&lt;/li&gt;
&lt;li&gt;How many sets it could hold at all? Now, still 1.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorPoolSize&lt;/span&gt; &lt;span class="n"&gt;l_cameraUBOPoolSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorPoolSize&lt;/span&gt; &lt;span class="n"&gt;l_meshUBOPoolSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorPoolSize&lt;/span&gt; &lt;span class="n"&gt;l_materialUBOPoolSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_cameraUBOPoolSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_cameraUBOPoolSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_meshUBOPoolSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_meshUBOPoolSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_materialUBOPoolSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_materialUBOPoolSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descriptorCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorPoolSize&lt;/span&gt; &lt;span class="n"&gt;l_UBOPoolSizes&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;l_cameraUBOPoolSize&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l_meshUBOPoolSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l_materialUBOPoolSize&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VkDescriptorPoolCreateInfo&lt;/span&gt; &lt;span class="n"&gt;l_poolInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poolSizeCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pPoolSizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l_UBOPoolSizes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;l_poolInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxSets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Other parts are all the same, just change the binding points and the buffer info. And when bind the &lt;code&gt;DescriptorSet&lt;/code&gt;, the dynamic offset is an array now:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;l_meshDataBlockSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MeshGPUData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;l_materialDataBlockSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MeshGPUData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;total_meshes_this_frame&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;l_meshOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l_meshDataBlockSize&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;l_materialOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l_materialDataBlockSize&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;l_offsets&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;l_meshOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l_materialOffset&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;vkCmdBindDescriptorSets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;m_commandBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;VK_PIPELINE_BIND_POINT_GRAPHICS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;m_pipelineLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the first set is set 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// and one set only
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Now we have two dymanic offsets
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l_offsets&lt;/span&gt; &lt;span class="c1"&gt;// the offset value array
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;// draw call
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it! The sampler descriptor is similar to uniform buffer, all the rules work as well as what I applied above. Freedom means more responsibility and caution, but we now have more possibility to optimize the whole rendering pipeline to a new level of efficiency, I would keep investigating how to utilize the power of Vulkan in a more concise way, after all, it&amp;rsquo;s quite a more complex API than OpenGL!&lt;/p&gt;</content:encoded><category>Rendering</category></item><item><title>Normal and normal mapping</title><link>https://zhangdoa.pages.dev/normal-and-normal-mapping/</link><pubDate>Sun, 18 Nov 2018 01:34:00 +0000</pubDate><author>zhangandhang@gmail.com (Hang Zhang)</author><dc:creator>Hang Zhang</dc:creator><guid>https://zhangdoa.pages.dev/normal-and-normal-mapping/</guid><description>&lt;h2 id="a-not-tedium-work"&gt;A (not) tedium work&lt;/h2&gt;
&lt;p&gt;Recently I started to port my project to DirectX 11, and it has a lot of interesting differences with OpenGL such like the coordinates and matrix convention, stronger type safety requirement, better shader resources management (I didn&amp;rsquo;t try OpenGL&amp;rsquo;s SSBO yet but the constant buffer is really easier to wrap into an elegant layer) and etc. One thing I stuck a little is the normal mapping there since I used the on-the-fly tangent generation in GLSL, it quite confuses me at first when I rewrote it in HLSL.&lt;/p&gt;</description><content:encoded>&lt;h2 id="a-not-tedium-work"&gt;A (not) tedium work&lt;/h2&gt;
&lt;p&gt;Recently I started to port my project to DirectX 11, and it has a lot of interesting differences with OpenGL such like the coordinates and matrix convention, stronger type safety requirement, better shader resources management (I didn&amp;rsquo;t try OpenGL&amp;rsquo;s SSBO yet but the constant buffer is really easier to wrap into an elegant layer) and etc. One thing I stuck a little is the normal mapping there since I used the on-the-fly tangent generation in GLSL, it quite confuses me at first when I rewrote it in HLSL.&lt;/p&gt;
&lt;h2 id="always-review"&gt;Always Review&lt;/h2&gt;
&lt;p&gt;Basically the normal vector is interpreted as one unit &lt;strong&gt;direction&lt;/strong&gt; vector who is perpendicular with a surface (or mathematically speaking in general, normal vector $\vec{n}$ is one gradient vector of the gradient $\nabla f = \left\langle {{f_x},{f_y},{f_z}} \right\rangle$ of a scalar field $f\left( {x,y,z} \right) = k$ in a vector space at a certain point $\left( {{x_0},{y_0},{z_0}} \right)$, who is orthogonal/&lt;strong&gt;normal&lt;/strong&gt; always with the field), and with this surface normal data in hand we could achieve some old-style flat shading which only gives us some discrete results of the surface color. Later &lt;a href="https://collections.lib.utah.edu/details?id=706615" target="_blank" rel="external noopener noreferrer nofollow"&gt;Gouraud&lt;/a&gt; (maybe not him) invented the vertex normal, as the average of the surface normals where the vertex is in, it could give a smoother shading result with the nature of interpolation in the pixel processing stage.&lt;/p&gt;
&lt;p&gt;Typically we would get the precomputed normal vector of the models from those DCC tools such as Blender 3DS Max or Maya (or generated with some cross-product on CPU side &lt;a href="https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal" target="_blank" rel="external noopener noreferrer nofollow"&gt;by your own&lt;/a&gt;), and they are stored in the local space of the model. Then if we want to render anything with its help in our world space (strictly speaking finally in screen space), we need to transform these normal vectors.&lt;/p&gt;
&lt;p&gt;The first idea is just to use the model&amp;rsquo;s local to world transformation matrix to transform the normal vector since it represents a direction, its w component is 0 in the homogeneous 4D space then the translation part (the last column of transformation matrix) won&amp;rsquo;t have any effect on it. And the rotation part is what we want to apply, but if we scaled an un-unified extension to the model, then the normal vector would be sheared too, the direction changes! So we need to figure out how to cancel the unwanted scaling only on normal vectors, multiple solutions here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Multiply with the inverse of the scale matrix $N_{ws} = S^{-1} * M * N_{ls}$;&lt;/li&gt;
&lt;li&gt;Without multiply the transformation matrix at the first, instead, just multiply a rotation matrix with local space normals $N_{ws} = R * N_{ls}$;&lt;/li&gt;
&lt;li&gt;A classic trick called &amp;ldquo;invert transpose normal matrix&amp;rdquo;, which means use the transpose of the inverse of the model&amp;rsquo;s local to world transformation matrix to multiply with the local space normals $N_{ws} = M^{-1T} * N_{ls}$. Since it looks less intuitive, you may ask why this works, well, with the inverse operation we could cancel the scaling, and because the scaling part is in the diagonal of the matrix (the coordinate system&amp;rsquo;s axes actually), so transpose operation didn&amp;rsquo;t affect anything. And since rotation matrix is an orthogonal matrix its inverse is its transpose, then the transpose operation just inverse again the rotation part, as a conclusion means we first invert it, then transpose it, same as invert it and invert again, which means nothing happened to the rotation part! Let&amp;rsquo;s write it in formula: $M^{-1T} = S^{-1T} * R^{-1T} * T^{-1T}$, since $S^{-1T} = S^{-1}, R^{-1} = R^{T}$, then $M^{-1T} = S^{-1} * R * T^{-1T}$. In practice we often shrink the 4x4 matrix to 3x3, just use the upper left part, which removes the useless translation components as $M_{3 \times 3}^{-1T} = S^{-1} * R$, and with this matrix, we could achieve our goal.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since most of the rendering pipelines(as far as I know) are designed to deal with scene transform on the CPU side, and always passes the already multiplied transformation matrix to GPU, then the method 3 is used commonly. But for HLSL it doesn&amp;rsquo;t provide a native inverse function like GLSL, that means whether you have to build a special hard-coded version for this purpose or inverse it previously on the CPU side. And with respect to this little cons the method 2 looks like another good choice because you don&amp;rsquo;t need any inverse operation on the GPU, the cost changes to an additional cbuffer data passed to GPU. Currently, I choose to use method 2, unless it hits me with some painful issues I think I won&amp;rsquo;t change to method 3 in DirectX.&lt;/p&gt;
&lt;h2 id="messy-micro"&gt;Messy micro&lt;/h2&gt;
&lt;p&gt;And then the normal mapping became a little bit annoyed. The technique to store the geometry detail as the offsets in surface/vertex&amp;rsquo;s tangent space was invented by &lt;a href="https://dl.acm.org/citation.cfm?id=507101" target="_blank" rel="external noopener noreferrer nofollow"&gt;Blinn&lt;/a&gt; in the &amp;rsquo;70s, which we called normal mapping commonly now, it is one kind of bump mapping techniques which could give a huge improvement about the surfaces detail without adding more vertices to hurt the performance. Since the common practice is storing normal texture in tangent space, unless we could get the tangent space-to-model&amp;rsquo;s local/world space transformation, we can&amp;rsquo;t apply it to our vertex normals. This means we need to construct a space that treats the vertex normals as the positive Z axis, then transform the normal texture data to world space, or transform the other light/position data to tangent space with its inverse matrix. There are still some different approaches:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Use precomputed tangent vector combining with normal vector to calculate the bitangent vector, and use these 3 as the axes to construct a tangent/TBN space;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compute tangent vector on-the-fly by using texture UV coordinates and vertices data. Since the edge direction of the triangle could be calculated by the vertex position, and at the same time it also could be constructed by the TBN space&amp;rsquo;s T and N axis and the UV coordinates, then we could combine them together to get the T and N, in formula it is $\vec{E} = \vec{p_1} - \vec{p_2}$, $\vec{E} = \Delta \vec{U}T + \Delta \vec{V}B$, then this is a solvable linear algebra problem.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In practical, if we choose method 1, then we have to precompute the tangent vector and store it offline somewhere, then passed to GPU in real-time. And if we choose method 2, we need to calculate it on CPU side then send to GPU, basically, it has no implementation difference with method 1. I chose method 2 and implement it on shader directly, rather than the original idea to compute it on CPU, it would spend less bandwidth and make the vertex data structure tighter. The idea comes from a nice &lt;a href="http://www.thetenthplanet.de/archives/1180" target="_blank" rel="external noopener noreferrer nofollow"&gt;blog post&lt;/a&gt;, it utilizes the built-in shader partial derivative functions to calculate the screen space gradient, then does some cross-product to get the final results. But since OpenGL users commonly use RHS and DirectX users commonly use LHS, also because of the different windows/texture coordinate system of them, it confused me a lot at the beginning.&lt;/p&gt;
&lt;p&gt;The texture coordinates of OpenGL starts from &lt;strong&gt;bottom-left&lt;/strong&gt; corner, and when submitting 2D texture data array (1D in memory) to OpenGL, it would fill the texture buffer from the &lt;strong&gt;bottom-left&lt;/strong&gt; corner to top; In DirectX the texture coordinates starts from &lt;strong&gt;top-left&lt;/strong&gt; corner, and DirectX texture buffer would be filled from &lt;strong&gt;top-left&lt;/strong&gt; corner to bottom. That means if you sample a texture data with &lt;strong&gt;same&lt;/strong&gt; coordinates, OpenGL and DirectX would return the &lt;strong&gt;same&lt;/strong&gt; results. And this means actually for every loaded textures data, I don&amp;rsquo;t need to change anything related with UV coordinates. The misinterpretation of this led me to a wrong UV convert parser at the first.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1543161259202.drawio.svg" alt="Diagram"&gt;&lt;/p&gt;
&lt;p&gt;Then the partial derivative functions, this is easier to map from OpenGL to DirectX, the ddx_fine() and ddy_fine() are exactly same as dFdx() and dFdy(). But then the T and B axes construction is a little bit tricky, I choose to use LHS in DirectX, then have to flip the T and B when the other implementation details are as same as OpenGL. Also, the final TBN 3x3 matrix in OpenGL and DirectX would be as the transpose to each other but meanwhile staying unified with each other&amp;rsquo;s math convention, so I just need to follow the matrix-vector multiplication rules on each side as usual and always.&lt;/p&gt;
&lt;p&gt;Here are some code pieces about:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;// get edge vectors of the pixel triangle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;dp1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dFdx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thefrag_WorldSpacePos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;dp2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dFdy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thefrag_WorldSpacePos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;duv1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dFdx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thefrag_TexCoord&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;duv2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dFdy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thefrag_TexCoord&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;// solve the linear system&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thefrag_Normal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 	&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;dp2perp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 	&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;dp1perp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dp1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 	&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp2perp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;duv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dp1perp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;duv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 	&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp2perp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;duv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dp1perp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;duv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;mat3&lt;/span&gt; &lt;span class="n"&gt;TBN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mat3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;WorldSpaceNormal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TBN&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uni_normalTexture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thefrag_TexCoord&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;rgb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;// get edge vectors of the pixel triangle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;dp1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddx_fine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thefrag_WorldSpacePos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;dp2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddy_fine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thefrag_WorldSpacePos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;duv1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddx_fine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thefrag_TexCoord&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;duv2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddy_fine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thefrag_TexCoord&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="c1"&gt;// solve the linear system&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thefrag_Normal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;dp2perp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;dp1perp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dp1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp2perp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;duv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dp1perp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;duv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp2perp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;duv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dp1perp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;duv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;float3x3&lt;/span&gt; &lt;span class="n"&gt;TBN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;float3x3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;normalInWorldSpace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t2d_normal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SampleTypeWrap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thefrag_TexCoord&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;rgb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TBN&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded><category>Rendering</category></item><item><title>Physically Based Rendering - Material</title><link>https://zhangdoa.pages.dev/physically-based-rendering-material/</link><pubDate>Sun, 12 Aug 2018 17:36:00 +0000</pubDate><author>zhangandhang@gmail.com (Hang Zhang)</author><dc:creator>Hang Zhang</dc:creator><guid>https://zhangdoa.pages.dev/physically-based-rendering-material/</guid><description>&lt;p&gt;I learned the shading magic like how the most of others did, from a classic Blinn-Phong model to some more complex models like Cook-Torrance model, but the understanding would always be confined around the common practices shaped by our slow speed computer, and our eager desires for the more realistic results. With these compensations, tricks, and hacks we may achieve lots of cool and amazing stuff at first, but if we don&amp;rsquo;t have a clear bird&amp;rsquo;s-eye view (for example people like me who is always suffering among the formulae), then we would lose ourselves inside just shader code and strange artifacts often and can&amp;rsquo;t find the solutions without painful testing and doubting.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I learned the shading magic like how the most of others did, from a classic Blinn-Phong model to some more complex models like Cook-Torrance model, but the understanding would always be confined around the common practices shaped by our slow speed computer, and our eager desires for the more realistic results. With these compensations, tricks, and hacks we may achieve lots of cool and amazing stuff at first, but if we don&amp;rsquo;t have a clear bird&amp;rsquo;s-eye view (for example people like me who is always suffering among the formulae), then we would lose ourselves inside just shader code and strange artifacts often and can&amp;rsquo;t find the solutions without painful testing and doubting.&lt;/p&gt;
&lt;p&gt;Then I was thinking, why not implement and learn something from the basic of the physics again? Let&amp;rsquo;s forget about a little what I&amp;rsquo;ve already written and start to construct a shading pipeline from scratch, following the physical interpretations, then seeking for solutions to run between each 16 ms. Let&amp;rsquo;s go back to the start.&lt;/p&gt;
&lt;h2 id="so-what-is-light"&gt;So, what is light?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;In physical optics, light is modeled as an &lt;em&gt;electromagnetic transverse wave&lt;/em&gt;, a wave
that oscillates the electric and magnetic fields perpendicularly to the direction of its
propagation&amp;hellip;&amp;rdquo; - pg. 293, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(The book already has become the bible of me, it&amp;rsquo;s a nice working reference, a balanced textbook and a well-formed dictionary, I&amp;rsquo;ll quote &lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;a lot&lt;/span&gt; from it later in this ctrl-c + ctrl-v style post (Why not?))&lt;/p&gt;
&lt;p&gt;Light, a kind of &lt;strong&gt;electromagnetic wave&lt;/strong&gt; whose property shaped by its &lt;strong&gt;wavelengths&lt;/strong&gt; majorly, is one of the fundamental beings in our universe, and all of our beautiful rendering works would focus on how to calculate the &lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;physical transmission&lt;/span&gt; of it always. But what about to think in this way, just because it could be received by our eyes (&lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;a psychophysical phenomenon&lt;/span&gt;) then it had a special name as &amp;ldquo;light&amp;rdquo;, and if we could treat it not so special in our theoretical discussion would something become easier and more generally to handle with?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say, the basic electromagnetic mechanism works well too with light, all and no exceptions (of course it is), then we could just model a general solution in the computer, and feed it with the light wavelengths $\lambda$ we emitted from some origins, and other relating properties of some objects which stops the light propagation to calculate the result (may also contain the influences from the space they are in later, now let&amp;rsquo;s just work inside an ideal static vacuum). The classic physics model of the electromagnetic mechanism is described by &lt;a href="https://en.wikipedia.org/wiki/Maxwell%27s_equations" target="_blank" rel="external noopener noreferrer nofollow"&gt;Maxwell&amp;rsquo;s equations&lt;/a&gt; successfully, and with it we could figure out how the electric field propagating through space, thus means to get the &lt;em&gt;correct&lt;/em&gt; phenomenon of the electromagnetic wave in our simulation box.&lt;/p&gt;
&lt;p&gt;But unfortunately, we don&amp;rsquo;t have such huge computational resources to run a field propagation simulation in real-time inside our personal computer, representing a non-analytical electric field also requires a 3D data structure at least, we must allow some compensations. Luckily, physicists have already simplified the scope of the problems for us, instead of using the original electromagnetic field related methods, we could try to use the &lt;em&gt;radiation&lt;/em&gt; methods to focus on the &lt;strong&gt;energy&lt;/strong&gt; property only, and this would bring us a simplification of only considering about 2 things, the emitter and the receiver of the electromagnetic wave.&lt;/p&gt;
&lt;h2 id="a-new-measurement"&gt;A new measurement&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;Light waves are emitted when the electric charges in an object oscillate. Part of
the energy that caused the oscillations—heat, electrical energy, chemical energy—is
converted to light energy, which is radiated away from the object. &amp;hellip;&amp;rdquo; - pg. 296, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then we would arrive at &lt;em&gt;Radiometry&lt;/em&gt;, which &lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;deals with the measurement of electromagnetic radiation&lt;/span&gt;. For the common usage, there are some radiometric quantities which represent the measurement of the electromagnetic radiation energy with respects to other basic physical quantities such like time/distance/area/angle, I&amp;rsquo;ll list some of them below which are more important to our rendering business, with the reference from &lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;the book&lt;/span&gt; and &lt;a href="https://en.wikipedia.org/wiki/Radiometry" target="_blank" rel="external noopener noreferrer nofollow"&gt;Wikipedia&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Quantity&lt;/th&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;Unit&lt;/th&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;Notes&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Name&lt;/td&gt;
					&lt;td&gt;Symbol&lt;/td&gt;
					&lt;td&gt;Name&lt;/td&gt;
					&lt;td&gt;Symbol&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Radiant energy&lt;/td&gt;
					&lt;td&gt;${Q_e}$&lt;/td&gt;
					&lt;td&gt;joule&lt;/td&gt;
					&lt;td&gt;${J}$&lt;/td&gt;
					&lt;td&gt;Energy of electromagnetic radiation&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Radiant flux&lt;/td&gt;
					&lt;td&gt;${\Phi_e}$&lt;/td&gt;
					&lt;td&gt;joule per second or watt&lt;/td&gt;
					&lt;td&gt;${\frac{J}{s}}$ or ${W}$&lt;/td&gt;
					&lt;td&gt;also called Radiant power&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Radiant intensity&lt;/td&gt;
					&lt;td&gt;${I_e}$&lt;/td&gt;
					&lt;td&gt;watt per steradian&lt;/td&gt;
					&lt;td&gt;${\frac{W}{sr}}$&lt;/td&gt;
					&lt;td&gt;steradian is similar with angle in 2D space&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Irradiance Flux density&lt;/td&gt;
					&lt;td&gt;${M_e}$ or ${E_e}$&lt;/td&gt;
					&lt;td&gt;watt per square metre&lt;/td&gt;
					&lt;td&gt;${\frac{W}{m^2}}$&lt;/td&gt;
					&lt;td&gt;&lt;em&gt;Ir&lt;/em&gt;-radiance, means the radiance received by a surface&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Radiance&lt;/td&gt;
					&lt;td&gt;${L_e}$&lt;/td&gt;
					&lt;td&gt;watt per steradian per square metre&lt;/td&gt;
					&lt;td&gt;${\frac{W}{m^2·sr}}$&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We won&amp;rsquo;t directly calculate Radiant energy, because usually we would handle with some objects which has &lt;strong&gt;shape&lt;/strong&gt; and some events which happens during a &lt;strong&gt;period&lt;/strong&gt;, it&amp;rsquo;s more convenient to cancel these quantities at first, that means we better choose to use Radiant intensity, Irradiance Flux density and Radiance.&lt;/p&gt;
&lt;p&gt;To evaluate how a light source emitted light or energy, we need to define what is a light source at first. In this blog post, I would choose to discuss the ideal punctual light source only, which has an infinity small shape, the same as a point in space. Also, I would idealize it with an omnidirectional radiation characteristic, which means the radiation wouldn&amp;rsquo;t variant around different steradians.&lt;/p&gt;
&lt;p&gt;We could deduce the definition of Radiant Intensity first, imagine a &lt;strong&gt;unit&lt;/strong&gt; sphere surrounding the point light source, a unit steradian would emit some energy per second, then $I_e = \frac{d\Phi}{d\omega}$.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1543478575023.drawio.svg" alt="Diagram"&gt;&lt;/p&gt;
&lt;p&gt;Similar, we could get Irradiance Flux density (or call it &lt;strong&gt;Radiant exitance&lt;/strong&gt; if we want to emphasize it&amp;rsquo;s more about emitting rather than receiving, but it assumes we use some area light sources), $M_e = \frac{d\Phi}{dA}$.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1543478989212.drawio.svg" alt="Diagram"&gt;&lt;/p&gt;
&lt;p&gt;And then Radiance is $L_e = \frac{d\Phi}{d\omega} * \frac{d\Phi}{dA \cos\theta} = \frac{d^2\Phi}{ d\omega dA \cos\theta}$, we now need to consider about the angle between the surface normal and the unit steradian because the actual effective area is not same as the original unit area, it is &lt;em&gt;projected&lt;/em&gt; ($A\cos\theta$ is called projected area), so here we add a cos to it. This kind of cos-weighted distribution is called &lt;a href="https://en.wikipedia.org/wiki/Lambert%27s_cosine_law" target="_blank" rel="external noopener noreferrer nofollow"&gt;Lambert&amp;rsquo;s cosine law&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1543479097755.drawio.svg" alt="Diagram"&gt;&lt;/p&gt;
&lt;h2 id="through-time-and-space-once-and-forever"&gt;Through time and space, once and forever?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;The oscillating electrical field pushes and pulls at the electrical
charges in the matter, causing them to oscillate in turn. The oscillating charges emit
new light waves, which redirect some of the energy of the incoming light wave in new
directions. This reaction, called &lt;em&gt;scattering&lt;/em&gt;, is the basis of a wide variety of optical
phenomena. &amp;hellip;&amp;rdquo; - pg. 297, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If we emit any light/energy in vacuum, it would never change the direction and the energy unless meets with some obstructions (for example some molecules or small dust floating in space or some large giant gas planets like Saturn, or our body/eyes), then it would be absorbed or scattered due to the characteristics of the obstructions. Actually, this is all what we need to care about, the obstructions &lt;em&gt;IS&lt;/em&gt; exactly the receiver! Then if we could model how the receiver it is we would solve the problem. But again, the limitation of our computational resources doesn&amp;rsquo;t allow us to simulate every molecule, instead, we have to follow some macro scope rules to abstract them. We call these single or group of trivial shape objects which influence the wave propagation as &lt;em&gt;particles&lt;/em&gt;, and the volume the particles fulfilled with as &lt;em&gt;media&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;We live on Earth where the air and water are the most common noticeable medium, who gives us an inspiration for aesthetic creations for thousands of years. For to measure how they influence the light propagation, we need to define a kind of ratio between the original light and the affected light.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;The ratio of the phase velocities of the original and new waves defines an optical
property of the medium called the &lt;em&gt;index of refraction&lt;/em&gt; (IOR) or refractive index,
denoted by the letter $n$. Some media are &lt;em&gt;absorptive&lt;/em&gt;. They convert part of the light
energy to heat, causing the wave amplitude to decrease exponentially with distance.
The rate of decrease is defined by the &lt;em&gt;attenuation index&lt;/em&gt;, denoted by the Greek letter
$\kappa$ (kappa). Both n and $\kappa$ typically vary by wavelength. Together, these two numbers
fully define how the medium affects light of a given wavelength, and they are often
combined into a single complex number $n + i \kappa$, called the &lt;em&gt;complex index of refraction&lt;/em&gt;. &amp;hellip;&amp;rdquo; - pg. 298, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As &lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;the book&lt;/span&gt; introduced, we use complex IOR to represent the characteristics of a media, but in practice of local illumination, we&amp;rsquo;d often only care about the real number part $n$, since the attenuation happens around the conductor medium more and we typically imply we would treat the air as our original media (or in the volume rendering business, but I won&amp;rsquo;t cover about that topic here). Then we could simply get IOR by $n = \frac{c}{v}$, where $c$ is the light speed in vacuum and $v$ is the light speed in the media. For a further detailed discussion about complex IOR, I recommend this &lt;a href="https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/#more-1921" target="_blank" rel="external noopener noreferrer nofollow"&gt;post&lt;/a&gt; by Sébastien Lagarde, which he talked around the situations that covering all the other possible medium interfaces.&lt;/p&gt;
&lt;p&gt;And then we would have a physical law called Snell&amp;rsquo;s law, which relates the incident angle with refracted angle by IOR of two mediums, written as $\sin(\theta_t) = \frac{n_1}{n_2}\sin(\theta_i)$, where $\theta_i$ is the angle between the interface normal and the incident light direction, $\theta_t$ as the angle between the inverse of the interface normal and the refracted/&lt;em&gt;transmitted&lt;/em&gt; light direction. &lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;We denote the index of refraction on the “outside” (the side where the incoming, or incident, wave originates) as &lt;span&gt;&lt;span style="color: inherit;"&gt;&lt;/span&gt;&lt;span style="font-size: 100%; display: inline-block;"&gt;&lt;img style="vertical-align: middle; max-width: 100%; height: auto; border: 0;" src="https://app.yinxiang.com/shard/s18/res/bcec0d8e-f392-44bc-aa9f-e7e0cd4299c6/c265ad38d170dee9df5191cfef57be30.png" name="bcec0d8e-f392-44bc-aa9f-e7e0cd4299c6" class="en-media"&gt;&lt;/span&gt;&lt;/span&gt; and the index of refraction on the “inside” (where the wave will be transmitted after passing through the surface) as &lt;span&gt;&lt;span style="color: inherit;"&gt;&lt;/span&gt;&lt;span style="font-size: 100%; display: inline-block;"&gt;&lt;img style="vertical-align: middle; max-width: 100%; height: auto; border: 0;" src="https://app.yinxiang.com/shard/s18/res/aac6fe9b-7b21-491f-ae38-d2f40d1f6b2c/82f426df9519d93188b5481181949bae.png" name="aac6fe9b-7b21-491f-ae38-d2f40d1f6b2c" class="en-media"&gt;&lt;/span&gt;&lt;/span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1543509296989.drawio.svg" alt="Diagram"&gt;&lt;/p&gt;
&lt;p&gt;Now if we know the angles (here we abstract the light to a single &lt;em&gt;monochromatic&lt;/em&gt; beam, which has &amp;ldquo;angle&amp;rdquo; and a single wavelength, but actually we&amp;rsquo;ve talked before how it is in real situation) and the IOR of the medium, could we calculate anything useful to display on the screen? Well, with Snell&amp;rsquo;s law we could figure out the direction change of the light, but we didn&amp;rsquo;t know how much energy would change, also if the light is not monochromatic, we don&amp;rsquo;t know which range of wavelengths would be reflected or refracted. And the most important problem is, we are receiving light through our eyes, but not always directly from the light source, what we want to receive are those light &amp;ldquo;reflected&amp;rdquo; from different surfaces, rather than those directly emitted from the sun or the bulbs!&lt;/p&gt;
&lt;p&gt;So actually, we need to figure out such a scenario: Light is emitted from a source, meets with some surfaces and changes, then somehow travels into our eyes. When the light &amp;ldquo;hit&amp;rdquo; the surface we then could try to apply Snell&amp;rsquo;s law. But Snell&amp;rsquo;s law is just about how light changes at the medium interface in a very ideal situation, we don&amp;rsquo;t know what would happen next, since the conservation of energy always work in this universe (until the moment I wrote this line it is still valid), the reflected light must be weaker than the incident light, but how much? Also, where the refracted light part goes?&lt;/p&gt;
&lt;p&gt;That requires us to give further information about how light continuously traveling. As I mentioned before, media is made up by particles, then the size of particles and the distance of particles should have an influence on the light transmission. But since it&amp;rsquo;s impossible to calculate everything happened inside the media, we choose to model the region around the surface of the media only which has much more contributions to the light into our eyes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;In rendering, we typically use &lt;em&gt;geometrical optics&lt;/em&gt;, which ignores wave effects such as
interference and diffraction. This is equivalent to assuming that all surface irregularities
are either smaller than a light wavelength or much larger. &amp;hellip;&amp;rdquo; - pg. 303, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;surface irregularities much larger than a wavelength
change the local orientation of the surface. When these irregularities are too small
to be individually rendered—in other words, smaller than a pixel—we refer to them
as &lt;em&gt;microgeometry&lt;/em&gt;. &amp;hellip;&amp;rdquo; - pg. 304, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;For rendering, rather than modeling the microgeometry explicitly, we treat it statistically
and view the surface as having a random distribution of microstructure
normals. As a result, we model the surface as reflecting (and refracting) light in a
continuous spread of directions. The width of this spread, and thus the blurriness of
reflected and refracted detail, depends on the statistical variance of the microgeometry
normal vectors—in other words, the surface microscale &lt;em&gt;roughness&lt;/em&gt;. &amp;hellip;&amp;rdquo; - pg. 304, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;The book&lt;/span&gt; introduced the microgeometry theory, which is balanced between the computational burden and the credibility of the result when it was adopted into real-time rendering community. Since our screen has limited discrete pixels, then it&amp;rsquo;s meaningless and wasting to calculate anything too accurately, instead we choose to use some statistical models for a cheaper routine. You may have heard some statistical methods used in rendering like the most famous Monte Carlo method before, here we would follow the similar path to figure out how to get the final light.&lt;/p&gt;
&lt;h2 id="the-rendering-equation"&gt;The Rendering Equation&lt;/h2&gt;
&lt;p&gt;Now we could introduce an almost accurate representation of the interaction between light and surfaces. For a single light in wavelength $\lambda$ at time $t$ who hits the surface in a unit area $A$ from a unit steradian $\omega_i$, and is &amp;ldquo;changed&amp;rdquo; by the surface then finally &amp;ldquo;re-emitted&amp;rdquo; to our eyes by an unit steradian $\omega_o$, we could write such a formula: $L_o(A,\omega_o,\lambda,t) = f_r L_i(A,\omega_i,\lambda,t)$, which describe the incident radiance is weighted by a factor $f_r$ who indicates the surface optic characteristic then contributes to the surface irradiance. Then we could do an integration for every possible incident direction of different light around the hemisphere centered at the surface normal, combine with the original emitted light from the surface itself, to get a more general formula. One summarization formula for rendering in this context is &lt;a href="https://en.wikipedia.org/wiki/Rendering_equation" target="_blank" rel="external noopener noreferrer nofollow"&gt;The Rendering Equation&lt;/a&gt;, $L_o(p,\omega_o,\lambda,t) = L_e(p,\omega_o,\lambda,t) + \int\limits_{\Omega} f_r(p,\omega_i,\omega_o,\lambda,t) L_i(p,\omega_i,\lambda,t) (n \cdot \omega_i) d\omega_i$, basically it is the same transcript of what I talked before, but with a simplification that we ignore the surface area, just minimize it to a point $p$.&lt;/p&gt;
&lt;p&gt;Further more, we would like to treat the light-surface interaction as an time-domain individual event, then we could omit $t$. And because we would finally send a RGB color space data to the screen, so a continuous spectral irradiance is fairly unnecessary, then we would like to also cancel the spectrum by replacing it with 3 individual similar formulae, which have same form but care only about each color channels, thus we could just write one instead as The Reflectance Equation: $L_o(p,\omega_o) = \int\limits_{\Omega} f_r(p,\omega_i,\omega_o) L_i(p,\omega_i) (n \cdot \omega_i) d\omega_i$.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip;Local reflectance
is quantified by the &lt;em&gt;bidirectional reflectance distribution function&lt;/em&gt; (BRDF), denoted
as $f(l, v)$. &amp;hellip;&amp;rdquo; - pg. 310, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now if we know how the incident light they are, then we just need to give a $f_r$ weight who described the entire optic characteristic of the media and would get the final results. This $f_r(p,\omega_i,\omega_o)$, like &lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;the book&lt;/span&gt; introduced, is called BRDF, combining with the microgeometry method I listed above, we would have chance to finally write some codes to calculate. But at first, let&amp;rsquo;s take a look back at the surface model we used here, we say &amp;ldquo;surface&amp;rdquo; not &amp;ldquo;interface&amp;rdquo;, the &amp;ldquo;surface&amp;rdquo; indicates we are inspecting in a region around the &amp;ldquo;interface&amp;rdquo;, so it should have more properties than what Snell&amp;rsquo;s law could describe. Let&amp;rsquo;s take a look:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://zhangdoa.pages.dev/attachments/1543560653216.drawio.svg" alt="Diagram"&gt;&lt;/p&gt;
&lt;p&gt;As the media won&amp;rsquo;t always absorb and scatter all the refracted light inside (for example most of the non-conductors won&amp;rsquo;t, since there are too less free electrons inside to do the business), some of them would leave the media and enter the previous media again, which we called this kind of phenomenon as Subsurface Scattering. A typical practice is to separate the BRDF to 2 parts, a reflection part as &lt;strong&gt;specular&lt;/strong&gt; and a &lt;em&gt;local&lt;/em&gt; subsurface scattering part as &lt;strong&gt;diffuse&lt;/strong&gt;. I would like to use the notation as $f_s$ and $f_d$ later.&lt;/p&gt;
&lt;p&gt;Also, sometimes we need to calculate more general subsurface scattering, then we would use a &lt;em&gt;bidirectional scattering distribution function&lt;/em&gt; (abbr. BSSRDF) instead, and for the light who travel through the entire media and leave in another surface it becomes &lt;em&gt;bidirectional transmittance distribution function&lt;/em&gt; (abbr. BTDF). Together they are called as BxDF.&lt;/p&gt;
&lt;p&gt;To make a BxDF physically correct, we need to achieve 3 goals：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;$f_r(\omega_i,\omega_o) \ge 0$, a BxDF never results negative weight;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$f_r(\omega_i,\omega_o) = f_r(\omega_o,\omega_i)$, it&amp;rsquo;s called Helmholtz reciprocity, simply speaking means if we change the incident and the observe direction it should have the same result;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$\forall \omega_i, \int\limits_{\Omega}f_r(\omega_i,\omega_o)(n \cdot \omega_i) d\omega_i \le 1$, means we need to follow the energy conservation law, the weight should never exceed than 1, the &lt;em&gt;relative&lt;/em&gt; outgoing light energy should never exceed than the &lt;em&gt;relative&lt;/em&gt; incoming light energy.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In practice there is a kind of convenient way to evaluate whether a BxDF is energy conservation or not called &lt;a href="http://jcgt.org/published/0003/02/03/paper.pdf" target="_blank" rel="external noopener noreferrer nofollow"&gt;White furnace test&lt;/a&gt;, I&amp;rsquo;ll talk about it later.&lt;/p&gt;
&lt;h2 id="brdf"&gt;BRDF&lt;/h2&gt;
&lt;p&gt;BRDF would be thought as $f_r(\omega_i,\omega_o) = \frac{dL_o(\omega_o)}{L_i(\omega_i)\cos\theta_i d\omega_i}$, which gives us a possibility to measure it in real, for example &lt;a href="https://www.merl.com/brdf/" target="_blank" rel="external noopener noreferrer nofollow"&gt;MERL&lt;/a&gt; is one kind of BRDF database. Also, we could write BRDF as $f_r(\omega_i,\omega_o) = f_d(\omega_i,\omega_o) + f_s(\omega_i,\omega_o)$, to indicate that we would like to seperate BRDF to the specular and diffuse parts and solve them independently.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s use $n$ as the macro surface normal vector and $m$ as the micro surface normal vector, and $h\ = \frac{\omega_o + \omega_i}{||\omega_o + \omega_i||}$ as the normalized halfway vector of the view direction and light direction.&lt;/p&gt;
&lt;p&gt;Also, for the sake of convenience to discuss BRDF more practically, I&amp;rsquo;d like to introduce the &lt;em&gt;Directional albedo&lt;/em&gt; which &lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;measures the amount of light coming from a given direction that is reflected at all, into any outgoing direction in the hemisphere around the surface normal&lt;/span&gt;, in formula as $R_s = \int\limits_{\Omega}f(l, v)(n·s)ds$, if the BRDF is Helmholtz reciprocal then could substitute $s$ with $l$ or $v$ freely.&lt;/p&gt;
&lt;h3 id="diffuse-part"&gt;Diffuse part&lt;/h3&gt;
&lt;h4 id="simple-lambert-model"&gt;Simple Lambert model&lt;/h4&gt;
&lt;p&gt;$f_{Lambert} = \frac{\rho} {\pi}$, $\rho$ as the &amp;ldquo;color&amp;rdquo; of the surface, strictly speaking it is the subsurface scattering part of the surface irradiance under a particular lighting circumstance; $\pi$ comes from the fact that we treat the surface as the &lt;a href="https://en.wikipedia.org/wiki/Lambertian_reflectance" target="_blank" rel="external noopener noreferrer nofollow"&gt;Lambertian&lt;/a&gt; surface, thus it won&amp;rsquo;t change due to the view and light direction, then the BRDF integral over the hemisphere yield it.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s visualize its directional albedo: (BRDF Explorer/Octave WIP)&lt;/p&gt;
&lt;p&gt;This simple Lambert diffuse model would ignore the surface micro scope variation, combines with the Lambert&amp;rsquo;s cosine law we could already get $L_o(\omega_o) = \int\limits_{\Omega}\frac{\rho} {\pi}L_i(\omega_i)\cos\theta_i d\omega_i$, since the integral of direction $\omega_i$ over the hemisphere is $\pi$, then it would cancel the $\pi$ in the BRDF, so finally we would just get $L_o(\omega_o) = \rho \cos\theta_i$, exactly the same as what we learned first in the real-time rendering class 101!&lt;/p&gt;
&lt;h4 id="cook-torrance-model"&gt;Cook-Torrance model&lt;/h4&gt;
&lt;p&gt;The lack of micro scope detail of the simple Lambert model limits us to get further realistic results, luckily as the researchers and scientists work on it for decades, we&amp;rsquo;ve already had some advanced replacements of the simple Lambert model, one of them which is used most commonly today in real-time rendering community is the microgeometry theory, we&amp;rsquo;d refer it as &lt;em&gt;microfacet&lt;/em&gt; theory here.&lt;/p&gt;
&lt;p&gt;R.L. Cook and K. E. Torrance [CT82] wrote a paper in 80&amp;rsquo;s which is the root of the most popular adopted microfacet model today, with the other nice references [Hei14][LR14] we could conclude the general Cook-Torrance model as $f_{Cook-Torrance}=\frac{1}{|n·\omega_o||n·\omega_i|}\int\limits_{\Omega}f_m(\omega_o,\omega_i,m)G(\omega_o,\omega_i,m) D(m,\alpha)\langle \omega_o·m\rangle \langle \omega_i·m\rangle dm$. It emphasizes that the macro BRDF is correlated with the micro BRDF, and it could be calculated through the integral over the microfacet $m$, with the additional weight functions $G$ and $D$ to help keeping the micro-macro mapping relationship stay &lt;strong&gt;correct&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The $D$ function is called &lt;em&gt;Distribution function&lt;/em&gt; or &lt;em&gt;Normal Distribution Function&lt;/em&gt;(abbr. NDF), gives the spatial/statical distribution of the micro normal $m$ over the macro normal $n$, the $\alpha$ here is a user-controlled variable which describes how &amp;ldquo;rough&amp;rdquo; the surface it is, so we call it &lt;em&gt;roughness&lt;/em&gt; or &lt;em&gt;smoothness&lt;/em&gt; in practice (typically we&amp;rsquo;d like to build a non-linear mapping between the user-controlled roughness and the real $\alpha$). We would use statical functions in practice since it&amp;rsquo;s the only possible way to calculate in real-time, about how to mapping from spatial function to statical function I recommend to read this paper [Hei14] for detail understanding.&lt;/p&gt;
&lt;p&gt;The $G$ function is called &lt;em&gt;Geometry function&lt;/em&gt; or &lt;em&gt;Masking-shadowing function&lt;/em&gt;, but strictly speaking, we would better call it $V$ as &lt;em&gt;Visibility function&lt;/em&gt;, since the Geometry function is usually used to compose the Visibility function actually, but in literal it is used exchangeably often. It gives a weight about how the microfacets influence themselves by masking each other along the view direction and shadowing each other along the incident light direction, it should be deduced accordingly with the $D$ function we chose.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll list some common used $D$ and $G$ functions below:&lt;/p&gt;
&lt;h5 id="d-function"&gt;$D$ function&lt;/h5&gt;
&lt;h6 id="gaussian-d-function-ct82"&gt;Gaussian $D$ function [CT82]&lt;/h6&gt;
&lt;p&gt;$D_{Gaussian}=ce^{(-\alpha/m)^2}$&lt;/p&gt;
&lt;h6 id="beckmann-d-function-ct82"&gt;Beckmann $D$ function [CT82]&lt;/h6&gt;
&lt;p&gt;$D_{Beckmann}=\frac{1}{m^2\cos^4\alpha}e^{-[ ,(\tan\alpha)/m ] ,^2}$&lt;/p&gt;
&lt;p&gt;For these two $D$ functions, $c$ is an optional scaling factor, $\alpha$ as the angle between $n$ and $h$, $m$ as the RMS of the slope of the microfacet. Since they are computationally expensive, it&amp;rsquo;s rare to see them in real products, but we&amp;rsquo;d like to treat them as the offline reference sometimes.&lt;/p&gt;
&lt;h6 id="berry-d-function-bur12"&gt;Berry $D$ function [Bur12]&lt;/h6&gt;
&lt;p&gt;$D_{Berry}=\frac{c}{((n·h)^2(\alpha^2-1)+1)}$&lt;/p&gt;
&lt;h6 id="ggxtrowbridge-reitz-d-function-bur12"&gt;GGX/Trowbridge-Reitz $D$ function [Bur12]&lt;/h6&gt;
&lt;p&gt;$D_{TR}=\frac{c}{((n·h)^2(\alpha^2-1)+1)^2}$&lt;/p&gt;
&lt;h6 id="generalized-ggxtrowbridge-reitz-d-function-bur12"&gt;Generalized GGX/Trowbridge-Reitz $D$ function [Bur12]&lt;/h6&gt;
&lt;p&gt;$D_{GTR}=\frac{c}{((n·h)^2(\alpha^2-1)+1)^\gamma}$&lt;/p&gt;
&lt;p&gt;For these three $D$ functions, $c$ is an optional scaling factor, $\alpha$ is the roughness, $\gamma$ is an optional exponential factor. If we choose $\gamma=10$ it is fairly close to the Beckmann $D$ function. They are most commonly used $D$ functions as far as I know, gives a &amp;ldquo;long-tailed&amp;rdquo; visual appearance.&lt;/p&gt;
&lt;h5 id="g-function"&gt;$G$ function&lt;/h5&gt;
&lt;h6 id="cook-torrance-g-function-ct82"&gt;Cook-Torrance $G$ function [CT82]&lt;/h6&gt;
&lt;p&gt;$G_{cook-torrance} = \min{1,\frac{2(n·h)(n·\omega_o)}{(\omega_o·h)},\frac{2(n·h)(n·\omega_i)}{(\omega_o·h)}}$&lt;/p&gt;
&lt;p&gt;This one comes from the original paper itself but I haven&amp;rsquo;t seen it in any product level shader yet since it could be deduced to other more optimal versions.&lt;/p&gt;
&lt;h6 id="smith-g-function-smith67-hei14"&gt;Smith $G$ function [Smith67] [Hei14]&lt;/h6&gt;
&lt;p&gt;$G_{Smith}=\frac{\chi^+(\omega_o·\omega_m)}{1 + \Lambda(\omega_o)}$&lt;/p&gt;
&lt;p&gt;The original paper is not publicly available, so I list the deduced version from a later reference. Here the nominator $\chi^+(u)$ is a heavy-side function, when $u&amp;gt;0$ then $\chi^+(u)=1$, otherwise $\chi^+(u)=0$, it ensures the sidedness effect, while the $\Lambda()$ function is an integral over the slopes of the microsurface, which gives the masking probability. So, unless we provide a possible $\Lambda()$ function, this formula can&amp;rsquo;t be translated to a shader.&lt;/p&gt;
&lt;h6 id="schlick-g-function-sch94"&gt;Schlick $G$ function [Sch94]&lt;/h6&gt;
&lt;p&gt;$G_{Schlick}=\frac{n·\omega_o}{(n·\omega_o)(1-k)+k}·\frac{n·\omega_i}{(n·\omega_i)(1-k)+k}$&lt;/p&gt;
&lt;p&gt;$k$ is the user-controlled roughness, in practice we could remapping roughness to $\alpha=\frac{Roughness 1}{2}$ [Bur12], $k=\alpha^2/2$ [Kar13] to get a better non-linearity result. Schlick $G$ function is the approximation of Smith $G$ function in $[ ,0,1] ,$, which is kind friendly for our application scene because its parameter requirement is acceptable.&lt;/p&gt;
&lt;h6 id="height-correlated-smith-g-function-hei14-lr14"&gt;Height correlated Smith $G$ function [Hei14] [LR14]&lt;/h6&gt;
&lt;p&gt;$G_{CorrelatedSchlick}=\frac{\chi^+(\omega_o·h)\chi^+(\omega_i·h)}{1+\Lambda(\omega_o)+\Lambda(\omega_i)}$&lt;/p&gt;
&lt;p&gt;$\Lambda(m)=\frac{-1+\sqrt{1+\alpha^2\tan^2(\theta_m)}}{2}=\frac{-1+\sqrt{1+\frac{\alpha^2(1-\cos^2(\theta_m))}{\cos^2(\theta_m)}}}{2}$, because the microfacet would mask and shadow at the same time, then if we correlate the masking and shadowing parts with the respect of its height would bring some energy loss back. The detailed deduction is math heavily, I&amp;rsquo;d recommend reading the corresponding papers for further understanding.&lt;/p&gt;
&lt;h6 id="multi-scattering-smith-g-function-hhed16"&gt;Multi-scattering Smith $G$ function [HHED16]&lt;/h6&gt;
&lt;p&gt;All of the $G$ functions I listed above only take care of a single-scattering phenomenon, while in reality, the rougher surface would have more possibility to bounce light around different microfacets, it&amp;rsquo;s important to count these part of the energy. The paper [HHED16] gives a stochastic method based ground truth, and later [IW17] gives an implementable compensation way to achieve it, an alternative version of the general formula is given by &lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;the book&lt;/span&gt; as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&amp;hellip; $f_{ms}(l, v) = \frac{\overline F \overline {Rs_{F_1}}}{\pi(1-\overline {Rs_{F_1}})(1-\overline F(1-\overline {Rs_{F_1}}))}(1-Rs_{F_1}(l))(1-Rs_{F_1}(v))$ &amp;hellip;&amp;rdquo; - pg. 346, &amp;ldquo;&lt;strong&gt;Real-Time Rendering&lt;/strong&gt;&amp;rdquo;, 4th Edition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&amp;rsquo;s start to decompose this formula from the $F$ Fresnel function. As we discussed before, the Snell&amp;rsquo;s law could give us the direction of the transmitted light, and the ideal mirror reflection could give us the reflected light direction, but we don&amp;rsquo;t know how much energy is reflected and how much is transmitted, this is quite an annoy problem. But luckily it has been solved (with some restricted conditions) already in 19th century by French scientist Augustin-Jean Fresnel as &lt;a href="https://en.wikipedia.org/wiki/Fresnel_equations#Power_%28intensity%29_reflection_and_transmission_coefficients" target="_blank" rel="external noopener noreferrer nofollow"&gt;Fresnel equations&lt;/a&gt;. Long talk in short, since we only care about the ideal sandbox in our real-time compensations, we would look for a &lt;em&gt;nice&lt;/em&gt; enough Fresnel function to simulate this phenomenon. The Schlick Fresnel approximation is widely adopted nowadays, it&amp;rsquo;s has a form as $F_{Schlick}=F_0 + (F_{90} - F_0)(1-(\cos\theta))^5)$, $F_0$ is the specular reflectance from normal incidence, it represents the IOR when the view direction is perpendicular with the surface as $\omega_o \parallel n$; similar $F_{90}$ is the IOR when $\omega_o \perp n$, some of the implementations would assume $F_{90}$ is always 1, and it could cover most of common conductor/dielectric materials type. Finally $\cos\theta=n·\omega_o$ or $\cos\theta=n·\omega_i$, it&amp;rsquo;s another kind of cosine-weighted contribution appears here.&lt;/p&gt;
&lt;p&gt;The $\overline F$ here means the average of $F$ over all the different cosine of angles, if we just use the Schlick Fresnel approximation mentioned above, it could simply be calculated as $\overline F = \frac{20}{21}F_0 + \frac{1}{21}$.&lt;/p&gt;
&lt;p&gt;Then we&amp;rsquo;d move on to another average, $Rs_{F_1}$, it &lt;span style="color: #ff9800; background-color: rgba(255 , 152 , 0 , 0.1); border: 0; padding-top: 2px; padding-right: 4px; padding-bottom: 2px; padding-left: 4px;"&gt;is the directional albedo of &lt;span&gt;&lt;span style="color: inherit;"&gt;&lt;/span&gt;&lt;span style="font-size: 100%; display: inline-block;"&gt;&lt;img style="vertical-align: middle; max-width: 100%; height: auto; border: 0;" src="https://app.yinxiang.com/shard/s18/res/c89eec6f-bd51-4fe0-bc07-21906cdeda4c/595706026c587d25181c842682d01717.png" name="c89eec6f-bd51-4fe0-bc07-21906cdeda4c" class="en-media"&gt;&lt;/span&gt;&lt;/span&gt;, which is the specular BRDF term with &lt;span&gt;&lt;span style="color: inherit;"&gt;&lt;/span&gt;&lt;span style="font-size: 100%; display: inline-block;"&gt;&lt;img style="vertical-align: middle; max-width: 100%; height: auto; border: 0;" src="https://app.yinxiang.com/shard/s18/res/3f5925c5-2fe3-4448-a6e6-51aa94e2c765/0e48ccc96cd4ee189aa913fbfa33e9d4.png" name="3f5925c5-2fe3-4448-a6e6-51aa94e2c765" class="en-media"&gt;&lt;/span&gt;&lt;/span&gt; set to 1&lt;/span&gt;, which could be interpreted as the irradiance of a pure &lt;em&gt;white&lt;/em&gt; surface when illuminated by a unit directional light source, and &lt;a href="https://blog.selfshadow.com/2018/05/13/multi-faceted-part-1/" target="_blank" rel="external noopener noreferrer nofollow"&gt;Stephen Hill&lt;/a&gt; wrote a nice blog series about it. Basically, if we could implement the single-scattering BRDF, then could just use some discrete numerical integral method (like Importance sampling) to calculate it and save it to a look-up table, which exactly similar like what the split-summing technique of IBL applicated in [Kar13].&lt;/p&gt;
&lt;h4 id="use-simple-lambert-model-as-the-microfacet-brdf-in-cook-torrance-model-lr14"&gt;Use Simple Lambert model as the microfacet BRDF in Cook-Torrance model [LR14]&lt;/h4&gt;
&lt;p&gt;$f_m(\omega_o,\omega_i,m) = f_{Lambert} = \frac{\rho} {\pi}$ and then $f_{cook-torrance}=\frac{\rho}{\pi}\frac{1}{|n·\omega_o||n·\omega_i|}\int\limits_{\Omega}G(v,l,m) D(m,\alpha)\max(0,\omega_o·m)\max(0,\omega_i·m)dm$&lt;/p&gt;
&lt;p&gt;Still, no analysis solution, but gives a theoretical fundamental about the problem we need to solve, and unified all the problem inside one microsurface theory.&lt;/p&gt;
&lt;h4 id="oren-nayar-model-on94"&gt;Oren-Nayar model [ON94]&lt;/h4&gt;
&lt;p&gt;$f_{oren-nayar}=\frac{\rho} {\pi}(A+(B·\max(0, \cos (\omega_i - \omega_o))·\sin(\max(\omega_i, \omega_o))·tan(\min(\omega_i,\omega_o))))$
$A=1-\frac{\alpha}{2\alpha+0.66}$, $B=0.45\frac{\alpha}{\alpha+0.09}$ and $\alpha$ is the roughness, it&amp;rsquo;s an approximation of the general Cook-Torrance model, when $\alpha = 0$ we&amp;rsquo;ll get the Simple Lambert model. Could treat Oren-Nayar model as a kind of generalization of Simple Lambert model.&lt;/p&gt;
&lt;h4 id="disney-model-bur12"&gt;Disney model [Bur12]&lt;/h4&gt;
&lt;p&gt;Another advanced Lambert-based diffuse model which considers about Fresnel effect, $f_{disney}=\frac{\rho} {\pi}(1+(F_{d90}-1)(1-(n · \omega_i))^5)(1+(F_{d90}-1)(1-(n · \omega_o))^5)$,
or written as $f_{Disney}=\frac{\rho} {\pi}F_{Schlick}(1, F_{d90}, n, \omega_o)·F_{Schlick}(1, F_{d90}, n, \omega_i)$, here $F_{d90}=0.5+2(h·\omega_i)^2\alpha$, $\alpha$ is roughness.&lt;/p&gt;
&lt;h4 id="normalized-disney-model-lr14"&gt;Normalized Disney model [LR14]&lt;/h4&gt;
&lt;p&gt;For the sake of energy conservation, we could remapping the original Disney model to $[ ,0,1] ,$, then $f_{normalizedDisney}=c·\frac{\rho} {\pi}F_{Schlick}(1, F_{d90}, n, \omega_o)·F_{Schlick}(1, F_{d90}, n, \omega_i)$, $c=\frac{1}{1.51}+\frac{0.51}{1.51}\alpha$ it&amp;rsquo;s a scaling factor, I deduced it here for to better compare with the original version, and now $F_{d90}=0.5+(2(h·\omega_i)^2-0.5)\alpha$, $\alpha=Roughness^\gamma$, in practice the original paper chooses $\gamma=2$, and it find when $\gamma=4$ it&amp;rsquo;s almost near the result in [Sch14] where $\alpha=(0.3+0.7Roughness)^6$.&lt;/p&gt;
&lt;h3 id="specular-part"&gt;Specular part&lt;/h3&gt;
&lt;h4 id="phong-model"&gt;Phong model&lt;/h4&gt;
&lt;p&gt;$f_{Phong} = (r·\omega_o)^\alpha$, $r$ is the reflection direction of $\omega_i$, it&amp;rsquo;s the most famous and commonly used specular model in last few decades, and even programmed inside the graphics hardware, it needs the exponential factor $\alpha$ as the user-controlled parameter.&lt;/p&gt;
&lt;h4 id="normalized-phong-model"&gt;Normalized Phong model&lt;/h4&gt;
&lt;p&gt;$f_{normalizedPhong} = c·(r·\omega_o)^\alpha$, $c=\frac{\alpha+1}{2\pi}$, actually Phong model gives a $D$ function in a microsurface view of point, here $\alpha$ thus could be thought as the roughness.&lt;/p&gt;
&lt;h4 id="blinn-phong-model"&gt;Blinn-Phong model&lt;/h4&gt;
&lt;p&gt;$f_{Blinn-Phong} = (n·h)^\alpha$, an optimization of Phong model, in practice if we choose mapping $\alpha_{blinn-phong}=4\alpha_{phong}$ then Blinn-Phong model would looks like Phong model [Wiki1].&lt;/p&gt;
&lt;h4 id="normalized-blinn-phong-model"&gt;Normalized Blinn-Phong model&lt;/h4&gt;
&lt;p&gt;$f_{normalizedBlinn-Phong} = c·(n·h)^\alpha$, $c=\frac{\alpha+2}{4\pi(2-2^{\frac{-\alpha}{2}})}$.&lt;/p&gt;
&lt;h4 id="cook-torrance-model-ct82-hei14"&gt;Cook-Torrance model [CT82] [Hei14]&lt;/h4&gt;
&lt;p&gt;$f_{cook-torrance} = \frac{F(\omega_o, h , f_0, f_{90})D(h, \alpha)G(\omega_o, \omega_i, h)}{4|n·\omega_o||n·\omega_i|}$, the new kids (popular from ~2012) in town! Everything we&amp;rsquo;ve talked before, the denominator is deduced from the Jacobian Matrix when we change the space from the microfacet space to macro and makes it&amp;rsquo;s quite elegant, we use the microfacet theory but calculate in macro!&lt;/p&gt;
&lt;h3 id="some-sample-codes"&gt;Some sample codes&lt;/h3&gt;
&lt;p&gt;a. Simple Lambert model + Blinn-Phong model&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;CalcDirectionalLight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirLight&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;diffuse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;specular&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;viewPos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;fragPos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewPos&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fragPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ambient color&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;ambientColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diffuse&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.04&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// diffuse color&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;diffuseColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diffuse&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// specular color&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;specularColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;specular&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ambientColor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;diffuseColor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;specularColor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;b. Oren-Nayar model + Normalized Blinn-Phong model&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Oren-Nayar diffuse BRDF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;orenNayarDiffuse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;LdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LdotV&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;sigma2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sigma2&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigma2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.33&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.45&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sigma2&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigma2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.09&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;CalcDirectionalLight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirLight&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;diffuse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;specular&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;viewPos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;fragPos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewPos&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fragPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;LdotV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="c1"&gt;// ambient color&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;ambientColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diffuse&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.04&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// diffuse color&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orenNayarDiffuse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;diffuseColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diffuse&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Fd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// specular color&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;normalizedScaleFactor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;specularColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;specular&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;normalizedScaleFactor&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ambientColor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;diffuseColor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;specularColor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;c. Normalized Disney model + Cook-Torrance (specular) model, use $D_{TR}$+$G_{CorrelatedSchlick}$+$F_{Schlick}$ combination&lt;/p&gt;
&lt;p&gt;(reference Frostbite Engine [LR14])&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Frostbite Engine model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Specular/Diffuse BRDF Fresnel Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;Frostbite_fresnelSchlick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;f0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;f90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f90&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;f0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Diffuse BRDF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Frostbite_DisneyDiffuse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;LdotH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;earRoughness&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;energyBias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mo"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;linearRoughness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;energyFactor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;1.51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;linearRoughness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;fd90&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;energyBias&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;LdotH&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;LdotH&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;linearRoughness&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;f0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;lightScatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Frostbite_fresnelSchlick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;viewScatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Frostbite_fresnelSchlick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lightScatter&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;viewScatter&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;energyFactor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Specular BRDF Geometry Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Frostbite_V_SmithGGXCorrelated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;alphaG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;alphaG2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alphaG&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;alphaG&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Lambda_GGXV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;alphaG2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;alphaG2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Lambda_GGXL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;alphaG2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;alphaG2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Lambda_GGXV&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Lambda_GGXL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.00001&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Specular BRDF Distribution Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Frostbite_D_GGX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotH&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// remapping to Quadratic curve&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;m2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotH&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;m2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;NdotH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NdotH&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;m2&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;Frostbite_CalcDirectionalLightRadiance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirLight&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;albedo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;metallic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;viewPos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;fragPos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewPos&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fragPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;LdotH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Specular BRDF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;f90&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Frostbite_fresnelSchlick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LdotH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;G&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Frostbite_V_SmithGGXCorrelated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Frostbite_D_GGX&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;Fr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;G&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Diffuse BRDF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Frostbite_DisneyDiffuse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LdotH&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;roughness&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Fd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;albedo&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Fr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;d.Simple Lambert model + Cook-Torrance (specular) model, use $D_{TR}$+$G_{Schlick}$+$F_{Schlick}$ combination&lt;/p&gt;
&lt;p&gt;(reference from Unreal Engine 4[Kar13])&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-glsl" data-lang="glsl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Unreal Engine model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Specular BRDF Distribution Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Unreal_DistributionGGX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// remapping to Quadratic curve&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;a2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotH2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NdotH&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;NdotH&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;nom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;denom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotH2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;denom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;denom&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;denom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nom&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;denom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Specular BRDF Geometry Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Unreal_GeometrySchlickGGX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;roughness&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;nom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;denom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nom&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;denom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Unreal_GeometrySmith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;ggx2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Unreal_GeometrySchlickGGX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;ggx1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Unreal_GeometrySchlickGGX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ggx1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ggx2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Specular BRDF Fresnel Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;Unreal_fresnelSchlick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;cosTheta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;F0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cosTheta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ----------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;Unreal_CalcDirectionalLightRadiance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirLight&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;albedo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;metallic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;viewPos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;fragPos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewPos&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fragPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;HdotV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Specular BRDF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Unreal_fresnelSchlick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HdotV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;G&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Unreal_GeometrySmith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Unreal_DistributionGGX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roughness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;nominator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;G&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;float&lt;/span&gt; &lt;span class="n"&gt;denominator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NdotV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;specular&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nominator&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;denominator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.00001&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// for energy conservation &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;kS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;kD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;kS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;kD&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;metallic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;kD&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;albedo&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;specular&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NdotL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To be continued.&lt;/p&gt;
&lt;p&gt;Bibliography：&lt;/p&gt;
&lt;p&gt;[Bur12] B. Burley. “Physically Based Shading at Disney”. In: Physically Based Shading in Film and Game Production, ACM SIGGRAPH 2012 Courses. SIGGRAPH ’12. Los Angeles, California: ACM, 2012, 10:1–7. isbn: 978-1-4503-1678-1. doi: 10.1145/2343483.2343493. url: &lt;a href="http://selfshadow.com/publications/s2012-shading-course/" target="_blank" rel="external noopener noreferrer nofollow"&gt;http://selfshadow.com/publications/s2012-shading-course/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[CT82] R. L. Cook and K. E. Torrance. “A Reﬂectance Model for Computer Graphics”. In: ACM Trans. Graph. 1.1 (Jan. 1982), pp. 7–24. issn: 0730-0301. doi: 10.1145/357290.357293. url: &lt;a href="http://graphics.pixar.com/library/ReflectanceModel/" target="_blank" rel="external noopener noreferrer nofollow"&gt;http://graphics.pixar.com/library/ReflectanceModel/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[Hei14] E. Heitz. “Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs”. In: Journal of Computer Graphics Techniques (JCGT) 3.2 (June 2014), pp. 32–91. issn: 2331-7418. url: &lt;a href="http://jcgt.org/published/0003/02/03/" target="_blank" rel="external noopener noreferrer nofollow"&gt;http://jcgt.org/published/0003/02/03/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[HHED16] E. Heitz, J. Hanika, E. d’Eon, C. Dachsbacher, &amp;ldquo;Multiple-Scattering Microfacet BSDFs with the Smith Model&amp;rdquo;. In: ACM Transactions on Graphics (TOG) - Proceedings of ACM SIGGRAPH 2016, Volume 35 Issue 4, July 2016, ISSN: 0730-0301 E, ISSN: 1557-7368 doi&amp;gt;10.1145/2897824.2925943, url: &lt;a href="https://eheitzresearch.wordpress.com/240-2/" target="_blank" rel="external noopener noreferrer nofollow"&gt;https://eheitzresearch.wordpress.com/240-2/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[HTSG91] Xiao D. He, Kenneth E, Torrance, Frangois X. Sillion and Donald P. Greenberg. &amp;ldquo;A Comprehensive Physical Model for Light Reflection&amp;rdquo;. In: ACM SIGGRAPH Computer Graphics Homepage, Volume 25 Issue 4, July 1991, Pages 175-186, ACM New York, NY, USA, doi&amp;gt;10.1145/127719.122738, url: &lt;a href="https://www.graphics.cornell.edu/pubs/1991/HTSG91.pdf" target="_blank" rel="external noopener noreferrer nofollow"&gt;https://www.graphics.cornell.edu/pubs/1991/HTSG91.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[Kar13] B. Karis. “Real Shading in Unreal Engine 4”. In: Physically Based Shading in Theory and Practice, ACM SIGGRAPH 2013 Courses. SIGGRAPH ’13. Anaheim, California: ACM, 2013, 22:1–22:8. isbn: 978-1-4503-2339-0. doi: 10.1145/2504435.2504457. url: &lt;a href="http://selfshadow.com/publications/s2013-shading-course/" target="_blank" rel="external noopener noreferrer nofollow"&gt;http://selfshadow.com/publications/s2013-shading-course/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[LR14] S. Lagarde and C. de Rousiers. “Moving Frostbite to PBR”. In: Physically Based Shading in Theory and Practice, ACM SIGGRAPH 2014 Courses. SIGGRAPH ’14. Vancouver, Canada: ACM, 2014, 23:1–23:8. isbn: 978-1-4503-2962-0. doi: 10.1145/2614028.2615431. url: &lt;a href="http://www.frostbite.com/2014/11/moving-frostbite-to-pbr/" target="_blank" rel="external noopener noreferrer nofollow"&gt;http://www.frostbite.com/2014/11/moving-frostbite-to-pbr/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[Sch94] Schlick, Christophe, “An Inexpensive BRDF Model for Physically-based Rendering”, Computer Graphics Forum, vol.13, no.3, Sept.1994, pp.149–162. &lt;a href="http://dept-info.labri.u-bordeaux.fr/" target="_blank" rel="external noopener noreferrer nofollow"&gt;http://dept-info.labri.u-bordeaux.fr/&lt;/a&gt; ~Schlick/DOC/eur2.html&lt;/p&gt;
&lt;p&gt;[Sch14] N. Schulz. “Moving to the Next Generation - The Rendering Technology of Ryse”. In: Game Developers Conference. 2014.&lt;/p&gt;
&lt;p&gt;[Wiki1] &lt;a href="https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_shading_model" target="_blank" rel="external noopener noreferrer nofollow"&gt;https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_shading_model&lt;/a&gt;&lt;/p&gt;</content:encoded><category>Rendering</category></item></channel></rss>