From 3bf12c69d345e4db019c05751b2277a7325acfa7 Mon Sep 17 00:00:00 2001 From: saschawillems Date: Sun, 23 Apr 2017 13:26:02 +0200 Subject: [PATCH] Textured PBR IBL example --- data/shaders/pbrtexture/filtercube.vert | 19 + data/shaders/pbrtexture/filtercube.vert.spv | Bin 0 -> 972 bytes data/shaders/pbrtexture/genbrdflut.frag | 90 + data/shaders/pbrtexture/genbrdflut.frag.spv | Bin 0 -> 8032 bytes data/shaders/pbrtexture/genbrdflut.vert | 9 + data/shaders/pbrtexture/genbrdflut.vert.spv | Bin 0 -> 1160 bytes data/shaders/pbrtexture/irradiancecube.frag | 37 + .../pbrtexture/irradiancecube.frag.spv | Bin 0 -> 2856 bytes data/shaders/pbrtexture/pbrtexture.frag | 178 ++ data/shaders/pbrtexture/pbrtexture.frag.spv | Bin 0 -> 14456 bytes data/shaders/pbrtexture/pbrtexture.vert | 35 + data/shaders/pbrtexture/pbrtexture.vert.spv | Bin 0 -> 2540 bytes data/shaders/pbrtexture/prefilterenvmap.frag | 105 ++ .../pbrtexture/prefilterenvmap.frag.spv | Bin 0 -> 9020 bytes data/shaders/pbrtexture/skybox.frag | 39 + data/shaders/pbrtexture/skybox.frag.spv | Bin 0 -> 2788 bytes data/shaders/pbrtexture/skybox.vert | 27 + data/shaders/pbrtexture/skybox.vert.spv | Bin 0 -> 1360 bytes pbrtexture/main.cpp | 1506 +++++++++++++++++ pbrtexture/pbrtexture.vcxproj | 106 ++ pbrtexture/pbrtexture.vcxproj.filters | 74 + vulkanExamples.sln | 7 + 22 files changed, 2232 insertions(+) create mode 100644 data/shaders/pbrtexture/filtercube.vert create mode 100644 data/shaders/pbrtexture/filtercube.vert.spv create mode 100644 data/shaders/pbrtexture/genbrdflut.frag create mode 100644 data/shaders/pbrtexture/genbrdflut.frag.spv create mode 100644 data/shaders/pbrtexture/genbrdflut.vert create mode 100644 data/shaders/pbrtexture/genbrdflut.vert.spv create mode 100644 data/shaders/pbrtexture/irradiancecube.frag create mode 100644 data/shaders/pbrtexture/irradiancecube.frag.spv create mode 100644 data/shaders/pbrtexture/pbrtexture.frag create mode 100644 data/shaders/pbrtexture/pbrtexture.frag.spv create mode 100644 data/shaders/pbrtexture/pbrtexture.vert create mode 100644 data/shaders/pbrtexture/pbrtexture.vert.spv create mode 100644 data/shaders/pbrtexture/prefilterenvmap.frag create mode 100644 data/shaders/pbrtexture/prefilterenvmap.frag.spv create mode 100644 data/shaders/pbrtexture/skybox.frag create mode 100644 data/shaders/pbrtexture/skybox.frag.spv create mode 100644 data/shaders/pbrtexture/skybox.vert create mode 100644 data/shaders/pbrtexture/skybox.vert.spv create mode 100644 pbrtexture/main.cpp create mode 100644 pbrtexture/pbrtexture.vcxproj create mode 100644 pbrtexture/pbrtexture.vcxproj.filters diff --git a/data/shaders/pbrtexture/filtercube.vert b/data/shaders/pbrtexture/filtercube.vert new file mode 100644 index 00000000..1226e28e --- /dev/null +++ b/data/shaders/pbrtexture/filtercube.vert @@ -0,0 +1,19 @@ +#version 450 + +layout (location = 0) in vec3 inPos; + +layout(push_constant) uniform PushConsts { + layout (offset = 0) mat4 mvp; +} pushConsts; + +layout (location = 0) out vec3 outUVW; + +out gl_PerVertex { + vec4 gl_Position; +}; + +void main() +{ + outUVW = inPos; + gl_Position = pushConsts.mvp * vec4(inPos.xyz, 1.0); +} diff --git a/data/shaders/pbrtexture/filtercube.vert.spv b/data/shaders/pbrtexture/filtercube.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..d858d61a30f4fb720e1050ee69f9716ec173a890 GIT binary patch literal 972 zcmYk4$w~uJ5JjJfiSrQW*{DlFT&M`5A}->>MMTu42nG$9w4_^c=`Z+8ew7=+bGp-# z*Ho(N)^uOJ6boAeA@uPLhOLmDN+{rmFcQkCu3cVV?!?LC&ffm68RMatCYm!rou?mZ zJv7_eL&yj+i4^$?=x>rsWcB9hn;X3)H@A0L+`4AF9>wSq^T*LoFZcCE=eE&F8Xvfz z8Ln+CiknF@YLj0~XNs5jX3+Jw`1w3)#|aI~%oFpp-e1$4IqcV-oLZODTACGtzMOLZ zf+{(A#630Uih0kAo*AXo&v;<|@7EqV50;;DwuhKKrukKGfVpYyIjl0eip(>^I(_3+ zd|yFZ{HW`@+dMvu?R({Meff*oZwzfe-)D^~?;~dK3ABC0?5&@@Z3wgY75pk^+N?r*s-Ia4Z;y)n1 z!!f9LA4R> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + float rdi = float(bits) * 2.3283064365386963e-10; + return vec2(float(i) /float(N), rdi); +} + +// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf +vec3 importanceSample_GGX(vec2 Xi, float roughness, vec3 normal) +{ + // Maps a 2D point to a hemisphere with spread based on roughness + float alpha = roughness * roughness; + float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); + + // Tangent space + vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangentX = normalize(cross(up, normal)); + vec3 tangentY = normalize(cross(normal, tangentX)); + + // Convert to world Space + return normalize(tangentX * H.x + tangentY * H.y + normal * H.z); +} + +// Geometric Shadowing function +float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness) +{ + float k = (roughness * roughness) / 2.0; + float GL = dotNL / (dotNL * (1.0 - k) + k); + float GV = dotNV / (dotNV * (1.0 - k) + k); + return GL * GV; +} + +vec2 BRDF(float NoV, float roughness) +{ + // Normal always points along z-axis for the 2D lookup + const vec3 N = vec3(0.0, 0.0, 1.0); + vec3 V = vec3(sqrt(1.0 - NoV*NoV), 0.0, NoV); + + vec2 LUT = vec2(0.0); + for(uint i = 0u; i < NUM_SAMPLES; i++) { + vec2 Xi = hammersley2d(i, NUM_SAMPLES); + vec3 H = importanceSample_GGX(Xi, roughness, N); + vec3 L = 2.0 * dot(V, H) * H - V; + + float dotNL = max(dot(N, L), 0.0); + float dotNV = max(dot(N, V), 0.0); + float dotVH = max(dot(V, H), 0.0); + float dotNH = max(dot(H, N), 0.0); + + if (dotNL > 0.0) { + float G = G_SchlicksmithGGX(dotNL, dotNV, roughness); + float G_Vis = (G * dotVH) / (dotNH * dotNV); + float Fc = pow(1.0 - dotVH, 5.0); + LUT += vec2((1.0 - Fc) * G_Vis, Fc * G_Vis); + } + } + return LUT / float(NUM_SAMPLES); +} + +void main() +{ + outColor = vec4(BRDF(inUV.s, 1.0-inUV.t), 0.0, 1.0); +} \ No newline at end of file diff --git a/data/shaders/pbrtexture/genbrdflut.frag.spv b/data/shaders/pbrtexture/genbrdflut.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..fa7ff160430992bcb515ccc2ca4f0e22a1c92dd1 GIT binary patch literal 8032 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 G=h#2raz-Zr literal 0 HcmV?d00001 diff --git a/data/shaders/pbrtexture/irradiancecube.frag b/data/shaders/pbrtexture/irradiancecube.frag new file mode 100644 index 00000000..3232db48 --- /dev/null +++ b/data/shaders/pbrtexture/irradiancecube.frag @@ -0,0 +1,37 @@ +// Generates an irradiance cube from an environment map using convolution + +#version 450 + +layout (location = 0) in vec3 inPos; +layout (location = 0) out vec4 outColor; +layout (binding = 0) uniform samplerCube samplerEnv; + +layout(push_constant) uniform PushConsts { + layout (offset = 64) float deltaPhi; + layout (offset = 68) float deltaTheta; +} consts; + +#define PI 3.1415926535897932384626433832795 + +void main() +{ + vec3 N = normalize(inPos); + vec3 up = vec3(0.0, 1.0, 0.0); + vec3 right = normalize(cross(up, N)); + up = cross(N, right); + + const float TWO_PI = PI * 2.0; + const float HALF_PI = PI * 0.5; + + vec3 color = vec3(0.0); + uint sampleCount = 0u; + for (float phi = 0.0; phi < TWO_PI; phi += consts.deltaPhi) { + for (float theta = 0.0; theta < HALF_PI; theta += consts.deltaTheta) { + vec3 tempVec = cos(phi) * right + sin(phi) * up; + vec3 sampleVector = cos(theta) * N + sin(theta) * tempVec; + color += texture(samplerEnv, sampleVector).rgb * cos(theta) * sin(theta); + sampleCount++; + } + } + outColor = vec4(PI * color / float(sampleCount), 1.0); +} diff --git a/data/shaders/pbrtexture/irradiancecube.frag.spv b/data/shaders/pbrtexture/irradiancecube.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..e5d9dea3ea337e0ee815028ca2d8a14cda97974b GIT binary patch literal 2856 zcmZ9NTXU2}5QZn&YycBYkduhwZh{I1;|UN&$YR70iD3~0&k~cBM3UV#`vq_D&MSY9 z*WURX_#c#7WtGqK?R4DoOLg_zZ})Wf^vrB3!_#|G8scwn`YH9l{b?9GrLiV>77 zOS8RVZT8IB*Nu28RmwnPCeofXf>yRxHrhJIko(9aaq<)ZRmhss?<}gq4#rpU?ZdZ> z=yUx>yV>cLc7=K=?R5LTr@`AB_tuNjHY#!>=g5POKBm`!+rT_+sjmG z{b##JzpJ?~@ifvvg0sHd2gm|*u~*N;oYsvR2Vmvv#CX!;!=O=g4LL%*XR58ghP@Xp zr)WQcwx0HdY>#Db2HUk0h+o)~rS)*`G+x*3M~snI2Y)Qva>^m{J%_F#e%5~;Z4PHh)k z8QO>IwY>{+F_%s7qlv}FIqsHs!Cw80`4gs!cCB~7x^d>(zTH9F_d96&e#7?6yn|ua z!1e#xGXu8yeZLW}eJR@)=|_GX@!pLyG3_UjA>>NQ8Q=fykr&&q_b%2S0Lz7a5bPeu z$!5Moi1~a|#_Eq+hrv=t8BL)*EA?Y$a{~P$;-~*5w0^a{cwb&d%<&s1dpifX@jvJM7s1xh=Zw6!a#3pz z?4G0V1~~fG7qu>fy)RMgeXv{uiCpvG!CcPd-O{Zx7k4_E(wn_u6=U za^7?AymwW7nfSZt6~y0auYG9UITw)?Rs@ADcWXPh 0.0) { + // D = Normal distribution (Distribution of the microfacets) + float D = D_GGX(dotNH, roughness); + // G = Geometric shadowing term (Microfacets shadowing) + float G = G_SchlicksmithGGX(dotNL, dotNV, roughness); + // F = Fresnel factor (Reflectance depending on angle of incidence) + vec3 F = F_Schlick(dotNV, F0); + vec3 spec = D * F * G / (4.0 * dotNL * dotNV + 0.001); + vec3 kD = (vec3(1.0) - F) * (1.0 - metallic); + color += (kD * ALBEDO / PI + spec) * dotNL; + } + + return color; +} + +// See http://www.thetenthplanet.de/archives/1180 +vec3 perturbNormal() +{ + vec3 tangentNormal = texture(normalMap, inUV).xyz * 2.0 - 1.0; + + vec3 q1 = dFdx(inWorldPos); + vec3 q2 = dFdy(inWorldPos); + vec2 st1 = dFdx(inUV); + vec2 st2 = dFdy(inUV); + + vec3 N = normalize(inNormal); + vec3 T = normalize(q1 * st2.t - q2 * st1.t); + vec3 B = -normalize(cross(N, T)); + mat3 TBN = mat3(T, B, N); + + return normalize(TBN * tangentNormal); +} + +void main() +{ + vec3 N = perturbNormal(); + vec3 V = normalize(ubo.camPos - inWorldPos); + vec3 R = reflect(-V, N); + + float metallic = texture(metallicMap, inUV).r; + float roughness = texture(roughnessMap, inUV).r; + + vec3 F0 = vec3(0.04); + F0 = mix(F0, ALBEDO, metallic); + + vec3 Lo = vec3(0.0); + for(int i = 0; i < uboParams.lights[i].length(); i++) { + vec3 L = normalize(uboParams.lights[i].xyz - inWorldPos); + Lo += specularContribution(L, V, N, F0, metallic, roughness); + } + + vec2 brdf = texture(samplerBRDFLUT, vec2(max(dot(N, V), 0.0), roughness)).rg; + vec3 reflection = prefilteredReflection(R, roughness).rgb; + vec3 irradiance = texture(samplerIrradiance, N).rgb; + + // Diffuse based on irradiance + vec3 diffuse = irradiance * ALBEDO; + + vec3 F = F_SchlickR(max(dot(N, V), 0.0), F0, roughness); + + // Specular reflectance + vec3 specular = reflection * (F * brdf.x + brdf.y); + + // Ambient part + vec3 kD = 1.0 - F; + kD *= 1.0 - metallic; + vec3 ambient = (kD * diffuse + specular) * texture(aoMap, inUV).rrr; + + vec3 color = ambient + Lo; + + // Tone mapping + color = Uncharted2Tonemap(color * uboParams.exposure); + color = color * (1.0f / Uncharted2Tonemap(vec3(11.2f))); + // Gamma correction + color = pow(color, vec3(1.0f / uboParams.gamma)); + + outColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/data/shaders/pbrtexture/pbrtexture.frag.spv b/data/shaders/pbrtexture/pbrtexture.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..f9731e557c6fbc6a6411b82810429418dcc1e6d9 GIT binary patch literal 14456 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 literal 0 HcmV?d00001 diff --git a/data/shaders/pbrtexture/prefilterenvmap.frag b/data/shaders/pbrtexture/prefilterenvmap.frag new file mode 100644 index 00000000..ae1212ed --- /dev/null +++ b/data/shaders/pbrtexture/prefilterenvmap.frag @@ -0,0 +1,105 @@ +#version 450 + +layout (location = 0) in vec3 inPos; +layout (location = 0) out vec4 outColor; + +layout (binding = 0) uniform samplerCube samplerEnv; + +layout(push_constant) uniform PushConsts { + layout (offset = 64) float roughness; + layout (offset = 68) uint numSamples; +} consts; + +const float PI = 3.1415926536; + +// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/ +float random(vec2 co) +{ + float a = 12.9898; + float b = 78.233; + float c = 43758.5453; + float dt= dot(co.xy ,vec2(a,b)); + float sn= mod(dt,3.14); + return fract(sin(sn) * c); +} + +vec2 hammersley2d(uint i, uint N) +{ + // Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html + uint bits = (i << 16u) | (i >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + float rdi = float(bits) * 2.3283064365386963e-10; + return vec2(float(i) /float(N), rdi); +} + +// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf +vec3 importanceSample_GGX(vec2 Xi, float roughness, vec3 normal) +{ + // Maps a 2D point to a hemisphere with spread based on roughness + float alpha = roughness * roughness; + float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); + + // Tangent space + vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangentX = normalize(cross(up, normal)); + vec3 tangentY = normalize(cross(normal, tangentX)); + + // Convert to world Space + return normalize(tangentX * H.x + tangentY * H.y + normal * H.z); +} + +// Normal Distribution function +float D_GGX(float dotNH, float roughness) +{ + float alpha = roughness * roughness; + float alpha2 = alpha * alpha; + float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0; + return (alpha2)/(PI * denom*denom); +} + +vec3 prefilterEnvMap(vec3 R, float roughness) +{ + vec3 N = R; + vec3 V = R; + vec3 color = vec3(0.0); + float totalWeight = 0.0; + float envMapDim = float(textureSize(samplerEnv, 0).s); + for(uint i = 0u; i < consts.numSamples; i++) { + vec2 Xi = hammersley2d(i, consts.numSamples); + vec3 H = importanceSample_GGX(Xi, roughness, N); + vec3 L = 2.0 * dot(V, H) * H - V; + float dotNL = clamp(dot(N, L), 0.0, 1.0); + if(dotNL > 0.0) { + // Filtering based on https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/ + + float dotNH = clamp(dot(N, H), 0.0, 1.0); + float dotVH = clamp(dot(V, H), 0.0, 1.0); + + // Probability Distribution Function + float pdf = D_GGX(dotNH, roughness) * dotNH / (4.0 * dotVH) + 0.0001; + // Slid angle of current smple + float omegaS = 1.0 / (float(consts.numSamples) * pdf); + // Solid angle of 1 pixel across all cube faces + float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim); + // Biased (+1.0) mip level for better result + float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0f); + color += textureLod(samplerEnv, L, mipLevel).rgb * dotNL; + totalWeight += dotNL; + + } + } + return (color / totalWeight); +} + + +void main() +{ + vec3 N = normalize(inPos); + outColor = vec4(prefilterEnvMap(N, consts.roughness), 1.0); +} diff --git a/data/shaders/pbrtexture/prefilterenvmap.frag.spv b/data/shaders/pbrtexture/prefilterenvmap.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..269e5d554dccfb65521b8bc7de7eaf662f9a6fe6 GIT binary patch literal 9020 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 W0bDK2{o{RKh-P2?{sxu%8}xtp_Bq4= literal 0 HcmV?d00001 diff --git a/data/shaders/pbrtexture/skybox.frag b/data/shaders/pbrtexture/skybox.frag new file mode 100644 index 00000000..e46fdfe0 --- /dev/null +++ b/data/shaders/pbrtexture/skybox.frag @@ -0,0 +1,39 @@ +#version 450 + +layout (binding = 2) uniform samplerCube samplerEnv; + +layout (location = 0) in vec3 inUVW; + +layout (location = 0) out vec4 outColor; + +layout (binding = 1) uniform UBOParams { + vec4 lights[4]; + float exposure; + float gamma; +} uboParams; + +// From http://filmicworlds.com/blog/filmic-tonemapping-operators/ +vec3 Uncharted2Tonemap(vec3 color) +{ + float A = 0.15; + float B = 0.50; + float C = 0.10; + float D = 0.20; + float E = 0.02; + float F = 0.30; + float W = 11.2; + return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F; +} + +void main() +{ + vec3 color = texture(samplerEnv, inUVW).rgb; + + // Tone mapping + color = Uncharted2Tonemap(color * uboParams.exposure); + color = color * (1.0f / Uncharted2Tonemap(vec3(11.2f))); + // Gamma correction + color = pow(color, vec3(1.0f / uboParams.gamma)); + + outColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/data/shaders/pbrtexture/skybox.frag.spv b/data/shaders/pbrtexture/skybox.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..d5632e9ebcd73da6b934699ccd76353276418807 GIT binary patch literal 2788 zcmZ9N+gH?86vuyD1`!lcQzOj==wfMPj@J+!KpiUzOwsN&!U!aDX#iJ`wOXy!Q}6id zf6}u(_pkI)tIucVY??f*&HnE1-skLn_Bp>ZJ>3&~vaE~0{_Oj#Jq~2u*jYB1^|f?q z{?7d5=GMyOx$|d?7|MEDfyNAHd$L}%QZ297bd-=s$Ov)rBmniWYL@-Zq#SH#e2(0M z_?BwRYvtxvW##PMdaY6|H%>l&SeR{x3(aHFvXIZERGUH)@aDb-1_n+S0xDoLshuy)?V9SZYLlmivEbTFRSHhwT#a3_X&FYL49{k`}G-I!z8Vk z-t6oD*|}D~>)Ts%sYRSYZ>#=s;v8X*fz7AwUQQqm?FDonVn1(n>|+Ak5uE#wD|T?M zDQCY+oR9mBJnm;4ahONjI$THFyApLAVwUs9S~sS>#u2b}<&mSrd!Mv>kUVxjT22|m z-kY$z{{6{z&M9p5F!E$)^lI1#uo>6FK7{edvuD#`A8y(6yYWY{2mX9|CH$kp3|n@YBQb?Co|cCP!?uU$%<{dpg3KEJWU z{Qn4@M;z9x{V}5dIR8C&=g6H%=N@;#bt4P>>|02zvjXQ_*ZB_Zxm&MwXnTL;E>OF1?#m(f7fcRqtoNaa zMBlXit~zbMEA46GXVJbH*OM)O3n?M@S=AaoK#Y-JAcHXd82ja0BmWyQ&iNE^p1f-(D(E+O7eYqY$Lsd>$L5WwzZmTIAQbn9?F};w@~c*zJp@d_6-!fuJ51NHT{00&&R>;Ezb63u(`tT zyRSd)_A6jH<6=%8Y#m{bfvwRy=lvK*@`!xgj|s4R$WN*Fufmx_f85#Ez}BfRa=Z?9 zzhS=tcF&RXB-j}F$oVE%J|uF!1!oTZu4m6rA)S3WjosOox065mGKno`T-5Xq*gi!5 zGhn%p$p0>!^YzD`&w{N%U#wREckcNdwr3lAJ`XlVK5||F%ZEhHi*V-9AMeE_u)WY1 pIWL3lYuN9B%^BbR6xbN~$aw`UAM$fL+i5s+=>Larscm;<{{noBvB>}c literal 0 HcmV?d00001 diff --git a/data/shaders/pbrtexture/skybox.vert b/data/shaders/pbrtexture/skybox.vert new file mode 100644 index 00000000..785a3010 --- /dev/null +++ b/data/shaders/pbrtexture/skybox.vert @@ -0,0 +1,27 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec3 inNormal; +layout (location = 2) in vec2 inUV; + +layout (binding = 0) uniform UBO +{ + mat4 projection; + mat4 model; +} ubo; + +layout (location = 0) out vec3 outUVW; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outUVW = inPos; + gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0); +} diff --git a/data/shaders/pbrtexture/skybox.vert.spv b/data/shaders/pbrtexture/skybox.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..a6d9ade4baa380db571f3ef522cedb2194a57bda GIT binary patch literal 1360 zcmY+C+iuf95QdkWC=@7d>48&m8YqWKRfP&dLLAzg^df=)O1QM##tLo|Z)7_Xm%IWz z5>Leq692dMN@b(T%s=zb%+7f0wa#T@F6p;mc1?MfO-5Sd|KrHa}-L_aX5+6@jM#G;lcC$S#)}CnyPfMFPPqTUJQm`jf-=SB<)+36mIG6 z#*8OnKhB16R>a>0Jolve+X& zYA;Ds2OqV;ylH%E!pqWugj%~5UXjh&6ke64R$06JF8C1YuSqi_`=dUX{oy0NQQ7c) z{X4RKKkofNV$6?s@E^(IZuF!cv$Aja%=$!{9AE!w6~hl7%=du*Qm+6Wcjr*|k377M zH}bO2YZ7+(R#A2iJ`{s{Z%UYjeZXgi+Y~}}P8(=Qx2E+TS IPOl{Y0D7rc=l}o! literal 0 HcmV?d00001 diff --git a/pbrtexture/main.cpp b/pbrtexture/main.cpp new file mode 100644 index 00000000..bad71109 --- /dev/null +++ b/pbrtexture/main.cpp @@ -0,0 +1,1506 @@ +/* +* Vulkan Example - Physical based rendering a textured object (metal/roughness workflow) with image based lighting +* +* Note: Requires the separate asset pack (see data/README.md) +* +* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +// For reference see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf + +#include +#include +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include + +#include +#include "vulkanexamplebase.h" +#include "VulkanBuffer.hpp" +#include "VulkanTexture.hpp" +#include "VulkanModel.hpp" + +#define ENABLE_VALIDATION false + +class VulkanExample : public VulkanExampleBase +{ +public: + bool displaySkybox = true; + + struct Textures { + vks::TextureCubeMap environmentCube; + // Generated at runtime + vks::Texture2D lutBrdf; + vks::TextureCubeMap irradianceCube; + vks::TextureCubeMap prefilteredCube; + // Object texture maps + vks::Texture2D albedoMap; + vks::Texture2D normalMap; + vks::Texture2D aoMap; + vks::Texture2D metallicMap; + vks::Texture2D roughnessMap; + } textures; + + // Vertex layout for the models + vks::VertexLayout vertexLayout = vks::VertexLayout({ + vks::VERTEX_COMPONENT_POSITION, + vks::VERTEX_COMPONENT_NORMAL, + vks::VERTEX_COMPONENT_UV, + }); + + struct Meshes { + vks::Model skybox; + vks::Model object; + } models; + + struct { + vks::Buffer object; + vks::Buffer skybox; + vks::Buffer params; + } uniformBuffers; + + struct UBOMatrices { + glm::mat4 projection; + glm::mat4 model; + glm::mat4 view; + glm::vec3 camPos; + } uboMatrices; + + struct UBOParams { + glm::vec4 lights[4]; + float exposure = 2.0f; + float gamma = 2.2f; + } uboParams; + + struct { + VkPipeline skybox; + VkPipeline pbr; + } pipelines; + + struct { + VkDescriptorSet object; + VkDescriptorSet skybox; + } descriptorSets; + + VkPipelineLayout pipelineLayout; + VkDescriptorSetLayout descriptorSetLayout; + + VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) + { + title = "Vulkan textured PBR using IBL"; + + enableTextOverlay = true; + camera.type = Camera::CameraType::firstperson; + camera.movementSpeed = 4.0f; + camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); + camera.rotationSpeed = 0.25f; + + camera.setRotation({ -10.75f, 153.0f, 0.0f }); + camera.setPosition({ 5.25f, 0.5f, 3.5f }); + } + + ~VulkanExample() + { + vkDestroyPipeline(device, pipelines.skybox, nullptr); + vkDestroyPipeline(device, pipelines.pbr, nullptr); + + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + models.object.destroy(); + models.skybox.destroy(); + + uniformBuffers.object.destroy(); + uniformBuffers.skybox.destroy(); + uniformBuffers.params.destroy(); + + textures.environmentCube.destroy(); + textures.irradianceCube.destroy(); + textures.prefilteredCube.destroy(); + textures.lutBrdf.destroy(); + textures.albedoMap.destroy(); + textures.normalMap.destroy(); + textures.aoMap.destroy(); + textures.metallicMap.destroy(); + textures.roughnessMap.destroy(); + } + + virtual void getEnabledFeatures() + { + if (deviceFeatures.samplerAnisotropy) { + enabledFeatures.samplerAnisotropy = VK_TRUE; + } + } + + void buildCommandBuffers() + { + VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); + + VkClearValue clearValues[2]; + clearValues[0].color = { { 0.1f, 0.1f, 0.1f, 1.0f } }; + clearValues[1].depthStencil = { 1.0f, 0 }; + + VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); + renderPassBeginInfo.renderPass = renderPass; + renderPassBeginInfo.renderArea.offset.x = 0; + renderPassBeginInfo.renderArea.offset.y = 0; + renderPassBeginInfo.renderArea.extent.width = width; + renderPassBeginInfo.renderArea.extent.height = height; + renderPassBeginInfo.clearValueCount = 2; + renderPassBeginInfo.pClearValues = clearValues; + + for (size_t i = 0; i < drawCmdBuffers.size(); ++i) + { + // Set target frame buffer + renderPassBeginInfo.framebuffer = frameBuffers[i]; + + VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); + + vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); + vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); + + VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); + vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); + + VkDeviceSize offsets[1] = { 0 }; + + // Skybox + if (displaySkybox) + { + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.skybox, 0, NULL); + vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.skybox.vertices.buffer, offsets); + vkCmdBindIndexBuffer(drawCmdBuffers[i], models.skybox.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox); + vkCmdDrawIndexed(drawCmdBuffers[i], models.skybox.indexCount, 1, 0, 0, 0); + } + + // Objects + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.object, 0, NULL); + vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.object.vertices.buffer, offsets); + vkCmdBindIndexBuffer(drawCmdBuffers[i], models.object.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.pbr); + + vkCmdDrawIndexed(drawCmdBuffers[i], models.object.indexCount, 1, 0, 0, 0); + + vkCmdEndRenderPass(drawCmdBuffers[i]); + + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + } + + void loadAssets() + { + textures.environmentCube.loadFromFile(ASSET_PATH "textures/hdr/gcanyon_cube.ktx", VK_FORMAT_R16G16B16A16_SFLOAT, vulkanDevice, queue); + models.skybox.loadFromFile(ASSET_PATH "models/cube.obj", vertexLayout, 1.0f, vulkanDevice, queue); + // PBR model + models.object.loadFromFile(ASSET_PATH "models/cerberus/cerberus.fbx", vertexLayout, 0.05f, vulkanDevice, queue); + textures.albedoMap.loadFromFile(ASSET_PATH "models/cerberus/albedo.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); + textures.normalMap.loadFromFile(ASSET_PATH "models/cerberus/normal.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); + textures.aoMap.loadFromFile(ASSET_PATH "models/cerberus/ao.ktx", VK_FORMAT_R8_UNORM, vulkanDevice, queue); + textures.metallicMap.loadFromFile(ASSET_PATH "models/cerberus/metallic.ktx", VK_FORMAT_R8_UNORM, vulkanDevice, queue); + textures.roughnessMap.loadFromFile(ASSET_PATH "models/cerberus/roughness.ktx", VK_FORMAT_R8_UNORM, vulkanDevice, queue); + } + + void setupDescriptors() + { + // Descriptor Pool + std::vector poolSizes = { + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 16) + }; + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + + // Descriptor set layout + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 1), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 3), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 4), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 5), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 6), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 7), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 8), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 9), + }; + VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); + + // Descriptor sets + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); + + // Objects + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.object)); + std::vector writeDescriptorSets = { + vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.object.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &uniformBuffers.params.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.irradianceCube.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &textures.lutBrdf.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4, &textures.prefilteredCube.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 5, &textures.albedoMap.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6, &textures.normalMap.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 7, &textures.aoMap.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 8, &textures.metallicMap.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 9, &textures.roughnessMap.descriptor), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + + // Sky box + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skybox)); + writeDescriptorSets = { + vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.skybox.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &uniformBuffers.params.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.environmentCube.descriptor), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + } + + void preparePipelines() + { + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = + vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + + VkPipelineRasterizationStateCreateInfo rasterizationState = + vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE); + + VkPipelineColorBlendAttachmentState blendAttachmentState = + vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + + VkPipelineColorBlendStateCreateInfo colorBlendState = + vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + + VkPipelineDepthStencilStateCreateInfo depthStencilState = + vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL); + + VkPipelineViewportStateCreateInfo viewportState = + vks::initializers::pipelineViewportStateCreateInfo(1, 1); + + VkPipelineMultisampleStateCreateInfo multisampleState = + vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); + + std::vector dynamicStateEnables = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState = + vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); + + // Pipeline layout + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + + // Pipelines + VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); + + std::array shaderStages; + + pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; + pipelineCreateInfo.pRasterizationState = &rasterizationState; + pipelineCreateInfo.pColorBlendState = &colorBlendState; + pipelineCreateInfo.pMultisampleState = &multisampleState; + pipelineCreateInfo.pViewportState = &viewportState; + pipelineCreateInfo.pDepthStencilState = &depthStencilState; + pipelineCreateInfo.pDynamicState = &dynamicState; + pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); + pipelineCreateInfo.pStages = shaderStages.data(); + + // Vertex bindings an attributes + // Binding description + std::vector vertexInputBindings = { + vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX), + }; + + // Attribute descriptions + std::vector vertexInputAttributes = { + vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Position + vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Normal + vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // UV + }; + + VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); + vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); + vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); + vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); + vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); + + pipelineCreateInfo.pVertexInputState = &vertexInputState; + + // Skybox pipeline (background cube) + shaderStages[0] = loadShader(ASSET_PATH "shaders/pbrtexture/skybox.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(ASSET_PATH "shaders/pbrtexture/skybox.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.skybox)); + + // PBR pipeline + shaderStages[0] = loadShader(ASSET_PATH "shaders/pbrtexture/pbrtexture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(ASSET_PATH "shaders/pbrtexture/pbrtexture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + // Enable depth test and write + depthStencilState.depthWriteEnable = VK_TRUE; + depthStencilState.depthTestEnable = VK_TRUE; + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.pbr)); + } + + // Generate a BRDF integration map used as a look-up-table (stores roughness / NdotV) + void generateBRDFLUT() + { + auto tStart = std::chrono::high_resolution_clock::now(); + + const VkFormat format = VK_FORMAT_R16G16_SFLOAT; // R16G16 is supported pretty much everywhere + const int32_t dim = 512; + + // Image + VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo(); + imageCI.imageType = VK_IMAGE_TYPE_2D; + imageCI.format = format; + imageCI.extent.width = dim; + imageCI.extent.height = dim; + imageCI.extent.depth = 1; + imageCI.mipLevels = 1; + imageCI.arrayLayers = 1; + imageCI.samples = VK_SAMPLE_COUNT_1_BIT; + imageCI.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &textures.lutBrdf.image)); + VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device, textures.lutBrdf.image, &memReqs); + memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &textures.lutBrdf.deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device, textures.lutBrdf.image, textures.lutBrdf.deviceMemory, 0)); + // Image view + VkImageViewCreateInfo viewCI = vks::initializers::imageViewCreateInfo(); + viewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewCI.format = format; + viewCI.subresourceRange = {}; + viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewCI.subresourceRange.levelCount = 1; + viewCI.subresourceRange.layerCount = 1; + viewCI.image = textures.lutBrdf.image; + VK_CHECK_RESULT(vkCreateImageView(device, &viewCI, nullptr, &textures.lutBrdf.view)); + // Sampler + VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo(); + samplerCI.magFilter = VK_FILTER_LINEAR; + samplerCI.minFilter = VK_FILTER_LINEAR; + samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCI.minLod = 0.0f; + samplerCI.maxLod = 1.0f; + samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &textures.lutBrdf.sampler)); + + textures.lutBrdf.descriptor.imageView = textures.lutBrdf.view; + textures.lutBrdf.descriptor.sampler = textures.lutBrdf.sampler; + textures.lutBrdf.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + textures.lutBrdf.device = vulkanDevice; + + // FB, Att, RP, Pipe, etc. + VkAttachmentDescription attDesc = {}; + // Color attachment + attDesc.format = format; + attDesc.samples = VK_SAMPLE_COUNT_1_BIT; + attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attDesc.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + + VkSubpassDescription subpassDescription = {}; + subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpassDescription.colorAttachmentCount = 1; + subpassDescription.pColorAttachments = &colorReference; + + // Use subpass dependencies for layout transitions + std::array dependencies; + dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; + dependencies[0].dstSubpass = 0; + dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; + dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + dependencies[1].srcSubpass = 0; + dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; + dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + + // Create the actual renderpass + VkRenderPassCreateInfo renderPassCI = vks::initializers::renderPassCreateInfo(); + renderPassCI.attachmentCount = 1; + renderPassCI.pAttachments = &attDesc; + renderPassCI.subpassCount = 1; + renderPassCI.pSubpasses = &subpassDescription; + renderPassCI.dependencyCount = 2; + renderPassCI.pDependencies = dependencies.data(); + + VkRenderPass renderpass; + VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass)); + + VkFramebufferCreateInfo framebufferCI = vks::initializers::framebufferCreateInfo(); + framebufferCI.renderPass = renderpass; + framebufferCI.attachmentCount = 1; + framebufferCI.pAttachments = &textures.lutBrdf.view; + framebufferCI.width = dim; + framebufferCI.height = dim; + framebufferCI.layers = 1; + + VkFramebuffer framebuffer; + VK_CHECK_RESULT(vkCreateFramebuffer(device, &framebufferCI, nullptr, &framebuffer)); + + // Desriptors + VkDescriptorSetLayout descriptorsetlayout; + std::vector setLayoutBindings = {}; + VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout)); + + // Descriptor Pool + std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) }; + VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); + VkDescriptorPool descriptorpool; + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorpool)); + + // Descriptor sets + VkDescriptorSet descriptorset; + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorpool, &descriptorsetlayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorset)); + + // Pipeline layout + VkPipelineLayout pipelinelayout; + VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorsetlayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelinelayout)); + + // Pipeline + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE); + VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL); + VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1); + VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); + std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); + VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); + std::array shaderStages; + + VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelinelayout, renderpass); + pipelineCI.pInputAssemblyState = &inputAssemblyState; + pipelineCI.pRasterizationState = &rasterizationState; + pipelineCI.pColorBlendState = &colorBlendState; + pipelineCI.pMultisampleState = &multisampleState; + pipelineCI.pViewportState = &viewportState; + pipelineCI.pDepthStencilState = &depthStencilState; + pipelineCI.pDynamicState = &dynamicState; + pipelineCI.stageCount = 2; + pipelineCI.pStages = shaderStages.data(); + pipelineCI.pVertexInputState = &emptyInputState; + + // Look-up-table (from BRDF) pipeline + shaderStages[0] = loadShader(ASSET_PATH "shaders/pbrtexture/genbrdflut.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(ASSET_PATH "shaders/pbrtexture/genbrdflut.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + VkPipeline pipeline; + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); + + // Render + VkClearValue clearValues[1]; + clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); + renderPassBeginInfo.renderPass = renderpass; + renderPassBeginInfo.renderArea.extent.width = dim; + renderPassBeginInfo.renderArea.extent.height = dim; + renderPassBeginInfo.clearValueCount = 1; + renderPassBeginInfo.pClearValues = clearValues; + renderPassBeginInfo.framebuffer = framebuffer; + + VkCommandBuffer cmdBuf = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + VkViewport viewport = vks::initializers::viewport((float)dim, (float)dim, 0.0f, 1.0f); + VkRect2D scissor = vks::initializers::rect2D(dim, dim, 0, 0); + vkCmdSetViewport(cmdBuf, 0, 1, &viewport); + vkCmdSetScissor(cmdBuf, 0, 1, &scissor); + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdDraw(cmdBuf, 3, 1, 0, 0); + vkCmdEndRenderPass(cmdBuf); + vulkanDevice->flushCommandBuffer(cmdBuf, queue); + + vkQueueWaitIdle(queue); + + // todo: cleanup + vkDestroyPipeline(device, pipeline, nullptr); + vkDestroyPipelineLayout(device, pipelinelayout, nullptr); + vkDestroyRenderPass(device, renderpass, nullptr); + vkDestroyFramebuffer(device, framebuffer, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorsetlayout, nullptr); + vkDestroyDescriptorPool(device, descriptorpool, nullptr); + + auto tEnd = std::chrono::high_resolution_clock::now(); + auto tDiff = std::chrono::duration(tEnd - tStart).count(); + std::cout << "Generating BRDF LUT took " << tDiff << " ms" << std::endl; + } + + // Generate an irradiance cube map from the environment cube map + void generateIrradianceCube() + { + auto tStart = std::chrono::high_resolution_clock::now(); + + const VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT; + const int32_t dim = 64; + const uint32_t numMips = static_cast(floor(log2(dim))) + 1; + + // Pre-filtered cube map + // Image + VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo(); + imageCI.imageType = VK_IMAGE_TYPE_2D; + imageCI.format = format; + imageCI.extent.width = dim; + imageCI.extent.height = dim; + imageCI.extent.depth = 1; + imageCI.mipLevels = numMips; + imageCI.arrayLayers = 6; + imageCI.samples = VK_SAMPLE_COUNT_1_BIT; + imageCI.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCI.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + imageCI.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &textures.irradianceCube.image)); + VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device, textures.irradianceCube.image, &memReqs); + memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &textures.irradianceCube.deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device, textures.irradianceCube.image, textures.irradianceCube.deviceMemory, 0)); + // Image view + VkImageViewCreateInfo viewCI = vks::initializers::imageViewCreateInfo(); + viewCI.viewType = VK_IMAGE_VIEW_TYPE_CUBE; + viewCI.format = format; + viewCI.subresourceRange = {}; + viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewCI.subresourceRange.levelCount = numMips; + viewCI.subresourceRange.layerCount = 6; + viewCI.image = textures.irradianceCube.image; + VK_CHECK_RESULT(vkCreateImageView(device, &viewCI, nullptr, &textures.irradianceCube.view)); + // Sampler + VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo(); + samplerCI.magFilter = VK_FILTER_LINEAR; + samplerCI.minFilter = VK_FILTER_LINEAR; + samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCI.minLod = 0.0f; + samplerCI.maxLod = static_cast(numMips); + samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &textures.irradianceCube.sampler)); + + textures.irradianceCube.descriptor.imageView = textures.irradianceCube.view; + textures.irradianceCube.descriptor.sampler = textures.irradianceCube.sampler; + textures.irradianceCube.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + textures.irradianceCube.device = vulkanDevice; + + // FB, Att, RP, Pipe, etc. + VkAttachmentDescription attDesc = {}; + // Color attachment + attDesc.format = format; + attDesc.samples = VK_SAMPLE_COUNT_1_BIT; + attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + + VkSubpassDescription subpassDescription = {}; + subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpassDescription.colorAttachmentCount = 1; + subpassDescription.pColorAttachments = &colorReference; + + // Use subpass dependencies for layout transitions + std::array dependencies; + dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; + dependencies[0].dstSubpass = 0; + dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; + dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + dependencies[1].srcSubpass = 0; + dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; + dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + + // Renderpass + VkRenderPassCreateInfo renderPassCI = vks::initializers::renderPassCreateInfo(); + renderPassCI.attachmentCount = 1; + renderPassCI.pAttachments = &attDesc; + renderPassCI.subpassCount = 1; + renderPassCI.pSubpasses = &subpassDescription; + renderPassCI.dependencyCount = 2; + renderPassCI.pDependencies = dependencies.data(); + VkRenderPass renderpass; + VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass)); + + struct { + VkImage image; + VkImageView view; + VkDeviceMemory memory; + VkFramebuffer framebuffer; + } offscreen; + + // Offfscreen framebuffer + { + // Color attachment + VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = format; + imageCreateInfo.extent.width = dim; + imageCreateInfo.extent.height = dim; + imageCreateInfo.extent.depth = 1; + imageCreateInfo.mipLevels = 1; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &offscreen.image)); + + VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device, offscreen.image, &memReqs); + memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreen.memory)); + VK_CHECK_RESULT(vkBindImageMemory(device, offscreen.image, offscreen.memory, 0)); + + VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo(); + colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D; + colorImageView.format = format; + colorImageView.flags = 0; + colorImageView.subresourceRange = {}; + colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + colorImageView.subresourceRange.baseMipLevel = 0; + colorImageView.subresourceRange.levelCount = 1; + colorImageView.subresourceRange.baseArrayLayer = 0; + colorImageView.subresourceRange.layerCount = 1; + colorImageView.image = offscreen.image; + VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreen.view)); + + VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo(); + fbufCreateInfo.renderPass = renderpass; + fbufCreateInfo.attachmentCount = 1; + fbufCreateInfo.pAttachments = &offscreen.view; + fbufCreateInfo.width = dim; + fbufCreateInfo.height = dim; + fbufCreateInfo.layers = 1; + VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreen.framebuffer)); + + VkCommandBuffer layoutCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + vks::tools::setImageLayout( + layoutCmd, + offscreen.image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + VulkanExampleBase::flushCommandBuffer(layoutCmd, queue, true); + } + + // Descriptors + VkDescriptorSetLayout descriptorsetlayout; + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0), + }; + VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout)); + + // Descriptor Pool + std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) }; + VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); + VkDescriptorPool descriptorpool; + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorpool)); + + // Descriptor sets + VkDescriptorSet descriptorset; + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorpool, &descriptorsetlayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorset)); + VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorset, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &textures.environmentCube.descriptor); + vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); + + // Pipeline layout + struct PushBlock { + glm::mat4 mvp; + // Sampling deltas + float deltaPhi = (2.0f * float(M_PI)) / 180.0f; + float deltaTheta = (0.5f * float(M_PI)) / 64.0f; + } pushBlock; + + VkPipelineLayout pipelinelayout; + std::vector pushConstantRanges = { + vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushBlock), 0), + }; + VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorsetlayout, 1); + pipelineLayoutCI.pushConstantRangeCount = 1; + pipelineLayoutCI.pPushConstantRanges = pushConstantRanges.data(); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelinelayout)); + + // Pipeline + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE); + VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL); + VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1); + VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); + std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); + // Vertex input state + VkVertexInputBindingDescription vertexInputBinding = vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX); + VkVertexInputAttributeDescription vertexInputAttribute = vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0); + + VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); + vertexInputState.vertexBindingDescriptionCount = 1; + vertexInputState.pVertexBindingDescriptions = &vertexInputBinding; + vertexInputState.vertexAttributeDescriptionCount = 1; + vertexInputState.pVertexAttributeDescriptions = &vertexInputAttribute; + + std::array shaderStages; + + VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelinelayout, renderpass); + pipelineCI.pInputAssemblyState = &inputAssemblyState; + pipelineCI.pRasterizationState = &rasterizationState; + pipelineCI.pColorBlendState = &colorBlendState; + pipelineCI.pMultisampleState = &multisampleState; + pipelineCI.pViewportState = &viewportState; + pipelineCI.pDepthStencilState = &depthStencilState; + pipelineCI.pDynamicState = &dynamicState; + pipelineCI.stageCount = 2; + pipelineCI.pStages = shaderStages.data(); + pipelineCI.pVertexInputState = &vertexInputState; + pipelineCI.renderPass = renderpass; + + shaderStages[0] = loadShader(ASSET_PATH "shaders/pbrtexture/filtercube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(ASSET_PATH "shaders/pbrtexture/irradiancecube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + VkPipeline pipeline; + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); + + // Render + + VkClearValue clearValues[1]; + clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } }; + + VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); + // Reuse render pass from example pass + renderPassBeginInfo.renderPass = renderpass; + renderPassBeginInfo.framebuffer = offscreen.framebuffer; + renderPassBeginInfo.renderArea.extent.width = dim; + renderPassBeginInfo.renderArea.extent.height = dim; + renderPassBeginInfo.clearValueCount = 1; + renderPassBeginInfo.pClearValues = clearValues; + + std::vector matrices = { + // POSITIVE_X + glm::rotate(glm::rotate(glm::mat4(), glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)), + // NEGATIVE_X + glm::rotate(glm::rotate(glm::mat4(), glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)), + // POSITIVE_Y + glm::rotate(glm::mat4(), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)), + // NEGATIVE_Y + glm::rotate(glm::mat4(), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)), + // POSITIVE_Z + glm::rotate(glm::mat4(), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)), + // NEGATIVE_Z + glm::rotate(glm::mat4(), glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)), + }; + + VkCommandBuffer cmdBuf = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + + VkViewport viewport = vks::initializers::viewport((float)dim, (float)dim, 0.0f, 1.0f); + VkRect2D scissor = vks::initializers::rect2D(dim, dim, 0, 0); + + vkCmdSetViewport(cmdBuf, 0, 1, &viewport); + vkCmdSetScissor(cmdBuf, 0, 1, &scissor); + + VkImageSubresourceRange subresourceRange = {}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = 0; + subresourceRange.levelCount = numMips; + subresourceRange.layerCount = 6; + + // Change image layout for all cubemap faces to transfer destination + vks::tools::setImageLayout( + cmdBuf, + textures.irradianceCube.image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + subresourceRange); + + for (uint32_t m = 0; m < numMips; m++) { + for (uint32_t f = 0; f < 6; f++) { + viewport.width = static_cast(dim * std::pow(0.5f, m)); + viewport.height = static_cast(dim * std::pow(0.5f, m)); + vkCmdSetViewport(cmdBuf, 0, 1, &viewport); + + // Render scene from cube face's point of view + vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + // Update shader push constant block + pushBlock.mvp = glm::perspective((float)(M_PI / 2.0), 1.0f, 0.1f, 512.0f) * matrices[f]; + + vkCmdPushConstants(cmdBuf, pipelinelayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushBlock), &pushBlock); + + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinelayout, 0, 1, &descriptorset, 0, NULL); + + VkDeviceSize offsets[1] = { 0 }; + + vkCmdBindVertexBuffers(cmdBuf, 0, 1, &models.skybox.vertices.buffer, offsets); + vkCmdBindIndexBuffer(cmdBuf, models.skybox.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdDrawIndexed(cmdBuf, models.skybox.indexCount, 1, 0, 0, 0); + + vkCmdEndRenderPass(cmdBuf); + + vks::tools::setImageLayout( + cmdBuf, + offscreen.image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + // Copy region for transfer from framebuffer to cube face + VkImageCopy copyRegion = {}; + + copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.srcSubresource.baseArrayLayer = 0; + copyRegion.srcSubresource.mipLevel = 0; + copyRegion.srcSubresource.layerCount = 1; + copyRegion.srcOffset = { 0, 0, 0 }; + + copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.dstSubresource.baseArrayLayer = f; + copyRegion.dstSubresource.mipLevel = m; + copyRegion.dstSubresource.layerCount = 1; + copyRegion.dstOffset = { 0, 0, 0 }; + + copyRegion.extent.width = static_cast(viewport.width); + copyRegion.extent.height = static_cast(viewport.height); + copyRegion.extent.depth = 1; + + vkCmdCopyImage( + cmdBuf, + offscreen.image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + textures.irradianceCube.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©Region); + + // Transform framebuffer color attachment back + vks::tools::setImageLayout( + cmdBuf, + offscreen.image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + } + } + + vks::tools::setImageLayout( + cmdBuf, + textures.irradianceCube.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + subresourceRange); + + vulkanDevice->flushCommandBuffer(cmdBuf, queue); + + // todo: cleanup + vkDestroyRenderPass(device, renderpass, nullptr); + vkDestroyFramebuffer(device, offscreen.framebuffer, nullptr); + vkFreeMemory(device, offscreen.memory, nullptr); + vkDestroyImageView(device, offscreen.view, nullptr); + vkDestroyImage(device, offscreen.image, nullptr); + vkDestroyDescriptorPool(device, descriptorpool, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorsetlayout, nullptr); + vkDestroyPipeline(device, pipeline, nullptr); + vkDestroyPipelineLayout(device, pipelinelayout, nullptr); + + auto tEnd = std::chrono::high_resolution_clock::now(); + auto tDiff = std::chrono::duration(tEnd - tStart).count(); + std::cout << "Generating irradiance cube with " << numMips << " mip levels took " << tDiff << " ms" << std::endl; + } + + // Prefilter environment cubemap + // See https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/ + void generatePrefilteredCube() + { + auto tStart = std::chrono::high_resolution_clock::now(); + + const VkFormat format = VK_FORMAT_R16G16B16A16_SFLOAT; + const int32_t dim = 512; + const uint32_t numMips = static_cast(floor(log2(dim))) + 1; + + // Pre-filtered cube map + // Image + VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo(); + imageCI.imageType = VK_IMAGE_TYPE_2D; + imageCI.format = format; + imageCI.extent.width = dim; + imageCI.extent.height = dim; + imageCI.extent.depth = 1; + imageCI.mipLevels = numMips; + imageCI.arrayLayers = 6; + imageCI.samples = VK_SAMPLE_COUNT_1_BIT; + imageCI.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCI.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + imageCI.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &textures.prefilteredCube.image)); + VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device, textures.prefilteredCube.image, &memReqs); + memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &textures.prefilteredCube.deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device, textures.prefilteredCube.image, textures.prefilteredCube.deviceMemory, 0)); + // Image view + VkImageViewCreateInfo viewCI = vks::initializers::imageViewCreateInfo(); + viewCI.viewType = VK_IMAGE_VIEW_TYPE_CUBE; + viewCI.format = format; + viewCI.subresourceRange = {}; + viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewCI.subresourceRange.levelCount = numMips; + viewCI.subresourceRange.layerCount = 6; + viewCI.image = textures.prefilteredCube.image; + VK_CHECK_RESULT(vkCreateImageView(device, &viewCI, nullptr, &textures.prefilteredCube.view)); + // Sampler + VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo(); + samplerCI.magFilter = VK_FILTER_LINEAR; + samplerCI.minFilter = VK_FILTER_LINEAR; + samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCI.minLod = 0.0f; + samplerCI.maxLod = static_cast(numMips); + samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &textures.prefilteredCube.sampler)); + + textures.prefilteredCube.descriptor.imageView = textures.prefilteredCube.view; + textures.prefilteredCube.descriptor.sampler = textures.prefilteredCube.sampler; + textures.prefilteredCube.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + textures.prefilteredCube.device = vulkanDevice; + + // FB, Att, RP, Pipe, etc. + VkAttachmentDescription attDesc = {}; + // Color attachment + attDesc.format = format; + attDesc.samples = VK_SAMPLE_COUNT_1_BIT; + attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + + VkSubpassDescription subpassDescription = {}; + subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpassDescription.colorAttachmentCount = 1; + subpassDescription.pColorAttachments = &colorReference; + + // Use subpass dependencies for layout transitions + std::array dependencies; + dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; + dependencies[0].dstSubpass = 0; + dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; + dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + dependencies[1].srcSubpass = 0; + dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; + dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + + // Renderpass + VkRenderPassCreateInfo renderPassCI = vks::initializers::renderPassCreateInfo(); + renderPassCI.attachmentCount = 1; + renderPassCI.pAttachments = &attDesc; + renderPassCI.subpassCount = 1; + renderPassCI.pSubpasses = &subpassDescription; + renderPassCI.dependencyCount = 2; + renderPassCI.pDependencies = dependencies.data(); + VkRenderPass renderpass; + VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass)); + + struct { + VkImage image; + VkImageView view; + VkDeviceMemory memory; + VkFramebuffer framebuffer; + } offscreen; + + // Offfscreen framebuffer + { + // Color attachment + VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = format; + imageCreateInfo.extent.width = dim; + imageCreateInfo.extent.height = dim; + imageCreateInfo.extent.depth = 1; + imageCreateInfo.mipLevels = 1; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &offscreen.image)); + + VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device, offscreen.image, &memReqs); + memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreen.memory)); + VK_CHECK_RESULT(vkBindImageMemory(device, offscreen.image, offscreen.memory, 0)); + + VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo(); + colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D; + colorImageView.format = format; + colorImageView.flags = 0; + colorImageView.subresourceRange = {}; + colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + colorImageView.subresourceRange.baseMipLevel = 0; + colorImageView.subresourceRange.levelCount = 1; + colorImageView.subresourceRange.baseArrayLayer = 0; + colorImageView.subresourceRange.layerCount = 1; + colorImageView.image = offscreen.image; + VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreen.view)); + + VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo(); + fbufCreateInfo.renderPass = renderpass; + fbufCreateInfo.attachmentCount = 1; + fbufCreateInfo.pAttachments = &offscreen.view; + fbufCreateInfo.width = dim; + fbufCreateInfo.height = dim; + fbufCreateInfo.layers = 1; + VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreen.framebuffer)); + + VkCommandBuffer layoutCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + vks::tools::setImageLayout( + layoutCmd, + offscreen.image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + VulkanExampleBase::flushCommandBuffer(layoutCmd, queue, true); + } + + // Descriptors + VkDescriptorSetLayout descriptorsetlayout; + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0), + }; + VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout)); + + // Descriptor Pool + std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) }; + VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); + VkDescriptorPool descriptorpool; + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorpool)); + + // Descriptor sets + VkDescriptorSet descriptorset; + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorpool, &descriptorsetlayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorset)); + VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorset, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &textures.environmentCube.descriptor); + vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); + + // Pipeline layout + struct PushBlock { + glm::mat4 mvp; + float roughness; + uint32_t numSamples = 32u; + } pushBlock; + + VkPipelineLayout pipelinelayout; + std::vector pushConstantRanges = { + vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushBlock), 0), + }; + VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorsetlayout, 1); + pipelineLayoutCI.pushConstantRangeCount = 1; + pipelineLayoutCI.pPushConstantRanges = pushConstantRanges.data(); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelinelayout)); + + // Pipeline + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE); + VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL); + VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1); + VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); + std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); + // Vertex input state + VkVertexInputBindingDescription vertexInputBinding = vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX); + VkVertexInputAttributeDescription vertexInputAttribute = vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0); + + VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); + vertexInputState.vertexBindingDescriptionCount = 1; + vertexInputState.pVertexBindingDescriptions = &vertexInputBinding; + vertexInputState.vertexAttributeDescriptionCount = 1; + vertexInputState.pVertexAttributeDescriptions = &vertexInputAttribute; + + std::array shaderStages; + + VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelinelayout, renderpass); + pipelineCI.pInputAssemblyState = &inputAssemblyState; + pipelineCI.pRasterizationState = &rasterizationState; + pipelineCI.pColorBlendState = &colorBlendState; + pipelineCI.pMultisampleState = &multisampleState; + pipelineCI.pViewportState = &viewportState; + pipelineCI.pDepthStencilState = &depthStencilState; + pipelineCI.pDynamicState = &dynamicState; + pipelineCI.stageCount = 2; + pipelineCI.pStages = shaderStages.data(); + pipelineCI.pVertexInputState = &vertexInputState; + pipelineCI.renderPass = renderpass; + + shaderStages[0] = loadShader(ASSET_PATH "shaders/pbrtexture/filtercube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(ASSET_PATH "shaders/pbrtexture/prefilterenvmap.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + VkPipeline pipeline; + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); + + // Render + + VkClearValue clearValues[1]; + clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } }; + + VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); + // Reuse render pass from example pass + renderPassBeginInfo.renderPass = renderpass; + renderPassBeginInfo.framebuffer = offscreen.framebuffer; + renderPassBeginInfo.renderArea.extent.width = dim; + renderPassBeginInfo.renderArea.extent.height = dim; + renderPassBeginInfo.clearValueCount = 1; + renderPassBeginInfo.pClearValues = clearValues; + + std::vector matrices = { + // POSITIVE_X + glm::rotate(glm::rotate(glm::mat4(), glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)), + // NEGATIVE_X + glm::rotate(glm::rotate(glm::mat4(), glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)), + // POSITIVE_Y + glm::rotate(glm::mat4(), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)), + // NEGATIVE_Y + glm::rotate(glm::mat4(), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)), + // POSITIVE_Z + glm::rotate(glm::mat4(), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)), + // NEGATIVE_Z + glm::rotate(glm::mat4(), glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)), + }; + + VkCommandBuffer cmdBuf = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + + VkViewport viewport = vks::initializers::viewport((float)dim, (float)dim, 0.0f, 1.0f); + VkRect2D scissor = vks::initializers::rect2D(dim, dim, 0, 0); + + vkCmdSetViewport(cmdBuf, 0, 1, &viewport); + vkCmdSetScissor(cmdBuf, 0, 1, &scissor); + + VkImageSubresourceRange subresourceRange = {}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = 0; + subresourceRange.levelCount = numMips; + subresourceRange.layerCount = 6; + + // Change image layout for all cubemap faces to transfer destination + vks::tools::setImageLayout( + cmdBuf, + textures.prefilteredCube.image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + subresourceRange); + + for (uint32_t m = 0; m < numMips; m++) { + pushBlock.roughness = (float)m / (float)(numMips - 1); + for (uint32_t f = 0; f < 6; f++) { + viewport.width = static_cast(dim * std::pow(0.5f, m)); + viewport.height = static_cast(dim * std::pow(0.5f, m)); + vkCmdSetViewport(cmdBuf, 0, 1, &viewport); + + // Render scene from cube face's point of view + vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + // Update shader push constant block + pushBlock.mvp = glm::perspective((float)(M_PI / 2.0), 1.0f, 0.1f, 512.0f) * matrices[f]; + + vkCmdPushConstants(cmdBuf, pipelinelayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushBlock), &pushBlock); + + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinelayout, 0, 1, &descriptorset, 0, NULL); + + VkDeviceSize offsets[1] = { 0 }; + + vkCmdBindVertexBuffers(cmdBuf, 0, 1, &models.skybox.vertices.buffer, offsets); + vkCmdBindIndexBuffer(cmdBuf, models.skybox.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdDrawIndexed(cmdBuf, models.skybox.indexCount, 1, 0, 0, 0); + + vkCmdEndRenderPass(cmdBuf); + + vks::tools::setImageLayout( + cmdBuf, + offscreen.image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + // Copy region for transfer from framebuffer to cube face + VkImageCopy copyRegion = {}; + + copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.srcSubresource.baseArrayLayer = 0; + copyRegion.srcSubresource.mipLevel = 0; + copyRegion.srcSubresource.layerCount = 1; + copyRegion.srcOffset = { 0, 0, 0 }; + + copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.dstSubresource.baseArrayLayer = f; + copyRegion.dstSubresource.mipLevel = m; + copyRegion.dstSubresource.layerCount = 1; + copyRegion.dstOffset = { 0, 0, 0 }; + + copyRegion.extent.width = static_cast(viewport.width); + copyRegion.extent.height = static_cast(viewport.height); + copyRegion.extent.depth = 1; + + vkCmdCopyImage( + cmdBuf, + offscreen.image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + textures.prefilteredCube.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©Region); + + // Transform framebuffer color attachment back + vks::tools::setImageLayout( + cmdBuf, + offscreen.image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + } + } + + vks::tools::setImageLayout( + cmdBuf, + textures.prefilteredCube.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + subresourceRange); + + vulkanDevice->flushCommandBuffer(cmdBuf, queue); + + // todo: cleanup + vkDestroyRenderPass(device, renderpass, nullptr); + vkDestroyFramebuffer(device, offscreen.framebuffer, nullptr); + vkFreeMemory(device, offscreen.memory, nullptr); + vkDestroyImageView(device, offscreen.view, nullptr); + vkDestroyImage(device, offscreen.image, nullptr); + vkDestroyDescriptorPool(device, descriptorpool, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorsetlayout, nullptr); + vkDestroyPipeline(device, pipeline, nullptr); + vkDestroyPipelineLayout(device, pipelinelayout, nullptr); + + auto tEnd = std::chrono::high_resolution_clock::now(); + auto tDiff = std::chrono::duration(tEnd - tStart).count(); + std::cout << "Generating pre-filtered enivornment cube with " << numMips << " mip levels took " << tDiff << " ms" << std::endl; + } + + // Prepare and initialize uniform buffer containing shader uniforms + void prepareUniformBuffers() + { + // Objact vertex shader uniform buffer + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &uniformBuffers.object, + sizeof(uboMatrices))); + + // Skybox vertex shader uniform buffer + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &uniformBuffers.skybox, + sizeof(uboMatrices))); + + // Shared parameter uniform buffer + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &uniformBuffers.params, + sizeof(uboParams))); + + // Map persistent + VK_CHECK_RESULT(uniformBuffers.object.map()); + VK_CHECK_RESULT(uniformBuffers.skybox.map()); + VK_CHECK_RESULT(uniformBuffers.params.map()); + + updateUniformBuffers(); + updateParams(); + } + + void updateUniformBuffers() + { + // 3D object + uboMatrices.projection = camera.matrices.perspective; + uboMatrices.view = camera.matrices.view; + uboMatrices.model = glm::rotate(glm::mat4(), glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)); + uboMatrices.camPos = camera.position * -1.0f; + memcpy(uniformBuffers.object.mapped, &uboMatrices, sizeof(uboMatrices)); + + // Skybox + uboMatrices.model = glm::mat4(glm::mat3(camera.matrices.view)); + memcpy(uniformBuffers.skybox.mapped, &uboMatrices, sizeof(uboMatrices)); + } + + void updateParams() + { + const float p = 15.0f; + uboParams.lights[0] = glm::vec4(-p, -p*0.5f, -p, 1.0f); + uboParams.lights[1] = glm::vec4(-p, -p*0.5f, p, 1.0f); + uboParams.lights[2] = glm::vec4( p, -p*0.5f, p, 1.0f); + uboParams.lights[3] = glm::vec4( p, -p*0.5f, -p, 1.0f); + + memcpy(uniformBuffers.params.mapped, &uboParams, sizeof(uboParams)); + } + + void draw() + { + VulkanExampleBase::prepareFrame(); + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + + VulkanExampleBase::submitFrame(); + } + + void prepare() + { + VulkanExampleBase::prepare(); + loadAssets(); + generateBRDFLUT(); + generateIrradianceCube(); + generatePrefilteredCube(); + prepareUniformBuffers(); + setupDescriptors(); + preparePipelines(); + buildCommandBuffers(); + prepared = true; + } + + virtual void render() + { + if (!prepared) + return; + draw(); + } + + virtual void viewChanged() + { + updateUniformBuffers(); + updateTextOverlay(); + } + + void toggleSkyBox() + { + displaySkybox = !displaySkybox; + buildCommandBuffers(); + } + + void changeExposure(float delta) + { + uboParams.exposure += delta; + if (uboParams.exposure < 0.01f) { + uboParams.exposure = 0.01f; + } + updateParams(); + updateTextOverlay(); + } + + void changeGamma(float delta) + { + uboParams.gamma += delta; + if (uboParams.gamma < 0.01f) { + uboParams.gamma = 0.01f; + } + updateParams(); + updateTextOverlay(); + } + + virtual void keyPressed(uint32_t keyCode) + { + switch (keyCode) + { + case KEY_F2: + case GAMEPAD_BUTTON_A: + toggleSkyBox(); + break; + case KEY_KPADD: + case GAMEPAD_BUTTON_R1: + changeExposure(0.1f); + break; + case KEY_KPSUB: + case GAMEPAD_BUTTON_L1: + changeExposure(-0.1f); + break; + case KEY_F3: + changeGamma(-0.1f); + break; + case KEY_F4: + changeGamma(0.1f); + break; + } + } + + virtual void getOverlayText(VulkanTextOverlay *textOverlay) + { +#if defined(__ANDROID__) +#else + textOverlay->addText("Exposure: " + std::to_string(uboParams.exposure) + " (-/+)", 5.0f, 85.0f, VulkanTextOverlay::alignLeft); + textOverlay->addText("Gamma: " + std::to_string(uboParams.gamma) + " (F3/F4)", 5.0f, 100.0f, VulkanTextOverlay::alignLeft); +#endif + } +}; + +VULKAN_EXAMPLE_MAIN() \ No newline at end of file diff --git a/pbrtexture/pbrtexture.vcxproj b/pbrtexture/pbrtexture.vcxproj new file mode 100644 index 00000000..424f1c7d --- /dev/null +++ b/pbrtexture/pbrtexture.vcxproj @@ -0,0 +1,106 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {60316D67-E879-4671-B9BA-ED6B26F13AC7} + Win32Proj + 8.1 + + + + Application + true + v140 + + + Application + false + v140 + + + + + + + + + + + + + true + $(SolutionDir)\bin\ + $(SolutionDir)\bin\intermediate\$(ProjectName)\$(ConfigurationName) + + + true + $(SolutionDir)\bin\ + $(SolutionDir)\bin\intermediate\$(ProjectName)\$(ConfigurationName) + + + + WIN32;_DEBUG;_WINDOWS;VK_USE_PLATFORM_WIN32_KHR;_USE_MATH_DEFINES;NOMINMAX;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + ..\base;..\external\glm;..\external\gli;..\external\assimp;..\external;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + true + Windows + ..\libs\vulkan\vulkan-1.lib;..\libs\assimp\assimp.lib;%(AdditionalDependencies) + + + + + WIN32;NDEBUG;_WINDOWS;VK_USE_PLATFORM_WIN32_KHR;_USE_MATH_DEFINES;NOMINMAX;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + ..\base;..\external\glm;..\external\gli;..\external\assimp;..\external;%(AdditionalIncludeDirectories) + + + true + Windows + true + true + ..\libs\vulkan\vulkan-1.lib;..\libs\assimp\assimp.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pbrtexture/pbrtexture.vcxproj.filters b/pbrtexture/pbrtexture.vcxproj.filters new file mode 100644 index 00000000..552e7658 --- /dev/null +++ b/pbrtexture/pbrtexture.vcxproj.filters @@ -0,0 +1,74 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {3adf072b-32a9-412a-8eb5-5329a601df1a} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + \ No newline at end of file diff --git a/vulkanExamples.sln b/vulkanExamples.sln index e174fc97..dc42a5ff 100644 --- a/vulkanExamples.sln +++ b/vulkanExamples.sln @@ -149,6 +149,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "viewportarray", "viewportar EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imgui", "imgui\imgui.vcxproj", "{3BC2A597-A8BD-41AE-816B-2C242579D64E}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pbrtexture", "pbrtexture\pbrtexture.vcxproj", "{60316D67-E879-4671-B9BA-ED6B26F13AC7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -363,6 +365,10 @@ Global {3BC2A597-A8BD-41AE-816B-2C242579D64E}.Debug|x64.Build.0 = Debug|x64 {3BC2A597-A8BD-41AE-816B-2C242579D64E}.Release|x64.ActiveCfg = Release|x64 {3BC2A597-A8BD-41AE-816B-2C242579D64E}.Release|x64.Build.0 = Release|x64 + {60316D67-E879-4671-B9BA-ED6B26F13AC7}.Debug|x64.ActiveCfg = Debug|x64 + {60316D67-E879-4671-B9BA-ED6B26F13AC7}.Debug|x64.Build.0 = Debug|x64 + {60316D67-E879-4671-B9BA-ED6B26F13AC7}.Release|x64.ActiveCfg = Release|x64 + {60316D67-E879-4671-B9BA-ED6B26F13AC7}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -387,5 +393,6 @@ Global {6B4BC372-5897-40FB-91D4-421C2817F656} = {6B47BC47-0394-429E-9441-867EC23DFCD4} {659987E9-863C-4B9B-A3D4-CBA7D67A9516} = {BE290A75-7E65-4D0A-B419-774A309B6A60} {92B2640A-0CC5-48EA-B34C-520BA13938D1} = {BE290A75-7E65-4D0A-B419-774A309B6A60} + {60316D67-E879-4671-B9BA-ED6B26F13AC7} = {BE290A75-7E65-4D0A-B419-774A309B6A60} EndGlobalSection EndGlobal