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 00000000..d858d61a Binary files /dev/null and b/data/shaders/pbrtexture/filtercube.vert.spv differ diff --git a/data/shaders/pbrtexture/genbrdflut.frag b/data/shaders/pbrtexture/genbrdflut.frag new file mode 100644 index 00000000..b6290dd0 --- /dev/null +++ b/data/shaders/pbrtexture/genbrdflut.frag @@ -0,0 +1,90 @@ +#version 450 + +layout (location = 0) in vec2 inUV; +layout (location = 0) out vec4 outColor; +layout (constant_id = 0) const uint NUM_SAMPLES = 1024u; + +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); +} + +// 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 00000000..fa7ff160 Binary files /dev/null and b/data/shaders/pbrtexture/genbrdflut.frag.spv differ diff --git a/data/shaders/pbrtexture/genbrdflut.vert b/data/shaders/pbrtexture/genbrdflut.vert new file mode 100644 index 00000000..f3dd2335 --- /dev/null +++ b/data/shaders/pbrtexture/genbrdflut.vert @@ -0,0 +1,9 @@ +#version 450 + +layout (location = 0) out vec2 outUV; + +void main() +{ + outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f); +} \ No newline at end of file diff --git a/data/shaders/pbrtexture/genbrdflut.vert.spv b/data/shaders/pbrtexture/genbrdflut.vert.spv new file mode 100644 index 00000000..d7bafdf3 Binary files /dev/null and b/data/shaders/pbrtexture/genbrdflut.vert.spv differ 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 00000000..e5d9dea3 Binary files /dev/null and b/data/shaders/pbrtexture/irradiancecube.frag.spv differ diff --git a/data/shaders/pbrtexture/pbrtexture.frag b/data/shaders/pbrtexture/pbrtexture.frag new file mode 100644 index 00000000..0ebb80af --- /dev/null +++ b/data/shaders/pbrtexture/pbrtexture.frag @@ -0,0 +1,178 @@ +#version 450 + +layout (location = 0) in vec3 inWorldPos; +layout (location = 1) in vec3 inNormal; +layout (location = 2) in vec2 inUV; + +layout (binding = 0) uniform UBO { + mat4 projection; + mat4 model; + mat4 view; + vec3 camPos; +} ubo; + +layout (binding = 1) uniform UBOParams { + vec4 lights[4]; + float exposure; + float gamma; +} uboParams; + +layout (binding = 2) uniform samplerCube samplerIrradiance; +layout (binding = 3) uniform sampler2D samplerBRDFLUT; +layout (binding = 4) uniform samplerCube prefilteredMap; + +layout (binding = 5) uniform sampler2D albedoMap; +layout (binding = 6) uniform sampler2D normalMap; +layout (binding = 7) uniform sampler2D aoMap; +layout (binding = 8) uniform sampler2D metallicMap; +layout (binding = 9) uniform sampler2D roughnessMap; + + +layout (location = 0) out vec4 outColor; + +#define PI 3.1415926535897932384626433832795 +#define ALBEDO pow(texture(albedoMap, inUV).rgb, vec3(2.2)) + +// From http://filmicgames.com/archives/75 +vec3 Uncharted2Tonemap(vec3 x) +{ + float A = 0.15; + float B = 0.50; + float C = 0.10; + float D = 0.20; + float E = 0.02; + float F = 0.30; + return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F; +} + +// 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); +} + +// Geometric Shadowing function -------------------------------------- +float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness) +{ + float r = (roughness + 1.0); + float k = (r*r) / 8.0; + float GL = dotNL / (dotNL * (1.0 - k) + k); + float GV = dotNV / (dotNV * (1.0 - k) + k); + return GL * GV; +} + +// Fresnel function ---------------------------------------------------- +vec3 F_Schlick(float cosTheta, vec3 F0) +{ + return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); +} +vec3 F_SchlickR(float cosTheta, vec3 F0, float roughness) +{ + return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0); +} + +vec3 prefilteredReflection(vec3 R, float roughness) +{ + const float MAX_REFLECTION_LOD = 9.0; // todo: param/const + float lod = roughness * MAX_REFLECTION_LOD; + float lodf = floor(lod); + float lodc = ceil(lod); + vec3 a = textureLod(prefilteredMap, R, lodf).rgb; + vec3 b = textureLod(prefilteredMap, R, lodc).rgb; + return mix(a, b, lod - lodf); +} + +vec3 specularContribution(vec3 L, vec3 V, vec3 N, vec3 F0, float metallic, float roughness) +{ + // Precalculate vectors and dot products + vec3 H = normalize (V + L); + float dotNH = clamp(dot(N, H), 0.0, 1.0); + float dotNV = clamp(dot(N, V), 0.0, 1.0); + float dotNL = clamp(dot(N, L), 0.0, 1.0); + + // Light color fixed + vec3 lightColor = vec3(1.0); + + vec3 color = vec3(0.0); + + if (dotNL > 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 00000000..f9731e55 Binary files /dev/null and b/data/shaders/pbrtexture/pbrtexture.frag.spv differ diff --git a/data/shaders/pbrtexture/pbrtexture.vert b/data/shaders/pbrtexture/pbrtexture.vert new file mode 100644 index 00000000..8d889ffc --- /dev/null +++ b/data/shaders/pbrtexture/pbrtexture.vert @@ -0,0 +1,35 @@ +#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; + mat4 view; + vec3 camPos; +} ubo; + +layout (location = 0) out vec3 outWorldPos; +layout (location = 1) out vec3 outNormal; +layout (location = 2) out vec2 outUV; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + vec3 locPos = vec3(ubo.model * vec4(inPos, 1.0)); + outWorldPos = locPos; + outNormal = mat3(ubo.model) * inNormal; + outUV = inUV; + outUV.t = 1.0 - inUV.t; + gl_Position = ubo.projection * ubo.view * vec4(outWorldPos, 1.0); +} diff --git a/data/shaders/pbrtexture/pbrtexture.vert.spv b/data/shaders/pbrtexture/pbrtexture.vert.spv new file mode 100644 index 00000000..31122734 Binary files /dev/null and b/data/shaders/pbrtexture/pbrtexture.vert.spv differ 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 00000000..269e5d55 Binary files /dev/null and b/data/shaders/pbrtexture/prefilterenvmap.frag.spv differ 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 00000000..d5632e9e Binary files /dev/null and b/data/shaders/pbrtexture/skybox.frag.spv differ 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 00000000..a6d9ade4 Binary files /dev/null and b/data/shaders/pbrtexture/skybox.vert.spv differ 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