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