ADD: multi light system docs
This commit is contained in:
290
docs/Compute.md
290
docs/Compute.md
@@ -4,70 +4,300 @@ Standalone compute subsystem with a small, explicit API. Used by passes (e.g., B
|
||||
|
||||
### Concepts
|
||||
|
||||
- Pipelines: Named compute pipelines created from a SPIR‑V module and a simple descriptor layout spec.
|
||||
- Instances: Persistently bound descriptor sets keyed by instance name; useful for effects that rebind images/buffers across frames without re‑creating pipelines.
|
||||
- Dispatch: Issue work with group counts, optional push constants, and ad‑hoc memory barriers.
|
||||
- **Pipelines**: Named compute pipelines created from a SPIR‑V module and a simple descriptor layout spec.
|
||||
- **Instances**: Persistently bound descriptor sets keyed by instance name; useful for effects that rebind images/buffers across frames without re‑creating pipelines.
|
||||
- **Dispatch**: Issue work with group counts, optional push constants, and ad‑hoc memory barriers.
|
||||
|
||||
### Key Types
|
||||
|
||||
- `ComputePipelineCreateInfo` — shader path, descriptor types, push constant size/stages, optional specialization (src/compute/vk_compute.h).
|
||||
- `ComputeDispatchInfo` — `groupCount{X,Y,Z}`, `bindings`, `pushConstants`, and `*_barriers` arrays for additional sync.
|
||||
- `ComputeBinding` — helpers for `uniformBuffer`, `storageBuffer`, `sampledImage`, `storeImage`.
|
||||
#### `ComputeBinding` — Descriptor Binding Abstraction
|
||||
|
||||
Unified wrapper for all descriptor types. Use static factory methods:
|
||||
|
||||
```c++
|
||||
struct ComputeBinding {
|
||||
uint32_t binding;
|
||||
VkDescriptorType type;
|
||||
// Union of buffer/image info
|
||||
|
||||
static ComputeBinding uniformBuffer(uint32_t binding, VkBuffer buffer, VkDeviceSize size, VkDeviceSize offset = 0);
|
||||
static ComputeBinding storageBuffer(uint32_t binding, VkBuffer buffer, VkDeviceSize size, VkDeviceSize offset = 0);
|
||||
static ComputeBinding sampledImage(uint32_t binding, VkImageView view, VkSampler sampler,
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
static ComputeBinding storeImage(uint32_t binding, VkImageView view,
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_GENERAL);
|
||||
};
|
||||
```
|
||||
|
||||
#### `ComputePipelineCreateInfo` — Pipeline Configuration
|
||||
|
||||
```c++
|
||||
struct ComputePipelineCreateInfo {
|
||||
std::string shaderPath; // Path to .comp.spv file
|
||||
std::vector<VkDescriptorType> descriptorTypes; // Descriptor layout (binding order)
|
||||
uint32_t pushConstantSize = 0;
|
||||
VkShaderStageFlags pushConstantStages = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
|
||||
// Optional: shader specialization constants
|
||||
std::vector<VkSpecializationMapEntry> specializationEntries;
|
||||
std::vector<uint32_t> specializationData;
|
||||
};
|
||||
```
|
||||
|
||||
#### `ComputeDispatchInfo` — Dispatch Configuration
|
||||
|
||||
```c++
|
||||
struct ComputeDispatchInfo {
|
||||
uint32_t groupCountX = 1; // Workgroup counts
|
||||
uint32_t groupCountY = 1;
|
||||
uint32_t groupCountZ = 1;
|
||||
|
||||
std::vector<ComputeBinding> bindings; // Per-dispatch bindings (transient)
|
||||
|
||||
const void *pushConstants = nullptr; // Push constant data
|
||||
uint32_t pushConstantSize = 0;
|
||||
|
||||
// Optional synchronization barriers (inserted after dispatch)
|
||||
std::vector<VkMemoryBarrier2> memoryBarriers;
|
||||
std::vector<VkBufferMemoryBarrier2> bufferBarriers;
|
||||
std::vector<VkImageMemoryBarrier2> imageBarriers;
|
||||
};
|
||||
```
|
||||
|
||||
### API Surface
|
||||
|
||||
- Register/Destroy
|
||||
- `bool ComputeManager::registerPipeline(name, ComputePipelineCreateInfo)`
|
||||
- `void ComputeManager::unregisterPipeline(name)`
|
||||
- Query: `bool ComputeManager::hasPipeline(name)`
|
||||
#### Register/Destroy
|
||||
|
||||
- Dispatch
|
||||
- `void ComputeManager::dispatch(cmd, name, ComputeDispatchInfo)`
|
||||
- `void ComputeManager::dispatchImmediate(name, ComputeDispatchInfo)` — records on a transient command buffer and submits.
|
||||
- Helpers: `createDispatch2D(w,h[,lsX,lsY])`, `createDispatch3D(w,h,d[,lsX,lsY,lsZ])`.
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `bool registerPipeline(name, ComputePipelineCreateInfo)` | Create and register a named compute pipeline |
|
||||
| `void unregisterPipeline(name)` | Destroy and unregister a pipeline |
|
||||
| `bool hasPipeline(name)` | Check if a pipeline exists |
|
||||
|
||||
- Instances
|
||||
- `bool ComputeManager::createInstance(instanceName, pipelineName)` / `destroyInstance(instanceName)`
|
||||
- `setInstanceStorageImage`, `setInstanceSampledImage`, `setInstanceBuffer`
|
||||
- `AllocatedImage createAndBindStorageImage(...)`, `AllocatedBuffer createAndBindStorageBuffer(...)`
|
||||
- `void dispatchInstance(cmd, instanceName, info)`
|
||||
#### Dispatch
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `void dispatch(cmd, name, ComputeDispatchInfo)` | Record dispatch to command buffer |
|
||||
| `void dispatchImmediate(name, ComputeDispatchInfo)` | Execute immediately (GPU-blocking) |
|
||||
|
||||
#### Dispatch Helpers
|
||||
|
||||
```c++
|
||||
static uint32_t calculateGroupCount(uint32_t workItems, uint32_t localSize);
|
||||
static ComputeDispatchInfo createDispatch2D(uint32_t w, uint32_t h, uint32_t lsX = 16, uint32_t lsY = 16);
|
||||
static ComputeDispatchInfo createDispatch3D(uint32_t w, uint32_t h, uint32_t d,
|
||||
uint32_t lsX = 8, uint32_t lsY = 8, uint32_t lsZ = 8);
|
||||
```
|
||||
|
||||
#### Instances
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `bool createInstance(instanceName, pipelineName)` | Create persistent instance |
|
||||
| `void destroyInstance(instanceName)` | Destroy instance and owned resources |
|
||||
| `void setInstanceStorageImage(name, binding, imageView)` | Bind storage image |
|
||||
| `void setInstanceSampledImage(name, binding, imageView, sampler)` | Bind sampled image |
|
||||
| `void setInstanceBuffer(name, binding, buffer, size, type)` | Bind buffer |
|
||||
| `AllocatedImage createAndBindStorageImage(...)` | Create and bind owned image |
|
||||
| `AllocatedBuffer createAndBindStorageBuffer(...)` | Create and bind owned buffer |
|
||||
| `void dispatchInstance(cmd, instanceName, info)` | Dispatch using instance bindings |
|
||||
|
||||
#### Utility Operations
|
||||
|
||||
```c++
|
||||
void clearImage(VkCommandBuffer cmd, VkImageView imageView, const glm::vec4 &clearColor);
|
||||
void copyBuffer(VkCommandBuffer cmd, VkBuffer src, VkBuffer dst, VkDeviceSize size,
|
||||
VkDeviceSize srcOffset = 0, VkDeviceSize dstOffset = 0);
|
||||
```
|
||||
|
||||
### Quick Start — One‑Shot Dispatch
|
||||
|
||||
For ad-hoc compute work where bindings change each dispatch:
|
||||
|
||||
```c++
|
||||
// 1. Register pipeline (once, e.g., in init)
|
||||
ComputePipelineCreateInfo ci{};
|
||||
ci.shaderPath = context->getAssets()->shaderPath("blur.comp.spv");
|
||||
ci.descriptorTypes = { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE };
|
||||
ci.pushConstantSize = sizeof(ComputePushConstants);
|
||||
ci.descriptorTypes = { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER };
|
||||
ci.pushConstantSize = sizeof(BlurPushConstants);
|
||||
context->compute->registerPipeline("blur", ci);
|
||||
|
||||
ComputeDispatchInfo di = ComputeManager::createDispatch2D(draw.w, draw.h);
|
||||
// 2. Create dispatch info with bindings
|
||||
ComputeDispatchInfo di = ComputeManager::createDispatch2D(width, height, 16, 16);
|
||||
di.bindings.push_back(ComputeBinding::storeImage(0, outImageView));
|
||||
di.bindings.push_back(ComputeBinding::sampledImage(1, inImageView, context->getSamplers()->defaultLinear()));
|
||||
ComputePushConstants pc{}; /* fill */
|
||||
di.pushConstants = &pc; di.pushConstantSize = sizeof(pc);
|
||||
|
||||
BlurPushConstants pc{ .radius = 5.0f };
|
||||
di.pushConstants = &pc;
|
||||
di.pushConstantSize = sizeof(pc);
|
||||
|
||||
// 3. Dispatch
|
||||
context->compute->dispatch(cmd, "blur", di);
|
||||
```
|
||||
|
||||
### Quick Start — Persistent Instance
|
||||
|
||||
For effects that reuse the same bindings across frames:
|
||||
|
||||
```c++
|
||||
// 1. Create instance (once, e.g., in init)
|
||||
context->compute->createInstance("background.sky", "sky");
|
||||
context->compute->setInstanceStorageImage("background.sky", 0, ctx->getSwapchain()->drawImage().imageView);
|
||||
|
||||
// 2. Dispatch each frame (bindings persist)
|
||||
ComputeDispatchInfo di = ComputeManager::createDispatch2D(ctx->getDrawExtent().width,
|
||||
ctx->getDrawExtent().height);
|
||||
di.pushConstants = &effect.data; di.pushConstantSize = sizeof(ComputePushConstants);
|
||||
di.pushConstants = &effect.data;
|
||||
di.pushConstantSize = sizeof(ComputePushConstants);
|
||||
context->compute->dispatchInstance(cmd, "background.sky", di);
|
||||
```
|
||||
|
||||
### Quick Start — Immediate Execution
|
||||
|
||||
For GPU-blocking operations (uploads, preprocessing):
|
||||
|
||||
```c++
|
||||
ComputeDispatchInfo di = ComputeManager::createDispatch2D(width, height);
|
||||
di.bindings.push_back(ComputeBinding::storeImage(0, imageView));
|
||||
di.pushConstants = &data;
|
||||
di.pushConstantSize = sizeof(data);
|
||||
|
||||
// Blocks CPU until GPU completes
|
||||
context->compute->dispatchImmediate("preprocess", di);
|
||||
// Safe to read results here
|
||||
```
|
||||
|
||||
### Integration With Render Graph
|
||||
|
||||
- Compute passes declare `write(image, RGImageUsage::ComputeWrite)` in their build callback; the graph inserts layout transitions to `GENERAL` and required barriers.
|
||||
- Background pass example: `src/render/vk_renderpass_background.cpp`.
|
||||
Use `RGPassType::Compute` for graph-managed compute passes:
|
||||
|
||||
### Sync Notes
|
||||
```c++
|
||||
graph->add_pass(
|
||||
"MyComputePass",
|
||||
RGPassType::Compute,
|
||||
// Build: declare resource usage
|
||||
[drawHandle](RGPassBuilder &builder, EngineContext *) {
|
||||
builder.write(drawHandle, RGImageUsage::ComputeWrite); // Storage image write
|
||||
},
|
||||
// Record: issue dispatch
|
||||
[this, drawHandle](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) {
|
||||
VkImageView drawView = res.image_view(drawHandle);
|
||||
|
||||
- ComputeManager inserts minimal barriers needed for common cases; prefer using the Render Graph for cross‑pass synchronization.
|
||||
- For advanced cases, add `imageBarriers`/`bufferBarriers` to `ComputeDispatchInfo`.
|
||||
// Update binding to current frame's target
|
||||
ctx->pipelines->setComputeInstanceStorageImage("myeffect", 0, drawView);
|
||||
|
||||
ComputeDispatchInfo di = ComputeManager::createDispatch2D(
|
||||
ctx->getDrawExtent().width, ctx->getDrawExtent().height, 16, 16);
|
||||
di.pushConstants = ¶ms;
|
||||
di.pushConstantSize = sizeof(params);
|
||||
|
||||
ctx->pipelines->dispatchComputeInstance(cmd, "myeffect", di);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Benefits:
|
||||
- Automatic layout transitions to `VK_IMAGE_LAYOUT_GENERAL` for storage images.
|
||||
- Integrated barrier management with other passes.
|
||||
- Per-frame synchronization tracking.
|
||||
|
||||
Reference: `src/render/vk_renderpass_background.cpp`.
|
||||
|
||||
### Synchronization
|
||||
|
||||
#### Automatic (Render Graph)
|
||||
|
||||
When using the render graph, declare resource usage and let the graph handle barriers:
|
||||
- `builder.write(image, RGImageUsage::ComputeWrite)` — storage image write
|
||||
- `builder.read(image, RGImageUsage::SampledCompute)` — sampled image read in compute
|
||||
|
||||
#### Manual (Dispatch Barriers)
|
||||
|
||||
For standalone dispatches, add barriers to `ComputeDispatchInfo`:
|
||||
|
||||
```c++
|
||||
ComputeDispatchInfo di{...};
|
||||
|
||||
// Image layout transition after compute write
|
||||
VkImageMemoryBarrier2 barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
.image = image,
|
||||
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }
|
||||
};
|
||||
di.imageBarriers.push_back(barrier);
|
||||
|
||||
context->compute->dispatch(cmd, "mycompute", di);
|
||||
```
|
||||
|
||||
#### Frame-in-Flight Safety
|
||||
|
||||
- Descriptor sets use `VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT` for safe concurrent updates.
|
||||
- `dispatchInstance()` uses per-frame descriptor allocator when available, falling back to instance-owned sets.
|
||||
|
||||
### Shader Example
|
||||
|
||||
```glsl
|
||||
// myeffect.comp
|
||||
#version 450
|
||||
|
||||
layout(local_size_x = 16, local_size_y = 16) in;
|
||||
|
||||
layout(set = 0, binding = 0, rgba16f) uniform image2D outImage;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 params;
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 size = imageSize(outImage);
|
||||
|
||||
if (texelCoord.x >= size.x || texelCoord.y >= size.y) return;
|
||||
|
||||
vec4 color = vec4(float(texelCoord.x) / size.x, float(texelCoord.y) / size.y, 0.0, 1.0);
|
||||
imageStore(outImage, texelCoord, color * pc.params);
|
||||
}
|
||||
```
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
src/compute/
|
||||
├── vk_compute.h # Core types and ComputeManager interface
|
||||
└── vk_compute.cpp # Implementation
|
||||
|
||||
src/core/
|
||||
├── vk_pipeline_manager.h/cpp # Unified graphics+compute wrapper
|
||||
├── engine_context.h # Contains ComputeManager* pointer
|
||||
└── vk_resource.h/cpp # immediate_submit() for GPU-blocking
|
||||
|
||||
src/render/
|
||||
├── vk_renderpass_background.cpp # Example: gradient/sky compute passes
|
||||
├── rg_types.h # RGPassType::Compute, RGImageUsage::ComputeWrite
|
||||
└── rg_graph.h # Render graph integration
|
||||
|
||||
shaders/
|
||||
├── gradient_color.comp # Example: gradient background
|
||||
└── sky.comp # Example: procedural starfield
|
||||
```
|
||||
|
||||
### Notes & Limits
|
||||
|
||||
- Compute pipelines are **not** hot-reloaded. Re-create via `registerPipeline()` if shader changes.
|
||||
- `dispatchImmediate()` blocks the CPU; use sparingly for interactive workloads.
|
||||
- Descriptor pool uses `UPDATE_AFTER_BIND` flag; ensure driver support (Vulkan 1.2+).
|
||||
- Instance-owned resources are destroyed with the instance; external resources must be managed separately.
|
||||
|
||||
### Debugging
|
||||
|
||||
- Use Vulkan validation layers to catch descriptor/barrier issues.
|
||||
- Each dispatch records debug labels when validation is enabled.
|
||||
- Check `ComputeManager::hasPipeline()` before dispatch to avoid silent failures.
|
||||
|
||||
|
||||
153
docs/MultiLighting.md
Normal file
153
docs/MultiLighting.md
Normal file
@@ -0,0 +1,153 @@
|
||||
## Multi-Light System: Punctual Point Lights
|
||||
|
||||
Extends the rendering pipeline to support multiple dynamic point lights alongside the existing directional sun light. All lighting calculations use a shared PBR (Cook-Torrance) BRDF implementation.
|
||||
|
||||
- Files: `src/scene/vk_scene.h/.cpp`, `src/core/vk_types.h`, `shaders/lighting_common.glsl`
|
||||
|
||||
### Data Structures
|
||||
|
||||
**CPU-side (C++)**
|
||||
|
||||
```cpp
|
||||
// src/scene/vk_scene.h
|
||||
struct PointLight {
|
||||
glm::vec3 position;
|
||||
float radius; // falloff radius
|
||||
glm::vec3 color;
|
||||
float intensity;
|
||||
};
|
||||
```
|
||||
|
||||
**GPU-side (GLSL)**
|
||||
|
||||
```glsl
|
||||
// shaders/input_structures.glsl
|
||||
#define MAX_PUNCTUAL_LIGHTS 64
|
||||
|
||||
struct GPUPunctualLight {
|
||||
vec4 position_radius; // xyz: world position, w: radius
|
||||
vec4 color_intensity; // rgb: color, a: intensity
|
||||
};
|
||||
```
|
||||
|
||||
The `GPUSceneData` uniform buffer includes:
|
||||
- `punctualLights[MAX_PUNCTUAL_LIGHTS]`: array of packed light data
|
||||
- `lightCounts.x`: number of active point lights
|
||||
|
||||
### Scene API
|
||||
|
||||
`SceneManager` exposes a simple API to manage point lights at runtime:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `addPointLight(const PointLight& light)` | Appends a point light to the scene |
|
||||
| `clearPointLights()` | Removes all point lights |
|
||||
| `getPointLights()` | Returns const reference to current lights |
|
||||
|
||||
Example usage:
|
||||
```cpp
|
||||
SceneManager::PointLight light{};
|
||||
light.position = glm::vec3(5.0f, 3.0f, 0.0f);
|
||||
light.radius = 15.0f;
|
||||
light.color = glm::vec3(1.0f, 0.9f, 0.7f);
|
||||
light.intensity = 20.0f;
|
||||
sceneManager->addPointLight(light);
|
||||
```
|
||||
|
||||
### GPU Upload
|
||||
|
||||
During `SceneManager::update_scene()`, point lights are packed into `GPUSceneData`:
|
||||
|
||||
1. Iterate over `pointLights` vector (capped at `kMaxPunctualLights = 64`)
|
||||
2. Pack each light into `GPUPunctualLight` format
|
||||
3. Zero-fill unused array slots
|
||||
4. Set `lightCounts.x` to active count
|
||||
|
||||
This data is uploaded to the per-frame UBO and bound via `DescriptorManager::gpuSceneDataLayout()` at set 0, binding 0.
|
||||
|
||||
### Shader Implementation
|
||||
|
||||
**lighting_common.glsl**
|
||||
|
||||
Shared PBR functions extracted for reuse across all lighting passes:
|
||||
|
||||
| Function | Purpose |
|
||||
|----------|---------|
|
||||
| `fresnelSchlick()` | Fresnel term (Schlick approximation) |
|
||||
| `DistributionGGX()` | Normal distribution function (GGX/Trowbridge-Reitz) |
|
||||
| `GeometrySchlickGGX()` | Geometry term (Schlick-GGX) |
|
||||
| `GeometrySmith()` | Combined geometry attenuation |
|
||||
| `evaluate_brdf(N, V, L, albedo, roughness, metallic)` | Full Cook-Torrance BRDF evaluation |
|
||||
| `eval_point_light(light, pos, N, V, albedo, roughness, metallic)` | Point light contribution with falloff |
|
||||
|
||||
**Point Light Falloff**
|
||||
|
||||
Uses smooth inverse-square attenuation with radius-based cutoff:
|
||||
```glsl
|
||||
float att = 1.0 / max(dist * dist, 0.0001);
|
||||
float x = clamp(dist / radius, 0.0, 1.0);
|
||||
float smth = (1.0 - x * x);
|
||||
smth *= smth;
|
||||
float falloff = att * smth;
|
||||
```
|
||||
|
||||
This provides physically-based falloff that smoothly reaches zero at the specified radius.
|
||||
|
||||
### Render Path Integration
|
||||
|
||||
**Deferred Lighting** (`deferred_lighting.frag`, `deferred_lighting_nort.frag`)
|
||||
|
||||
```glsl
|
||||
// Directional sun
|
||||
vec3 Lsun = normalize(-sceneData.sunlightDirection.xyz);
|
||||
float sunVis = calcShadowVisibility(pos, N, Lsun);
|
||||
vec3 sunBRDF = evaluate_brdf(N, V, Lsun, albedo, roughness, metallic);
|
||||
vec3 direct = sunBRDF * sceneData.sunlightColor.rgb * sceneData.sunlightColor.a * sunVis;
|
||||
|
||||
// Accumulate point lights
|
||||
uint pointCount = sceneData.lightCounts.x;
|
||||
for (uint i = 0u; i < pointCount; ++i) {
|
||||
direct += eval_point_light(sceneData.punctualLights[i], pos, N, V, albedo, roughness, metallic);
|
||||
}
|
||||
```
|
||||
|
||||
**Forward Pass** (`mesh.frag`)
|
||||
|
||||
Same accumulation loop, but without shadow visibility (used for transparent objects).
|
||||
|
||||
### Ray-Traced Point Light Shadows
|
||||
|
||||
When ray tracing is enabled (hybrid mode), the first 4 point lights receive RT shadows:
|
||||
|
||||
```glsl
|
||||
if (sceneData.rtOptions.x == 1u && i < 4u) {
|
||||
// Cast shadow ray from surface toward light
|
||||
vec3 toL = light.position_radius.xyz - pos;
|
||||
// ... rayQueryInitializeEXT / rayQueryProceedEXT ...
|
||||
if (hit) contrib = vec3(0.0);
|
||||
}
|
||||
```
|
||||
|
||||
This provides accurate point light shadows without additional shadow maps.
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- **Light Count**: Up to 64 point lights supported; practical limit depends on scene complexity
|
||||
- **Loop Unrolling**: Shader compilers typically unroll small loops; consider dynamic branching for very high light counts
|
||||
- **Shadow Maps**: Point light shadows use RT only; traditional cubemap shadows are not implemented
|
||||
- **Bandwidth**: Each light adds 32 bytes to the UBO; full array is 2KB
|
||||
|
||||
### Default Lights
|
||||
|
||||
`SceneManager::init()` creates two default point lights for testing:
|
||||
|
||||
| Light | Position | Radius | Color | Intensity |
|
||||
|-------|----------|--------|-------|-----------|
|
||||
| Warm Key | (0, 0, 0) | 25 | (1.0, 0.95, 0.8) | 15 |
|
||||
| Cool Fill | (-10, 4, 10) | 20 | (0.6, 0.7, 1.0) | 10 |
|
||||
|
||||
Call `clearPointLights()` before adding your own lights to remove these defaults.
|
||||
|
||||
### Future Extensions
|
||||
|
||||
- Spot lights (add cone angle to `GPUPunctualLight`)
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
std::vector<ComputeEffect> &getEffects() { return _backgroundEffects; }
|
||||
|
||||
std::vector<ComputeEffect> _backgroundEffects;
|
||||
int _currentEffect = 0;
|
||||
int _currentEffect = 2;
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user