Engine²
Open-source game engine written in C++.
Loading...
Searching...
No Matches
Deferred.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "Logger.hpp"
8#include "core/Core.hpp"
9#include "entity/Entity.hpp"
15#include "utils/Lights.hpp"
19#include <entt/core/hashed_string.hpp>
20#include <string_view>
21
23static inline constexpr std::string_view DEFERRED_PASS_OUTPUT = "DEFERRED_PASS_OUTPUT";
24static inline const entt::hashed_string DEFERRED_PASS_OUTPUT_ID{DEFERRED_PASS_OUTPUT.data(),
26static inline constexpr std::string_view DEFERRED_PASS_NAME = "DEFERRED_PASS";
27static inline const entt::hashed_string DEFERRED_PASS_ID{DEFERRED_PASS_NAME.data(), DEFERRED_PASS_NAME.size()};
28static inline constexpr std::string_view DEFERRED_SHADER_NAME = "DEFERRED_SHADER";
29static inline const entt::hashed_string DEFERRED_SHADER_ID =
30 entt::hashed_string{DEFERRED_SHADER_NAME.data(), DEFERRED_SHADER_NAME.size()};
31
32static inline constexpr std::string_view DEFERRED_BINDGROUP_TEXTURES_NAME = "DEFERRED_BINDGROUP_TEXTURES";
33static inline const entt::hashed_string DEFERRED_BINDGROUP_TEXTURES_ID =
35
36static inline constexpr std::string_view DEFERRED_SHADE_CONTENT = R"(
37const MAX_POINT_LIGHTS: u32 = 64u;
38const MAX_DIRECTIONAL_LIGHTS: u32 = 64u;
39
40struct DeferredInput {
41 @builtin(vertex_index) VertexIndex : u32
42};
43
44struct VertexToFragment {
45 @builtin(position) coord : vec4f
46}
47
48struct Camera {
49 viewProjectionMatrix : mat4x4f,
50 invViewProjectionMatrix : mat4x4f,
51 position : vec3f,
52}
53
54struct DeferredOutput {
55 @location(0) color : vec4f,
56}
57
58struct AmbientLight {
59 color : vec3f,
60 padding : f32,
61};
62
63struct GPUPointLight {
64 position: vec3f,
65 intensity: f32,
66 color: vec3f,
67 radius: f32,
68 falloff: f32,
69 _padding1: f32,
70 _padding2: f32,
71 _padding3: f32,
72};
73
74struct PointLightsData {
75 lights: array<GPUPointLight, MAX_POINT_LIGHTS>,
76 count: u32,
77 _padding1: f32,
78 _padding2: f32,
79 _padding3: f32,
80};
81
82struct DirectionalLight {
83 viewProjection: mat4x4f,
84 color: vec4f,
85 direction: vec3f,
86 shadowIndex: u32,
87};
88
89struct DirectionalLightsData {
90 lights: array<DirectionalLight, 64>,
91 count: u32,
92 _padding1: f32,
93 _padding2: f32,
94 _padding3: f32,
95};
96
97@group(0) @binding(0) var<uniform> camera: Camera;
98
99@group(1) @binding(0) var gBufferNormal: texture_2d<f32>;
100@group(1) @binding(1) var gBufferAlbedo: texture_2d<f32>;
101@group(1) @binding(2) var gBufferDepth: texture_2d<f32>;
102
103@group(2) @binding(0) var<uniform> ambientLight : AmbientLight;
104@group(2) @binding(1) var<uniform> pointLights : PointLightsData;
105@group(2) @binding(2) var<uniform> directionalLights : DirectionalLightsData;
106@group(2) @binding(3) var lightsDirectionalTextures: texture_depth_2d_array;
107@group(2) @binding(4) var lightsDirectionalTextureSampler: sampler_comparison;
108
109@vertex
110fn vs_main(
111 input : DeferredInput
112) -> VertexToFragment {
113 var coord : vec4f;
114 const pos = array(
115 vec2(-1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, -1.0),
116 vec2(-1.0, 1.0), vec2(1.0, 1.0), vec2(1.0, -1.0)
117 );
118
119 coord = vec4f(pos[input.VertexIndex], 0.9, 1.0);
120 return VertexToFragment(coord);
121}
122
123fn world_from_screen_coord(coord : vec2f, depth_sample: f32) -> vec3f {
124 let posClip = vec4(coord.x * 2.0 - 1.0, (1.0 - coord.y) * 2.0 - 1.0, depth_sample, 1.0);
125 let posWorldW = camera.invViewProjectionMatrix * posClip;
126 let posWorld = posWorldW.xyz / posWorldW.www;
127 return posWorld;
128}
129
130// Physically plausible point-light attenuation with finite radius
131// Formula inside the radius: A * (1 - s^2)^2 / (1 + F * s), where s = d / R
132// For s >= 1 (distance >= R) the attenuation is explicitly clamped to 0.0.
133// This yields a compact-support profile that is C1-smooth at distance R (value and derivative are zero there).
134// See https://lisyarus.github.io/blog/posts/point-light-attenuation.html for more details on this model.
135fn attenuate(distance: f32, radius: f32, max_intensity: f32, falloff: f32) -> f32 {
136 let s = distance / radius;
137
138 if (s >= 1.0) {
139 return 0.0;
140 }
141
142 let s2 = s * s;
143 let one_minus_s2 = 1.0 - s2;
144
145 return max_intensity * one_minus_s2 * one_minus_s2 / (1.0 + falloff * s);
146}
147
148fn calculatePointLight(light: GPUPointLight, worldPos: vec3f, normal: vec3f) -> vec3f {
149 let lightDir = normalize(light.position - worldPos);
150 let distance = length(light.position - worldPos);
151 let attenuation = attenuate(distance, light.radius, light.intensity, light.falloff);
152 let diff = max(dot(normal, lightDir), 0.0);
153
154 return light.color * diff * attenuation;
155}
156
157fn calculateDirectionalLight(light: DirectionalLight, N: vec3f, V: vec3f, MatKd: vec3f, MatKs: vec3f, Shiness: f32, position: vec3f, shadowBias: f32) -> vec3f
158{
159 let FragPosLightSpace = light.viewProjection * vec4f(position, 1.0);
160 let shadowCoord = FragPosLightSpace.xyz / FragPosLightSpace.w;
161 let projCoord = shadowCoord * vec3f(0.5, -0.5, 1.0) + vec3f(0.5, 0.5, 0.0);
162
163 var visibility = 0.0;
164 let oneOverShadowDepthTextureSize = 1.0 / 2048.0;
165 let offsets = array<vec2f, 25>(
166 vec2f(-2, -2), vec2f(-1, -2), vec2f(0, -2), vec2f(1, -2), vec2f(2, -2),
167 vec2f(-2, -1), vec2f(-1, -1), vec2f(0, -1), vec2f(1, -1), vec2f(2, -1),
168 vec2f(-2, 0), vec2f(-1, 0), vec2f(0, 0), vec2f(1, 0), vec2f(2, 0),
169 vec2f(-2, 1), vec2f(-1, 1), vec2f(0, 1), vec2f(1, 1), vec2f(2, 1),
170 vec2f(-2, 2), vec2f(-1, 2), vec2f(0, 2), vec2f(1, 2), vec2f(2, 2)
171 );
172
173 const PCF_SAMPLES: u32 = 25u;
174
175 for (var i = 0u; i < PCF_SAMPLES; i++) {
176 let offset = offsets[i] * oneOverShadowDepthTextureSize;
177 visibility += textureSampleCompare(
178 lightsDirectionalTextures, lightsDirectionalTextureSampler,
179 projCoord.xy + offset, i32(light.shadowIndex), projCoord.z - shadowBias
180 );
181 }
182 visibility /= 25.0;
183 if (visibility < 0.01) {
184 return vec3f(0.0);
185 }
186
187 let L = normalize(light.direction);
188 let R = reflect(-L, N); // equivalent to 2.0 * dot(N, L) * N - L
189
190 let diffuse = max(0.0, dot(L, N)) * light.color.rgb;
191 // let diffuse = light.color.rgb;
192
193 // We clamp the dot product to 0 when it is negative
194 let RoV = max(0.0, dot(R, V));
195 let specular = pow(RoV, Shiness) * light.color.rgb;
196
197 return (MatKd * diffuse + MatKs * specular) * visibility;
198}
199
200@fragment
201fn fs_main(
202 vertexToFragment : VertexToFragment,
203) -> DeferredOutput {
204 var output : DeferredOutput;
205 output.color = vec4(0.0, 0.0, 0.0, 1.0);
206 var coords = vec2i(floor(vertexToFragment.coord.xy));
207 const Shiness = 32.0;
208
209 let depth = textureLoad(gBufferDepth, coords, 0).x;
210
211 if (depth >= 1.0) {
212 return output;
213 }
214
215 let bufferSize = textureDimensions(gBufferDepth);
216 let coordUV = floor(vertexToFragment.coord.xy) / vec2f(bufferSize);
217 let position = world_from_screen_coord(coordUV, depth);
218
219 let normal = textureLoad(gBufferNormal, coords, 0).xyz;
220 let albedo = textureLoad(gBufferAlbedo, coords, 0).rgb;
221
222 let N = normalize(normal);
223 let V = normalize(camera.position - position);
224
225 var lighting = ambientLight.color;
226
227 for (var i = 0u; i < MAX_POINT_LIGHTS; i++) {
228 if (i >= pointLights.count) {
229 break;
230 }
231 lighting += calculatePointLight(pointLights.lights[i], position, N);
232 }
233 for (var i = 0u; i < MAX_DIRECTIONAL_LIGHTS; i++) {
234 if (i >= directionalLights.count) {
235 break;
236 }
237 lighting += calculateDirectionalLight(directionalLights.lights[i], N, V, albedo, vec3f(1.0), Shiness, position, 0.007);
238 }
239
240 var color : vec4f = vec4f(albedo * lighting, 1.0);
241 output.color = color;
242 return output;
243}
244)";
245
247 public:
248 explicit Deferred(std::string_view name = DEFERRED_PASS_NAME) : ASingleExecutionRenderPass<Deferred>(name) {}
249
250 void UniqueRenderCallback(wgpu::RenderPassEncoder &renderPass, Engine::Core &core) override
251 {
252 const auto &bindGroupManager = core.GetResource<Graphic::Resource::BindGroupManager>();
253 const auto &bufferContainer = core.GetResource<Graphic::Resource::GPUBufferContainer>();
254
255 auto cameraView = core.GetRegistry().view<Component::GPUCamera>();
256 if (cameraView.empty())
257 {
258 Log::Error("Deferred::UniqueRenderCallback: No camera with GPUCamera component found.");
259 return;
260 }
261
262 Engine::Entity camera{core, cameraView.front()};
263 const auto &cameraGPUComponent = camera.GetComponents<Component::GPUCamera>();
264 const auto &cameraBindGroup = bindGroupManager.Get(cameraGPUComponent.bindGroup);
265 renderPass.setBindGroup(0, cameraBindGroup.GetBindGroup(), 0, nullptr);
266
267 const auto &texturesBindgroup = bindGroupManager.Get(DEFERRED_BINDGROUP_TEXTURES_ID);
268 renderPass.setBindGroup(1, texturesBindgroup.GetBindGroup(), 0, nullptr);
269
270 const auto &lightsBindGroup = bindGroupManager.Get(Utils::LIGHTS_BIND_GROUP_ID);
271 renderPass.setBindGroup(2, lightsBindGroup.GetBindGroup(), 0, nullptr);
272
273 renderPass.draw(6, 1, 0, 0);
274 }
275
277 {
279
280 auto gBufferTexturesLayout = Graphic::Utils::BindGroupLayout("gBufferTextures")
282 .setSampleType(wgpu::TextureSampleType::UnfilterableFloat)
283 .setViewDimension(wgpu::TextureViewDimension::_2D)
284 .setVisibility(wgpu::ShaderStage::Fragment)
285 .setBinding(0))
287 .setSampleType(wgpu::TextureSampleType::UnfilterableFloat)
288 .setViewDimension(wgpu::TextureViewDimension::_2D)
289 .setVisibility(wgpu::ShaderStage::Fragment)
290 .setBinding(1))
292 .setSampleType(wgpu::TextureSampleType::UnfilterableFloat)
293 .setViewDimension(wgpu::TextureViewDimension::_2D)
294 .setVisibility(wgpu::ShaderStage::Fragment)
295 .setBinding(2));
296 auto cameraLayout = Graphic::Utils::BindGroupLayout("camera").addEntry(
298 .setType(wgpu::BufferBindingType::Uniform)
300 .setVisibility(wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Vertex)
301 .setBinding(0));
302 auto lightsLayout = Graphic::Utils::BindGroupLayout("LightsLayout")
304 .setType(wgpu::BufferBindingType::Uniform)
305 .setMinBindingSize(sizeof(glm::vec3) + sizeof(float))
306 .setVisibility(wgpu::ShaderStage::Fragment)
307 .setBinding(0))
309 .setType(wgpu::BufferBindingType::Uniform)
310 .setMinBindingSize(Resource::PointLightsBuffer::GPUSize())
311 .setVisibility(wgpu::ShaderStage::Fragment)
312 .setBinding(1))
314 .setType(wgpu::BufferBindingType::Uniform)
316 .setVisibility(wgpu::ShaderStage::Fragment)
317 .setBinding(2))
319 .setSampleType(wgpu::TextureSampleType::Depth)
320 .setViewDimension(wgpu::TextureViewDimension::_2DArray)
321 .setVisibility(wgpu::ShaderStage::Fragment)
322 .setBinding(3))
323 .addEntry(Graphic::Utils::SamplerBindGroupLayoutEntry("directionalShadowMapSampler")
324 .setType(wgpu::SamplerBindingType::Comparison)
325 .setVisibility(wgpu::ShaderStage::Fragment)
326 .setBinding(4));
327
328 auto colorOutput =
329 Graphic::Utils::ColorTargetState("DEFERRED_OUTPUT").setFormat(wgpu::TextureFormat::BGRA8UnormSrgb);
330
331 shaderDescriptor.setShader(DEFERRED_SHADE_CONTENT)
333 .setVertexEntryPoint("vs_main")
334 .setFragmentEntryPoint("fs_main")
335 .addBindGroupLayout(cameraLayout)
336 .addBindGroupLayout(gBufferTexturesLayout)
337 .addBindGroupLayout(lightsLayout)
338 .addOutputColorFormat(colorOutput);
339 const auto validations = shaderDescriptor.validate();
340 if (!validations.empty())
341 {
342 for (const auto &validation : validations)
343 {
344 if (validation.severity == Graphic::Utils::ValidationError::Severity::Error)
345 {
346 Log::Error(fmt::format("Shader Descriptor Validation Error: {} at {}", validation.message,
347 validation.location));
348 }
349 else if (validation.severity == Graphic::Utils::ValidationError::Severity::Warning)
350 {
351 Log::Warning(fmt::format("Shader Descriptor Validation Warning: {} at {}", validation.message,
352 validation.location));
353 }
354 }
355 }
356 return Graphic::Resource::Shader::Create(shaderDescriptor, graphicContext);
357 }
358};
359
360} // namespace DefaultPipeline::Resource
Deferred(std::string_view name=DEFERRED_PASS_NAME)
Definition Deferred.hpp:248
static Graphic::Resource::Shader CreateShader(Graphic::Resource::Context &graphicContext)
Definition Deferred.hpp:276
void UniqueRenderCallback(wgpu::RenderPassEncoder &renderPass, Engine::Core &core) override
Definition Deferred.hpp:250
static uint32_t GPUSize()
Definition DirectionalLightsBuffer.hpp:109
static uint32_t GPUSize()
Definition PointLightsBuffer.hpp:113
The core is the place where all the data of the engine is stored. It contains the registry (entities)...
Definition Core.hpp:33
TResource & GetResource()
Get a reference of a resource.
Definition Core.inl:14
Registry & GetRegistry()
Get the entt::registry that contains all components. It should be used to update component through sy...
Definition Core.hpp:50
Wrapper class providing a convenient interface for entity manipulation with the Core....
Definition Entity.hpp:20
decltype(auto) GetComponents()
Get components of type TComponent from the entity.
Definition Entity.hpp:118
Definition ASingleExecutionRenderPass.hpp:9
ASingleExecutionRenderPass(std::string_view name)
Definition ASingleExecutionRenderPass.hpp:11
Definition Context.hpp:8
Definition ShaderDescriptor.hpp:21
ShaderDescriptor & setVertexEntryPoint(std::string_view entryPoint)
Definition ShaderDescriptor.hpp:50
ShaderDescriptor & setName(std::string_view name)
Definition ShaderDescriptor.hpp:108
ShaderDescriptor & setFragmentEntryPoint(std::string_view entryPoint)
Definition ShaderDescriptor.hpp:56
ShaderDescriptor & addBindGroupLayout(const Utils::BindGroupLayout &layout)
Definition ShaderDescriptor.hpp:73
ShaderDescriptor & addOutputColorFormat(const Utils::ColorTargetState &state)
Definition ShaderDescriptor.hpp:84
std::vector< Utils::ValidationError > validate(void) const override
Validate the ShaderDescriptor and all contained layouts/states.
Definition ShaderDescriptor.hpp:127
ShaderDescriptor & setShader(std::string_view source)
Definition ShaderDescriptor.hpp:26
Definition Shader.hpp:14
static Shader Create(const ShaderDescriptor &descriptor, Context &context)
Definition Shader.hpp:45
Definition BindGroupLayout.hpp:13
BindGroupLayout & addEntry(const TEntry &entry)
Definition BindGroupLayout.hpp:18
Definition BufferBindGroupLayoutEntry.hpp:7
Definition ColorTargetState.hpp:8
ColorTargetState & setFormat(wgpu::TextureFormat format)
Definition ColorTargetState.hpp:13
Definition SamplerBindGroupLayoutEntry.hpp:7
Definition TextureBindGroupLayoutEntry.hpp:7
Definition AmbientLight.cpp:6
static constexpr std::string_view DEFERRED_PASS_NAME
Definition Deferred.hpp:26
static const entt::hashed_string DEFERRED_PASS_ID
Definition Deferred.hpp:27
static const entt::hashed_string DEFERRED_SHADER_ID
Definition Deferred.hpp:29
static constexpr std::string_view DEFERRED_SHADER_NAME
Definition Deferred.hpp:28
static const entt::hashed_string DEFERRED_PASS_OUTPUT_ID
Definition Deferred.hpp:24
static constexpr std::string_view DEFERRED_BINDGROUP_TEXTURES_NAME
Definition Deferred.hpp:32
static constexpr std::string_view DEFERRED_SHADE_CONTENT
Definition Deferred.hpp:36
static const entt::hashed_string DEFERRED_BINDGROUP_TEXTURES_ID
Definition Deferred.hpp:33
static constexpr std::string_view DEFERRED_PASS_OUTPUT
Definition Deferred.hpp:23
static const entt::hashed_string LIGHTS_BIND_GROUP_ID
Definition Lights.hpp:9
Object::Resource::ResourceManager< std::unique_ptr< AGPUBuffer > > GPUBufferContainer
GPUBufferContainer is a resource that stores GPUBuffer resources.
Definition GPUBufferContainer.hpp:17
Object::Resource::ResourceManager< BindGroup > BindGroupManager
BindGroupManager is a resource that stores BindGroup resources.
Definition BindGroupManager.hpp:17
void Warning(const T &msg) noexcept
Definition Logger.hpp:49
void Error(const T &msg) noexcept
Definition Logger.hpp:51
Definition GPUCamera.hpp:10
static uint32_t GPUSize()
Definition CameraGPUBuffer.hpp:28
@ Warning
Definition IValidable.hpp:13
@ Error
Definition IValidable.hpp:14