1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtQuick3DRuntimeRender/private/qssgrenderreflectionprobe_p.h> |
5 | #include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h> |
6 | #include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h> |
7 | #include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h> |
8 | #include <QtQuick3DUtils/private/qssgrenderbasetypes_p.h> |
9 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | const int prefilterSampleCount = 16; |
13 | |
14 | QSSGRenderReflectionMap::QSSGRenderReflectionMap(const QSSGRenderContextInterface &inContext) |
15 | : m_context(inContext) |
16 | { |
17 | } |
18 | |
19 | QSSGRenderReflectionMap::~QSSGRenderReflectionMap() |
20 | { |
21 | releaseCachedResources(); |
22 | } |
23 | |
24 | void QSSGRenderReflectionMap::releaseCachedResources() |
25 | { |
26 | for (QSSGReflectionMapEntry &entry : m_reflectionMapList) |
27 | entry.destroyRhiResources(); |
28 | |
29 | m_reflectionMapList.clear(); |
30 | } |
31 | |
32 | static QRhiTexture *allocateRhiReflectionTexture(QRhi *rhi, |
33 | QRhiTexture::Format format, |
34 | const QSize &size, |
35 | QRhiTexture::Flags flags = {}) |
36 | { |
37 | auto texture = rhi->newTexture(format, pixelSize: size, sampleCount: 1, flags); |
38 | if (!texture->create()) |
39 | qWarning(msg: "Failed to create reflection map texture of size %dx%d" , size.width(), size.height()); |
40 | return texture; |
41 | } |
42 | |
43 | static QRhiRenderBuffer *allocateRhiReflectionRenderBuffer(QRhi *rhi, |
44 | QRhiRenderBuffer::Type type, |
45 | const QSize &size) |
46 | { |
47 | auto renderBuffer = rhi->newRenderBuffer(type, pixelSize: size, sampleCount: 1); |
48 | if (!renderBuffer->create()) |
49 | qWarning(msg: "Failed to build depth-stencil buffer of size %dx%d" , size.width(), size.height()); |
50 | return renderBuffer; |
51 | } |
52 | |
53 | |
54 | void QSSGRenderReflectionMap::addReflectionMapEntry(qint32 probeIdx, const QSSGRenderReflectionProbe &probe) |
55 | { |
56 | QRhi *rhi = m_context.rhiContext()->rhi(); |
57 | // Bail out if there is no QRhi, since we can't add entries without it |
58 | if (!rhi) |
59 | return; |
60 | |
61 | QRhiTexture::Format rhiFormat = QRhiTexture::RGBA16F; |
62 | |
63 | const QByteArray rtName = probe.debugObjectName.toLatin1(); |
64 | |
65 | const int mapRes = 1 << probe.reflectionMapRes; |
66 | QSize pixelSize(mapRes, mapRes); |
67 | QSSGReflectionMapEntry *pEntry = reflectionMapEntry(probeIdx); |
68 | |
69 | if (!pEntry) { |
70 | QRhiRenderBuffer *depthStencil = allocateRhiReflectionRenderBuffer(rhi, type: QRhiRenderBuffer::DepthStencil, size: pixelSize); |
71 | QRhiTexture *map = allocateRhiReflectionTexture(rhi, format: rhiFormat, size: pixelSize, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap |
72 | | QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips); |
73 | QRhiTexture *prefiltered = allocateRhiReflectionTexture(rhi, format: rhiFormat, size: pixelSize, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap |
74 | | QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips); |
75 | m_reflectionMapList.push_back(t: QSSGReflectionMapEntry::withRhiCubeMap(probeIdx, cube: map, prefiltered, depthStencil)); |
76 | |
77 | pEntry = &m_reflectionMapList.back(); |
78 | } |
79 | |
80 | if (pEntry) { |
81 | pEntry->m_needsRender = true; |
82 | |
83 | if (probe.hasScheduledUpdate) |
84 | pEntry->m_rendered = false; |
85 | |
86 | if (!pEntry->m_rhiDepthStencil || mapRes != pEntry->m_rhiCube->pixelSize().width()) { |
87 | pEntry->destroyRhiResources(); |
88 | pEntry->m_rhiDepthStencil = allocateRhiReflectionRenderBuffer(rhi, type: QRhiRenderBuffer::DepthStencil, size: pixelSize); |
89 | pEntry->m_rhiCube = allocateRhiReflectionTexture(rhi, format: rhiFormat, size: pixelSize, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap |
90 | | QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips); |
91 | pEntry->m_rhiPrefilteredCube = allocateRhiReflectionTexture(rhi, format: rhiFormat, size: pixelSize, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap |
92 | | QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips); |
93 | } |
94 | |
95 | // Additional graphics resources: samplers, render targets. |
96 | if (pEntry->m_rhiRenderTargets.isEmpty()) { |
97 | pEntry->m_rhiRenderTargets.resize(sz: 6); |
98 | for (int i = 0; i < 6; ++i) |
99 | pEntry->m_rhiRenderTargets[i] = nullptr; |
100 | } |
101 | Q_ASSERT(pEntry->m_rhiRenderTargets.size() == 6); |
102 | |
103 | if (pEntry->m_skyBoxSrbs.isEmpty()) { |
104 | pEntry->m_skyBoxSrbs.resize(sz: 6); |
105 | for (int i = 0; i < 6; ++i) |
106 | pEntry->m_skyBoxSrbs[i] = nullptr; |
107 | } |
108 | |
109 | |
110 | for (const auto face : QSSGRenderTextureCubeFaces) { |
111 | QRhiTextureRenderTarget *&rt(pEntry->m_rhiRenderTargets[quint8(face)]); |
112 | if (!rt) { |
113 | QRhiColorAttachment att(pEntry->m_rhiCube); |
114 | att.setLayer(quint8(face)); // 6 render targets, each referencing one face of the cubemap |
115 | QRhiTextureRenderTargetDescription rtDesc; |
116 | rtDesc.setColorAttachments({ att }); |
117 | rtDesc.setDepthStencilBuffer(pEntry->m_rhiDepthStencil); |
118 | rt = rhi->newTextureRenderTarget(desc: rtDesc); |
119 | rt->setDescription(rtDesc); |
120 | if (!pEntry->m_rhiRenderPassDesc) |
121 | pEntry->m_rhiRenderPassDesc = rt->newCompatibleRenderPassDescriptor(); |
122 | rt->setRenderPassDescriptor(pEntry->m_rhiRenderPassDesc); |
123 | if (!rt->create()) |
124 | qWarning(msg: "Failed to build reflection map render target" ); |
125 | } |
126 | rt->setName(rtName + QByteArrayLiteral(" reflection cube face: " ) + QSSGBaseTypeHelpers::displayName(face)); |
127 | } |
128 | |
129 | if (!pEntry->m_prefilterPipeline) { |
130 | const QSize mapSize = pEntry->m_rhiCube->pixelSize(); |
131 | |
132 | int mipmapCount = rhi->mipLevelsForSize(size: mapSize); |
133 | mipmapCount = qMin(a: mipmapCount, b: 6); // don't create more than 6 mip levels |
134 | |
135 | // Create a renderbuffer for each mip level |
136 | for (int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) { |
137 | const QSize levelSize = QSize(mapSize.width() * std::pow(x: 0.5, y: mipLevel), |
138 | mapSize.height() * std::pow(x: 0.5, y: mipLevel)); |
139 | pEntry->m_prefilterMipLevelSizes.insert(key: mipLevel, value: levelSize); |
140 | // Setup Render targets (6 * mipmapCount) |
141 | QVarLengthArray<QRhiTextureRenderTarget *, 6> renderTargets; |
142 | for (const auto face : QSSGRenderTextureCubeFaces) { |
143 | QRhiColorAttachment att(pEntry->m_rhiPrefilteredCube); |
144 | att.setLayer(quint8(face)); |
145 | att.setLevel(mipLevel); |
146 | QRhiTextureRenderTargetDescription rtDesc; |
147 | rtDesc.setColorAttachments({att}); |
148 | auto renderTarget = rhi->newTextureRenderTarget(desc: rtDesc); |
149 | renderTarget->setName(rtName + QByteArrayLiteral(" reflection prefilter mip/face " ) |
150 | + QByteArray::number(mipLevel) + QByteArrayLiteral("/" ) + QSSGBaseTypeHelpers::displayName(face)); |
151 | renderTarget->setDescription(rtDesc); |
152 | if (!pEntry->m_rhiPrefilterRenderPassDesc) |
153 | pEntry->m_rhiPrefilterRenderPassDesc = renderTarget->newCompatibleRenderPassDescriptor(); |
154 | renderTarget->setRenderPassDescriptor(pEntry->m_rhiPrefilterRenderPassDesc); |
155 | if (!renderTarget->create()) |
156 | qWarning(msg: "Failed to build prefilter cube map render target" ); |
157 | renderTargets << renderTarget; |
158 | } |
159 | pEntry->m_rhiPrefilterRenderTargetsMap.insert(key: mipLevel, value: renderTargets); |
160 | } |
161 | |
162 | const auto &prefilterShaderStages = m_context.shaderCache()->loadBuiltinForRhi(inKey: "reflectionprobeprefilter" ); |
163 | |
164 | const QSSGRhiSamplerDescription samplerMipMapDesc { |
165 | .minFilter: QRhiSampler::Linear, |
166 | .magFilter: QRhiSampler::Linear, |
167 | .mipmap: QRhiSampler::Linear, |
168 | .hTiling: QRhiSampler::ClampToEdge, |
169 | .vTiling: QRhiSampler::ClampToEdge, |
170 | .zTiling: QRhiSampler::Repeat |
171 | }; |
172 | |
173 | const QSSGRhiSamplerDescription samplerDesc { |
174 | .minFilter: QRhiSampler::Linear, |
175 | .magFilter: QRhiSampler::Linear, |
176 | .mipmap: QRhiSampler::None, |
177 | .hTiling: QRhiSampler::ClampToEdge, |
178 | .vTiling: QRhiSampler::ClampToEdge, |
179 | .zTiling: QRhiSampler::Repeat |
180 | }; |
181 | |
182 | QRhiSampler *sampler = m_context.rhiContext()->sampler(samplerDescription: samplerDesc); |
183 | QRhiSampler *cubeSampler = m_context.rhiContext()->sampler(samplerDescription: samplerMipMapDesc); |
184 | |
185 | QRhiVertexInputLayout inputLayout; |
186 | inputLayout.setBindings({ |
187 | { 3 * sizeof(float) } |
188 | }); |
189 | inputLayout.setAttributes({ |
190 | { 0, 0, QRhiVertexInputAttribute::Float3, 0 } |
191 | }); |
192 | |
193 | int ubufElementSize = rhi->ubufAligned(v: 128); |
194 | pEntry->m_prefilterVertBuffer = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufElementSize * 6); |
195 | pEntry->m_prefilterVertBuffer->create(); |
196 | |
197 | const int uBufSamplesSize = 16 * prefilterSampleCount + 8; |
198 | int uBufSamplesElementSize = rhi->ubufAligned(v: uBufSamplesSize); |
199 | pEntry->m_prefilterFragBuffer = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: uBufSamplesElementSize * mipmapCount); |
200 | pEntry->m_prefilterFragBuffer->create(); |
201 | |
202 | pEntry->m_prefilterPipeline = rhi->newGraphicsPipeline(); |
203 | pEntry->m_prefilterPipeline->setCullMode(QRhiGraphicsPipeline::Front); |
204 | pEntry->m_prefilterPipeline->setFrontFace(QRhiGraphicsPipeline::CCW); |
205 | pEntry->m_prefilterPipeline->setDepthOp(QRhiGraphicsPipeline::LessOrEqual); |
206 | pEntry->m_prefilterPipeline->setShaderStages({ |
207 | *prefilterShaderStages->vertexStage(), |
208 | *prefilterShaderStages->fragmentStage() |
209 | }); |
210 | |
211 | pEntry->m_prefilterSrb = rhi->newShaderResourceBindings(); |
212 | pEntry->m_prefilterSrb->setBindings({ |
213 | QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: pEntry->m_prefilterVertBuffer, size: 128), |
214 | QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(binding: 2, stage: QRhiShaderResourceBinding::FragmentStage, buf: pEntry->m_prefilterFragBuffer, size: uBufSamplesSize), |
215 | QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: pEntry->m_rhiCube, sampler: cubeSampler) |
216 | }); |
217 | pEntry->m_prefilterSrb->create(); |
218 | |
219 | pEntry->m_prefilterPipeline->setVertexInputLayout(inputLayout); |
220 | pEntry->m_prefilterPipeline->setShaderResourceBindings(pEntry->m_prefilterSrb); |
221 | pEntry->m_prefilterPipeline->setRenderPassDescriptor(pEntry->m_rhiPrefilterRenderPassDesc); |
222 | if (!pEntry->m_prefilterPipeline->create()) |
223 | qWarning(msg: "failed to create pre-filter reflection map pipeline state" ); |
224 | |
225 | const auto &irradianceShaderStages = m_context.shaderCache()->loadBuiltinForRhi(inKey: "environmentmapprefilter" ); |
226 | |
227 | pEntry->m_irradiancePipeline = rhi->newGraphicsPipeline(); |
228 | pEntry->m_irradiancePipeline->setCullMode(QRhiGraphicsPipeline::Front); |
229 | pEntry->m_irradiancePipeline->setFrontFace(QRhiGraphicsPipeline::CCW); |
230 | pEntry->m_irradiancePipeline->setDepthOp(QRhiGraphicsPipeline::LessOrEqual); |
231 | pEntry->m_irradiancePipeline->setShaderStages({ |
232 | *irradianceShaderStages->vertexStage(), |
233 | *irradianceShaderStages->fragmentStage() |
234 | }); |
235 | |
236 | int ubufIrradianceSize = rhi->ubufAligned(v: 20); |
237 | pEntry->m_irradianceFragBuffer = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufIrradianceSize); |
238 | pEntry->m_irradianceFragBuffer->create(); |
239 | |
240 | pEntry->m_irradianceSrb = rhi->newShaderResourceBindings(); |
241 | pEntry->m_irradianceSrb->setBindings({ |
242 | QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: pEntry->m_prefilterVertBuffer, size: 128), |
243 | QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(binding: 2, stage: QRhiShaderResourceBinding::FragmentStage, buf: pEntry->m_irradianceFragBuffer, size: 20), |
244 | QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: pEntry->m_rhiCube, sampler) |
245 | }); |
246 | pEntry->m_irradianceSrb->create(); |
247 | |
248 | pEntry->m_irradiancePipeline->setShaderResourceBindings(pEntry->m_irradianceSrb); |
249 | pEntry->m_irradiancePipeline->setVertexInputLayout(inputLayout); |
250 | pEntry->m_irradiancePipeline->setRenderPassDescriptor(pEntry->m_rhiPrefilterRenderPassDesc); |
251 | if (!pEntry->m_irradiancePipeline->create()) |
252 | qWarning(msg: "failed to create irradiance reflection map pipeline state" ); |
253 | } |
254 | |
255 | pEntry->m_timeSlicing = probe.timeSlicing; |
256 | pEntry->m_probeIndex = probeIdx; |
257 | Q_QUICK3D_PROFILE_ASSIGN_ID(&probe, pEntry); |
258 | } |
259 | } |
260 | |
261 | void QSSGRenderReflectionMap::addTexturedReflectionMapEntry(qint32 probeIdx, const QSSGRenderReflectionProbe &probe) |
262 | { |
263 | QSSGReflectionMapEntry *pEntry = reflectionMapEntry(probeIdx); |
264 | const QSSGRenderImageTexture probeTexture = m_context.bufferManager()->loadRenderImage(image: probe.texture, inMipMode: QSSGBufferManager::MipModeFollowRenderImage); |
265 | if (!pEntry) { |
266 | if (probeTexture.m_texture) |
267 | m_reflectionMapList.push_back(t: QSSGReflectionMapEntry::withRhiTexturedCubeMap(probeIdx, preFiltered: probeTexture.m_texture)); |
268 | else |
269 | addReflectionMapEntry(probeIdx, probe); |
270 | } else { |
271 | if (pEntry->m_rhiDepthStencil) |
272 | pEntry->destroyRhiResources(); |
273 | if (probeTexture.m_texture) |
274 | pEntry->m_rhiPrefilteredCube = probeTexture.m_texture; |
275 | } |
276 | } |
277 | |
278 | QSSGReflectionMapEntry *QSSGRenderReflectionMap::reflectionMapEntry(int probeIdx) |
279 | { |
280 | Q_ASSERT(probeIdx >= 0); |
281 | |
282 | for (int i = 0; i < m_reflectionMapList.size(); i++) { |
283 | QSSGReflectionMapEntry *pEntry = &m_reflectionMapList[i]; |
284 | if (pEntry->m_probeIndex == quint32(probeIdx)) |
285 | return pEntry; |
286 | } |
287 | |
288 | return nullptr; |
289 | } |
290 | |
291 | QSSGReflectionMapEntry::QSSGReflectionMapEntry() |
292 | : m_probeIndex(std::numeric_limits<quint32>::max()) |
293 | { |
294 | } |
295 | |
296 | // Vertex data for rendering reflection cube map |
297 | static const float cube[] = { |
298 | -1.0f,-1.0f,-1.0f, // -X side |
299 | -1.0f,-1.0f, 1.0f, |
300 | -1.0f, 1.0f, 1.0f, |
301 | -1.0f, 1.0f, 1.0f, |
302 | -1.0f, 1.0f,-1.0f, |
303 | -1.0f,-1.0f,-1.0f, |
304 | |
305 | -1.0f,-1.0f,-1.0f, // -Z side |
306 | 1.0f, 1.0f,-1.0f, |
307 | 1.0f,-1.0f,-1.0f, |
308 | -1.0f,-1.0f,-1.0f, |
309 | -1.0f, 1.0f,-1.0f, |
310 | 1.0f, 1.0f,-1.0f, |
311 | |
312 | -1.0f,-1.0f,-1.0f, // -Y side |
313 | 1.0f,-1.0f,-1.0f, |
314 | 1.0f,-1.0f, 1.0f, |
315 | -1.0f,-1.0f,-1.0f, |
316 | 1.0f,-1.0f, 1.0f, |
317 | -1.0f,-1.0f, 1.0f, |
318 | |
319 | -1.0f, 1.0f,-1.0f, // +Y side |
320 | -1.0f, 1.0f, 1.0f, |
321 | 1.0f, 1.0f, 1.0f, |
322 | -1.0f, 1.0f,-1.0f, |
323 | 1.0f, 1.0f, 1.0f, |
324 | 1.0f, 1.0f,-1.0f, |
325 | |
326 | 1.0f, 1.0f,-1.0f, // +X side |
327 | 1.0f, 1.0f, 1.0f, |
328 | 1.0f,-1.0f, 1.0f, |
329 | 1.0f,-1.0f, 1.0f, |
330 | 1.0f,-1.0f,-1.0f, |
331 | 1.0f, 1.0f,-1.0f, |
332 | |
333 | -1.0f, 1.0f, 1.0f, // +Z side |
334 | -1.0f,-1.0f, 1.0f, |
335 | 1.0f, 1.0f, 1.0f, |
336 | -1.0f,-1.0f, 1.0f, |
337 | 1.0f,-1.0f, 1.0f, |
338 | 1.0f, 1.0f, 1.0f, |
339 | |
340 | 0.0f, 1.0f, // -X side |
341 | 1.0f, 1.0f, |
342 | 1.0f, 0.0f, |
343 | 1.0f, 0.0f, |
344 | 0.0f, 0.0f, |
345 | 0.0f, 1.0f, |
346 | |
347 | 1.0f, 1.0f, // -Z side |
348 | 0.0f, 0.0f, |
349 | 0.0f, 1.0f, |
350 | 1.0f, 1.0f, |
351 | 1.0f, 0.0f, |
352 | 0.0f, 0.0f, |
353 | |
354 | 1.0f, 0.0f, // -Y side |
355 | 1.0f, 1.0f, |
356 | 0.0f, 1.0f, |
357 | 1.0f, 0.0f, |
358 | 0.0f, 1.0f, |
359 | 0.0f, 0.0f, |
360 | |
361 | 1.0f, 0.0f, // +Y side |
362 | 0.0f, 0.0f, |
363 | 0.0f, 1.0f, |
364 | 1.0f, 0.0f, |
365 | 0.0f, 1.0f, |
366 | 1.0f, 1.0f, |
367 | |
368 | 1.0f, 0.0f, // +X side |
369 | 0.0f, 0.0f, |
370 | 0.0f, 1.0f, |
371 | 0.0f, 1.0f, |
372 | 1.0f, 1.0f, |
373 | 1.0f, 0.0f, |
374 | |
375 | 0.0f, 0.0f, // +Z side |
376 | 0.0f, 1.0f, |
377 | 1.0f, 0.0f, |
378 | 0.0f, 1.0f, |
379 | 1.0f, 1.0f, |
380 | 1.0f, 0.0f, |
381 | }; |
382 | |
383 | float radicalInverseVdC(uint bits) |
384 | { |
385 | bits = (bits << 16u) | (bits >> 16u); |
386 | bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); |
387 | bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); |
388 | bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); |
389 | bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); |
390 | return float(bits) * 2.3283064365386963e-10; // / 0x100000000 |
391 | } |
392 | |
393 | QVector2D hammersley(uint i, uint N) |
394 | { |
395 | return QVector2D(float(i) / float(N), radicalInverseVdC(bits: i)); |
396 | } |
397 | |
398 | QVector3D importanceSampleGGX(QVector2D xi, float roughness) |
399 | { |
400 | float a = roughness*roughness; |
401 | |
402 | float phi = 2.0f * M_PI * xi.x(); |
403 | float cosTheta = sqrt(x: (1.0f - xi.y()) / (1.0f + (a*a - 1.0f) * xi.y())); |
404 | float sinTheta = sqrt(x: 1.0f - cosTheta * cosTheta); |
405 | |
406 | // from spherical coordinates to cartesian coordinates |
407 | return QVector3D(cos(x: phi) * sinTheta, sin(x: phi) * sinTheta, cosTheta); |
408 | } |
409 | |
410 | float distributionGGX(float nDotH, float roughness) |
411 | { |
412 | float a = roughness * roughness; |
413 | float a2 = a * a; |
414 | float nDotH2 = nDotH * nDotH; |
415 | |
416 | float nom = a2; |
417 | float denom = nDotH2 * (a2 - 1.0f) + 1.0f; |
418 | denom = M_PI * denom * denom; |
419 | |
420 | return nom / denom; |
421 | } |
422 | |
423 | void fillPrefilterValues(float roughness, float resolution, |
424 | QVarLengthArray<QVector4D, prefilterSampleCount> &sampleDirections, |
425 | float &invTotalWeight, uint &sampleCount) |
426 | { |
427 | for (int i = 0; i < prefilterSampleCount * 8; ++i) { |
428 | const QVector2D xi = hammersley(i, N: prefilterSampleCount); |
429 | const QVector3D half = importanceSampleGGX(xi, roughness); |
430 | QVector3D light = 2.0f * half.z() * half - QVector3D(0, 0, 1); |
431 | light.normalize(); |
432 | const float D = distributionGGX(nDotH: half.z(), roughness); |
433 | const float pdf = D * half.z() / (4.0f * half.z()) + 0.0001f; |
434 | const float saTexel = 4.0f * M_PI / (6.0f * resolution * resolution); |
435 | const float saSample = 1.0f / (float(prefilterSampleCount) * pdf + 0.0001f); |
436 | float mipLevel = roughness == 0.0f ? 0.0f : 0.5f * log2(x: saSample / saTexel); |
437 | if (light.z() > 0) { |
438 | sampleDirections.append(t: QVector4D(light, mipLevel)); |
439 | invTotalWeight += light.z(); |
440 | sampleCount++; |
441 | if (sampleCount >= prefilterSampleCount) |
442 | break; |
443 | } |
444 | } |
445 | invTotalWeight = 1.0f / invTotalWeight; |
446 | } |
447 | |
448 | void QSSGReflectionMapEntry::renderMips(QSSGRhiContext *context) |
449 | { |
450 | auto *rhi = context->rhi(); |
451 | auto *cb = context->commandBuffer(); |
452 | |
453 | auto *rub = rhi->nextResourceUpdateBatch(); |
454 | rub->generateMips(tex: m_rhiCube); |
455 | QRhiBuffer *vertexBuffer = rhi->newBuffer(type: QRhiBuffer::Immutable, usage: QRhiBuffer::VertexBuffer, size: sizeof(cube)); |
456 | vertexBuffer->create(); |
457 | vertexBuffer->deleteLater(); |
458 | rub->uploadStaticBuffer(buf: vertexBuffer, data: cube); |
459 | cb->resourceUpdate(resourceUpdates: rub); |
460 | |
461 | const QRhiCommandBuffer::VertexInput vbufBinding(vertexBuffer, 0); |
462 | |
463 | int ubufElementSize = rhi->ubufAligned(v: 128); |
464 | |
465 | const int uBufSamplesSize = 16 * prefilterSampleCount + 8; |
466 | int uBufSamplesElementSize = rhi->ubufAligned(v: uBufSamplesSize); |
467 | int uBufIrradianceElementSize = rhi->ubufAligned(v: 20); |
468 | |
469 | // Uniform Data |
470 | QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix(); |
471 | mvp.perspective(verticalAngle: 90.0f, aspectRatio: 1.0f, nearPlane: 0.1f, farPlane: 10.0f); |
472 | |
473 | auto lookAt = [](const QVector3D &eye, const QVector3D ¢er, const QVector3D &up) { |
474 | QMatrix4x4 viewMatrix; |
475 | viewMatrix.lookAt(eye, center, up); |
476 | return viewMatrix; |
477 | }; |
478 | QVarLengthArray<QMatrix4x4, 6> views; |
479 | views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(1.0, 0.0, 0.0), QVector3D(0.0f, -1.0f, 0.0f))); |
480 | views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(-1.0, 0.0, 0.0), QVector3D(0.0f, -1.0f, 0.0f))); |
481 | if (rhi->isYUpInFramebuffer()) { |
482 | views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 1.0, 0.0), QVector3D(0.0f, 0.0f, 1.0f))); |
483 | views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, -1.0, 0.0), QVector3D(0.0f, 0.0f, -1.0f))); |
484 | } else { |
485 | views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, -1.0, 0.0), QVector3D(0.0f, 0.0f, -1.0f))); |
486 | views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 1.0, 0.0), QVector3D(0.0f, 0.0f, 1.0f))); |
487 | } |
488 | views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 0.0, 1.0), QVector3D(0.0f, -1.0f, 0.0f))); |
489 | views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 0.0, -1.0), QVector3D(0.0f, -1.0f, 0.0f))); |
490 | |
491 | rub = rhi->nextResourceUpdateBatch(); |
492 | for (const auto face : QSSGRenderTextureCubeFaces) { |
493 | rub->updateDynamicBuffer(buf: m_prefilterVertBuffer, offset: quint8(face) * ubufElementSize, size: 64, data: mvp.constData()); |
494 | rub->updateDynamicBuffer(buf: m_prefilterVertBuffer, offset: quint8(face) * ubufElementSize + 64, size: 64, data: views[quint8(face)].constData()); |
495 | } |
496 | |
497 | const QSize mapSize = m_rhiCube->pixelSize(); |
498 | |
499 | int mipmapCount = rhi->mipLevelsForSize(size: mapSize); |
500 | mipmapCount = qMin(a: mipmapCount, b: 6); |
501 | |
502 | const float resolution = mapSize.width(); |
503 | QVarLengthArray<QVector4D, prefilterSampleCount> sampleDirections; |
504 | |
505 | // set the samples uniform buffer data |
506 | for (int mipLevel = 0; mipLevel < mipmapCount - 1; ++mipLevel) { |
507 | Q_ASSERT(mipmapCount - 2); |
508 | const float roughness = float(mipLevel) / float(mipmapCount - 2); |
509 | float invTotalWeight = 0.0f; |
510 | uint sampleCount = 0; |
511 | |
512 | sampleDirections.clear(); |
513 | fillPrefilterValues(roughness, resolution, sampleDirections, invTotalWeight, sampleCount); |
514 | |
515 | rub->updateDynamicBuffer(buf: m_prefilterFragBuffer, offset: mipLevel * uBufSamplesElementSize, size: 16 * prefilterSampleCount, data: sampleDirections.constData()); |
516 | rub->updateDynamicBuffer(buf: m_prefilterFragBuffer, offset: mipLevel * uBufSamplesElementSize + 16 * prefilterSampleCount, size: 4, data: &invTotalWeight); |
517 | rub->updateDynamicBuffer(buf: m_prefilterFragBuffer, offset: mipLevel * uBufSamplesElementSize + 16 * prefilterSampleCount + 4, size: 4, data: &sampleCount); |
518 | } |
519 | { |
520 | const float roughness = 0.0f; // doesn't matter for irradiance |
521 | const float lodBias = 0.0f; |
522 | const int distribution = 0; |
523 | const int sampleCount = resolution / 4; |
524 | |
525 | rub->updateDynamicBuffer(buf: m_irradianceFragBuffer, offset: 0, size: 4, data: &roughness); |
526 | rub->updateDynamicBuffer(buf: m_irradianceFragBuffer, offset: 4, size: 4, data: &resolution); |
527 | rub->updateDynamicBuffer(buf: m_irradianceFragBuffer, offset: 4 + 4, size: 4, data: &lodBias); |
528 | rub->updateDynamicBuffer(buf: m_irradianceFragBuffer, offset: 4 + 4 + 4, size: 4, data: &sampleCount); |
529 | rub->updateDynamicBuffer(buf: m_irradianceFragBuffer, offset: 4 + 4 + 4 + 4, size: 4, data: &distribution); |
530 | } |
531 | |
532 | cb->resourceUpdate(resourceUpdates: rub); |
533 | |
534 | // Render |
535 | for (int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) { |
536 | if (mipLevel > 0 && m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::AllFacesAtOnce) |
537 | mipLevel = m_timeSliceFrame; |
538 | |
539 | for (auto face : QSSGRenderTextureCubeFaces) { |
540 | if (m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces) |
541 | face = m_timeSliceFace; |
542 | |
543 | cb->beginPass(rt: m_rhiPrefilterRenderTargetsMap[mipLevel][quint8(face)], colorClearValue: QColor(0, 0, 0, 1), depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: QSSGRhiContext::commonPassFlags()); |
544 | QSSGRHICTX_STAT(context, beginRenderPass(m_rhiPrefilterRenderTargetsMap[mipLevel][quint8(face)])); |
545 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
546 | if (mipLevel < mipmapCount - 1) { |
547 | // Specular pre-filtered Cube Map levels |
548 | cb->setGraphicsPipeline(m_prefilterPipeline); |
549 | cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbufBinding); |
550 | cb->setViewport(QRhiViewport(0, 0, m_prefilterMipLevelSizes[mipLevel].width(), m_prefilterMipLevelSizes[mipLevel].height())); |
551 | QVector<QPair<int, quint32>> dynamicOffsets = { |
552 | { 0, quint32(ubufElementSize * quint8(face)) }, |
553 | { 2, quint32(uBufSamplesElementSize * mipLevel) } |
554 | }; |
555 | cb->setShaderResources(srb: m_prefilterSrb, dynamicOffsetCount: 2, dynamicOffsets: dynamicOffsets.constData()); |
556 | } else { |
557 | // Diffuse Irradiance |
558 | cb->setGraphicsPipeline(m_irradiancePipeline); |
559 | cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbufBinding); |
560 | cb->setViewport(QRhiViewport(0, 0, m_prefilterMipLevelSizes[mipLevel].width(), m_prefilterMipLevelSizes[mipLevel].height())); |
561 | QVector<QPair<int, quint32>> dynamicOffsets = { |
562 | { 0, quint32(ubufElementSize * quint8(face)) }, |
563 | { 2, quint32(uBufIrradianceElementSize) } |
564 | }; |
565 | cb->setShaderResources(srb: m_irradianceSrb, dynamicOffsetCount: 1, dynamicOffsets: dynamicOffsets.constData()); |
566 | } |
567 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall); |
568 | cb->draw(vertexCount: 36); |
569 | QSSGRHICTX_STAT(context, draw(36, 1)); |
570 | Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderCall, 36llu | (1llu << 32), profilingId); |
571 | cb->endPass(); |
572 | QSSGRHICTX_STAT(context, endRenderPass()); |
573 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("reflection_map" , mipLevel, face)); |
574 | |
575 | if (m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces) |
576 | break; |
577 | } |
578 | |
579 | if (mipLevel > 0 && m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::AllFacesAtOnce) { |
580 | m_timeSliceFrame++; |
581 | if (m_timeSliceFrame >= mipmapCount) |
582 | m_timeSliceFrame = 1; |
583 | break; |
584 | } |
585 | } |
586 | cb->debugMarkEnd(); |
587 | } |
588 | |
589 | QSSGReflectionMapEntry QSSGReflectionMapEntry::withRhiTexturedCubeMap(quint32 probeIdx, QRhiTexture *prefiltered) |
590 | { |
591 | QSSGReflectionMapEntry e; |
592 | e.m_probeIndex = probeIdx; |
593 | e.m_rhiPrefilteredCube = prefiltered; |
594 | return e; |
595 | } |
596 | |
597 | QSSGReflectionMapEntry QSSGReflectionMapEntry::withRhiCubeMap(quint32 probeIdx, |
598 | QRhiTexture *cube, |
599 | QRhiTexture *prefiltered, |
600 | QRhiRenderBuffer *depthStencil) |
601 | { |
602 | QSSGReflectionMapEntry e; |
603 | e.m_probeIndex = probeIdx; |
604 | e.m_rhiCube = cube; |
605 | e.m_rhiPrefilteredCube = prefiltered; |
606 | e.m_rhiDepthStencil = depthStencil; |
607 | return e; |
608 | } |
609 | |
610 | void QSSGReflectionMapEntry::destroyRhiResources() |
611 | { |
612 | delete m_rhiCube; |
613 | m_rhiCube = nullptr; |
614 | // Without depth stencil the prefiltered cubemap is assumed to be not owned here and shouldn't be deleted |
615 | if (m_rhiDepthStencil) |
616 | delete m_rhiPrefilteredCube; |
617 | m_rhiPrefilteredCube = nullptr; |
618 | delete m_rhiDepthStencil; |
619 | m_rhiDepthStencil = nullptr; |
620 | |
621 | qDeleteAll(c: m_rhiRenderTargets); |
622 | m_rhiRenderTargets.clear(); |
623 | delete m_rhiRenderPassDesc; |
624 | m_rhiRenderPassDesc = nullptr; |
625 | |
626 | delete m_prefilterPipeline; |
627 | m_prefilterPipeline = nullptr; |
628 | delete m_irradiancePipeline; |
629 | m_irradiancePipeline = nullptr; |
630 | delete m_prefilterSrb; |
631 | m_prefilterSrb = nullptr; |
632 | delete m_irradianceSrb; |
633 | m_irradianceSrb = nullptr; |
634 | delete m_prefilterVertBuffer; |
635 | m_prefilterVertBuffer = nullptr; |
636 | delete m_prefilterFragBuffer; |
637 | m_prefilterFragBuffer = nullptr; |
638 | delete m_irradianceFragBuffer; |
639 | m_irradianceFragBuffer = nullptr; |
640 | delete m_rhiPrefilterRenderPassDesc; |
641 | m_rhiPrefilterRenderPassDesc = nullptr; |
642 | for (const auto &e : std::as_const(t&: m_rhiPrefilterRenderTargetsMap)) |
643 | qDeleteAll(c: e); |
644 | m_rhiPrefilterRenderTargetsMap.clear(); |
645 | m_prefilterMipLevelSizes.clear(); |
646 | } |
647 | |
648 | QT_END_NAMESPACE |
649 | |