Skip to content

Commit 92c7f7d

Browse files
committed
addressing more comments.
1 parent 7eaa47e commit 92c7f7d

File tree

3 files changed

+8
-278
lines changed

3 files changed

+8
-278
lines changed

en/Building_a_Simple_Engine/GUI/04_ui_elements.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,13 @@ ImGui requires descriptors for its font texture. Ensure your descriptor pool has
8888
----
8989
// Create descriptor pool with enough capacity for ImGui
9090
vk::DescriptorPoolSize poolSizes[] = {
91-
{ vk::DescriptorType::eCombinedImageSampler, 1000 },
91+
{ vk::DescriptorType::eCombinedImageSampler, 50 },
9292
// Other descriptor types...
9393
};
9494
9595
vk::DescriptorPoolCreateInfo poolInfo{};
9696
poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet;
97-
poolInfo.maxSets = 1000;
97+
poolInfo.maxSets = 50;
9898
poolInfo.poolSizeCount = static_cast<uint32_t>(std::size(poolSizes));
9999
poolInfo.pPoolSizes = poolSizes;
100100

en/Building_a_Simple_Engine/Lighting_Materials/02_lighting_models.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ One of the most widely used traditional lighting models, developed by Bui Tuong
6060
* *Disadvantages*: Not physically accurate, can look artificial under certain lighting conditions
6161
* *When to use*: For simple real-time applications where PBR is too expensive
6262

63-
For more information on the Phong lighting model, see the link:https://en.wikipedia.org/wiki/Phong_reflection_model[Wikipedia article] or this link:https://www.cs.utah.edu/~shirley/books/fcg2/rt.pdf[computer graphics textbook].
63+
For more information on the Phong lighting model, see the link:https://en.wikipedia.org/wiki/Phong_reflection_model[Wikipedia article].
6464

6565
==== Blinn-Phong Model
6666

@@ -149,7 +149,7 @@ Global Illumination (GI) simulates how light bounces between surfaces, creating
149149
* *Path Tracing*: Traces light paths through the scene
150150
* *Photon Mapping*: Stores light information in a spatial data structure
151151

152-
For more information, see this link:https://developer.nvidia.com/gpugems/gpugems2/part-ii-shading-lighting-and-shadows/chapter-12-tricks-real-time-radiosity[GPU Gems chapter on radiosity].
152+
For more information, see this link:https://developer.download.nvidia.com/books/HTML/gpugems2/chapters/gpugems2_chapter12.html[GPU Gems chapter on radiosity].
153153

154154
=== Subsurface Scattering
155155

@@ -161,7 +161,7 @@ For more information, see this link:https://developer.nvidia.com/gpugems/gpugems
161161

162162
Ambient Occlusion (AO) approximates how much ambient light a surface point would receive, darkening corners and crevices.
163163

164-
For more information, see this link:https://developer.nvidia.com/gpugems/gpugems/part-ii-lighting-and-shadows/chapter-17-ambient-occlusion[GPU Gems chapter on ambient occlusion].
164+
For more information, see this link:https://developer.download.nvidia.com/books/HTML/gpugems/gpugems_ch17.html[GPU Gems chapter on ambient occlusion].
165165

166166
== Choosing the Right Lighting Model
167167

en/Building_a_Simple_Engine/Lighting_Materials/05_vulkan_integration.adoc

Lines changed: 3 additions & 273 deletions
Original file line numberDiff line numberDiff line change
@@ -144,288 +144,18 @@ void Renderer::recordCommandBuffer(vk::CommandBuffer commandBuffer, uint32_t ima
144144
}
145145
----
146146

147-
== Creating the PBR Shader
147+
== PBR Shader Reference
148148

149-
Now that we've updated our renderer to support our PBR implementation, we need to create the PBR shader that implements the concepts we've discussed in this chapter. Rather than presenting this as a monolithic code dump, let's break down the shader creation into logical sections that explain both the technical implementation and the reasoning behind each component.
149+
This chapter reuses the exact PBR shader defined in the previous section to avoid duplication and drift. Please refer to link:04_lighting_implementation.adoc[Implementing the PBR Shader] for the full pbr.slang source and detailed explanations. Here we focus strictly on Vulkan integration: pipeline layout, descriptor bindings, push constants, and draw submission.
150150

151-
=== Section 1: Shader Structure and Data Interface
152-
153-
The first section establishes the communication interface between our CPU application and GPU shader, defining how data flows through the rendering pipeline.
154-
155-
[source,cpp]
156-
----
157-
// Combined vertex and fragment shader for PBR rendering
158-
159-
// Input from vertex buffer - Data sent per vertex from CPU
160-
struct VSInput {
161-
float3 Position : POSITION; // 3D position in model space
162-
float3 Normal : NORMAL; // Surface normal for lighting calculations
163-
float2 UV : TEXCOORD0; // Texture coordinates for material sampling
164-
float4 Tangent : TANGENT; // Tangent vector for normal mapping (w component = handedness)
165-
};
166-
167-
// Output from vertex shader / Input to fragment shader - Interpolated data
168-
struct VSOutput {
169-
float4 Position : SV_POSITION; // Required clip space position for rasterization
170-
float3 WorldPos : POSITION; // World space position for lighting calculations
171-
float3 Normal : NORMAL; // World space normal (interpolated)
172-
float2 UV : TEXCOORD0; // Texture coordinates (interpolated)
173-
float4 Tangent : TANGENT; // World space tangent (interpolated)
174-
};
175-
176-
// Uniform buffer - Global data shared across all vertices/fragments
177-
struct UniformBufferObject {
178-
float4x4 model; // Model-to-world transformation matrix
179-
float4x4 view; // World-to-camera transformation matrix
180-
float4x4 proj; // Camera-to-clip space projection matrix
181-
float4 lightPositions[4]; // Light positions in world space
182-
float4 lightColors[4]; // Light intensities and colors
183-
float4 camPos; // Camera position for view-dependent effects
184-
float exposure; // HDR exposure control
185-
float gamma; // Gamma correction value (typically 2.2)
186-
float prefilteredCubeMipLevels; // IBL prefiltered environment map mip levels
187-
float scaleIBLAmbient; // IBL ambient contribution scale
188-
};
189-
190-
// Push constants - Fast, small data updated frequently per material/object
191-
struct PushConstants {
192-
float4 baseColorFactor; // Base color tint/multiplier
193-
float metallicFactor; // Metallic property multiplier
194-
float roughnessFactor; // Surface roughness multiplier
195-
int baseColorTextureSet; // Texture binding index for base color (-1 = none)
196-
int physicalDescriptorTextureSet; // Texture binding for metallic/roughness
197-
int normalTextureSet; // Texture binding for normal maps
198-
int occlusionTextureSet; // Texture binding for ambient occlusion
199-
int emissiveTextureSet; // Texture binding for emissive maps
200-
float alphaMask; // Alpha masking enable flag
201-
float alphaMaskCutoff; // Alpha cutoff threshold
202-
};
203-
204-
// Mathematical constants
205-
static const float PI = 3.14159265359;
206-
207-
// Resource bindings - Connect CPU resources to GPU shader registers
208-
[[vk::binding(0, 0)]] ConstantBuffer<UniformBufferObject> ubo;
209-
[[vk::binding(1, 0)]] Texture2D baseColorMap;
210-
[[vk::binding(1, 0)]] SamplerState baseColorSampler;
211-
[[vk::binding(2, 0)]] Texture2D metallicRoughnessMap;
212-
[[vk::binding(2, 0)]] SamplerState metallicRoughnessSampler;
213-
[[vk::binding(3, 0)]] Texture2D normalMap;
214-
[[vk::binding(3, 0)]] SamplerState normalSampler;
215-
[[vk::binding(4, 0)]] Texture2D occlusionMap;
216-
[[vk::binding(4, 0)]] SamplerState occlusionSampler;
217-
[[vk::binding(5, 0)]] Texture2D emissiveMap;
218-
[[vk::binding(5, 0)]] SamplerState emissiveSampler;
219-
220-
[[vk::push_constant]] PushConstants material;
221-
----
222-
223-
This interface design reflects modern GPU architecture principles where different types of data flow through different pathways based on their update frequency and size. Uniform buffers efficiently handle large, infrequently changing data like transformation matrices, while push constants provide ultra-fast updates for small, frequently changing material properties.
224-
225-
=== Section 2: PBR Mathematical Foundation
226-
227-
The second section implements the core mathematical functions that form the foundation of physically-based rendering, translating complex light-surface interactions into computationally efficient approximations.
228-
229-
[source,cpp]
230-
----
231-
// Normal Distribution Function (D) - GGX/Trowbridge-Reitz Distribution
232-
// Describes the statistical distribution of microfacet orientations
233-
float DistributionGGX(float NdotH, float roughness) {
234-
float a = roughness * roughness; // Remapping for more perceptual linearity
235-
float a2 = a * a;
236-
float NdotH2 = NdotH * NdotH;
237-
238-
float nom = a2; // Numerator: concentration factor
239-
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
240-
denom = PI * denom * denom; // Normalization factor
241-
242-
return nom / denom; // Normalized distribution
243-
}
244-
245-
// Geometry Function (G) - Smith's method with Schlick-GGX approximation
246-
// Models self-shadowing and masking between microfacets
247-
float GeometrySmith(float NdotV, float NdotL, float roughness) {
248-
float r = roughness + 1.0;
249-
float k = (r * r) / 8.0; // Direct lighting remapping
250-
251-
// Geometry obstruction from view direction (masking)
252-
float ggx1 = NdotV / (NdotV * (1.0 - k) + k);
253-
// Geometry obstruction from light direction (shadowing)
254-
float ggx2 = NdotL / (NdotL * (1.0 - k) + k);
255-
256-
return ggx1 * ggx2; // Combined masking-shadowing
257-
}
258-
259-
// Fresnel Reflectance (F) - Schlick's approximation
260-
// Models how reflectance changes with viewing angle
261-
float3 FresnelSchlick(float cosTheta, float3 F0) {
262-
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
263-
}
264-
----
265-
266-
These mathematical functions represent decades of computer graphics research distilled into efficient real-time approximations. The GGX distribution provides more realistic highlight falloff compared to older models, while the Smith geometry function ensures energy conservation at grazing angles. The Fresnel approximation captures the essential angle-dependent reflection behavior that makes materials look convincing under different viewing conditions.
267-
268-
=== Section 3: Vertex and Fragment Shader Implementation
269-
270-
The final section contains the actual shader entry points that execute for each vertex and pixel, implementing the complete PBR pipeline from geometry transformation through final color output.
271-
272-
[source,cpp]
273-
----
274-
// Vertex shader entry point - Executes once per vertex
275-
[[shader("vertex")]]
276-
VSOutput VSMain(VSInput input)
277-
{
278-
VSOutput output;
279-
280-
// Transform vertex position through the rendering pipeline
281-
// Model -> World -> Camera -> Clip space transformation chain
282-
float4 worldPos = mul(ubo.model, float4(input.Position, 1.0));
283-
output.Position = mul(ubo.proj, mul(ubo.view, worldPos));
284-
285-
// Pass world position for fragment lighting calculations
286-
// Fragment shader needs world space position to calculate light vectors
287-
output.WorldPos = worldPos.xyz;
288-
289-
// Transform normal from model space to world space
290-
// Use only rotation/scale part of model matrix (upper-left 3x3)
291-
// Normalize to ensure unit length after transformation
292-
output.Normal = normalize(mul((float3x3)ubo.model, input.Normal));
293-
294-
// Pass through texture coordinates unchanged
295-
// UV coordinates are typically in [0,1] range and don't need transformation
296-
output.UV = input.UV;
297-
298-
// Pass tangent vector for normal mapping
299-
// Will be used in fragment shader to construct tangent-space basis
300-
output.Tangent = input.Tangent;
301-
302-
return output;
303-
}
304-
305-
// Fragment shader entry point - Executes once per pixel
306-
[[shader("fragment")]]
307-
float4 PSMain(VSOutput input) : SV_TARGET
308-
{
309-
// === MATERIAL PROPERTY SAMPLING ===
310-
// Sample base color texture and apply material color factor
311-
float4 baseColor = baseColorMap.Sample(baseColorSampler, input.UV) * material.baseColorFactor;
312-
313-
// Sample metallic-roughness texture (metallic=B channel, roughness=G channel)
314-
// glTF standard: metallic stored in blue, roughness in green
315-
float2 metallicRoughness = metallicRoughnessMap.Sample(metallicRoughnessSampler, input.UV).bg;
316-
float metallic = metallicRoughness.x * material.metallicFactor;
317-
float roughness = metallicRoughness.y * material.roughnessFactor;
318-
319-
// Sample ambient occlusion (typically stored in red channel)
320-
float ao = occlusionMap.Sample(occlusionSampler, input.UV).r;
321-
322-
// Sample emissive texture for self-illuminating materials
323-
float3 emissive = emissiveMap.Sample(emissiveSampler, input.UV).rgb;
324-
325-
// === NORMAL CALCULATION ===
326-
// Start with interpolated surface normal
327-
float3 N = normalize(input.Normal);
328-
329-
// Apply normal mapping if texture is available
330-
if (material.normalTextureSet >= 0) {
331-
// Sample normal map and convert from [0,1] to [-1,1] range
332-
float3 tangentNormal = normalMap.Sample(normalSampler, input.UV).xyz * 2.0 - 1.0;
333-
334-
// Construct tangent-space to world-space transformation matrix (TBN)
335-
float3 T = normalize(input.Tangent.xyz); // Tangent
336-
float3 B = normalize(cross(N, T)) * input.Tangent.w; // Bitangent (w = handedness)
337-
float3x3 TBN = float3x3(T, B, N); // Tangent-Bitangent-Normal matrix
338-
339-
// Transform normal from tangent space to world space
340-
N = normalize(mul(tangentNormal, TBN));
341-
}
342-
343-
// === LIGHTING SETUP ===
344-
// Calculate view direction (camera to fragment)
345-
float3 V = normalize(ubo.camPos.xyz - input.WorldPos);
346-
347-
// Calculate reflection vector for environment mapping
348-
float3 R = reflect(-V, N);
349-
350-
// === PBR MATERIAL SETUP ===
351-
// Calculate F0 (reflectance at normal incidence)
352-
// Non-metals: low reflectance (~0.04), Metals: colored reflectance from base color
353-
float3 F0 = float3(0.04, 0.04, 0.04); // Dielectric default
354-
F0 = lerp(F0, baseColor.rgb, metallic); // Lerp to metallic behavior
355-
356-
// Initialize outgoing radiance accumulator
357-
float3 Lo = float3(0.0, 0.0, 0.0);
358-
359-
// === DIRECT LIGHTING LOOP ===
360-
// Calculate contribution from each light source
361-
for (int i = 0; i < 4; i++) {
362-
float3 lightPos = ubo.lightPositions[i].xyz;
363-
float3 lightColor = ubo.lightColors[i].rgb;
364-
365-
// Calculate light direction and attenuation
366-
float3 L = normalize(lightPos - input.WorldPos); // Light direction
367-
float distance = length(lightPos - input.WorldPos); // Distance for falloff
368-
float attenuation = 1.0 / (distance * distance); // Inverse square falloff
369-
float3 radiance = lightColor * attenuation; // Attenuated light color
370-
371-
// Calculate half vector (between view and light directions)
372-
float3 H = normalize(V + L);
373-
374-
// === BRDF EVALUATION ===
375-
// Calculate all necessary dot products for BRDF terms
376-
float NdotL = max(dot(N, L), 0.0); // Lambertian falloff
377-
float NdotV = max(dot(N, V), 0.0); // View angle
378-
float NdotH = max(dot(N, H), 0.0); // Half vector for specular
379-
float HdotV = max(dot(H, V), 0.0); // For Fresnel calculation
380-
381-
// Evaluate Cook-Torrance BRDF components
382-
float D = DistributionGGX(NdotH, roughness); // Normal distribution
383-
float G = GeometrySmith(NdotV, NdotL, roughness); // Geometry function
384-
float3 F = FresnelSchlick(HdotV, F0); // Fresnel reflectance
385-
386-
// Calculate specular BRDF
387-
float3 numerator = D * G * F;
388-
float denominator = 4.0 * NdotV * NdotL + 0.0001; // Prevent division by zero
389-
float3 specular = numerator / denominator;
390-
391-
// === ENERGY CONSERVATION ===
392-
// Fresnel term represents specular reflection ratio
393-
float3 kS = F; // Specular contribution
394-
float3 kD = float3(1.0, 1.0, 1.0) - kS; // Diffuse contribution (energy conservation)
395-
kD *= 1.0 - metallic; // Metals have no diffuse reflection
396-
397-
// === RADIANCE ACCUMULATION ===
398-
// Combine diffuse (Lambertian) and specular (Cook-Torrance) terms
399-
// Multiply by incident radiance and cosine foreshortening
400-
Lo += (kD * baseColor.rgb / PI + specular) * radiance * NdotL;
401-
}
402-
403-
// === AMBIENT AND EMISSIVE ===
404-
// Add simple ambient lighting (should be replaced with IBL in production)
405-
float3 ambient = float3(0.03, 0.03, 0.03) * baseColor.rgb * ao;
406-
407-
// Combine all lighting contributions
408-
float3 color = ambient + Lo + emissive;
409-
410-
// === HDR TONE MAPPING AND GAMMA CORRECTION ===
411-
// Apply Reinhard tone mapping to compress HDR values to [0,1] range
412-
color = color / (color + float3(1.0, 1.0, 1.0));
413-
414-
// Apply gamma correction for sRGB display (inverse gamma)
415-
color = pow(color, float3(1.0 / ubo.gamma, 1.0 / ubo.gamma, 1.0 / ubo.gamma));
416-
417-
// Output final color with original alpha
418-
return float4(color, baseColor.a);
419-
}
420-
----
421151

422152
== Compiling the Shader
423153

424154
After creating the shader file, we need to compile it using slangc. This is typically done as part of the build process, but we can also do it manually:
425155

426156
[source,bash]
427157
----
428-
slangc shaders/pbr.slang -target spirv -profile spirv_1_4 -emit-spirv-directly -o shaders/pbr.spv
158+
slangc shaders/pbr.slang -target spirv -profile spirv_1_4 -o shaders/pbr.spv
429159
----
430160

431161
== Testing the Implementation with glTF Models

0 commit comments

Comments
 (0)