From c47017f2be5b46bd88ea898c88c5b88c03bce5d5 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sat, 22 Mar 2025 07:23:36 +0100 Subject: [PATCH 01/39] Chore: Add shaders to version control --- .gitignore | 8 +- resources/shaders/common/depth.asl | 18 ++ resources/shaders/common/selected.asl | 8 + resources/shaders/compute/brdf.frag | 120 ++++++++ resources/shaders/compute/brdf.vert | 16 + resources/shaders/compute/entityClick.asl | 8 + resources/shaders/compute/entityClick.frag | 10 + resources/shaders/compute/entityClick.vert | 13 + resources/shaders/compute/equiToCubemap.frag | 23 ++ resources/shaders/compute/equiToCubemap.vert | 13 + resources/shaders/compute/irradiance.frag | 44 +++ resources/shaders/compute/irradiance.vert | 13 + resources/shaders/compute/prefilter.frag | 103 +++++++ resources/shaders/compute/prefilter.vert | 13 + resources/shaders/drawer/line.asl | 12 + resources/shaders/drawer/point.asl | 14 + .../shaders/fastRenderer/fastRenderer.asl | 24 ++ resources/shaders/grid/grid.asl | 28 ++ resources/shaders/pbrRenderer/background.frag | 17 + resources/shaders/pbrRenderer/background.vert | 18 ++ .../shaders/pbrRenderer/equiToCubemap.asl | 25 ++ resources/shaders/pbrRenderer/omniShadow.asl | 32 ++ resources/shaders/pbrRenderer/omniShadow.frag | 24 ++ resources/shaders/pbrRenderer/omniShadow.geom | 29 ++ resources/shaders/pbrRenderer/omniShadow.vert | 18 ++ resources/shaders/pbrRenderer/pbrRenderer.asl | 289 +++++++++++++++++ resources/shaders/pbrRenderer/shader.frag | 291 ++++++++++++++++++ resources/shaders/pbrRenderer/shader.vert | 37 +++ resources/shaders/pbrRenderer/shadow.frag | 12 + resources/shaders/pbrRenderer/shadow.vert | 19 ++ .../shaders/phongRenderer/phongRenderer.asl | 135 ++++++++ resources/shaders/quad/shader.frag | 11 + resources/shaders/quad/shader.vert | 11 + src/extern/solveResources.cmake | 23 +- 34 files changed, 1475 insertions(+), 4 deletions(-) create mode 100644 resources/shaders/common/depth.asl create mode 100644 resources/shaders/common/selected.asl create mode 100644 resources/shaders/compute/brdf.frag create mode 100644 resources/shaders/compute/brdf.vert create mode 100644 resources/shaders/compute/entityClick.asl create mode 100644 resources/shaders/compute/entityClick.frag create mode 100644 resources/shaders/compute/entityClick.vert create mode 100644 resources/shaders/compute/equiToCubemap.frag create mode 100644 resources/shaders/compute/equiToCubemap.vert create mode 100644 resources/shaders/compute/irradiance.frag create mode 100644 resources/shaders/compute/irradiance.vert create mode 100644 resources/shaders/compute/prefilter.frag create mode 100644 resources/shaders/compute/prefilter.vert create mode 100644 resources/shaders/drawer/line.asl create mode 100644 resources/shaders/drawer/point.asl create mode 100644 resources/shaders/fastRenderer/fastRenderer.asl create mode 100644 resources/shaders/grid/grid.asl create mode 100644 resources/shaders/pbrRenderer/background.frag create mode 100644 resources/shaders/pbrRenderer/background.vert create mode 100644 resources/shaders/pbrRenderer/equiToCubemap.asl create mode 100644 resources/shaders/pbrRenderer/omniShadow.asl create mode 100644 resources/shaders/pbrRenderer/omniShadow.frag create mode 100644 resources/shaders/pbrRenderer/omniShadow.geom create mode 100644 resources/shaders/pbrRenderer/omniShadow.vert create mode 100644 resources/shaders/pbrRenderer/pbrRenderer.asl create mode 100644 resources/shaders/pbrRenderer/shader.frag create mode 100644 resources/shaders/pbrRenderer/shader.vert create mode 100644 resources/shaders/pbrRenderer/shadow.frag create mode 100644 resources/shaders/pbrRenderer/shadow.vert create mode 100644 resources/shaders/phongRenderer/phongRenderer.asl create mode 100644 resources/shaders/quad/shader.frag create mode 100644 resources/shaders/quad/shader.vert diff --git a/.gitignore b/.gitignore index 6691710c..8a4577f5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,13 @@ # Build /build/ -/scripts/local_*.sh /projects/ -/resources/ + +# Resources +/resources/fonts/ +/resources/icons/ +/resources/meshes/ +/resources/textures/ # Visual Studio /.vs diff --git a/resources/shaders/common/depth.asl b/resources/shaders/common/depth.asl new file mode 100644 index 00000000..5b3dd184 --- /dev/null +++ b/resources/shaders/common/depth.asl @@ -0,0 +1,18 @@ +perFrame mat4 uView; +perFrame mat4 uProjection; +perDraw mat4 uModel; +perDraw vec4 uColor; +// uniform sampler2D uDepthMap; + +perVertex vec3 vUV; + +vec4 vertex(vec3 iVertex, vec3 iNormal, vec3 iUV) { + vUV = iUV; + return uProjection * uView * uModel * vec4(iVertex, 1.0f); +} + +vec4 fragment() { + // float depthValue = texture(depthMap, vUV).r; + // return vec4(vec3(depthValue), 1.0f); + return vec4(0, 0, 0, 1); +} diff --git a/resources/shaders/common/selected.asl b/resources/shaders/common/selected.asl new file mode 100644 index 00000000..50f974d5 --- /dev/null +++ b/resources/shaders/common/selected.asl @@ -0,0 +1,8 @@ +perFrame mat4 uView; +perFrame mat4 uProjection; +perDraw mat4 uModel; +perDraw vec4 uColor; + +vec4 vertex(vec3 iVertex, vec3 iNormal, vec3 iUV) { return uProjection * uView * uModel * vec4(iVertex, 1.0f); } + +vec4 fragment() { return uColor; } diff --git a/resources/shaders/compute/brdf.frag b/resources/shaders/compute/brdf.frag new file mode 100644 index 00000000..2bf8801b --- /dev/null +++ b/resources/shaders/compute/brdf.frag @@ -0,0 +1,120 @@ +#version 300 es +precision mediump float; + +in vec2 TexCoords; +in vec3 Pos; + +out vec2 FragColor; + +const float PI = 3.14159265359; +// ---------------------------------------------------------------------------- +// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html +// efficient VanDerCorpus calculation. +float RadicalInverse_VdC(uint bits) +{ + bits = (bits << 16u) | (bits >> 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); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 +} +// ---------------------------------------------------------------------------- +vec2 Hammersley(uint i, uint N) +{ + return vec2(float(i)/float(N), RadicalInverse_VdC(i)); +} +// ---------------------------------------------------------------------------- +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) +{ + float a = roughness*roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // from spherical coordinates to cartesian coordinates - halfway vector + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + // from tangent-space H vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(sampleVec); +} +// ---------------------------------------------------------------------------- +float GeometrySchlickGGX(float NdotV, float roughness) +{ + // note that we use a different k for IBL + float a = roughness; + float k = (a * a) / 2.0; + + float nom = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return nom / denom; +} +// ---------------------------------------------------------------------------- +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) +{ + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} +// ---------------------------------------------------------------------------- +vec2 IntegrateBRDF(float NdotV, float roughness) +{ + vec3 V; + V.x = sqrt(1.0 - NdotV*NdotV); + V.y = 0.0; + V.z = NdotV; + + float A = 0.0; + float B = 0.0; + + vec3 N = vec3(0.0, 0.0, 1.0); + + const uint SAMPLE_COUNT = 1024u; + for(uint i = 0u; i < SAMPLE_COUNT; ++i) + { + // generates a sample vector that's biased towards the + // preferred alignment direction (importance sampling). + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, roughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(L.z, 0.0); + float NdotH = max(H.z, 0.0); + float VdotH = max(dot(V, H), 0.0); + + if(NdotL > 0.0) + { + float G = GeometrySmith(N, V, L, roughness); + float G_Vis = (G * VdotH) / (NdotH * NdotV); + float Fc = pow(1.0 - VdotH, 5.0); + + A += (1.0 - Fc) * G_Vis; + B += Fc * G_Vis; + } + } + A /= float(SAMPLE_COUNT); + B /= float(SAMPLE_COUNT); + return vec2(A, B); +} +// ---------------------------------------------------------------------------- +void main() +{ + // TODO Should work using the texture coordinates + float NdotV = (Pos.x+1.0f)/2.0f; + float roughness = (Pos.y+1.0f)/2.0f; + vec2 integratedBRDF = IntegrateBRDF(NdotV, roughness); + FragColor = integratedBRDF; +} diff --git a/resources/shaders/compute/brdf.vert b/resources/shaders/compute/brdf.vert new file mode 100644 index 00000000..4d07585b --- /dev/null +++ b/resources/shaders/compute/brdf.vert @@ -0,0 +1,16 @@ +#version 300 es +//in vec3 aPos; +//in vec2 aTexCoords; +in vec3 inPos; +in vec3 inNormal; +in vec2 inTexCoords; + +out vec2 TexCoords; +out vec3 Pos; + +void main() +{ + TexCoords = inTexCoords; + Pos = inPos; + gl_Position = vec4(inPos, 1.0); +} diff --git a/resources/shaders/compute/entityClick.asl b/resources/shaders/compute/entityClick.asl new file mode 100644 index 00000000..6282f4ab --- /dev/null +++ b/resources/shaders/compute/entityClick.asl @@ -0,0 +1,8 @@ +perFrame mat4 projection; +perFrame mat4 view; +perDraw mat4 model; +perDraw int entityId; + +vec4 vertex(vec3 inPos, vec3 inNormal, vec2 inUV) { return projection * view * model * vec4(inPos, 1.0f); } + +void fragment(out int eid) { eid = entityId; } diff --git a/resources/shaders/compute/entityClick.frag b/resources/shaders/compute/entityClick.frag new file mode 100644 index 00000000..858f22ae --- /dev/null +++ b/resources/shaders/compute/entityClick.frag @@ -0,0 +1,10 @@ +#version 300 es +precision mediump float; +uniform int entityId; + +out int FragColor; + +void main() +{ + FragColor = entityId; +} diff --git a/resources/shaders/compute/entityClick.vert b/resources/shaders/compute/entityClick.vert new file mode 100644 index 00000000..1e7401e9 --- /dev/null +++ b/resources/shaders/compute/entityClick.vert @@ -0,0 +1,13 @@ +#version 300 es +in vec3 inPosition; +in vec3 inNormal; +in vec2 inTexCoord; + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; + +void main() +{ + gl_Position = projection * view * model * vec4(inPosition, 1.0f); +} diff --git a/resources/shaders/compute/equiToCubemap.frag b/resources/shaders/compute/equiToCubemap.frag new file mode 100644 index 00000000..24b2273d --- /dev/null +++ b/resources/shaders/compute/equiToCubemap.frag @@ -0,0 +1,23 @@ +#version 300 es +precision mediump float; +out vec4 FragColor; +in vec3 localPos; + +uniform sampler2D equirectangularMap; + +const vec2 invAtan = vec2(0.1591, 0.3183); +vec2 sampleSphericalMap(vec3 v) +{ + vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + uv *= invAtan; + uv += 0.5; + return uv; +} + +void main() +{ + vec2 uv = sampleSphericalMap(normalize(localPos)); // make sure to normalize localPos + vec3 color = texture(equirectangularMap, uv).rgb; + + FragColor = vec4(color, 1.0); +} diff --git a/resources/shaders/compute/equiToCubemap.vert b/resources/shaders/compute/equiToCubemap.vert new file mode 100644 index 00000000..8391d12b --- /dev/null +++ b/resources/shaders/compute/equiToCubemap.vert @@ -0,0 +1,13 @@ +#version 300 es +in vec3 aPos; + +out vec3 localPos; + +uniform mat4 projection; +uniform mat4 view; + +void main() +{ + localPos = aPos; + gl_Position = projection * view * vec4(localPos, 1.0); +} diff --git a/resources/shaders/compute/irradiance.frag b/resources/shaders/compute/irradiance.frag new file mode 100644 index 00000000..51b511f4 --- /dev/null +++ b/resources/shaders/compute/irradiance.frag @@ -0,0 +1,44 @@ +#version 300 es +precision mediump float; +out vec4 FragColor; +in vec3 WorldPos; + +uniform samplerCube environmentMap; + +const float PI = 3.14159265359; + +void main() +{ + // The world vector acts as the normal of a tangent surface + // from the origin, aligned to WorldPos. Given this normal, calculate all + // incoming radiance of the environment. The result of this radiance + // is the radiance of light coming from -Normal direction, which is what + // we use in the PBR shader to sample irradiance. + vec3 N = normalize(WorldPos); + + vec3 irradiance = vec3(0.0); + + // tangent space calculation from origin point + vec3 up = vec3(0.0, 1.0, 0.0); + vec3 right = normalize(cross(up, N)); + up = normalize(cross(N, right)); + + float sampleDelta = 0.025; + float nrSamples = 0.0; + for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) + { + for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) + { + // spherical to cartesian (in tangent space) + vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); + // tangent space to world + vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; + + irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta); + nrSamples++; + } + } + irradiance = PI * irradiance * (1.0 / float(nrSamples)); + + FragColor = vec4(irradiance, 1.0); +} diff --git a/resources/shaders/compute/irradiance.vert b/resources/shaders/compute/irradiance.vert new file mode 100644 index 00000000..21d015e1 --- /dev/null +++ b/resources/shaders/compute/irradiance.vert @@ -0,0 +1,13 @@ +#version 300 es +in vec3 aPos; + +out vec3 WorldPos; + +uniform mat4 projection; +uniform mat4 view; + +void main() +{ + WorldPos = aPos; + gl_Position = projection * view * vec4(WorldPos, 1.0); +} diff --git a/resources/shaders/compute/prefilter.frag b/resources/shaders/compute/prefilter.frag new file mode 100644 index 00000000..5ea009f6 --- /dev/null +++ b/resources/shaders/compute/prefilter.frag @@ -0,0 +1,103 @@ +#version 300 es +precision mediump float; +out vec4 FragColor; +in vec3 localPos; + +uniform samplerCube environmentMap; +uniform float roughness; + +const float PI = 3.14159265359; + +float RadicalInverse_VdC(uint bits); +vec2 Hammersley(uint i, uint N); +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness); + +void main() +{ + vec3 N = normalize(localPos); + vec3 R = N; + vec3 V = R; + + const uint SAMPLE_COUNT = 1024u; + float totalWeight = 0.0; + vec3 prefilteredColor = vec3(0.0); + for(uint i = 0u; i < SAMPLE_COUNT; ++i) + { + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, roughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(dot(N, L), 0.0); + if(NdotL > 0.0) + { + prefilteredColor += texture(environmentMap, L).rgb * NdotL; + totalWeight += NdotL; + } + } + prefilteredColor = prefilteredColor / totalWeight; + + FragColor = vec4(prefilteredColor, 1.0); +} + +float VanDerCorput(uint n, uint base) +{ + float invBase = 1.0 / float(base); + float denom = 1.0; + float result = 0.0; + + for(uint i = 0u; i < 32u; ++i) + { + if(n > 0u) + { + denom = mod(float(n), 2.0); + result += denom * invBase; + invBase = invBase / 2.0; + n = uint(float(n) / 2.0); + } + } + + return result; +} +// ---------------------------------------------------------------------------- +vec2 HammersleyNoBitOps(uint i, uint N) +{ + return vec2(float(i)/float(N), VanDerCorput(i, 2u)); +} + +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) +{ + float a = roughness*roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // from spherical coordinates to cartesian coordinates + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + // from tangent-space vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(sampleVec); +} + +float RadicalInverse_VdC(uint bits) +{ + bits = (bits << 16u) | (bits >> 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); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 +} +// ---------------------------------------------------------------------------- +vec2 Hammersley(uint i, uint N) +{ + return vec2(float(i)/float(N), RadicalInverse_VdC(i)); +} diff --git a/resources/shaders/compute/prefilter.vert b/resources/shaders/compute/prefilter.vert new file mode 100644 index 00000000..f1626ca8 --- /dev/null +++ b/resources/shaders/compute/prefilter.vert @@ -0,0 +1,13 @@ +#version 300 es +in vec3 aPos; + +out vec3 localPos; + +uniform mat4 projection; +uniform mat4 view; + +void main() +{ + localPos = aPos; + gl_Position = projection * view * vec4(localPos, 1.0); +} diff --git a/resources/shaders/drawer/line.asl b/resources/shaders/drawer/line.asl new file mode 100644 index 00000000..1c96d768 --- /dev/null +++ b/resources/shaders/drawer/line.asl @@ -0,0 +1,12 @@ +perFrame mat4 uView; +perFrame mat4 uProjection; + +perVertex vec4 vColor; + +vec4 vertex(vec3 iPos, vec4 iColor) { + vec4 pos = uProjection * uView * vec4(iPos, 1.0); + vColor = iColor; + return vec4(pos.xyz, pos.w); +} + +void fragment(out vec4 color) { color = vColor; } diff --git a/resources/shaders/drawer/point.asl b/resources/shaders/drawer/point.asl new file mode 100644 index 00000000..7c11932e --- /dev/null +++ b/resources/shaders/drawer/point.asl @@ -0,0 +1,14 @@ +perFrame mat4 uView; +perFrame mat4 uProjection; + +perVertex vec4 vColor; + +vec4 vertex(vec3 iPos, vec4 iColor) { + vec4 pos = uProjection * uView * vec4(iPos, 1.0); + vColor = iColor; + gl_PointSize = 5.0; + return pos; +} + +void fragment(out vec4 color) { color = vColor; } + diff --git a/resources/shaders/fastRenderer/fastRenderer.asl b/resources/shaders/fastRenderer/fastRenderer.asl new file mode 100644 index 00000000..d4679a14 --- /dev/null +++ b/resources/shaders/fastRenderer/fastRenderer.asl @@ -0,0 +1,24 @@ +perFrame mat4 uView; +perFrame mat4 uProjection; +perDraw mat4 uModel; +perDraw vec3 uAlbedo; +perDraw sampler2D uAlbedoTexture; + +perVertex vec2 vUV; + +vec4 vertex(vec3 iVertex, vec3 iNormal, vec2 iUV) { + vUV = iUV; + float dummyNormal = iNormal.x / 10000.0f + 1.0f;// To avoid being optimized out + return uProjection * uView * uModel * vec4(iVertex, 1.0f) * dummyNormal; +} + +void fragment(out vec4 color) { + if (uAlbedo.x < 0.0f) { + color = texture(uAlbedoTexture, vUV).rgba; + if (color.a < 0.1) + discard; + return; + } + + color = vec4(uAlbedo, 1.0f); +} diff --git a/resources/shaders/grid/grid.asl b/resources/shaders/grid/grid.asl new file mode 100644 index 00000000..2a5939fb --- /dev/null +++ b/resources/shaders/grid/grid.asl @@ -0,0 +1,28 @@ +perFrame mat4 uView; +perFrame mat4 uProjection; +perFrame vec3 uCamPos; + +perVertex vec4 vColor; +perVertex vec3 vFragPos; + +vec4 vertex(vec3 iPos, vec4 iColor, float iWidth) { + vec4 pos = uProjection * uView * vec4(iPos, 1.0); + vColor = iColor; + vFragPos = iPos; + return vec4(pos.xyz, pos.w); +} + +void fragment(out vec4 color) { + float dist = length(uCamPos - vFragPos) + 0.0001; + color = vColor; + + // Parameters for controlling the fade effect + float fadeStart = 20.0; // Distance where the fading starts + float fadeEnd = 50.0; // Distance where the grid is fully faded/transparent + + // Clamped ratio between where fading starts and where it ends + float fadeRatio = clamp((dist - fadeStart) / (fadeEnd - fadeStart), 0.0, 1.0); + + // Apply fade falloff + color.a *= 1.0 - fadeRatio; +} diff --git a/resources/shaders/pbrRenderer/background.frag b/resources/shaders/pbrRenderer/background.frag new file mode 100644 index 00000000..b3b643d0 --- /dev/null +++ b/resources/shaders/pbrRenderer/background.frag @@ -0,0 +1,17 @@ +#version 300 es +precision mediump float; +out vec4 FragColor; +in vec3 WorldPos; + +uniform samplerCube environmentMap; + +void main() +{ + vec3 envColor = texture(environmentMap, WorldPos).rgb; + + // HDR tonemap and gamma correct + envColor = envColor / (envColor + vec3(1.0)); + envColor = pow(envColor, vec3(1.0/2.2)); + + FragColor = vec4(envColor, 1.0); +} diff --git a/resources/shaders/pbrRenderer/background.vert b/resources/shaders/pbrRenderer/background.vert new file mode 100644 index 00000000..6546655e --- /dev/null +++ b/resources/shaders/pbrRenderer/background.vert @@ -0,0 +1,18 @@ +#version 300 es +in vec3 aPos; + +uniform mat4 projection; +uniform mat4 view; +uniform mat3 environmentMapOri; + +out vec3 WorldPos; + +void main() +{ + WorldPos = aPos; + + mat4 rotView = mat4(mat3(view)*environmentMapOri); + vec4 clipPos = projection * rotView * vec4(WorldPos, 1.0); + + gl_Position = clipPos.xyww; +} diff --git a/resources/shaders/pbrRenderer/equiToCubemap.asl b/resources/shaders/pbrRenderer/equiToCubemap.asl new file mode 100644 index 00000000..056e6358 --- /dev/null +++ b/resources/shaders/pbrRenderer/equiToCubemap.asl @@ -0,0 +1,25 @@ +perFrame mat4 projection; +perFrame mat4 view; +perFrame sampler2D equirectangularMap; + +perVertex vec3 localPos; + +vec2 sampleSphericalMap(vec3 v); + +vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { + localPos = iPos; + return projection * view * vec4(localPos, 1.0); +} + +vec4 fragment() { + vec2 uv = sampleSphericalMap(normalize(localPos)); + return vec4(texture(equirectangularMap, uv).rgb, 1.0); +} + +const vec2 invAtan = vec2(0.1591, 0.3183); +vec2 sampleSphericalMap(vec3 v) { + vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + uv *= invAtan; + uv += 0.5; + return uv; +} diff --git a/resources/shaders/pbrRenderer/omniShadow.asl b/resources/shaders/pbrRenderer/omniShadow.asl new file mode 100644 index 00000000..b5611b61 --- /dev/null +++ b/resources/shaders/pbrRenderer/omniShadow.asl @@ -0,0 +1,32 @@ +perPass mat4 model; +perPass vec3 lightPos; +perPass float far_plane; +perPass mat4 shadowMatrices[6]; + +perVertex vec4 FragPos; + +void vertex(in vec3 inPosition, in vec3 inNormal, in vec2 inTexCoord, out vec4 position) { position = model * vec4(inPosition, 1.0); } + +void geometry(in(triangles), out(triangle_strip, max_vertices = 18)) { + for (int face = 0; face < 6; face++) { + gl_Layer = face; // built-in variable that specifies to which face we render. + for (int i = 0; i < 3; i++) // for each triangle vertex + { + FragPos = gl_in[i].gl_Position; + gl_Position = shadowMatrices[face] * FragPos; + EmitVertex(); + } + EndPrimitive(); + } +} + +void fragment(out vec4 color, out float depth) { + // get distance between fragment and light source + float lightDistance = length(FragPos.xyz - lightPos); + + // map to [0;1] range by dividing by far_plane + lightDistance = lightDistance / far_plane; + + // write this as modified depth + depth = lightDistance; +} diff --git a/resources/shaders/pbrRenderer/omniShadow.frag b/resources/shaders/pbrRenderer/omniShadow.frag new file mode 100644 index 00000000..fa6324b7 --- /dev/null +++ b/resources/shaders/pbrRenderer/omniShadow.frag @@ -0,0 +1,24 @@ +#version 320 es +//-------------------------------------------------- +// Atta Shaders +// omniShadow.frag +// Date: 2022-01-08 +// By Breno Cunha Queiroz +//-------------------------------------------------- +precision mediump float; +in vec4 FragPos; + +uniform vec3 lightPos; +uniform float far_plane; + +void main() +{ + // get distance between fragment and light source + float lightDistance = length(FragPos.xyz - lightPos); + + // map to [0;1] range by dividing by far_plane + lightDistance = lightDistance / far_plane; + + // write this as modified depth + gl_FragDepth = lightDistance; +} diff --git a/resources/shaders/pbrRenderer/omniShadow.geom b/resources/shaders/pbrRenderer/omniShadow.geom new file mode 100644 index 00000000..369326e8 --- /dev/null +++ b/resources/shaders/pbrRenderer/omniShadow.geom @@ -0,0 +1,29 @@ +#version 320 es +//-------------------------------------------------- +// Atta Shaders +// omniShadow.geom +// Date: 2022-01-08 +// By Breno Cunha Queiroz +//-------------------------------------------------- + +layout (triangles) in; +layout (triangle_strip, max_vertices=18) out; + +uniform mat4 shadowMatrices[6]; + +out vec4 FragPos; // FragPos from GS (output per emitvertex) + +void main() +{ + for(int face = 0; face < 6; face++) + { + gl_Layer = face; // built-in variable that specifies to which face we render. + for(int i = 0; i < 3; i++) // for each triangle vertex + { + FragPos = gl_in[i].gl_Position; + gl_Position = shadowMatrices[face] * FragPos; + EmitVertex(); + } + EndPrimitive(); + } +} diff --git a/resources/shaders/pbrRenderer/omniShadow.vert b/resources/shaders/pbrRenderer/omniShadow.vert new file mode 100644 index 00000000..2fda870d --- /dev/null +++ b/resources/shaders/pbrRenderer/omniShadow.vert @@ -0,0 +1,18 @@ +#version 320 es +//-------------------------------------------------- +// Atta Shaders +// omniShadow.vert +// Date: 2022-01-08 +// By Breno Cunha Queiroz +//-------------------------------------------------- + +in vec3 inPosition; +in vec3 inNormal; +in vec2 inTexCoord; + +uniform mat4 model; + +void main() +{ + gl_Position = model * vec4(inPosition, 1.0); +} diff --git a/resources/shaders/pbrRenderer/pbrRenderer.asl b/resources/shaders/pbrRenderer/pbrRenderer.asl new file mode 100644 index 00000000..fbc3becb --- /dev/null +++ b/resources/shaders/pbrRenderer/pbrRenderer.asl @@ -0,0 +1,289 @@ +struct Material { + float metallic; + float roughness; + float ao; + int hasNormalTexture; + vec3 albedo; +}; +struct PointLight { + vec3 position; + vec3 intensity; +}; +struct DirectionalLight { + vec3 direction; + vec3 intensity; +}; +const float PI = 3.14159265359; + +//----- PerFrame -----// +perFrame mat4 projection; +perFrame mat4 view; +perFrame mat4 directionalLightMatrix; +perFrame vec3 camPos; +perFrame float p0; + +perFrame int numPointLights; +perFrame int numDirectionalLights; +perFrame int numEnvironmentLights; + +// Point light +#define MAX_NUM_POINT_LIGHTS 10 +perFrame PointLight pointLights[MAX_NUM_POINT_LIGHTS]; + +// Directional light - Shadow map +perFrame DirectionalLight directionalLight; +// perFrame sampler2D directionalShadowMap; +// perFrame samplerCube omniShadowMap; +perFrame float omniFarPlane; + +// Environment light - IBL +perFrame mat3 environmentLightOri; +// perFrame samplerCube prefilterMap; +// perFrame samplerCube irradianceMap; +// perFrame sampler2D brdfLUT; + +perDraw mat4 model; +perDraw mat4 invModel; +perDraw Material material; +perDraw sampler2D albedoTexture; +perDraw sampler2D metallicTexture; +perDraw sampler2D roughnessTexture; +perDraw sampler2D aoTexture; +perDraw sampler2D normalTexture; + +perVertex vec3 worldPos; +perVertex vec4 posDirectionalLightSpace; +perVertex vec3 normal; +perVertex vec2 texCoord; + +//----- Vertex Shader -----// +vec4 vertex(vec3 inPosition, vec3 inNormal, vec2 inTexCoord) { + vec4 coord = model * vec4(inPosition, 1.0f); + vec4 coordImage = view * coord; + + // float z = coordImage.z; + // coordImage /= z; + // float r = sqrt(coordImage.x*coordImage.x + coordImage.y*coordImage.y); + // float L = 1 + r*0.103689 + r*r*0.00487908 + r*r*r*0.00116894 + r*r*r*r*0.000941614; + // coordImage.x *= L; + // coordImage.y *= L; + // coordImage *= z; + + worldPos = coord.xyz; + posDirectionalLightSpace = directionalLightMatrix * vec4(worldPos, 1.0f); + normal = mat3(transpose(invModel)) * inNormal; + texCoord = inTexCoord; + + return projection * coordImage; +} + +//----- Fragment Shader -----// +vec3 calcLightContribution(vec3 L, vec3 N, vec3 V, vec3 radiance, vec3 albedo, float metallic, float roughness, vec3 F0); +float directionalShadowCalculation(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir); +float omniShadowCalculation(vec3 lightPos, vec3 normal); +float distributionGGX(vec3 N, vec3 H, float roughness); // Distribution of microsurface normals +float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness); // Microsurface shadowing +vec3 fresnelSchlick(float cosTheta, vec3 F0); // Reflect-refract ratio +vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness); + +void fragment(out vec4 outColor) { + // Calculate material parameters + vec3 albedo = material.albedo; + if (albedo.x < 0.0f) + albedo = pow(texture(albedoTexture, texCoord).rgb, vec3(2.2)); + + float metallic = material.metallic; + if (metallic < 0.0f) + metallic = texture(metallicTexture, texCoord).r; + + float roughness = material.roughness; + if (roughness < 0.0f) + roughness = texture(roughnessTexture, texCoord).r; + + float ao = material.ao; + if (ao < 0.0f) + ao = texture(aoTexture, texCoord).r; + + // Calculate vectors + vec3 N = normal; + vec3 V = normalize(camPos - worldPos); + + // Fresnel coefficient + vec3 F0 = vec3(0.04); + F0 = mix(F0, albedo, metallic); + + vec3 Lo = vec3(0.0); + //----- Point lights -----// + for (int i = 0; i < numPointLights; i++) { + vec3 L = normalize(pointLights[i].position - worldPos); + vec3 H = normalize(V + L); + float dist = length(pointLights[i].position - worldPos); + float attenuation = 1.0 / (dist * dist); + vec3 radiance = pointLights[i].intensity * attenuation; + + float shadow = 0.0f; + // shadow = omniShadowCalculation(pointLights[i].position, N); + Lo += calcLightContribution(L, N, V, radiance, albedo, metallic, roughness, F0) * (1.0f - shadow); + } + + //----- Directional light -----// + if (numDirectionalLights == 1) { + vec3 L = -directionalLight.direction; + vec3 radiance = directionalLight.intensity; + + float shadow = 0.0f; // directionalShadowCalculation(posDirectionalLightSpace, N, L); + Lo += calcLightContribution(L, N, V, radiance, albedo, metallic, roughness, F0) * (1.0f - shadow); + } + + //----- Environment light -----// + vec3 ambient = vec3(0.0f); + if (numEnvironmentLights == 1) { + // Diffuse IBL + vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); + vec3 kS = F; + vec3 kD = 1.0 - kS; + kD *= 1.0 - metallic; + vec3 irradiance = vec3(1, 1, 1); // texture(irradianceMap, environmentLightOri * N).rgb; + vec3 diffuse = irradiance * albedo; + + // Specular IBL + const float MAX_REFLECTION_LOD = 4.0; + vec3 R = reflect(-V, N); + vec3 prefilteredColor = vec3(1, 1, 1); // textureLod(prefilterMap, environmentLightOri * R, roughness * MAX_REFLECTION_LOD).rgb; + vec2 brdf = vec2(0, 0); // texture(brdfLUT, vec2(max(dot(environmentLightOri * N, V), 0.0), roughness)).rg; + vec3 specular = prefilteredColor * (F * brdf.x + brdf.y); + + ambient = (kD * diffuse + specular) * ao; + } else if (numEnvironmentLights == 0) { + ambient = vec3(0.03) * albedo * ao; + } + + vec3 color = ambient + Lo; + + // HDR tonemapping + color = color / (color + vec3(1.0)); + // Gamma correction + color = pow(color, vec3(1.0 / 2.2)); + + outColor = vec4(color, 1.0f); +} + +vec3 calcLightContribution(vec3 L, vec3 N, vec3 V, vec3 radiance, vec3 albedo, float metallic, float roughness, vec3 F0) { + vec3 H = normalize(V + L); + + // Cook-torrance BRDF + float D = distributionGGX(N, H, roughness); + float G = geometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + + vec3 numerator = D * G * F; + float denominator = 4.0f * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; + vec3 specular = numerator / denominator; + + vec3 kS = F; // Reflection/specular fraction + vec3 kD = vec3(1.0) - kS; // Refraction/diffuse fraction + kD *= 1.0 - metallic; + + // Add outgoing radiance to Lo + float NdotL = max(dot(N, L), 0.0); + return (kD * albedo / PI + specular) * radiance * NdotL; +} + +float directionalShadowCalculation(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) { + // perform perspective divide + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + // transform to [0,1] range + projCoords = projCoords * 0.5 + 0.5; + // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) + float closestDepth = 0.0f; // texture(directionalShadowMap, projCoords.xy).r; + // get depth of current fragment from light's perspective + float currentDepth = projCoords.z; + // check whether current frag pos is in shadow + float bias = max(0.01 * (1.0 - dot(normal, lightDir)), 0.0001); + float shadow = 0.0; + ivec2 texSize = ivec2(0, 0); // textureSize(directionalShadowMap, 0); + vec2 texelSize = 1.0f / vec2(texSize); + for (int x = -2; x <= 2; ++x) { + for (int y = -2; y <= 2; ++y) { + float pcfDepth = 0.0f; // texture(directionalShadowMap, projCoords.xy + vec2(x, y) * texelSize).r; + shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; + } + } + shadow /= 25.0; + + if (projCoords.z > 1.0) + shadow = 0.0; + + return shadow; +} + +vec3 sampleOffsetDirections[20] = + vec3[](vec3(1, 1, 1), vec3(1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1), vec3(1, 1, -1), vec3(1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), + vec3(1, 1, 0), vec3(1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), vec3(1, 0, 1), vec3(-1, 0, 1), vec3(1, 0, -1), vec3(-1, 0, -1), + vec3(0, 1, 1), vec3(0, -1, 1), vec3(0, -1, -1), vec3(0, 1, -1)); + +float omniShadowCalculation(vec3 lightPos, vec3 normal) { + // get vector between fragment position and light position + vec3 fragToLight = worldPos - lightPos; + // use the light to fragment vector to sample from the depth map + float closestDepth = 0.0f; // texture(omniShadowMap, fragToLight).r; + if (closestDepth == 1.0f) + return 0.0f; + // it is currently in linear range between [0,1]. Re-transform back to original value + closestDepth *= omniFarPlane; + // now get current linear depth as the length between the fragment and light position + float currentDepth = length(fragToLight); + // now test for shadows + float shadow = 0.0; + float bias = 0.15; + int samples = 20; + float viewDistance = length(camPos - worldPos); + float diskRadius = (1.0 + (viewDistance / omniFarPlane)) / 25.0; + for (int i = 0; i < samples; ++i) { + float closestDepth = 0.0f; // texture(omniShadowMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r; + closestDepth *= omniFarPlane; // undo mapping [0;1] + if (currentDepth - bias > closestDepth) + shadow += 1.0; + } + shadow /= float(samples); + + return shadow; +} + +float distributionGGX(vec3 N, vec3 H, float roughness) { + float a = roughness * roughness; + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = NdotH * NdotH; + + float num = a2; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return num / denom; +} + +float geometrySchlickGGX(float NdotV, float roughness) { + float r = (roughness + 1.0); + float k = (r * r) / 8.0; + + float num = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return num / denom; +} + +float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = geometrySchlickGGX(NdotV, roughness); + float ggx1 = geometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +vec3 fresnelSchlick(float cosTheta, vec3 F0) { return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); } + +vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) { + return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} diff --git a/resources/shaders/pbrRenderer/shader.frag b/resources/shaders/pbrRenderer/shader.frag new file mode 100644 index 00000000..70d7d478 --- /dev/null +++ b/resources/shaders/pbrRenderer/shader.frag @@ -0,0 +1,291 @@ +#version 300 es +precision mediump float; +//---------- Definitions ----------// +struct Material +{ + vec3 albedo; + float metallic; + float roughness; + float ao; + int hasNormalTexture; +}; +struct PointLight +{ + vec3 position; + vec3 intensity; +}; +struct DirectionalLight +{ + vec3 direction; + vec3 intensity; +}; +const float PI = 3.14159265359; + +//---------- Variables ----------// +//----- In/Out -----// +in vec3 worldPos; +in vec4 posDirectionalLightSpace; +in vec3 normal; +in vec2 texCoord; +out vec4 FragColor; + +//----- Lighing -----// +#define MAX_NUM_POINT_LIGHTS 10 +uniform PointLight pointLights[MAX_NUM_POINT_LIGHTS]; +uniform DirectionalLight directionalLight; +uniform sampler2D directionalShadowMap; +uniform mat3 environmentLightOri; +uniform samplerCube omniShadowMap; +uniform float omniFarPlane; + +uniform int numPointLights; +uniform int numDirectionalLights; +uniform int numEnvironmentLights; + +//----- PBR -----// +uniform vec3 camPos; +// IBL +uniform samplerCube prefilterMap; +uniform samplerCube irradianceMap; +uniform sampler2D brdfLUT; + +//----- Material -----// +uniform Material material; +uniform sampler2D albedoTexture; +uniform sampler2D metallicTexture; +uniform sampler2D roughnessTexture; +uniform sampler2D aoTexture; +uniform sampler2D normalTexture; + +//---------- Definitions ----------// +vec3 calcLightContribution(vec3 L, vec3 N, vec3 V, vec3 radiance, vec3 albedo, float metallic, float roughness, vec3 F0); +float directionalShadowCalculation(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir); +float omniShadowCalculation(vec3 lightPos, vec3 normal); + +float distributionGGX(vec3 N, vec3 H, float roughness);// Distribution of microsurface normals +float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness);// Microsurface shadowing +vec3 fresnelSchlick(float cosTheta, vec3 F0);// Reflect-refract ratio +vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness); + +//---------- Main ----------// +void main() +{ + vec3 albedo = material.albedo; + if(albedo.x < 0.0f) + albedo = pow(texture(albedoTexture, texCoord).rgb, vec3(2.2)); + + float metallic = material.metallic; + if(metallic < 0.0f) + metallic = texture(metallicTexture, texCoord).r; + + float roughness = material.roughness; + if(roughness < 0.0f) + roughness = texture(roughnessTexture, texCoord).r; + + float ao = material.ao; + if(ao < 0.0f) + ao = texture(aoTexture, texCoord).r; + + vec3 N = normal; + vec3 V = normalize(camPos - worldPos); + vec3 R = reflect(-V, N); + + vec3 F0 = vec3(0.04); + F0 = mix(F0, albedo, metallic); + + vec3 Lo = vec3(0.0); + //----- Point lights -----// + for(int i = 0; i < numPointLights; i++) + { + vec3 L = normalize(pointLights[i].position - worldPos); + vec3 H = normalize(V + L); + float dist = length(pointLights[i].position - worldPos); + float attenuation = 1.0/(dist * dist); + vec3 radiance = pointLights[i].intensity * attenuation; + + float shadow = 0.0f; + shadow = omniShadowCalculation(pointLights[i].position, N); + Lo += calcLightContribution(L, N, V, radiance, albedo, metallic, roughness, F0) * (1.0f - shadow); + } + + //----- Directional light -----// + if(numDirectionalLights == 1) + { + vec3 L = -directionalLight.direction; + vec3 radiance = directionalLight.intensity; + + float shadow = directionalShadowCalculation(posDirectionalLightSpace, N, L); + Lo += calcLightContribution(L, N, V, radiance, albedo, metallic, roughness, F0) * (1.0f - shadow); + } + + //----- Environment light -----// + vec3 ambient = vec3(0.0f); + if(numEnvironmentLights == 1) + { + // Diffuse IBL + vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); + vec3 kS = F; + vec3 kD = 1.0 - kS; + kD *= 1.0 - metallic; + vec3 irradiance = texture(irradianceMap, environmentLightOri*N).rgb; + vec3 diffuse = irradiance * albedo; + + // Specular IBL + const float MAX_REFLECTION_LOD = 4.0; + vec3 prefilteredColor = textureLod(prefilterMap, environmentLightOri*R, roughness * MAX_REFLECTION_LOD).rgb; + vec2 brdf = texture(brdfLUT, vec2(max(dot(environmentLightOri*N, V), 0.0), roughness)).rg; + vec3 specular = prefilteredColor * (F * brdf.x + brdf.y); + + ambient = (kD*diffuse + specular) * ao; + } + else if(numEnvironmentLights == 0) + { + ambient = vec3(0.3) * albedo * ao; + } + + vec3 color = ambient + Lo; + + // HDR tonemapping + color = color/(color + vec3(1.0)); + // Gamma correction + color = pow(color, vec3(1.0/2.2)); + + FragColor = vec4(color, 1.0f); +} + +vec3 calcLightContribution(vec3 L, vec3 N, vec3 V, vec3 radiance, vec3 albedo, float metallic, float roughness, vec3 F0) +{ + vec3 H = normalize(V + L); + + // Cook-torrance BRDF + float D = distributionGGX(N, H, roughness); + float G = geometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + + vec3 numerator = D * G * F; + float denominator = 4.0f * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; + vec3 specular = numerator/denominator; + + vec3 kS = F; + vec3 kD = vec3(1.0) - kS; + kD *= 1.0 - metallic; + + // Add outgoing radiance to Lo + float NdotL = max(dot(N, L), 0.0); + return (kD * albedo / PI + specular) * radiance * NdotL; +} + +float directionalShadowCalculation(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) +{ + // perform perspective divide + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + // transform to [0,1] range + projCoords = projCoords * 0.5 + 0.5; + // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) + float closestDepth = texture(directionalShadowMap, projCoords.xy).r; + // get depth of current fragment from light's perspective + float currentDepth = projCoords.z; + // check whether current frag pos is in shadow + float bias = max(0.01 * (1.0 - dot(normal, lightDir)), 0.0001); + float shadow = 0.0; + ivec2 texSize = textureSize(directionalShadowMap, 0); + vec2 texelSize = 1.0f/vec2(texSize); + for(int x = -2; x <= 2; ++x) + { + for(int y = -2; y <= 2; ++y) + { + float pcfDepth = texture(directionalShadowMap, projCoords.xy + vec2(x, y) * texelSize).r; + shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; + } + } + shadow /= 25.0; + + if(projCoords.z > 1.0) + shadow = 0.0; + + return shadow; +} + +vec3 sampleOffsetDirections[20] = vec3[] +( + vec3( 1, 1, 1), vec3( 1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1), + vec3( 1, 1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), + vec3( 1, 1, 0), vec3( 1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), + vec3( 1, 0, 1), vec3(-1, 0, 1), vec3( 1, 0, -1), vec3(-1, 0, -1), + vec3( 0, 1, 1), vec3( 0, -1, 1), vec3( 0, -1, -1), vec3( 0, 1, -1) +); + +float omniShadowCalculation(vec3 lightPos, vec3 normal) +{ + // get vector between fragment position and light position + vec3 fragToLight = worldPos - lightPos; + // use the light to fragment vector to sample from the depth map + float closestDepth = texture(omniShadowMap, fragToLight).r; + if(closestDepth == 1.0f) return 0.0f; + // it is currently in linear range between [0,1]. Re-transform back to original value + closestDepth *= omniFarPlane; + // now get current linear depth as the length between the fragment and light position + float currentDepth = length(fragToLight); + // now test for shadows + float shadow = 0.0; + float bias = 0.15; + int samples = 20; + float viewDistance = length(camPos - worldPos); + float diskRadius = (1.0 + (viewDistance / omniFarPlane)) / 25.0; + for(int i = 0; i < samples; ++i) + { + float closestDepth = texture(omniShadowMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r; + closestDepth *= omniFarPlane; // undo mapping [0;1] + if(currentDepth - bias > closestDepth) + shadow += 1.0; + } + shadow /= float(samples); + + return shadow; +} + +//---------- Declarations ----------// +float distributionGGX(vec3 N, vec3 H, float roughness) +{ + float a = roughness*roughness; + float a2 = a*a; + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = NdotH*NdotH; + + float num = a2; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return num/denom; +} + +float geometrySchlickGGX(float NdotV, float roughness) +{ + float r = (roughness + 1.0); + float k = (r*r) / 8.0; + + float num = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return num/denom; +} + +float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) +{ + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = geometrySchlickGGX(NdotV, roughness); + float ggx1 = geometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +vec3 fresnelSchlick(float cosTheta, vec3 F0) +{ + return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + +vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) +{ + return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} diff --git a/resources/shaders/pbrRenderer/shader.vert b/resources/shaders/pbrRenderer/shader.vert new file mode 100644 index 00000000..eff3418f --- /dev/null +++ b/resources/shaders/pbrRenderer/shader.vert @@ -0,0 +1,37 @@ +#version 300 es +in vec3 inPosition; +in vec3 inNormal; +in vec2 inTexCoord; + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; +uniform mat4 invModel; +uniform mat4 directionalLightMatrix; + +out vec3 worldPos; +out vec4 posDirectionalLightSpace; +out vec3 normal; +out vec2 texCoord; + + +void main() +{ + vec4 coord = model * vec4(inPosition, 1.0f); + vec4 coordImage = view * coord; + + //float z = coordImage.z; + //coordImage /= z; + //float r = sqrt(coordImage.x*coordImage.x + coordImage.y*coordImage.y); + //float L = 1 + r*0.103689 + r*r*0.00487908 + r*r*r*0.00116894 + r*r*r*r*0.000941614; + //coordImage.x *= L; + //coordImage.y *= L; + //coordImage *= z; + + gl_Position = projection * coordImage; + + worldPos = coord.xyz; + posDirectionalLightSpace = directionalLightMatrix*vec4(worldPos, 1.0f); + normal = mat3(transpose(invModel))*inNormal; + texCoord = inTexCoord; +} diff --git a/resources/shaders/pbrRenderer/shadow.frag b/resources/shaders/pbrRenderer/shadow.frag new file mode 100644 index 00000000..377b8264 --- /dev/null +++ b/resources/shaders/pbrRenderer/shadow.frag @@ -0,0 +1,12 @@ +#version 300 es +//-------------------------------------------------- +// Atta Shaders +// shadow.frag +// Date: 2022-01-04 +// By Breno Cunha Queiroz +//-------------------------------------------------- + +void main() +{ + // gl_FragDepth = gl_FragCoord.z; +} diff --git a/resources/shaders/pbrRenderer/shadow.vert b/resources/shaders/pbrRenderer/shadow.vert new file mode 100644 index 00000000..9a165f56 --- /dev/null +++ b/resources/shaders/pbrRenderer/shadow.vert @@ -0,0 +1,19 @@ +#version 300 es +//-------------------------------------------------- +// Atta Shaders +// shadow.vert +// Date: 2022-01-04 +// By Breno Cunha Queiroz +//-------------------------------------------------- + +in vec3 inPosition; +in vec3 inNormal; +in vec2 inTexCoord; + +uniform mat4 lightSpaceMatrix; +uniform mat4 model; + +void main() +{ + gl_Position = lightSpaceMatrix * model * vec4(inPosition, 1.0); +} diff --git a/resources/shaders/phongRenderer/phongRenderer.asl b/resources/shaders/phongRenderer/phongRenderer.asl new file mode 100644 index 00000000..e4d73914 --- /dev/null +++ b/resources/shaders/phongRenderer/phongRenderer.asl @@ -0,0 +1,135 @@ +struct Material { + vec3 albedo; + float pad0; + float metallic; + float roughness; + float ao; +}; +struct PointLight { + vec3 position; + float pad0; + vec3 intensity; + float pad1; +}; +struct DirectionalLight { + vec3 direction; + float pad0; + vec3 intensity; + float pad1; +}; + +perFrame mat4 uView; +perFrame mat4 uProjection; +perFrame vec3 uViewPos; +perFrame float pad0; +perFrame vec3 uAmbientColor; +perFrame float pad1; +perFrame float uAmbientStrength; +perFrame float uDiffuseStrength; +perFrame float uSpecularStrength; +perFrame bool uHasDirectionalLight; +perFrame DirectionalLight uDirectionalLight; + +#define MAX_POINT_LIGHTS 10 +perFrame int uNumPointLights; +perFrame PointLight uPointLights[MAX_POINT_LIGHTS]; + +perDraw mat4 uModel; +perDraw mat4 uInvModel; +perDraw Material uMaterial; +perDraw sampler2D uAlbedoTexture; +perDraw sampler2D uMetallicTexture; +perDraw sampler2D uRoughnessTexture; +perDraw sampler2D uAoTexture; + +perVertex vec3 vFragPos; +perVertex vec3 vNormal; +perVertex vec2 vUV; + +vec3 calcDirectionalLight(DirectionalLight light, vec3 normal, vec3 viewDir); +vec3 calcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir); + +vec4 vertex(vec3 iVertex, vec3 iNormal, vec2 iUV) { + vec4 coord = uModel * vec4(iVertex, 1.0f); + vFragPos = coord.xyz; + vNormal = mat3(transpose(uInvModel)) * iNormal; + vUV = iUV; + return uProjection * uView * coord; +} + +vec3 albedo; +float metallic; +float roughness; +float ao; +float alpha; +void fragment(out vec4 color) { + // Initialize material + albedo = uMaterial.albedo; + alpha = 1.0f; + if (albedo.x < 0.0f) { + albedo = texture(uAlbedoTexture, vUV).rgb; + alpha = texture(uAlbedoTexture, vUV).a; + } + + if (alpha < 0.1) + discard; + + metallic = uMaterial.metallic; + if (metallic < 0.0f) + metallic = texture(uMetallicTexture, vUV).r; + + roughness = uMaterial.roughness; + if (roughness < 0.0f) + roughness = texture(uRoughnessTexture, vUV).r; + + ao = uMaterial.ao; + if (ao < 0.0f) + ao = texture(uAoTexture, vUV).r; + + vec3 result = vec3(0.0f); + + // Directional light + vec3 norm = normalize(vNormal); + vec3 viewDir = normalize(uViewPos - vFragPos); + if (uHasDirectionalLight) + result += calcDirectionalLight(uDirectionalLight, norm, viewDir); + + // Point light + if (uNumPointLights > 0 && uNumPointLights <= MAX_POINT_LIGHTS) + for (int i = 0; i < uNumPointLights; i++) + result += calcPointLight(uPointLights[i], norm, vFragPos, viewDir); + + result += uAmbientColor * uAmbientStrength * albedo; + result *= ao; + + color = vec4(result, 1.0f); +} + +vec3 calcDirectionalLight(DirectionalLight light, vec3 normal, vec3 viewDir) { + vec3 lightDir = normalize(-light.direction); + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + // specular shading + vec3 reflectDir = reflect(-lightDir, normal); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 3.0f); + // combine results + float diffuse = uDiffuseStrength * diff * roughness; + float specular = uSpecularStrength * spec * metallic; + return light.intensity * (diffuse + specular); +} + +vec3 calcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) { + vec3 lightDir = normalize(light.position - fragPos); + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + // specular shading + vec3 reflectDir = reflect(-lightDir, normal); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 5.0f); + // attenuation + float dist = length(light.position - fragPos); + float attenuation = 1.0 / (2.0f + 2.0f * dist + 2.0 * (dist * dist)); + // combine results + float diffuse = uDiffuseStrength * diff * roughness; + float specular = uSpecularStrength * spec * metallic; + return light.intensity * attenuation * (diffuse + specular); +} diff --git a/resources/shaders/quad/shader.frag b/resources/shaders/quad/shader.frag new file mode 100644 index 00000000..0e1b8d79 --- /dev/null +++ b/resources/shaders/quad/shader.frag @@ -0,0 +1,11 @@ +#version 300 es +precision mediump float; +out vec4 FragColor; +in vec2 TexCoords; + +uniform sampler2D screenTexture; + +void main() +{ + FragColor = texture(screenTexture, TexCoords); +} diff --git a/resources/shaders/quad/shader.vert b/resources/shaders/quad/shader.vert new file mode 100644 index 00000000..d6c190a1 --- /dev/null +++ b/resources/shaders/quad/shader.vert @@ -0,0 +1,11 @@ +#version 300 es +in vec2 aPos; +in vec2 aTexCoords; + +out vec2 TexCoords; + +void main() +{ + gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); + TexCoords = aTexCoords; +} diff --git a/src/extern/solveResources.cmake b/src/extern/solveResources.cmake index c6ebbbf2..1cdc9ef1 100644 --- a/src/extern/solveResources.cmake +++ b/src/extern/solveResources.cmake @@ -1,8 +1,27 @@ FetchContent_Declare(atta_resources - URL "https://atta-resources.s3.amazonaws.com/v0.3.7.zip" - SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/resources + URL "https://atta-resources.s3.amazonaws.com/v0.3.8.zip" ) atta_log(Info Extern "Downloading resources...") FetchContent_Populate(atta_resources) atta_log(Success Extern "Resources downloaded") + +# Copy to resources directory +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/fonts") + file(COPY "${atta_resources_SOURCE_DIR}/fonts" DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/resources") +endif() + +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/icons") + file(COPY "${atta_resources_SOURCE_DIR}/icons" DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/resources") +endif() + +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/meshes") + file(COPY "${atta_resources_SOURCE_DIR}/meshes" DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/resources") +endif() + +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/textures") + file(COPY "${atta_resources_SOURCE_DIR}/textures" DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/resources") +endif() + +# Clean up the temporary directory +file(REMOVE_RECURSE "${atta_resources_SOURCE_DIR}") From e7eb44c5a77c4f6c728ab1dc584ba7200e3525cd Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sat, 22 Mar 2025 10:04:39 +0100 Subject: [PATCH 02/39] Fix: Normal not normalized in PBR fragment shader --- resources/shaders/pbrRenderer/pbrRenderer.asl | 33 +- resources/shaders/pbrRenderer/shader.frag | 291 ------------------ resources/shaders/pbrRenderer/shader.vert | 37 --- 3 files changed, 18 insertions(+), 343 deletions(-) delete mode 100644 resources/shaders/pbrRenderer/shader.frag delete mode 100644 resources/shaders/pbrRenderer/shader.vert diff --git a/resources/shaders/pbrRenderer/pbrRenderer.asl b/resources/shaders/pbrRenderer/pbrRenderer.asl index fbc3becb..ceb7cf97 100644 --- a/resources/shaders/pbrRenderer/pbrRenderer.asl +++ b/resources/shaders/pbrRenderer/pbrRenderer.asl @@ -2,7 +2,6 @@ struct Material { float metallic; float roughness; float ao; - int hasNormalTexture; vec3 albedo; }; struct PointLight { @@ -15,12 +14,12 @@ struct DirectionalLight { }; const float PI = 3.14159265359; -//----- PerFrame -----// +//----- Per Frame -----// perFrame mat4 projection; perFrame mat4 view; perFrame mat4 directionalLightMatrix; perFrame vec3 camPos; -perFrame float p0; +perFrame float p0;// Dummy perFrame int numPointLights; perFrame int numDirectionalLights; @@ -42,6 +41,7 @@ perFrame mat3 environmentLightOri; // perFrame samplerCube irradianceMap; // perFrame sampler2D brdfLUT; +//----- Per Draw -----// perDraw mat4 model; perDraw mat4 invModel; perDraw Material material; @@ -105,7 +105,7 @@ void fragment(out vec4 outColor) { ao = texture(aoTexture, texCoord).r; // Calculate vectors - vec3 N = normal; + vec3 N = normalize(normal); vec3 V = normalize(camPos - worldPos); // Fresnel coefficient @@ -251,33 +251,36 @@ float omniShadowCalculation(vec3 lightPos, vec3 normal) { } float distributionGGX(vec3 N, vec3 H, float roughness) { + // Trowbridge-Reitz GGX normal distribution function float a = roughness * roughness; float a2 = a * a; float NdotH = max(dot(N, H), 0.0); float NdotH2 = NdotH * NdotH; - float num = a2; + float nom = a2; float denom = (NdotH2 * (a2 - 1.0) + 1.0); denom = PI * denom * denom; - return num / denom; + return nom / denom; } -float geometrySchlickGGX(float NdotV, float roughness) { - float r = (roughness + 1.0); - float k = (r * r) / 8.0; - - float num = NdotV; +float geometrySchlickGGX(float NdotV, float k) { + float nom = NdotV; float denom = NdotV * (1.0 - k) + k; - - return num / denom; + return nom / denom; } float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { + // Compute k for direct lighting + float r = (roughness + 1.0); + float k = (r * r) / 8.0; + // TODO Compute k for IBL + + // Smith's method to take into account both view direction and light direction float NdotV = max(dot(N, V), 0.0); float NdotL = max(dot(N, L), 0.0); - float ggx2 = geometrySchlickGGX(NdotV, roughness); - float ggx1 = geometrySchlickGGX(NdotL, roughness); + float ggx2 = geometrySchlickGGX(NdotV, k); + float ggx1 = geometrySchlickGGX(NdotL, k); return ggx1 * ggx2; } diff --git a/resources/shaders/pbrRenderer/shader.frag b/resources/shaders/pbrRenderer/shader.frag deleted file mode 100644 index 70d7d478..00000000 --- a/resources/shaders/pbrRenderer/shader.frag +++ /dev/null @@ -1,291 +0,0 @@ -#version 300 es -precision mediump float; -//---------- Definitions ----------// -struct Material -{ - vec3 albedo; - float metallic; - float roughness; - float ao; - int hasNormalTexture; -}; -struct PointLight -{ - vec3 position; - vec3 intensity; -}; -struct DirectionalLight -{ - vec3 direction; - vec3 intensity; -}; -const float PI = 3.14159265359; - -//---------- Variables ----------// -//----- In/Out -----// -in vec3 worldPos; -in vec4 posDirectionalLightSpace; -in vec3 normal; -in vec2 texCoord; -out vec4 FragColor; - -//----- Lighing -----// -#define MAX_NUM_POINT_LIGHTS 10 -uniform PointLight pointLights[MAX_NUM_POINT_LIGHTS]; -uniform DirectionalLight directionalLight; -uniform sampler2D directionalShadowMap; -uniform mat3 environmentLightOri; -uniform samplerCube omniShadowMap; -uniform float omniFarPlane; - -uniform int numPointLights; -uniform int numDirectionalLights; -uniform int numEnvironmentLights; - -//----- PBR -----// -uniform vec3 camPos; -// IBL -uniform samplerCube prefilterMap; -uniform samplerCube irradianceMap; -uniform sampler2D brdfLUT; - -//----- Material -----// -uniform Material material; -uniform sampler2D albedoTexture; -uniform sampler2D metallicTexture; -uniform sampler2D roughnessTexture; -uniform sampler2D aoTexture; -uniform sampler2D normalTexture; - -//---------- Definitions ----------// -vec3 calcLightContribution(vec3 L, vec3 N, vec3 V, vec3 radiance, vec3 albedo, float metallic, float roughness, vec3 F0); -float directionalShadowCalculation(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir); -float omniShadowCalculation(vec3 lightPos, vec3 normal); - -float distributionGGX(vec3 N, vec3 H, float roughness);// Distribution of microsurface normals -float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness);// Microsurface shadowing -vec3 fresnelSchlick(float cosTheta, vec3 F0);// Reflect-refract ratio -vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness); - -//---------- Main ----------// -void main() -{ - vec3 albedo = material.albedo; - if(albedo.x < 0.0f) - albedo = pow(texture(albedoTexture, texCoord).rgb, vec3(2.2)); - - float metallic = material.metallic; - if(metallic < 0.0f) - metallic = texture(metallicTexture, texCoord).r; - - float roughness = material.roughness; - if(roughness < 0.0f) - roughness = texture(roughnessTexture, texCoord).r; - - float ao = material.ao; - if(ao < 0.0f) - ao = texture(aoTexture, texCoord).r; - - vec3 N = normal; - vec3 V = normalize(camPos - worldPos); - vec3 R = reflect(-V, N); - - vec3 F0 = vec3(0.04); - F0 = mix(F0, albedo, metallic); - - vec3 Lo = vec3(0.0); - //----- Point lights -----// - for(int i = 0; i < numPointLights; i++) - { - vec3 L = normalize(pointLights[i].position - worldPos); - vec3 H = normalize(V + L); - float dist = length(pointLights[i].position - worldPos); - float attenuation = 1.0/(dist * dist); - vec3 radiance = pointLights[i].intensity * attenuation; - - float shadow = 0.0f; - shadow = omniShadowCalculation(pointLights[i].position, N); - Lo += calcLightContribution(L, N, V, radiance, albedo, metallic, roughness, F0) * (1.0f - shadow); - } - - //----- Directional light -----// - if(numDirectionalLights == 1) - { - vec3 L = -directionalLight.direction; - vec3 radiance = directionalLight.intensity; - - float shadow = directionalShadowCalculation(posDirectionalLightSpace, N, L); - Lo += calcLightContribution(L, N, V, radiance, albedo, metallic, roughness, F0) * (1.0f - shadow); - } - - //----- Environment light -----// - vec3 ambient = vec3(0.0f); - if(numEnvironmentLights == 1) - { - // Diffuse IBL - vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); - vec3 kS = F; - vec3 kD = 1.0 - kS; - kD *= 1.0 - metallic; - vec3 irradiance = texture(irradianceMap, environmentLightOri*N).rgb; - vec3 diffuse = irradiance * albedo; - - // Specular IBL - const float MAX_REFLECTION_LOD = 4.0; - vec3 prefilteredColor = textureLod(prefilterMap, environmentLightOri*R, roughness * MAX_REFLECTION_LOD).rgb; - vec2 brdf = texture(brdfLUT, vec2(max(dot(environmentLightOri*N, V), 0.0), roughness)).rg; - vec3 specular = prefilteredColor * (F * brdf.x + brdf.y); - - ambient = (kD*diffuse + specular) * ao; - } - else if(numEnvironmentLights == 0) - { - ambient = vec3(0.3) * albedo * ao; - } - - vec3 color = ambient + Lo; - - // HDR tonemapping - color = color/(color + vec3(1.0)); - // Gamma correction - color = pow(color, vec3(1.0/2.2)); - - FragColor = vec4(color, 1.0f); -} - -vec3 calcLightContribution(vec3 L, vec3 N, vec3 V, vec3 radiance, vec3 albedo, float metallic, float roughness, vec3 F0) -{ - vec3 H = normalize(V + L); - - // Cook-torrance BRDF - float D = distributionGGX(N, H, roughness); - float G = geometrySmith(N, V, L, roughness); - vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); - - vec3 numerator = D * G * F; - float denominator = 4.0f * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; - vec3 specular = numerator/denominator; - - vec3 kS = F; - vec3 kD = vec3(1.0) - kS; - kD *= 1.0 - metallic; - - // Add outgoing radiance to Lo - float NdotL = max(dot(N, L), 0.0); - return (kD * albedo / PI + specular) * radiance * NdotL; -} - -float directionalShadowCalculation(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) -{ - // perform perspective divide - vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; - // transform to [0,1] range - projCoords = projCoords * 0.5 + 0.5; - // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) - float closestDepth = texture(directionalShadowMap, projCoords.xy).r; - // get depth of current fragment from light's perspective - float currentDepth = projCoords.z; - // check whether current frag pos is in shadow - float bias = max(0.01 * (1.0 - dot(normal, lightDir)), 0.0001); - float shadow = 0.0; - ivec2 texSize = textureSize(directionalShadowMap, 0); - vec2 texelSize = 1.0f/vec2(texSize); - for(int x = -2; x <= 2; ++x) - { - for(int y = -2; y <= 2; ++y) - { - float pcfDepth = texture(directionalShadowMap, projCoords.xy + vec2(x, y) * texelSize).r; - shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; - } - } - shadow /= 25.0; - - if(projCoords.z > 1.0) - shadow = 0.0; - - return shadow; -} - -vec3 sampleOffsetDirections[20] = vec3[] -( - vec3( 1, 1, 1), vec3( 1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1), - vec3( 1, 1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), - vec3( 1, 1, 0), vec3( 1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), - vec3( 1, 0, 1), vec3(-1, 0, 1), vec3( 1, 0, -1), vec3(-1, 0, -1), - vec3( 0, 1, 1), vec3( 0, -1, 1), vec3( 0, -1, -1), vec3( 0, 1, -1) -); - -float omniShadowCalculation(vec3 lightPos, vec3 normal) -{ - // get vector between fragment position and light position - vec3 fragToLight = worldPos - lightPos; - // use the light to fragment vector to sample from the depth map - float closestDepth = texture(omniShadowMap, fragToLight).r; - if(closestDepth == 1.0f) return 0.0f; - // it is currently in linear range between [0,1]. Re-transform back to original value - closestDepth *= omniFarPlane; - // now get current linear depth as the length between the fragment and light position - float currentDepth = length(fragToLight); - // now test for shadows - float shadow = 0.0; - float bias = 0.15; - int samples = 20; - float viewDistance = length(camPos - worldPos); - float diskRadius = (1.0 + (viewDistance / omniFarPlane)) / 25.0; - for(int i = 0; i < samples; ++i) - { - float closestDepth = texture(omniShadowMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r; - closestDepth *= omniFarPlane; // undo mapping [0;1] - if(currentDepth - bias > closestDepth) - shadow += 1.0; - } - shadow /= float(samples); - - return shadow; -} - -//---------- Declarations ----------// -float distributionGGX(vec3 N, vec3 H, float roughness) -{ - float a = roughness*roughness; - float a2 = a*a; - float NdotH = max(dot(N, H), 0.0); - float NdotH2 = NdotH*NdotH; - - float num = a2; - float denom = (NdotH2 * (a2 - 1.0) + 1.0); - denom = PI * denom * denom; - - return num/denom; -} - -float geometrySchlickGGX(float NdotV, float roughness) -{ - float r = (roughness + 1.0); - float k = (r*r) / 8.0; - - float num = NdotV; - float denom = NdotV * (1.0 - k) + k; - - return num/denom; -} - -float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) -{ - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = geometrySchlickGGX(NdotV, roughness); - float ggx1 = geometrySchlickGGX(NdotL, roughness); - - return ggx1 * ggx2; -} - -vec3 fresnelSchlick(float cosTheta, vec3 F0) -{ - return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); -} - -vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) -{ - return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); -} diff --git a/resources/shaders/pbrRenderer/shader.vert b/resources/shaders/pbrRenderer/shader.vert deleted file mode 100644 index eff3418f..00000000 --- a/resources/shaders/pbrRenderer/shader.vert +++ /dev/null @@ -1,37 +0,0 @@ -#version 300 es -in vec3 inPosition; -in vec3 inNormal; -in vec2 inTexCoord; - -uniform mat4 projection; -uniform mat4 view; -uniform mat4 model; -uniform mat4 invModel; -uniform mat4 directionalLightMatrix; - -out vec3 worldPos; -out vec4 posDirectionalLightSpace; -out vec3 normal; -out vec2 texCoord; - - -void main() -{ - vec4 coord = model * vec4(inPosition, 1.0f); - vec4 coordImage = view * coord; - - //float z = coordImage.z; - //coordImage /= z; - //float r = sqrt(coordImage.x*coordImage.x + coordImage.y*coordImage.y); - //float L = 1 + r*0.103689 + r*r*0.00487908 + r*r*r*0.00116894 + r*r*r*r*0.000941614; - //coordImage.x *= L; - //coordImage.y *= L; - //coordImage *= z; - - gl_Position = projection * coordImage; - - worldPos = coord.xyz; - posDirectionalLightSpace = directionalLightMatrix*vec4(worldPos, 1.0f); - normal = mat3(transpose(invModel))*inNormal; - texCoord = inTexCoord; -} From fd383b548f9cbd070a9c6d523dc720990f1a5bbb Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sat, 22 Mar 2025 11:26:40 +0100 Subject: [PATCH 03/39] Feat: Environment light HDR texture selection --- resources/shaders/pbrRenderer/pbrRenderer.asl | 33 ++++++++++--------- .../component/components/environmentLight.cpp | 11 +++++-- src/atta/ui/manager.cpp | 26 +++++++++++++++ 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/resources/shaders/pbrRenderer/pbrRenderer.asl b/resources/shaders/pbrRenderer/pbrRenderer.asl index ceb7cf97..5e6d2d95 100644 --- a/resources/shaders/pbrRenderer/pbrRenderer.asl +++ b/resources/shaders/pbrRenderer/pbrRenderer.asl @@ -115,8 +115,10 @@ void fragment(out vec4 outColor) { vec3 Lo = vec3(0.0); //----- Point lights -----// for (int i = 0; i < numPointLights; i++) { + // Compute light direction vec3 L = normalize(pointLights[i].position - worldPos); - vec3 H = normalize(V + L); + + // Compute radiance float dist = length(pointLights[i].position - worldPos); float attenuation = 1.0 / (dist * dist); vec3 radiance = pointLights[i].intensity * attenuation; @@ -176,9 +178,9 @@ vec3 calcLightContribution(vec3 L, vec3 N, vec3 V, vec3 radiance, vec3 albedo, f float G = geometrySmith(N, V, L, roughness); vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); - vec3 numerator = D * G * F; - float denominator = 4.0f * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; - vec3 specular = numerator / denominator; + vec3 num = D * G * F; + float denom = 4.0f * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; + vec3 specular = num / denom; vec3 kS = F; // Reflection/specular fraction vec3 kD = vec3(1.0) - kS; // Refraction/diffuse fraction @@ -257,30 +259,29 @@ float distributionGGX(vec3 N, vec3 H, float roughness) { float NdotH = max(dot(N, H), 0.0); float NdotH2 = NdotH * NdotH; - float nom = a2; + float num = a2; float denom = (NdotH2 * (a2 - 1.0) + 1.0); denom = PI * denom * denom; - return nom / denom; + return num / denom; } -float geometrySchlickGGX(float NdotV, float k) { - float nom = NdotV; - float denom = NdotV * (1.0 - k) + k; - return nom / denom; -} - -float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { +float geometrySchlickGGX(float NdotV, float roughness) { // Compute k for direct lighting float r = (roughness + 1.0); float k = (r * r) / 8.0; - // TODO Compute k for IBL + float num = NdotV; + float denom = NdotV * (1.0 - k) + k; + return num / denom; +} + +float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { // Smith's method to take into account both view direction and light direction float NdotV = max(dot(N, V), 0.0); float NdotL = max(dot(N, L), 0.0); - float ggx2 = geometrySchlickGGX(NdotV, k); - float ggx1 = geometrySchlickGGX(NdotL, k); + float ggx2 = geometrySchlickGGX(NdotV, roughness); + float ggx1 = geometrySchlickGGX(NdotL, roughness); return ggx1 * ggx2; } diff --git a/src/atta/component/components/environmentLight.cpp b/src/atta/component/components/environmentLight.cpp index 7a95bc74..70a7e22c 100644 --- a/src/atta/component/components/environmentLight.cpp +++ b/src/atta/component/components/environmentLight.cpp @@ -5,6 +5,8 @@ // By Breno Cunha Queiroz //-------------------------------------------------- #include +#include +#include namespace atta::component { @@ -20,8 +22,13 @@ ComponentDescription& TypedComponentRegistry::getDescription() } EnvironmentLight::EnvironmentLight() { - if (TypedComponentRegistry::description->attributeDescriptions[0].options.size()) - sid = StringId(*(TypedComponentRegistry::description->attributeDescriptions[0].options.begin())); + // Set first .hdr image as default image for environment lights + for (StringId resourceSid : resource::getResources()) { + if (resourceSid.getString().find(".hdr") != std::string::npos) { + sid = resourceSid; + break; + } + } } } // namespace atta::component diff --git a/src/atta/ui/manager.cpp b/src/atta/ui/manager.cpp index 6f76215b..0dd2753d 100644 --- a/src/atta/ui/manager.cpp +++ b/src/atta/ui/manager.cpp @@ -4,6 +4,7 @@ // Date: 2021-09-28 // By Breno Cunha Queiroz //-------------------------------------------------- +#include #include #include #include @@ -324,6 +325,31 @@ void Manager::registerCustomComponentUIs() { m->setAoImage(aoImage); }); + //---------- Environment Light ----------// + ui::registerComponentUI([](cmp::Entity entity, cmp::Component* comp) { + cmp::EnvironmentLight* envLight = static_cast(comp); + + std::string selectedImage = envLight->sid.getString(); + + if (ImGui::BeginCombo("Image##EnvComboImage", selectedImage.c_str())) { + std::vector rImages = resource::getResources(); + for (StringId rImage : rImages) { + std::string imageStr = rImage.getString(); + bool isHdrImage = imageStr.find(".hdr") != std::string::npos; + if (!isHdrImage) + continue; + const bool selected = (rImage == selectedImage); + if (ImGui::Selectable(imageStr.c_str(), selected)) { + envLight->sid = rImage; + selectedImage = rImage.getString(); + } + if (selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + }); + //---------- Polygon Collider 2D ----------// ui::registerComponentUI([](cmp::Entity entity, cmp::Component* comp) { // Render offset From 23217426371092a21b863c5e58cccdd85c00ba69 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sat, 22 Mar 2025 11:54:18 +0100 Subject: [PATCH 04/39] Feat: Skybox shader Refactor: Move equiToCubemap shader to compute --- resources/shaders/common/skybox.asl | 22 ++++++++++++++++++ .../equiToCubemap.asl | 0 resources/shaders/compute/equiToCubemap.frag | 23 ------------------- resources/shaders/compute/equiToCubemap.vert | 13 ----------- 4 files changed, 22 insertions(+), 36 deletions(-) create mode 100644 resources/shaders/common/skybox.asl rename resources/shaders/{pbrRenderer => compute}/equiToCubemap.asl (100%) delete mode 100644 resources/shaders/compute/equiToCubemap.frag delete mode 100644 resources/shaders/compute/equiToCubemap.vert diff --git a/resources/shaders/common/skybox.asl b/resources/shaders/common/skybox.asl new file mode 100644 index 00000000..faf90d4e --- /dev/null +++ b/resources/shaders/common/skybox.asl @@ -0,0 +1,22 @@ +perFrame mat4 uView; +perFrame mat4 uProjection; +perFrame samplerCube uEnvironmentMap; + +perVertex vLocalPos; + +vec4 vertex(vec3 iVertex, vec3 iNormal, vec3 iUV) { + vLocalPos = iVertex; + mat4 rotView = mat4(mat3(view)); // Remove translation from the view matrix + vec4 clipPos = projection * rotView * vec4(iVertex, 1.0); + return clipPos.xyww; +} + +vec4 fragment() { + vec3 envColor = texture(uEnvironmentMap, vLocalPos).rgb; + + // Gamma correction + envColor = envColor / (envColor + vec3(1.0)); + envColor = pow(envColor, vec3(1.0/2.2)); + + return vec4(envColor, 1.0); +} diff --git a/resources/shaders/pbrRenderer/equiToCubemap.asl b/resources/shaders/compute/equiToCubemap.asl similarity index 100% rename from resources/shaders/pbrRenderer/equiToCubemap.asl rename to resources/shaders/compute/equiToCubemap.asl diff --git a/resources/shaders/compute/equiToCubemap.frag b/resources/shaders/compute/equiToCubemap.frag deleted file mode 100644 index 24b2273d..00000000 --- a/resources/shaders/compute/equiToCubemap.frag +++ /dev/null @@ -1,23 +0,0 @@ -#version 300 es -precision mediump float; -out vec4 FragColor; -in vec3 localPos; - -uniform sampler2D equirectangularMap; - -const vec2 invAtan = vec2(0.1591, 0.3183); -vec2 sampleSphericalMap(vec3 v) -{ - vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); - uv *= invAtan; - uv += 0.5; - return uv; -} - -void main() -{ - vec2 uv = sampleSphericalMap(normalize(localPos)); // make sure to normalize localPos - vec3 color = texture(equirectangularMap, uv).rgb; - - FragColor = vec4(color, 1.0); -} diff --git a/resources/shaders/compute/equiToCubemap.vert b/resources/shaders/compute/equiToCubemap.vert deleted file mode 100644 index 8391d12b..00000000 --- a/resources/shaders/compute/equiToCubemap.vert +++ /dev/null @@ -1,13 +0,0 @@ -#version 300 es -in vec3 aPos; - -out vec3 localPos; - -uniform mat4 projection; -uniform mat4 view; - -void main() -{ - localPos = aPos; - gl_Position = projection * view * vec4(localPos, 1.0); -} From 0601e95dfb2a1205c28bf1b32e12056096b731b9 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 08:51:22 +0200 Subject: [PATCH 05/39] Feat: Basic skybox pipeline Feat: Create pipeline with disabled depth testing --- resources/shaders/common/skybox.asl | 29 ++++++++--------- src/atta/graphics/CMakeLists.txt | 1 + src/atta/graphics/apis/openGL/pipeline.cpp | 20 ++++++++++-- src/atta/graphics/apis/vulkan/pipeline.cpp | 5 +-- src/atta/graphics/pipeline.cpp | 2 +- src/atta/graphics/pipeline.h | 2 ++ .../renderers/common/skyboxPipeline.cpp | 31 +++++++++++++++++++ .../renderers/common/skyboxPipeline.h | 27 ++++++++++++++++ src/atta/graphics/renderers/pbrRenderer.cpp | 5 +++ src/atta/graphics/renderers/pbrRenderer.h | 3 ++ 10 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 src/atta/graphics/renderers/common/skyboxPipeline.cpp create mode 100644 src/atta/graphics/renderers/common/skyboxPipeline.h diff --git a/resources/shaders/common/skybox.asl b/resources/shaders/common/skybox.asl index faf90d4e..9591edea 100644 --- a/resources/shaders/common/skybox.asl +++ b/resources/shaders/common/skybox.asl @@ -1,22 +1,19 @@ -perFrame mat4 uView; perFrame mat4 uProjection; -perFrame samplerCube uEnvironmentMap; +perFrame mat4 uView; +//perFrame samplerCube uEnvironmentMap; -perVertex vLocalPos; +perVertex vec3 vTexCoords; -vec4 vertex(vec3 iVertex, vec3 iNormal, vec3 iUV) { - vLocalPos = iVertex; - mat4 rotView = mat4(mat3(view)); // Remove translation from the view matrix - vec4 clipPos = projection * rotView * vec4(iVertex, 1.0); - return clipPos.xyww; -} +vec4 vertex(vec3 iVertex, vec3 iNormal, vec2 iUV) { + // Add small dependency between vertex and normal/uv to avoid compilation error + vTexCoords = (iVertex + 1.0) / 2.0; + vTexCoords += iNormal / 10000.0f + vec3(iUV,0) / 10000.0f; -vec4 fragment() { - vec3 envColor = texture(uEnvironmentMap, vLocalPos).rgb; - - // Gamma correction - envColor = envColor / (envColor + vec3(1.0)); - envColor = pow(envColor, vec3(1.0/2.2)); + mat4 rotView = mat4(mat3(uView)); // Remove translation from the view matrix + return uProjection * rotView * vec4(iVertex, 1.0); +} - return vec4(envColor, 1.0); +void fragment(out vec4 color) { + //return vec4((vTexCoords + 1.0)/2.0, 1.0); + color = vec4(vTexCoords, 1.0); } diff --git a/src/atta/graphics/CMakeLists.txt b/src/atta/graphics/CMakeLists.txt index 2778444b..1ea0480f 100644 --- a/src/atta/graphics/CMakeLists.txt +++ b/src/atta/graphics/CMakeLists.txt @@ -22,6 +22,7 @@ set(ATTA_GRAPHICS_MODULE_SOURCE renderers/common/drawerPipeline.cpp renderers/common/gridPipeline.cpp renderers/common/selectedPipeline.cpp + renderers/common/skyboxPipeline.cpp renderers/fastRenderer.cpp renderers/pbrRenderer.cpp renderers/phongRenderer.cpp diff --git a/src/atta/graphics/apis/openGL/pipeline.cpp b/src/atta/graphics/apis/openGL/pipeline.cpp index b035e42a..c48b639e 100644 --- a/src/atta/graphics/apis/openGL/pipeline.cpp +++ b/src/atta/graphics/apis/openGL/pipeline.cpp @@ -19,9 +19,25 @@ Pipeline::Pipeline(const Pipeline::CreateInfo& info) : gfx::Pipeline(info) { Pipeline::~Pipeline() {} -void Pipeline::begin() { _shader->bind(); } +void Pipeline::begin() { + // Disable depth testing + if (!_enableDepthTest) { + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + } + + _shader->bind(); +} -void Pipeline::end() { _shader->unbind(); } +void Pipeline::end() { + _shader->unbind(); + + // Restore depth testing + if (!_enableDepthTest) { + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + } +} void Pipeline::resize(uint32_t width, uint32_t height) { _renderPass->getFramebuffer()->resize(width, height); } diff --git a/src/atta/graphics/apis/vulkan/pipeline.cpp b/src/atta/graphics/apis/vulkan/pipeline.cpp index ed5c3a5c..93aeaaf1 100644 --- a/src/atta/graphics/apis/vulkan/pipeline.cpp +++ b/src/atta/graphics/apis/vulkan/pipeline.cpp @@ -15,6 +15,7 @@ namespace atta::graphics::vk { Pipeline::Pipeline(const gfx::Pipeline::CreateInfo& info) : gfx::Pipeline(info), _device(common::getDevice()) { // Create framebuffer + // TODO do not create framebuffer if already created for this render pass _framebuffer = std::dynamic_pointer_cast(_renderPass->getFramebuffer()); _framebuffer->create(std::dynamic_pointer_cast(_renderPass)); @@ -67,11 +68,11 @@ Pipeline::Pipeline(const gfx::Pipeline::CreateInfo& info) : gfx::Pipeline(info), // Depth stencil VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencil.depthTestEnable = VK_FALSE; depthStencil.depthWriteEnable = VK_FALSE; depthStencil.stencilTestEnable = VK_FALSE; - if (_framebuffer->hasDepthAttachment()) { - depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + if (_framebuffer->hasDepthAttachment() && _enableDepthTest) { depthStencil.depthTestEnable = VK_TRUE; depthStencil.depthWriteEnable = VK_TRUE; depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; diff --git a/src/atta/graphics/pipeline.cpp b/src/atta/graphics/pipeline.cpp index 76ad1675..2bb28854 100644 --- a/src/atta/graphics/pipeline.cpp +++ b/src/atta/graphics/pipeline.cpp @@ -17,7 +17,7 @@ namespace atta::graphics { Pipeline::Pipeline(const CreateInfo& info) : _shader(info.shader), _renderPass(info.renderPass), _primitive(info.primitive), _backfaceCulling(info.backfaceCulling), - _wireframe(info.wireframe), _lineWidth(info.lineWidth), _debugName(info.debugName) { + _wireframe(info.wireframe), _lineWidth(info.lineWidth), _enableDepthTest(info.enableDepthTest), _debugName(info.debugName) { //---------- Track material update ----------// event::subscribe(BIND_EVENT_FUNC(Pipeline::onMaterialCreate)); event::subscribe(BIND_EVENT_FUNC(Pipeline::onMaterialDestroy)); diff --git a/src/atta/graphics/pipeline.h b/src/atta/graphics/pipeline.h index 27cf0db6..e57ace29 100644 --- a/src/atta/graphics/pipeline.h +++ b/src/atta/graphics/pipeline.h @@ -27,6 +27,7 @@ class Pipeline { bool backfaceCulling = true; bool wireframe = false; bool lineWidth = 1.0f; + bool enableDepthTest = true; // Can be set to false to disable depth testing for this pipeline StringId debugName = StringId("Unnamed Pipeline"); }; @@ -94,6 +95,7 @@ class Pipeline { const bool _backfaceCulling; const bool _wireframe; const bool _lineWidth; + const bool _enableDepthTest; const StringId _debugName; diff --git a/src/atta/graphics/renderers/common/skyboxPipeline.cpp b/src/atta/graphics/renderers/common/skyboxPipeline.cpp new file mode 100644 index 00000000..bf5e43e2 --- /dev/null +++ b/src/atta/graphics/renderers/common/skyboxPipeline.cpp @@ -0,0 +1,31 @@ +//-------------------------------------------------- +// Atta Graphics Module +// skyboxPipeline.cpp +// Date: 2025-04-06 +// By Breno Cunha Queiroz +//-------------------------------------------------- +#include +#include + +namespace atta::graphics { + +SkyboxPipeline::SkyboxPipeline(std::shared_ptr renderPass) { + Pipeline::CreateInfo pipelineInfo{}; + pipelineInfo.shader = gfx::create("shaders/common/skybox.asl"); + pipelineInfo.renderPass = renderPass; + pipelineInfo.enableDepthTest = false; // Disable depth testing for the skybox + pipelineInfo.debugName = StringId("Skybox Pipeline"); + _pipeline = gfx::create(pipelineInfo); +} + +void SkyboxPipeline::render(std::shared_ptr camera) { + _pipeline->begin(); + { + _pipeline->setMat4("uProjection", camera->getProj()); + _pipeline->setMat4("uView", camera->getView()); + _pipeline->renderCube(); + } + _pipeline->end(); +} + +} // namespace atta::graphics diff --git a/src/atta/graphics/renderers/common/skyboxPipeline.h b/src/atta/graphics/renderers/common/skyboxPipeline.h new file mode 100644 index 00000000..eac72d07 --- /dev/null +++ b/src/atta/graphics/renderers/common/skyboxPipeline.h @@ -0,0 +1,27 @@ +//-------------------------------------------------- +// Atta Graphics Module +// skyboxPipeline.h +// Date: 2025-04-06 +// By Breno Cunha Queiroz +//-------------------------------------------------- +#ifndef ATTA_GRAPHICS_RENDERERS_COMMON_SKYBOX_PIPELINE_H +#define ATTA_GRAPHICS_RENDERERS_COMMON_SKYBOX_PIPELINE_H +#include +#include + +namespace atta::graphics { + +class SkyboxPipeline final { + public: + SkyboxPipeline(std::shared_ptr renderPass); + + /// Render skybox + void render(std::shared_ptr camera); + + private: + std::shared_ptr _pipeline; +}; + +} // namespace atta::graphics + +#endif // ATTA_GRAPHICS_RENDERERS_COMMON_SKYBOX_PIPELINE_H diff --git a/src/atta/graphics/renderers/pbrRenderer.cpp b/src/atta/graphics/renderers/pbrRenderer.cpp index 5b4d99e5..28d5891b 100644 --- a/src/atta/graphics/renderers/pbrRenderer.cpp +++ b/src/atta/graphics/renderers/pbrRenderer.cpp @@ -56,6 +56,7 @@ PbrRenderer::PbrRenderer() : Renderer("PbrRenderer"), _firstRender(true), _wasRe //---------- Common pipelines ----------// //_selectedPipeline = std::make_unique(_geometryRenderPass); + _skyboxPipeline = std::make_unique(_geometryRenderPass); _gridPipeline = std::make_unique(_geometryRenderPass); _drawerPipeline = std::make_unique(_geometryRenderPass); @@ -312,6 +313,10 @@ void PbrRenderer::geometryPass(std::shared_ptr camera) { { _geometryRenderPass->begin(_renderQueue); { + // Render skybox + _skyboxPipeline->render(camera); + + // Render meshes _geometryPipeline->begin(); { //---------- PBR shader ----------// diff --git a/src/atta/graphics/renderers/pbrRenderer.h b/src/atta/graphics/renderers/pbrRenderer.h index fac3de9f..6e6fe17d 100644 --- a/src/atta/graphics/renderers/pbrRenderer.h +++ b/src/atta/graphics/renderers/pbrRenderer.h @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace atta::graphics { @@ -38,6 +39,8 @@ class PbrRenderer final : public Renderer { std::shared_ptr _renderQueue; std::shared_ptr _geometryRenderPass; std::shared_ptr _geometryPipeline; + + std::unique_ptr _skyboxPipeline; std::unique_ptr _drawerPipeline; std::unique_ptr _gridPipeline; std::unique_ptr _selectedPipeline; From fcf919acdc2f355ea4d36dd30f3586b668056763 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 08:56:26 +0200 Subject: [PATCH 06/39] Refactor: Implement renderQuad/renderCube in pipeline base class --- src/atta/graphics/apis/openGL/pipeline.cpp | 18 ------------------ src/atta/graphics/apis/openGL/pipeline.h | 3 --- src/atta/graphics/apis/vulkan/pipeline.cpp | 3 --- src/atta/graphics/apis/vulkan/pipeline.h | 3 --- src/atta/graphics/pipeline.cpp | 7 +++---- src/atta/graphics/pipeline.h | 2 +- 6 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/atta/graphics/apis/openGL/pipeline.cpp b/src/atta/graphics/apis/openGL/pipeline.cpp index c48b639e..3b55c35d 100644 --- a/src/atta/graphics/apis/openGL/pipeline.cpp +++ b/src/atta/graphics/apis/openGL/pipeline.cpp @@ -49,24 +49,6 @@ void Pipeline::renderMesh(StringId meshSid, size_t numVertices) { LOG_WARN("gfx::gl::Pipeline", "Could not render mesh [w]$0[], mesh not found", meshSid); } -void Pipeline::renderQuad() { - renderMesh("atta::gfx::quad"); - if (_renderPass->getFramebuffer()->hasDepthAttachment()) - glClear(GL_DEPTH_BUFFER_BIT); -} - -void Pipeline::renderQuad3() { - renderMesh("atta::gfx::quad3"); - if (_renderPass->getFramebuffer()->hasDepthAttachment()) - glClear(GL_DEPTH_BUFFER_BIT); -} - -void Pipeline::renderCube() { - renderMesh("atta::gfx::cube"); - if (_renderPass->getFramebuffer()->hasDepthAttachment()) - glClear(GL_DEPTH_BUFFER_BIT); -} - void* Pipeline::getImGuiTexture() const { return reinterpret_cast(std::static_pointer_cast(_renderPass->getFramebuffer()->getImage(0))->getImGuiImage()); } diff --git a/src/atta/graphics/apis/openGL/pipeline.h b/src/atta/graphics/apis/openGL/pipeline.h index a534109f..d5541dbb 100644 --- a/src/atta/graphics/apis/openGL/pipeline.h +++ b/src/atta/graphics/apis/openGL/pipeline.h @@ -23,9 +23,6 @@ class Pipeline final : public gfx::Pipeline { void resize(uint32_t width, uint32_t height) override; void renderMesh(StringId meshSid, size_t numVertices = 0) override; - void renderQuad() override; - void renderQuad3() override; - void renderCube() override; void* getImGuiTexture() const override; diff --git a/src/atta/graphics/apis/vulkan/pipeline.cpp b/src/atta/graphics/apis/vulkan/pipeline.cpp index 93aeaaf1..a14060e3 100644 --- a/src/atta/graphics/apis/vulkan/pipeline.cpp +++ b/src/atta/graphics/apis/vulkan/pipeline.cpp @@ -279,9 +279,6 @@ void Pipeline::renderMesh(StringId meshSid, size_t numVertices) { } else LOG_WARN("gfx::vk::Pipeline", "Could not render mesh [w]$0[], mesh not found", meshSid); } -void Pipeline::renderQuad() { renderMesh("atta::gfx::quad"); } -void Pipeline::renderQuad3() { renderMesh("atta::gfx::quad3"); } -void Pipeline::renderCube() { renderMesh("atta::gfx::cube"); } void* Pipeline::getImGuiTexture() const { return reinterpret_cast(std::static_pointer_cast(_framebuffer->getImage(0))->getImGuiImage()); diff --git a/src/atta/graphics/apis/vulkan/pipeline.h b/src/atta/graphics/apis/vulkan/pipeline.h index 551e6fe9..186540ac 100644 --- a/src/atta/graphics/apis/vulkan/pipeline.h +++ b/src/atta/graphics/apis/vulkan/pipeline.h @@ -32,9 +32,6 @@ class Pipeline final : public gfx::Pipeline { void resize(uint32_t width, uint32_t height) override; void renderMesh(StringId meshSid, size_t numVertices = 0) override; - void renderQuad() override; - void renderQuad3() override; - void renderCube() override; void* getImGuiTexture() const override; diff --git a/src/atta/graphics/pipeline.cpp b/src/atta/graphics/pipeline.cpp index 2bb28854..cbec2b2a 100644 --- a/src/atta/graphics/pipeline.cpp +++ b/src/atta/graphics/pipeline.cpp @@ -45,10 +45,9 @@ void Pipeline::setMat4(const char* name, mat4 m) { _shader->setMat4(name, m); } void Pipeline::setImageGroup(std::string name) { setImageGroup(name.c_str()); } void Pipeline::setImageGroup(StringId name) { setImageGroup(name.getString().c_str()); } -void Pipeline::renderMesh(StringId meshSid, size_t numVertices) { LOG_WARN("Pipeline", "[w]renderMesh[] was not implemented yet"); } -void Pipeline::renderQuad() { LOG_WARN("Pipeline", "[w]renderQuad[] was not implemented yet"); } -void Pipeline::renderQuad3() { LOG_WARN("Pipeline", "[w]renderQuad3[] was not implemented yet"); } -void Pipeline::renderCube() { LOG_WARN("Pipeline", "[w]renderCube[] was not implemented yet"); } +void Pipeline::renderQuad() { renderMesh("atta::gfx::quad"); } +void Pipeline::renderQuad3() { renderMesh("atta::gfx::quad3"); } +void Pipeline::renderCube() { renderMesh("atta::gfx::cube"); } std::shared_ptr Pipeline::getShader() const { return _shader; }; std::shared_ptr Pipeline::getRenderPass() const { return _renderPass; }; diff --git a/src/atta/graphics/pipeline.h b/src/atta/graphics/pipeline.h index e57ace29..31af74ca 100644 --- a/src/atta/graphics/pipeline.h +++ b/src/atta/graphics/pipeline.h @@ -77,7 +77,7 @@ class Pipeline { * @note If numVertices is set to zero, all vertices are rendered * @warning numVertices should only be used if there is no index buffer */ - virtual void renderMesh(StringId meshSid, size_t numVertices = 0); + virtual void renderMesh(StringId meshSid, size_t numVertices = 0) = 0; virtual void renderQuad(); virtual void renderQuad3(); virtual void renderCube(); From ace010d31ac1ac1db865c3f61fd6a818b600d209 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 08:57:18 +0200 Subject: [PATCH 07/39] Refactor: Remove quad shader --- resources/shaders/quad/shader.frag | 11 ----------- resources/shaders/quad/shader.vert | 11 ----------- 2 files changed, 22 deletions(-) delete mode 100644 resources/shaders/quad/shader.frag delete mode 100644 resources/shaders/quad/shader.vert diff --git a/resources/shaders/quad/shader.frag b/resources/shaders/quad/shader.frag deleted file mode 100644 index 0e1b8d79..00000000 --- a/resources/shaders/quad/shader.frag +++ /dev/null @@ -1,11 +0,0 @@ -#version 300 es -precision mediump float; -out vec4 FragColor; -in vec2 TexCoords; - -uniform sampler2D screenTexture; - -void main() -{ - FragColor = texture(screenTexture, TexCoords); -} diff --git a/resources/shaders/quad/shader.vert b/resources/shaders/quad/shader.vert deleted file mode 100644 index d6c190a1..00000000 --- a/resources/shaders/quad/shader.vert +++ /dev/null @@ -1,11 +0,0 @@ -#version 300 es -in vec2 aPos; -in vec2 aTexCoords; - -out vec2 TexCoords; - -void main() -{ - gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); - TexCoords = aTexCoords; -} From 5b77389dc085530b22bac51a95fb6236a7af3e4c Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 08:58:33 +0200 Subject: [PATCH 08/39] Chore: Move grid shader to common folder --- resources/shaders/{grid => common}/grid.asl | 0 src/atta/graphics/renderers/common/gridPipeline.cpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename resources/shaders/{grid => common}/grid.asl (100%) diff --git a/resources/shaders/grid/grid.asl b/resources/shaders/common/grid.asl similarity index 100% rename from resources/shaders/grid/grid.asl rename to resources/shaders/common/grid.asl diff --git a/src/atta/graphics/renderers/common/gridPipeline.cpp b/src/atta/graphics/renderers/common/gridPipeline.cpp index fbc6a03b..d14f6063 100644 --- a/src/atta/graphics/renderers/common/gridPipeline.cpp +++ b/src/atta/graphics/renderers/common/gridPipeline.cpp @@ -14,7 +14,7 @@ size_t GridPipeline::_gridId = 0; GridPipeline::GridPipeline(std::shared_ptr renderPass) : _numLines(0) { Pipeline::CreateInfo pipelineInfo{}; - pipelineInfo.shader = gfx::create("shaders/grid/grid.asl"); + pipelineInfo.shader = gfx::create("shaders/common/grid.asl"); pipelineInfo.renderPass = renderPass; pipelineInfo.primitive = Pipeline::Primitive::LINE; pipelineInfo.debugName = StringId("Grid Pipeline"); From a9c4cadf948f11dcaeba772e678a2cf6931a64d8 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 09:00:09 +0200 Subject: [PATCH 09/39] Chore: Move drawer shaders to common folder --- resources/shaders/{drawer/line.asl => common/drawerLine.asl} | 0 .../shaders/{drawer/point.asl => common/drawerPoint.asl} | 0 src/atta/graphics/renderers/common/drawerPipeline.cpp | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename resources/shaders/{drawer/line.asl => common/drawerLine.asl} (100%) rename resources/shaders/{drawer/point.asl => common/drawerPoint.asl} (100%) diff --git a/resources/shaders/drawer/line.asl b/resources/shaders/common/drawerLine.asl similarity index 100% rename from resources/shaders/drawer/line.asl rename to resources/shaders/common/drawerLine.asl diff --git a/resources/shaders/drawer/point.asl b/resources/shaders/common/drawerPoint.asl similarity index 100% rename from resources/shaders/drawer/point.asl rename to resources/shaders/common/drawerPoint.asl diff --git a/src/atta/graphics/renderers/common/drawerPipeline.cpp b/src/atta/graphics/renderers/common/drawerPipeline.cpp index 2c2bd67f..c15e74b2 100644 --- a/src/atta/graphics/renderers/common/drawerPipeline.cpp +++ b/src/atta/graphics/renderers/common/drawerPipeline.cpp @@ -14,7 +14,7 @@ DrawerPipeline::DrawerPipeline(std::shared_ptr renderPass) { //---------- Create line pipeline ----------// { Pipeline::CreateInfo pipelineInfo{}; - pipelineInfo.shader = graphics::create("shaders/drawer/line.asl"); + pipelineInfo.shader = graphics::create("shaders/common/drawerLine.asl"); pipelineInfo.renderPass = renderPass; pipelineInfo.primitive = Pipeline::Primitive::LINE; pipelineInfo.debugName = StringId("Drawer Line Pipeline"); @@ -24,7 +24,7 @@ DrawerPipeline::DrawerPipeline(std::shared_ptr renderPass) { //---------- Create point pipeline ----------// { Pipeline::CreateInfo pipelineInfo{}; - pipelineInfo.shader = graphics::create("shaders/drawer/point.asl"); + pipelineInfo.shader = graphics::create("shaders/common/drawerPoint.asl"); pipelineInfo.renderPass = renderPass; pipelineInfo.primitive = Pipeline::Primitive::POINT; pipelineInfo.debugName = StringId("Drawer Point Pipeline"); From e205e8a4f626aebf765869901482a0f22b50d597 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 09:06:01 +0200 Subject: [PATCH 10/39] Chore: Remove old entity click shaders --- resources/shaders/compute/entityClick.frag | 10 ---------- resources/shaders/compute/entityClick.vert | 13 ------------- 2 files changed, 23 deletions(-) delete mode 100644 resources/shaders/compute/entityClick.frag delete mode 100644 resources/shaders/compute/entityClick.vert diff --git a/resources/shaders/compute/entityClick.frag b/resources/shaders/compute/entityClick.frag deleted file mode 100644 index 858f22ae..00000000 --- a/resources/shaders/compute/entityClick.frag +++ /dev/null @@ -1,10 +0,0 @@ -#version 300 es -precision mediump float; -uniform int entityId; - -out int FragColor; - -void main() -{ - FragColor = entityId; -} diff --git a/resources/shaders/compute/entityClick.vert b/resources/shaders/compute/entityClick.vert deleted file mode 100644 index 1e7401e9..00000000 --- a/resources/shaders/compute/entityClick.vert +++ /dev/null @@ -1,13 +0,0 @@ -#version 300 es -in vec3 inPosition; -in vec3 inNormal; -in vec2 inTexCoord; - -uniform mat4 projection; -uniform mat4 view; -uniform mat4 model; - -void main() -{ - gl_Position = projection * view * model * vec4(inPosition, 1.0f); -} From a1ecf6571fabb7c449ada6eca9a593c6dbc87b2a Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 10:22:33 +0200 Subject: [PATCH 11/39] Feat: EquiToCubemap compute shader Feat: Create default white/black/pink gfx::Image --- src/atta/graphics/CMakeLists.txt | 1 + src/atta/graphics/apis/vulkan/pipeline.cpp | 2 +- src/atta/graphics/compute/equiToCubemap.cpp | 76 ++++++++++++++++++ src/atta/graphics/compute/equiToCubemap.h | 40 ++++++++++ src/atta/graphics/manager.cpp | 85 +++++++++++++++++---- src/atta/graphics/manager.h | 6 ++ 6 files changed, 193 insertions(+), 17 deletions(-) create mode 100644 src/atta/graphics/compute/equiToCubemap.cpp create mode 100644 src/atta/graphics/compute/equiToCubemap.h diff --git a/src/atta/graphics/CMakeLists.txt b/src/atta/graphics/CMakeLists.txt index 1ea0480f..050a34eb 100644 --- a/src/atta/graphics/CMakeLists.txt +++ b/src/atta/graphics/CMakeLists.txt @@ -18,6 +18,7 @@ set(ATTA_GRAPHICS_MODULE_SOURCE cameras/orthographicCamera.cpp compute/entityClick.cpp + compute/equiToCubemap.cpp renderers/common/drawerPipeline.cpp renderers/common/gridPipeline.cpp diff --git a/src/atta/graphics/apis/vulkan/pipeline.cpp b/src/atta/graphics/apis/vulkan/pipeline.cpp index a14060e3..a243de06 100644 --- a/src/atta/graphics/apis/vulkan/pipeline.cpp +++ b/src/atta/graphics/apis/vulkan/pipeline.cpp @@ -340,7 +340,7 @@ void Pipeline::updateImageGroup(std::string name, ImageGroup imageGroup) { imageGroupInfo.descriptorSet->update(binding++, image); else { // Bind pink - std::shared_ptr image = std::dynamic_pointer_cast(gfx::Manager::getInstance().getImages().at("textures/pink.png")); + std::shared_ptr image = std::dynamic_pointer_cast(gfx::Manager::getInstance().getImages().at("atta::gfx::pink")); imageGroupInfo.descriptorSet->update(binding++, image); } } diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp new file mode 100644 index 00000000..dd29e40a --- /dev/null +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -0,0 +1,76 @@ +//-------------------------------------------------- +// Atta Graphics Module +// equiToCubemap.cpp +// Date: 2025-04-06 +// By Breno Cunha Queiroz +//-------------------------------------------------- +#include +#include +#include + +namespace atta::graphics { + +EquiToCubemap::EquiToCubemap() : _width(512), _height(512) { + // Render Queue + _renderQueue = graphics::create(); + + // Framebuffer + Framebuffer::CreateInfo framebufferInfo{}; + framebufferInfo.attachments.push_back({Image::Format::RGBA}); + framebufferInfo.width = _width; + framebufferInfo.height = _height; + framebufferInfo.clearColor = {1, 0, 0, 1}; + framebufferInfo.debugName = StringId("EquiToCubemap Framebuffer"); + std::shared_ptr framebuffer = graphics::create(framebufferInfo); + + // Render Pass + RenderPass::CreateInfo renderPassInfo{}; + renderPassInfo.framebuffer = framebuffer; + renderPassInfo.debugName = StringId("EquiToCubemap Render Pass"); + _renderPass = graphics::create(renderPassInfo); + + //---------- Graphics pipeline ----------// + // Vertex input layout + Pipeline::CreateInfo pipelineInfo{}; + pipelineInfo.shader = graphics::create("shaders/compute/equiToCubemap.asl"); + pipelineInfo.renderPass = _renderPass; + renderPassInfo.debugName = StringId("EquiToCubemap Pipeline"); + _pipeline = graphics::create(pipelineInfo); +} + +std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { + LOG_VERBOSE("gfx::EquiToCubemap", "Create cubemap from image [w]$0[]", imageSid); + + res::Image* imageRes = resource::get(imageSid); + if (imageRes == nullptr) { + LOG_ERROR("gfx::EquiToCubemap", "Could not create cubemap, image resource [w]$0[] does not exist", imageSid); + return nullptr; + } + + gfx::Image::CreateInfo imageInfo{}; + imageInfo.format = Image::Format::RGBA; + imageInfo.width = _width; + imageInfo.height = _height; + imageInfo.isCubemap = true; + imageInfo.debugName = imageSid.getString() + " Cubemap"; + std::shared_ptr cubemapImage = nullptr;//graphics::create(imageInfo); + + // Render + //_renderQueue->begin(); + //{ + // _renderPass->begin(_renderQueue); + // { + // _pipeline->begin(); + // { + // // TODO + // } + // _pipeline->end(); + // } + // _renderPass->end(); + //} + //_renderQueue->end(); + + return cubemapImage; +} + +} // namespace atta::graphics diff --git a/src/atta/graphics/compute/equiToCubemap.h b/src/atta/graphics/compute/equiToCubemap.h new file mode 100644 index 00000000..a27e35b4 --- /dev/null +++ b/src/atta/graphics/compute/equiToCubemap.h @@ -0,0 +1,40 @@ +//-------------------------------------------------- +// Atta Graphics Module +// equiToCubemap.h +// Date: 2025-04-06 +// By Breno Cunha Queiroz +//-------------------------------------------------- +#ifndef ATTA_GRAPHICS_COMPUTE_EQUI_TO_CUBEMAP_H +#define ATTA_GRAPHICS_COMPUTE_EQUI_TO_CUBEMAP_H + +#include +#include +#include + +namespace atta::graphics { + +class EquiToCubemap { + public: + EquiToCubemap(); + + /** + * @brief Create a cubemap image from an equirectangular image + * + * @param textureSid The name image resource to be created from + * @param rotationMatrix The rotation matrix to be applied to the cubemap + */ + std::shared_ptr createCubemap(StringId imageSid); + + private: + // Cubemap face width/height + uint32_t _width; + uint32_t _height; + + std::shared_ptr _renderQueue; + std::shared_ptr _renderPass; + std::shared_ptr _pipeline; +}; + +} // namespace atta::graphics + +#endif // ATTA_GRAPHICS_COMPUTE_EQUI_TO_CUBEMAP_H diff --git a/src/atta/graphics/manager.cpp b/src/atta/graphics/manager.cpp index 4a347fc3..9963d8bd 100644 --- a/src/atta/graphics/manager.cpp +++ b/src/atta/graphics/manager.cpp @@ -74,6 +74,15 @@ void Manager::startUpImpl() { _graphicsAPI = std::static_pointer_cast(std::make_shared(_window)); _graphicsAPI->startUp(); + //----- Create default images/meshes -----// + // Create images (e.g. white, black, pink) + createDefaultImages(); + // Create meshes (e.g. quad, quad3, cube) + createDefaultMeshes(); + + //----- Create shaders -----// + _equiToCubemap = std::make_unique(); + //----- Resource sync -----// event::subscribe(BIND_EVENT_FUNC(Manager::onMeshLoadEvent)); event::subscribe(BIND_EVENT_FUNC(Manager::onMeshUpdateEvent)); @@ -92,6 +101,7 @@ void Manager::shutDownImpl() { _graphicsAPI->waitDevice(); + _equiToCubemap.reset(); _meshes.clear(); _images.clear(); _graphicsAPI->shutDown(); @@ -179,16 +189,44 @@ gfx::Image::Format Manager::convertFormat(res::Image::Format format) const { return gfx::Image::Format::NONE; } -void Manager::syncResources() { - _meshes.clear(); - _images.clear(); - // Initialize meshes already loaded - for (auto meshSid : resource::getResources()) - createMesh(meshSid); - // Initialize textures already loaded - for (auto imgSid : resource::getResources()) - createImage(imgSid); +void Manager::createDefaultImages() { + //----- Create basic images -----// + // White + { + static uint8_t white[] = {255, 255, 255, 255}; + Image::CreateInfo info{}; + info.format = Image::Format::RGBA; + info.width = 1; + info.height = 1; + info.data = white; + info.debugName = "White"; + _images["atta::gfx::white"] = create(info); + } + // Black + { + static uint8_t black[] = {0, 0, 0, 255}; + Image::CreateInfo info{}; + info.format = Image::Format::RGBA; + info.width = 1; + info.height = 1; + info.data = black; + info.debugName = "Black"; + _images["atta::gfx::black"] = create(info); + } + // Pink + { + static uint8_t pink[] = {255, 0, 255, 255}; + Image::CreateInfo info{}; + info.format = Image::Format::RGBA; + info.width = 1; + info.height = 1; + info.data = pink; + info.debugName = "Pink"; + _images["atta::gfx::pink"] = create(info); + } +} +void Manager::createDefaultMeshes() { //----- Create basic meshes -----// // Quad { @@ -312,6 +350,15 @@ void Manager::syncResources() { } } +void Manager::syncResources() { + // Initialize meshes already loaded + for (auto meshSid : resource::getResources()) + createMesh(meshSid); + // Initialize textures already loaded + for (auto imgSid : resource::getResources()) + createImage(imgSid); +} + void Manager::onMeshLoadEvent(event::Event& event) { event::MeshLoad& e = reinterpret_cast(event); createMesh(e.sid); @@ -413,13 +460,19 @@ void Manager::createImage(StringId sid) { if (image == nullptr) LOG_WARN("gfx::Manager", "Could not initialize gfx::Image from [w]$0[]", sid); - Image::CreateInfo info{}; - info.width = image->getWidth(); - info.height = image->getHeight(); - info.data = image->getData(); - info.format = convertFormat(image->getFormat()); - info.debugName = sid; - _images[sid] = create(info); + if (sid.getString().find(".hdr") != std::string::npos) { + // Create cubemap image + _images[sid] = _equiToCubemap->createCubemap(sid); + } else { + // Create 2D image + Image::CreateInfo info{}; + info.width = image->getWidth(); + info.height = image->getHeight(); + info.data = image->getData(); + info.format = convertFormat(image->getFormat()); + info.debugName = sid; + _images[sid] = create(info); + } } const std::unordered_map>& Manager::getMeshes() const { return _meshes; } diff --git a/src/atta/graphics/manager.h b/src/atta/graphics/manager.h index 85cbf44b..3c79849d 100644 --- a/src/atta/graphics/manager.h +++ b/src/atta/graphics/manager.h @@ -8,6 +8,7 @@ #define ATTA_GRAPHICS_GRAPHICS_MANAGER_H #include +#include #include #include #include @@ -72,6 +73,8 @@ class Manager final { gfx::Image::Format convertFormat(res::Image::Format format) const; // Handle resources + void createDefaultImages(); + void createDefaultMeshes(); void syncResources(); void onMeshLoadEvent(event::Event& event); void onMeshUpdateEvent(event::Event& event); @@ -88,6 +91,9 @@ class Manager final { GraphicsAPI::Type _desiredGraphicsAPI; std::shared_ptr _graphicsAPI; + // Compute shaders + std::unique_ptr _equiToCubemap; ///< Used to convert equirectangular res::Image to cubemap gfx::Image + // Resource binding std::unordered_map> _meshes; std::unordered_map> _images; From 217e6244eca79890622f8e5b3eaa0d3293674fdf Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 10:33:22 +0200 Subject: [PATCH 12/39] Fix: Vulkan cubemap image creation --- src/atta/graphics/apis/vulkan/image.cpp | 20 +++++++++++++------- src/atta/graphics/compute/equiToCubemap.cpp | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/atta/graphics/apis/vulkan/image.cpp b/src/atta/graphics/apis/vulkan/image.cpp index 42cdfa68..0a1c5b36 100644 --- a/src/atta/graphics/apis/vulkan/image.cpp +++ b/src/atta/graphics/apis/vulkan/image.cpp @@ -310,7 +310,14 @@ void Image::createImage() { info.imageType = VK_IMAGE_TYPE_2D; info.extent = {_width, _height, 1}; info.mipLevels = 1; - info.arrayLayers = 1; + // If this is a cubemap, use 6 layers, else 1. + if (_isCubemap) { + info.arrayLayers = 6; + info.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + } else { + info.arrayLayers = 1; + info.flags = 0; + } info.format = convertFormat(_supportedFormat); info.tiling = VK_IMAGE_TILING_OPTIMAL; info.initialLayout = _layout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -321,7 +328,6 @@ void Image::createImage() { info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; info.samples = VK_SAMPLE_COUNT_1_BIT; - info.flags = 0; if (vkCreateImage(_device->getHandle(), &info, nullptr, &_image) != VK_SUCCESS) LOG_ERROR("gfx::vk::Image", "Failed to create image"); } @@ -331,15 +337,15 @@ void Image::createImageView() { info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; info.image = _image; info.format = convertFormat(_supportedFormat); + info.subresourceRange.aspectMask = convertAspectFlags(_supportedFormat); + info.subresourceRange.baseMipLevel = 0; + info.subresourceRange.levelCount = _mipLevels; + info.subresourceRange.baseArrayLayer = 0; if (_isCubemap) { info.viewType = VK_IMAGE_VIEW_TYPE_CUBE; info.subresourceRange.layerCount = 6; } else { info.viewType = VK_IMAGE_VIEW_TYPE_2D; - info.subresourceRange.aspectMask = convertAspectFlags(_supportedFormat); - info.subresourceRange.baseMipLevel = 0; - info.subresourceRange.levelCount = _mipLevels; - info.subresourceRange.baseArrayLayer = 0; info.subresourceRange.layerCount = 1; } @@ -463,7 +469,7 @@ void Image::transitionLayout(VkCommandBuffer commandBuffer, VkImageLayout newLay barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.layerCount = _isCubemap ? 6 : 1; VkPipelineStageFlags sourceStage; VkPipelineStageFlags destinationStage; diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index dd29e40a..53902d7b 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -53,7 +53,7 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { imageInfo.height = _height; imageInfo.isCubemap = true; imageInfo.debugName = imageSid.getString() + " Cubemap"; - std::shared_ptr cubemapImage = nullptr;//graphics::create(imageInfo); + std::shared_ptr cubemapImage = graphics::create(imageInfo); // Render //_renderQueue->begin(); From e458843e56354005053f4f6a04c00e864330a75a Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 10:35:44 +0200 Subject: [PATCH 13/39] Fix: Do not allow to use HDR images in materials --- src/atta/ui/manager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/atta/ui/manager.cpp b/src/atta/ui/manager.cpp index 0dd2753d..99b3fcad 100644 --- a/src/atta/ui/manager.cpp +++ b/src/atta/ui/manager.cpp @@ -4,8 +4,8 @@ // Date: 2021-09-28 // By Breno Cunha Queiroz //-------------------------------------------------- -#include #include +#include #include #include #include @@ -119,6 +119,9 @@ bool renderComboImage(std::string attribute, StringId& image) { if (ImGui::BeginCombo(("##ComboImage" + imguiId).c_str(), selectedName.c_str())) { std::vector rImages = resource::getResources(); for (StringId rImage : rImages) { + // Ignore .hdr images (they are created as cubemaps instead of 2D images) + if (rImage.getString().find(".hdr") != std::string::npos) + continue; std::string imageStr = rImage.getString(); if (imageStr == "") imageStr = "##"; From 883b6c79e3cd3316b8cdb2066b859bcec84878de Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 11:13:53 +0200 Subject: [PATCH 14/39] Feat: Only render skybox if there is an environment light --- .../renderers/common/skyboxPipeline.cpp | 2 +- .../renderers/common/skyboxPipeline.h | 2 +- src/atta/graphics/renderers/pbrRenderer.cpp | 51 ++++++++++--------- src/atta/graphics/renderers/pbrRenderer.h | 2 +- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/atta/graphics/renderers/common/skyboxPipeline.cpp b/src/atta/graphics/renderers/common/skyboxPipeline.cpp index bf5e43e2..5fef1376 100644 --- a/src/atta/graphics/renderers/common/skyboxPipeline.cpp +++ b/src/atta/graphics/renderers/common/skyboxPipeline.cpp @@ -18,7 +18,7 @@ SkyboxPipeline::SkyboxPipeline(std::shared_ptr renderPass) { _pipeline = gfx::create(pipelineInfo); } -void SkyboxPipeline::render(std::shared_ptr camera) { +void SkyboxPipeline::render(std::shared_ptr camera, StringId envImg) { _pipeline->begin(); { _pipeline->setMat4("uProjection", camera->getProj()); diff --git a/src/atta/graphics/renderers/common/skyboxPipeline.h b/src/atta/graphics/renderers/common/skyboxPipeline.h index eac72d07..6a987350 100644 --- a/src/atta/graphics/renderers/common/skyboxPipeline.h +++ b/src/atta/graphics/renderers/common/skyboxPipeline.h @@ -16,7 +16,7 @@ class SkyboxPipeline final { SkyboxPipeline(std::shared_ptr renderPass); /// Render skybox - void render(std::shared_ptr camera); + void render(std::shared_ptr camera, StringId envImg); private: std::shared_ptr _pipeline; diff --git a/src/atta/graphics/renderers/pbrRenderer.cpp b/src/atta/graphics/renderers/pbrRenderer.cpp index 28d5891b..2b7aee96 100644 --- a/src/atta/graphics/renderers/pbrRenderer.cpp +++ b/src/atta/graphics/renderers/pbrRenderer.cpp @@ -24,7 +24,7 @@ namespace atta::graphics { -PbrRenderer::PbrRenderer() : Renderer("PbrRenderer"), _firstRender(true), _wasResized(false), _lastEnvironmentMap(StringId("Not defined")) { +PbrRenderer::PbrRenderer() : Renderer("PbrRenderer"), _firstRender(true), _wasResized(false), _lastEnvironmentImg(StringId()) { // Render Queue _renderQueue = graphics::create(); @@ -144,24 +144,24 @@ void PbrRenderer::render(std::shared_ptr camera) { // brdfLUT(); // Check current envinroment map - // std::vector entities = component::getNoPrototypeView(); - // StringId currEnvironmentMap = StringId("textures/white.png"); // Default environment map is white texture - // for (auto entity : entities) { - // component::EnvironmentLight* el = component::getComponent(entity); - - // if (el) { - // currEnvironmentMap = el->sid; - // break; - // } - //} + std::vector entities = component::getNoPrototypeView(); + StringId currEnvironmentImg{}; + for (auto entity : entities) { + component::EnvironmentLight* el = component::getComponent(entity); + if (el) { + currEnvironmentImg = el->sid; + break; + } + } // Recreate environment map if it is different from last one - // if (currEnvironmentMap != _lastEnvironmentMap) { - // _lastEnvironmentMap = currEnvironmentMap; - // graphics::getGraphicsAPI()->generateCubemap(_lastEnvironmentMap); - // irradianceCubemap(); - // prefilterCubemap(); - //} + if (currEnvironmentImg != _lastEnvironmentImg) { + _lastEnvironmentImg = currEnvironmentImg; + if (_lastEnvironmentImg != StringId()) { + // irradianceCubemap(); + // prefilterCubemap(); + } + } // shadowPass(); geometryPass(camera); @@ -313,8 +313,9 @@ void PbrRenderer::geometryPass(std::shared_ptr camera) { { _geometryRenderPass->begin(_renderQueue); { - // Render skybox - _skyboxPipeline->render(camera); + // Render skybox if there is an environment light + if (_lastEnvironmentImg != StringId()) + _skyboxPipeline->render(camera, _lastEnvironmentImg); // Render meshes _geometryPipeline->begin(); @@ -333,7 +334,7 @@ void PbrRenderer::geometryPass(std::shared_ptr camera) { // _geometryPipeline->setCubemap("prefilterMap", "PbrRenderer::prefilter"); //_geometryPipeline->setImage("brdfLUT", "PbrRenderer::brdfLUT"); - // if (_lastEnvironmentMap != "Not defined"_sid) + // if (_lastEnvironmentImg != "Not defined"_sid) // _geometryPipeline->setInt("numEnvironmentLights", 1); // else { // LOG_WARN("graphics::PbrRenderer", "Number of environment light should always be 1 (white texture if not defined)"); @@ -441,7 +442,7 @@ void PbrRenderer::geometryPass(std::shared_ptr camera) { // shader->setCubemap("prefilterMap", "PbrRenderer::prefilter"); // shader->setImage("brdfLUT", "PbrRenderer::brdfLUT"); - // if (_lastEnvironmentMap != "Not defined"_sid) + // if (_lastEnvironmentImg != "Not defined"_sid) // shader->setInt("numEnvironmentLights", 1); // else { // LOG_WARN("graphics::PbrRenderer", "Number of environment light should always be 1 (white texture if not defined)"); @@ -545,11 +546,11 @@ void PbrRenderer::geometryPass(std::shared_ptr camera) { // } // //---------- Background shader ----------// - // if (_lastEnvironmentMap != "Not defined"_sid) { + // if (_lastEnvironmentImg != "Not defined"_sid) { // _backgroundShader->bind(); // _backgroundShader->setMat4("projection", transpose(camera->getProj())); // _backgroundShader->setMat4("view", transpose(camera->getView())); - // _backgroundShader->setCubemap("environmentMap", _lastEnvironmentMap); + // _backgroundShader->setCubemap("environmentMap", _lastEnvironmentImg); // //_backgroundShader->setCubemap("environmentMap", _omnidirectionalShadowMap); // //_backgroundShader->setCubemap("environmentMap", "PbrRenderer::irradiance"); // _backgroundShader->setMat3("environmentMapOri", _environmentMapOri); @@ -573,7 +574,7 @@ void PbrRenderer::irradianceCubemap() { // info.numMipLevels = 1; // info.func = [&](std::shared_ptr shader, mat4 proj, mat4 view, int face, int mipLevel) { // if (mipLevel == 0 && face == 0) { - // // TODO shader->setCubemap("environmentMap", _lastEnvironmentMap); + // // TODO shader->setCubemap("environmentMap", _lastEnvironmentImg); // shader->setMat4("projection", transpose(proj)); // } // shader->setMat4("view", transpose(view)); @@ -594,7 +595,7 @@ void PbrRenderer::prefilterCubemap() { // info.numMipLevels = 5; // info.func = [&](std::shared_ptr shader, mat4 proj, mat4 view, int face, int mipLevel) { // if (mipLevel == 0 && face == 0) { - // // TODO shader->setCubemap("environmentMap", _lastEnvironmentMap); + // // TODO shader->setCubemap("environmentMap", _lastEnvironmentImg); // shader->setMat4("projection", transpose(proj)); // } diff --git a/src/atta/graphics/renderers/pbrRenderer.h b/src/atta/graphics/renderers/pbrRenderer.h index 6e6fe17d..4234c596 100644 --- a/src/atta/graphics/renderers/pbrRenderer.h +++ b/src/atta/graphics/renderers/pbrRenderer.h @@ -53,7 +53,7 @@ class PbrRenderer final : public Renderer { // Directional light mat3 _directionalLightMatrix; // Environment light - StringId _lastEnvironmentMap; + StringId _lastEnvironmentImg; mat3 _environmentMapOri; // Shadow mapping From 5a2355c26043c61f1f2dff439c19ebb8eea4cd38 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 11:44:30 +0200 Subject: [PATCH 15/39] Feat: Store cubemap images in gfx manager --- resources/shaders/common/skybox.asl | 11 +++--- resources/shaders/compute/equiToCubemap.asl | 2 - src/atta/graphics/apis/vulkan/pipeline.cpp | 2 +- src/atta/graphics/compute/equiToCubemap.cpp | 6 +-- src/atta/graphics/compute/equiToCubemap.h | 3 +- src/atta/graphics/manager.cpp | 41 ++++++++++++++------- src/atta/graphics/manager.h | 5 +++ 7 files changed, 44 insertions(+), 26 deletions(-) diff --git a/resources/shaders/common/skybox.asl b/resources/shaders/common/skybox.asl index 9591edea..ccd31034 100644 --- a/resources/shaders/common/skybox.asl +++ b/resources/shaders/common/skybox.asl @@ -1,19 +1,20 @@ perFrame mat4 uProjection; perFrame mat4 uView; -//perFrame samplerCube uEnvironmentMap; +perFrame samplerCube uEnvironmentMap; perVertex vec3 vTexCoords; vec4 vertex(vec3 iVertex, vec3 iNormal, vec2 iUV) { + vTexCoords = iVertex; // Add small dependency between vertex and normal/uv to avoid compilation error - vTexCoords = (iVertex + 1.0) / 2.0; vTexCoords += iNormal / 10000.0f + vec3(iUV,0) / 10000.0f; - mat4 rotView = mat4(mat3(uView)); // Remove translation from the view matrix + // Remove translation from the view matrix (so the skybox remains centered) + mat4 rotView = mat4(mat3(uView)); return uProjection * rotView * vec4(iVertex, 1.0); } void fragment(out vec4 color) { - //return vec4((vTexCoords + 1.0)/2.0, 1.0); - color = vec4(vTexCoords, 1.0); + // Sample the cubemap using the normalized direction + color = texture(uEnvironmentMap, normalize(vTexCoords)); } diff --git a/resources/shaders/compute/equiToCubemap.asl b/resources/shaders/compute/equiToCubemap.asl index 056e6358..73e4420c 100644 --- a/resources/shaders/compute/equiToCubemap.asl +++ b/resources/shaders/compute/equiToCubemap.asl @@ -4,8 +4,6 @@ perFrame sampler2D equirectangularMap; perVertex vec3 localPos; -vec2 sampleSphericalMap(vec3 v); - vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { localPos = iPos; return projection * view * vec4(localPos, 1.0); diff --git a/src/atta/graphics/apis/vulkan/pipeline.cpp b/src/atta/graphics/apis/vulkan/pipeline.cpp index a243de06..bb57ceec 100644 --- a/src/atta/graphics/apis/vulkan/pipeline.cpp +++ b/src/atta/graphics/apis/vulkan/pipeline.cpp @@ -340,7 +340,7 @@ void Pipeline::updateImageGroup(std::string name, ImageGroup imageGroup) { imageGroupInfo.descriptorSet->update(binding++, image); else { // Bind pink - std::shared_ptr image = std::dynamic_pointer_cast(gfx::Manager::getInstance().getImages().at("atta::gfx::pink")); + std::shared_ptr image = std::dynamic_pointer_cast(gfx::Manager::getInstance().getImage("atta::gfx::pink")); imageGroupInfo.descriptorSet->update(binding++, image); } } diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index 53902d7b..8b658a0f 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -41,9 +41,9 @@ EquiToCubemap::EquiToCubemap() : _width(512), _height(512) { std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { LOG_VERBOSE("gfx::EquiToCubemap", "Create cubemap from image [w]$0[]", imageSid); - res::Image* imageRes = resource::get(imageSid); - if (imageRes == nullptr) { - LOG_ERROR("gfx::EquiToCubemap", "Could not create cubemap, image resource [w]$0[] does not exist", imageSid); + std::shared_ptr image = gfx::Manager::getInstance().getImage(imageSid); + if (image == nullptr) { + LOG_ERROR("gfx::EquiToCubemap", "Could not create cubemap, image [w]$0[] does not exist", imageSid); return nullptr; } diff --git a/src/atta/graphics/compute/equiToCubemap.h b/src/atta/graphics/compute/equiToCubemap.h index a27e35b4..6ccf2839 100644 --- a/src/atta/graphics/compute/equiToCubemap.h +++ b/src/atta/graphics/compute/equiToCubemap.h @@ -20,8 +20,7 @@ class EquiToCubemap { /** * @brief Create a cubemap image from an equirectangular image * - * @param textureSid The name image resource to be created from - * @param rotationMatrix The rotation matrix to be applied to the cubemap + * @param textureSid The name gfx::Image to be created from */ std::shared_ptr createCubemap(StringId imageSid); diff --git a/src/atta/graphics/manager.cpp b/src/atta/graphics/manager.cpp index 9963d8bd..b0a6217f 100644 --- a/src/atta/graphics/manager.cpp +++ b/src/atta/graphics/manager.cpp @@ -82,6 +82,8 @@ void Manager::startUpImpl() { //----- Create shaders -----// _equiToCubemap = std::make_unique(); + // Create pink default cubemap + _cubemapImages["atta::gfx::pinkCubemap"] = _equiToCubemap->createCubemap("atta::gfx::pink"); //----- Resource sync -----// event::subscribe(BIND_EVENT_FUNC(Manager::onMeshLoadEvent)); @@ -104,6 +106,7 @@ void Manager::shutDownImpl() { _equiToCubemap.reset(); _meshes.clear(); _images.clear(); + _cubemapImages.clear(); _graphicsAPI->shutDown(); _graphicsAPI.reset(); _window.reset(); @@ -460,23 +463,35 @@ void Manager::createImage(StringId sid) { if (image == nullptr) LOG_WARN("gfx::Manager", "Could not initialize gfx::Image from [w]$0[]", sid); - if (sid.getString().find(".hdr") != std::string::npos) { - // Create cubemap image - _images[sid] = _equiToCubemap->createCubemap(sid); - } else { - // Create 2D image - Image::CreateInfo info{}; - info.width = image->getWidth(); - info.height = image->getHeight(); - info.data = image->getData(); - info.format = convertFormat(image->getFormat()); - info.debugName = sid; - _images[sid] = create(info); - } + // Create 2D image + Image::CreateInfo info{}; + info.width = image->getWidth(); + info.height = image->getHeight(); + info.data = image->getData(); + info.format = convertFormat(image->getFormat()); + info.debugName = sid; + _images[sid] = create(info); + + // If it is HDR image, also create cubemap image + if (sid.getString().find(".hdr") != std::string::npos) + _cubemapImages[sid] = _equiToCubemap->createCubemap(sid); } +std::shared_ptr Manager::getMesh(StringId sid) const { + auto it = _meshes.find(sid); + return it != _meshes.end() ? it->second : nullptr; +} +std::shared_ptr Manager::getImage(StringId sid) const { + auto it = _images.find(sid); + return it != _images.end() ? it->second : nullptr; +} +std::shared_ptr Manager::getCubemapImage(StringId sid) const { + auto it = _cubemapImages.find(sid); + return it != _cubemapImages.end() ? it->second : nullptr; +} const std::unordered_map>& Manager::getMeshes() const { return _meshes; } const std::unordered_map>& Manager::getImages() const { return _images; } +const std::unordered_map>& Manager::getCubemapImages() const { return _cubemapImages; } //---------- Register API specific implementations ----------// // For each type, will return the OpenGL, Vulkan, ... implementation based on the current active graphicsAPI diff --git a/src/atta/graphics/manager.h b/src/atta/graphics/manager.h index 3c79849d..2d21ef10 100644 --- a/src/atta/graphics/manager.h +++ b/src/atta/graphics/manager.h @@ -48,8 +48,12 @@ class Manager final { friend std::shared_ptr getWindow(); friend void* getImGuiImage(StringId sid); + std::shared_ptr getMesh(StringId sid) const; + std::shared_ptr getImage(StringId sid) const; + std::shared_ptr getCubemapImage(StringId sid) const; const std::unordered_map>& getMeshes() const; const std::unordered_map>& getImages() const; + const std::unordered_map>& getCubemapImages() const; private: void startUpImpl(); @@ -97,6 +101,7 @@ class Manager final { // Resource binding std::unordered_map> _meshes; std::unordered_map> _images; + std::unordered_map> _cubemapImages; // UI std::function _uiRenderViewportsFunc; From b633ac3f4f7ef1ec5482f21bcb75d7517ea6214a Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 12:36:17 +0200 Subject: [PATCH 16/39] Feat: OpenGL framebuffer set cubemap layer --- src/atta/graphics/apis/openGL/framebuffer.cpp | 36 ++++-- src/atta/graphics/apis/openGL/framebuffer.h | 2 + src/atta/graphics/apis/vulkan/framebuffer.cpp | 4 + src/atta/graphics/apis/vulkan/framebuffer.h | 2 + src/atta/graphics/apis/vulkan/image.cpp | 111 +++++++++--------- src/atta/graphics/compute/equiToCubemap.cpp | 30 +++-- src/atta/graphics/framebuffer.h | 3 + 7 files changed, 109 insertions(+), 79 deletions(-) diff --git a/src/atta/graphics/apis/openGL/framebuffer.cpp b/src/atta/graphics/apis/openGL/framebuffer.cpp index 2dfe6a1f..02aeb720 100644 --- a/src/atta/graphics/apis/openGL/framebuffer.cpp +++ b/src/atta/graphics/apis/openGL/framebuffer.cpp @@ -58,23 +58,16 @@ void Framebuffer::bindAttachments() { std::shared_ptr image = std::dynamic_pointer_cast(_images[i]); bool isColor = ((int)i != _depthAttachmentIndex) && ((int)i != _stencilAttachmentIndex); if ((int)i == _depthAttachmentIndex) { - if (!image->isCubemap()) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, image->getHandle(), 0); - else - glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, image->getHandle(), 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, image->getHandle(), 0); } if ((int)i == _stencilAttachmentIndex) { - if (!image->isCubemap()) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, image->getHandle(), 0); - else - glFramebufferTexture(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, image->getHandle(), 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, image->getHandle(), 0); } - if (isColor) { - if (!image->isCubemap()) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + colorIndex, GL_TEXTURE_2D, image->getHandle(), 0); + if (image->isCubemap()) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + colorIndex, GL_TEXTURE_CUBE_MAP_POSITIVE_X, image->getHandle(), 0); else - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + colorIndex, image->getHandle(), 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + colorIndex, GL_TEXTURE_2D, image->getHandle(), 0); colorIndex++; } } @@ -139,6 +132,25 @@ void Framebuffer::resize(uint32_t width, uint32_t height, bool forceRecreate) { glBindFramebuffer(GL_FRAMEBUFFER, 0); } +void Framebuffer::setLayer(uint32_t layer) { + if (_images.size() != 1 || _colorAttachmentIndex == -1) { + LOG_ERROR( + "gfx::gl::Framebuffer", + "Error when setting layer for framebuffer [w]$0[]. It is only possible to set layer for a framebuffer with one cubemap color attachment", + _debugName); + return; + } + // Bind framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, _id); + + // Update layer + std::shared_ptr image = std::dynamic_pointer_cast(_images[0]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer, image->getHandle(), 0); + + // Unbind framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + //------------------------------------------------// //---------- Atta to OpenGL conversions ----------// //------------------------------------------------// diff --git a/src/atta/graphics/apis/openGL/framebuffer.h b/src/atta/graphics/apis/openGL/framebuffer.h index 7bd3e5b2..30a9a2fc 100644 --- a/src/atta/graphics/apis/openGL/framebuffer.h +++ b/src/atta/graphics/apis/openGL/framebuffer.h @@ -22,6 +22,8 @@ class Framebuffer final : public gfx::Framebuffer { void resize(uint32_t width, uint32_t height, bool forceRecreate = false) override; + void setLayer(uint32_t layer) override; + static GLenum convertDepthAttachmentType(Image::Format format); private: diff --git a/src/atta/graphics/apis/vulkan/framebuffer.cpp b/src/atta/graphics/apis/vulkan/framebuffer.cpp index f4a77ed3..202fe921 100644 --- a/src/atta/graphics/apis/vulkan/framebuffer.cpp +++ b/src/atta/graphics/apis/vulkan/framebuffer.cpp @@ -51,6 +51,10 @@ void Framebuffer::resize(uint32_t width, uint32_t height, bool forceRecreate) { } } +void Framebuffer::setLayer(uint32_t layer) { + // TODO +} + VkFramebuffer Framebuffer::getHandle() const { return _framebuffer; } std::shared_ptr Framebuffer::getDevice() const { return _device; } std::shared_ptr Framebuffer::getRenderPass() const { return _renderPass; } diff --git a/src/atta/graphics/apis/vulkan/framebuffer.h b/src/atta/graphics/apis/vulkan/framebuffer.h index 52ee0814..40f2e042 100644 --- a/src/atta/graphics/apis/vulkan/framebuffer.h +++ b/src/atta/graphics/apis/vulkan/framebuffer.h @@ -26,6 +26,8 @@ class Framebuffer final : public gfx::Framebuffer { void resize(uint32_t width, uint32_t height, bool forceRecreate = false) override; + void setLayer(uint32_t layer) override; + VkFramebuffer getHandle() const; std::shared_ptr getDevice() const; std::shared_ptr getRenderPass() const; diff --git a/src/atta/graphics/apis/vulkan/image.cpp b/src/atta/graphics/apis/vulkan/image.cpp index 0a1c5b36..2aec53b6 100644 --- a/src/atta/graphics/apis/vulkan/image.cpp +++ b/src/atta/graphics/apis/vulkan/image.cpp @@ -33,62 +33,65 @@ Image::Image(const gfx::Image::CreateInfo& info, std::shared_ptr device, Image::~Image() { destroy(); } void Image::write(uint8_t* data) { - uint8_t* finalData = data; - uint32_t finalSize = _width * _height * Image::getPixelSize(_format); - if (_format != _supportedFormat) { - uint32_t co = Image::getNumChannels(_format); // Num channels original - uint32_t cs = Image::getNumChannels(_supportedFormat); // Num channels supported - // Convert data from _format to _supportedFormat - if (_supportedFormat == Image::Format::RGBA) { - _supportedData.resize(_width * _height * cs); - for (size_t y = 0; y < _height; y++) - for (size_t x = 0; x < _width; x++) { - size_t idx = x + y * _width; - size_t idxo = idx * co; - size_t idxs = idx * cs; - for (size_t c = 0; c < cs; c++) - _supportedData[idxs + c] = c < co ? data[idxo + c] : 255; - } - } else if (_supportedFormat == Image::Format::RGBA32F) { - _supportedData.resize(_width * _height * cs * sizeof(float)); - float* oDataF = (float*)data; - float* sDataF = (float*)_supportedData.data(); - for (size_t y = 0; y < _height; y++) - for (size_t x = 0; x < _width; x++) { - size_t idx = x + y * _width; - size_t idxo = idx * co; - size_t idxs = idx * cs; - for (size_t c = 0; c < cs; c++) - sDataF[idxs + c] = c < co ? oDataF[idxo + c] : 1.0f; - } - } else { - LOG_ERROR("gfx::vk::Image", "Unknown format conversion when writing image. Image will not be written"); - return; + if (!_isCubemap) { + uint8_t* finalData = data; + uint32_t finalSize = _width * _height * Image::getPixelSize(_format); + if (_format != _supportedFormat) { + uint32_t co = Image::getNumChannels(_format); // Num channels original + uint32_t cs = Image::getNumChannels(_supportedFormat); // Num channels supported + // Convert data from _format to _supportedFormat + if (_supportedFormat == Image::Format::RGBA) { + _supportedData.resize(_width * _height * cs); + for (size_t y = 0; y < _height; y++) + for (size_t x = 0; x < _width; x++) { + size_t idx = x + y * _width; + size_t idxo = idx * co; + size_t idxs = idx * cs; + for (size_t c = 0; c < cs; c++) + _supportedData[idxs + c] = c < co ? data[idxo + c] : 255; + } + } else if (_supportedFormat == Image::Format::RGBA32F) { + _supportedData.resize(_width * _height * cs * sizeof(float)); + float* oDataF = (float*)data; + float* sDataF = (float*)_supportedData.data(); + for (size_t y = 0; y < _height; y++) + for (size_t x = 0; x < _width; x++) { + size_t idx = x + y * _width; + size_t idxo = idx * co; + size_t idxs = idx * cs; + for (size_t c = 0; c < cs; c++) + sDataF[idxs + c] = c < co ? oDataF[idxo + c] : 1.0f; + } + } else { + LOG_ERROR("gfx::vk::Image", "Unknown format conversion when writing image. Image will not be written"); + return; + } + + finalData = _supportedData.data(); + finalSize = _supportedData.size(); } - finalData = _supportedData.data(); - finalSize = _supportedData.size(); - } - - // Copy data to GPU - std::shared_ptr stagingBuffer = std::make_shared(finalData, finalSize); - VkCommandBuffer commandBuffer = common::getCommandPool()->beginSingleTimeCommands(); - { - transitionLayout(commandBuffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - VkBufferImageCopy region{}; - region.bufferOffset = 0; - region.bufferRowLength = 0; - region.bufferImageHeight = 0; - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.mipLevel = 0; - region.imageSubresource.baseArrayLayer = 0; - region.imageSubresource.layerCount = 1; - region.imageOffset = {0, 0, 0}; - region.imageExtent = {_width, _height, 1}; - vkCmdCopyBufferToImage(commandBuffer, stagingBuffer->getHandle(), _image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); - transitionLayout(commandBuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - } - common::getCommandPool()->endSingleTimeCommands(commandBuffer); + // Copy data to GPU + std::shared_ptr stagingBuffer = std::make_shared(finalData, finalSize); + VkCommandBuffer commandBuffer = common::getCommandPool()->beginSingleTimeCommands(); + { + transitionLayout(commandBuffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = {_width, _height, 1}; + vkCmdCopyBufferToImage(commandBuffer, stagingBuffer->getHandle(), _image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + transitionLayout(commandBuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + common::getCommandPool()->endSingleTimeCommands(commandBuffer); + } else + LOG_WARN("gfx::vk::Image", "Writing to cubemap image is not implemented yet. Image debug name: [w]$0[]", _debugName); } std::vector Image::read(vec2i offset, vec2i size) { diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index 8b658a0f..ca2037d0 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -56,19 +56,23 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { std::shared_ptr cubemapImage = graphics::create(imageInfo); // Render - //_renderQueue->begin(); - //{ - // _renderPass->begin(_renderQueue); - // { - // _pipeline->begin(); - // { - // // TODO - // } - // _pipeline->end(); - // } - // _renderPass->end(); - //} - //_renderQueue->end(); + _renderQueue->begin(); + { + for (size_t i = 0; i < 6; i++) { + _renderPass->getFramebuffer()->setLayer(i); + _renderPass->begin(_renderQueue); + { + _pipeline->begin(); + { + // TODO + _pipeline->renderQuad(); + } + _pipeline->end(); + } + _renderPass->end(); + } + } + _renderQueue->end(); return cubemapImage; } diff --git a/src/atta/graphics/framebuffer.h b/src/atta/graphics/framebuffer.h index ce940b25..72b1ce2c 100644 --- a/src/atta/graphics/framebuffer.h +++ b/src/atta/graphics/framebuffer.h @@ -49,6 +49,9 @@ class Framebuffer { virtual void resize(uint32_t width, uint32_t height, bool forceRecreate = false) = 0; + // When the attachment is a cubemap image, you can use this method set the layer to be used + virtual void setLayer(uint32_t layer) = 0; + std::vector> getImages() const; std::shared_ptr getImage(uint32_t attachment = 0); uint32_t getWidth() const { return _width; } From 5587e5bf88e45c2658d6e46615cd33982e878915 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 13:54:42 +0200 Subject: [PATCH 17/39] Feat: Create vulkan image view for each cubemap layer --- src/atta/graphics/apis/vulkan/image.cpp | 41 ++++++++++++++------ src/atta/graphics/apis/vulkan/image.h | 7 +++- src/atta/graphics/apis/vulkan/renderPass.cpp | 4 +- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/atta/graphics/apis/vulkan/image.cpp b/src/atta/graphics/apis/vulkan/image.cpp index 2aec53b6..428c8047 100644 --- a/src/atta/graphics/apis/vulkan/image.cpp +++ b/src/atta/graphics/apis/vulkan/image.cpp @@ -11,7 +11,6 @@ #include // TODO should not have UI code here -#include "imgui.h" #include namespace atta::graphics::vk { @@ -27,7 +26,7 @@ Image::Image(const gfx::Image::CreateInfo& info, std::shared_ptr device, : gfx::Image(info), _image(image), _imageView(VK_NULL_HANDLE), _sampler(VK_NULL_HANDLE), _memory(VK_NULL_HANDLE), _imGuiDescriptorSet(VK_NULL_HANDLE), _device(device), _destroyImage(false) { _supportedFormat = supportedFormat(_format); - createImageView(); + createImageViews(); } Image::~Image() { destroy(); } @@ -144,7 +143,7 @@ void Image::resize(uint32_t width, uint32_t height, bool forceRecreate) { // Create new handles createImage(); allocMemory(); - createImageView(); + createImageViews(); if (isColorFormat(_supportedFormat)) createSampler(); @@ -169,6 +168,14 @@ VkImage Image::getImageHandle() const { return _image; } VkImageView Image::getImageViewHandle() const { return _imageView; } +VkImageView Image::getCubemapImageViewHandle(uint32_t layer) const { + if (layer >= _cubemapImageViews.size()) { + LOG_ERROR("gfx::vk::Image", "getCubemapImageViewHandle with layer index out of bounds. Image debug name: [w]$0[]", _debugName); + return VK_NULL_HANDLE; + } + return _cubemapImageViews[layer]; +} + VkSampler Image::getSamplerHandle() const { return _sampler; } std::shared_ptr Image::getDevice() const { return _device; } @@ -335,7 +342,7 @@ void Image::createImage() { LOG_ERROR("gfx::vk::Image", "Failed to create image"); } -void Image::createImageView() { +VkImageView Image::createImageView(uint32_t layer, uint32_t layerCount) { VkImageViewCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; info.image = _image; @@ -343,17 +350,24 @@ void Image::createImageView() { info.subresourceRange.aspectMask = convertAspectFlags(_supportedFormat); info.subresourceRange.baseMipLevel = 0; info.subresourceRange.levelCount = _mipLevels; - info.subresourceRange.baseArrayLayer = 0; + info.subresourceRange.baseArrayLayer = layer; + info.viewType = layerCount == 6 ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D; + info.subresourceRange.layerCount = layerCount; + + VkImageView imageView; + if (vkCreateImageView(_device->getHandle(), &info, nullptr, &imageView) != VK_SUCCESS) + LOG_ERROR("gfx::vk::Image", "Failed to create image view for image [w]$0[] (layer $0 layerCount $1)", _debugName, layer, layerCount); + return imageView; +} + +void Image::createImageViews() { if (_isCubemap) { - info.viewType = VK_IMAGE_VIEW_TYPE_CUBE; - info.subresourceRange.layerCount = 6; + _imageView = createImageView(0, 6); + for (uint32_t i = 0; i < 6; i++) + _cubemapImageViews[i] = createImageView(i, 1); } else { - info.viewType = VK_IMAGE_VIEW_TYPE_2D; - info.subresourceRange.layerCount = 1; + _imageView = createImageView(); } - - if (vkCreateImageView(_device->getHandle(), &info, nullptr, &_imageView) != VK_SUCCESS) - LOG_ERROR("gfx::vk::Image", "Failed to create image view"); } void Image::createSampler() { @@ -409,6 +423,9 @@ void Image::destroy() { } if (_imageView != VK_NULL_HANDLE) vkDestroyImageView(_device->getHandle(), _imageView, nullptr); + for (const VkImageView& imageView : _cubemapImageViews) + if (imageView != VK_NULL_HANDLE) + vkDestroyImageView(_device->getHandle(), imageView, nullptr); if (_sampler != VK_NULL_HANDLE) vkDestroySampler(_device->getHandle(), _sampler, nullptr); if (_image != VK_NULL_HANDLE && _destroyImage) diff --git a/src/atta/graphics/apis/vulkan/image.h b/src/atta/graphics/apis/vulkan/image.h index e9a3dfb9..1c954d90 100644 --- a/src/atta/graphics/apis/vulkan/image.h +++ b/src/atta/graphics/apis/vulkan/image.h @@ -46,6 +46,7 @@ class Image final : public gfx::Image { void* getImGuiImage() override; VkImage getImageHandle() const; VkImageView getImageViewHandle() const; + VkImageView getCubemapImageViewHandle(uint32_t layer) const; VkSampler getSamplerHandle() const; std::shared_ptr getDevice() const; @@ -81,7 +82,8 @@ class Image final : public gfx::Image { private: void createImage(); - void createImageView(); + VkImageView createImageView(uint32_t layer = 0, uint32_t layerCount = 1); + void createImageViews(); void createSampler(); void allocMemory(); void destroy(); @@ -97,7 +99,8 @@ class Image final : public gfx::Image { std::vector _supportedData; ///< Converted _data to _supportedFormat VkImage _image; - VkImageView _imageView; + VkImageView _imageView; // Image view for the whole image (all layers) + std::array _cubemapImageViews{}; // Image view for each face of a cubemap image VkSampler _sampler; VkDeviceMemory _memory; VkImageLayout _layout; diff --git a/src/atta/graphics/apis/vulkan/renderPass.cpp b/src/atta/graphics/apis/vulkan/renderPass.cpp index ebc476d4..6d4b4767 100644 --- a/src/atta/graphics/apis/vulkan/renderPass.cpp +++ b/src/atta/graphics/apis/vulkan/renderPass.cpp @@ -135,7 +135,9 @@ void RenderPass::begin(VkCommandBuffer commandBuffer) { } clearValues.push_back(clearColor); } else if (Image::isDepthFormat(format)) { - VkClearValue clearDepth = {1.0f, 0}; + VkClearValue clearDepth = {}; + clearDepth.depthStencil.depth = 1.0f; + clearDepth.depthStencil.stencil = 0; clearValues.push_back(clearDepth); } } From f804d05c4de83ffac0bfed474ae27f8341f80958 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 14:08:01 +0200 Subject: [PATCH 18/39] Feat: Recreate vulkan framebuffer with selected cubemap layer --- src/atta/graphics/apis/vulkan/framebuffer.cpp | 44 ++++++++++++- src/atta/graphics/compute/equiToCubemap.cpp | 62 ++++++++++--------- 2 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/atta/graphics/apis/vulkan/framebuffer.cpp b/src/atta/graphics/apis/vulkan/framebuffer.cpp index 202fe921..f59afa47 100644 --- a/src/atta/graphics/apis/vulkan/framebuffer.cpp +++ b/src/atta/graphics/apis/vulkan/framebuffer.cpp @@ -52,7 +52,49 @@ void Framebuffer::resize(uint32_t width, uint32_t height, bool forceRecreate) { } void Framebuffer::setLayer(uint32_t layer) { - // TODO + // For now, support only framebuffers with a single attachment that is a cubemap + if (_images.size() != 1) { + LOG_ERROR("gfx::vk::Framebuffer", "setLayer is only supported for framebuffers with one attachment, not for framebuffer [w]$0[]", _debugName); + return; + } + + // Get the image and ensure it's a cubemap + std::shared_ptr image = _images[0]; + std::shared_ptr vkImage = std::dynamic_pointer_cast(image); + if (!vkImage->isCubemap()) { + LOG_ERROR("gfx::vk::Framebuffer", "setLayer called on a non-cubemap image for framebuffer [w]$0[]", _debugName); + return; + } + + // Destroy the existing framebuffer if it exists + if (_framebuffer != VK_NULL_HANDLE) { + vkDestroyFramebuffer(_device->getHandle(), _framebuffer, nullptr); + _framebuffer = VK_NULL_HANDLE; + } + + // Retrieve the image view for the desired cubemap face (layer) + VkImageView faceView = vkImage->getCubemapImageViewHandle(layer); + if (faceView == VK_NULL_HANDLE) { + LOG_ERROR("gfx::vk::Framebuffer", "Failed to get cubemap image view for layer %d", layer); + return; + } + + // Create a new framebuffer with this per-face view + std::vector attachments = {faceView}; + + VkFramebufferCreateInfo fbInfo{}; + fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + fbInfo.renderPass = _renderPass->getHandle(); + fbInfo.attachmentCount = static_cast(attachments.size()); + fbInfo.pAttachments = attachments.data(); + fbInfo.width = _width; + fbInfo.height = _height; + fbInfo.layers = 1; + + if (vkCreateFramebuffer(_device->getHandle(), &fbInfo, nullptr, &_framebuffer) != VK_SUCCESS) { + LOG_ERROR("gfx::vk::Framebuffer", "Failed to create framebuffer for cubemap layer %d", layer); + return; + } } VkFramebuffer Framebuffer::getHandle() const { return _framebuffer; } diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index ca2037d0..e07a0c13 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -10,20 +10,42 @@ namespace atta::graphics { -EquiToCubemap::EquiToCubemap() : _width(512), _height(512) { - // Render Queue +EquiToCubemap::EquiToCubemap() : _width(512), _height(512) {} + +std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { + LOG_VERBOSE("gfx::EquiToCubemap", "Create cubemap from image [w]$0[]", imageSid); + + // Get equirectangular image + std::shared_ptr image = gfx::Manager::getInstance().getImage(imageSid); + if (image == nullptr) { + LOG_ERROR("gfx::EquiToCubemap", "Could not create cubemap, image [w]$0[] does not exist", imageSid); + return nullptr; + } + + // Create cubemap image + gfx::Image::CreateInfo imageInfo{}; + imageInfo.format = image->getFormat(); + imageInfo.width = _width; + imageInfo.height = _height; + imageInfo.isCubemap = true; + imageInfo.debugName = imageSid.getString() + " Cubemap"; + std::shared_ptr cubemapImage = graphics::create(imageInfo); + + // Create Render Queue _renderQueue = graphics::create(); - // Framebuffer + // Create Framebuffer + Framebuffer::Attachment attachment{}; + attachment.image = cubemapImage; Framebuffer::CreateInfo framebufferInfo{}; - framebufferInfo.attachments.push_back({Image::Format::RGBA}); + framebufferInfo.attachments.push_back(attachment); framebufferInfo.width = _width; framebufferInfo.height = _height; - framebufferInfo.clearColor = {1, 0, 0, 1}; + framebufferInfo.clearColor = {0, 0, 0, 1}; framebufferInfo.debugName = StringId("EquiToCubemap Framebuffer"); std::shared_ptr framebuffer = graphics::create(framebufferInfo); - // Render Pass + // Create Render Pass RenderPass::CreateInfo renderPassInfo{}; renderPassInfo.framebuffer = framebuffer; renderPassInfo.debugName = StringId("EquiToCubemap Render Pass"); @@ -36,30 +58,12 @@ EquiToCubemap::EquiToCubemap() : _width(512), _height(512) { pipelineInfo.renderPass = _renderPass; renderPassInfo.debugName = StringId("EquiToCubemap Pipeline"); _pipeline = graphics::create(pipelineInfo); -} - -std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { - LOG_VERBOSE("gfx::EquiToCubemap", "Create cubemap from image [w]$0[]", imageSid); - - std::shared_ptr image = gfx::Manager::getInstance().getImage(imageSid); - if (image == nullptr) { - LOG_ERROR("gfx::EquiToCubemap", "Could not create cubemap, image [w]$0[] does not exist", imageSid); - return nullptr; - } - - gfx::Image::CreateInfo imageInfo{}; - imageInfo.format = Image::Format::RGBA; - imageInfo.width = _width; - imageInfo.height = _height; - imageInfo.isCubemap = true; - imageInfo.debugName = imageSid.getString() + " Cubemap"; - std::shared_ptr cubemapImage = graphics::create(imageInfo); // Render - _renderQueue->begin(); - { - for (size_t i = 0; i < 6; i++) { - _renderPass->getFramebuffer()->setLayer(i); + for (size_t i = 0; i < 6; i++) { + framebuffer->setLayer(i); + _renderQueue->begin(); + { _renderPass->begin(_renderQueue); { _pipeline->begin(); @@ -71,8 +75,8 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { } _renderPass->end(); } + _renderQueue->end(); } - _renderQueue->end(); return cubemapImage; } From 5f7315a64d338b103219b70c5e330ebdc9952c14 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 14:41:04 +0200 Subject: [PATCH 19/39] Feat: Set proj and view matrices for equiToCubemap --- resources/shaders/compute/equiToCubemap.asl | 10 ++++---- src/atta/graphics/compute/equiToCubemap.cpp | 27 +++++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/resources/shaders/compute/equiToCubemap.asl b/resources/shaders/compute/equiToCubemap.asl index 73e4420c..85383240 100644 --- a/resources/shaders/compute/equiToCubemap.asl +++ b/resources/shaders/compute/equiToCubemap.asl @@ -1,17 +1,17 @@ -perFrame mat4 projection; -perFrame mat4 view; -perFrame sampler2D equirectangularMap; +perFrame mat4 uProjection; +perFrame mat4 uView; +perFrame sampler2D uEquirectangularMap; perVertex vec3 localPos; vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { localPos = iPos; - return projection * view * vec4(localPos, 1.0); + return uProjection * uView * vec4(localPos, 1.0); } vec4 fragment() { vec2 uv = sampleSphericalMap(normalize(localPos)); - return vec4(texture(equirectangularMap, uv).rgb, 1.0); + return vec4(texture(uEquirectangularMap, uv).rgb, 1.0); } const vec2 invAtan = vec2(0.1591, 0.3183); diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index e07a0c13..c6bcbaf6 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -52,13 +52,34 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { _renderPass = graphics::create(renderPassInfo); //---------- Graphics pipeline ----------// - // Vertex input layout + // Create Pipeline Pipeline::CreateInfo pipelineInfo{}; pipelineInfo.shader = graphics::create("shaders/compute/equiToCubemap.asl"); pipelineInfo.renderPass = _renderPass; renderPassInfo.debugName = StringId("EquiToCubemap Pipeline"); _pipeline = graphics::create(pipelineInfo); + // Create Image Group + _pipeline->createImageGroup(Pipeline::ImageGroupType::PER_DRAW, "imgGroup"); + _pipeline->updateImageGroup("imgGroup", {{"uEquirectangularMap", image}}); + + // Define projection and view matrices + mat4 proj = perspective(M_PI, 1.0f, 0.1f, 10.0f); + std::array views = { + // Positive X face: look toward +X, with Z as up. + lookAt(vec3(0.0f), vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), + // Negative X face: look toward -X, with Z as up. + lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), + // Positive Y face: look toward +Y, with Z as up. + lookAt(vec3(0.0f), vec3(0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), + // Negative Y face: look toward -Y, with Z as up. + lookAt(vec3(0.0f), vec3(0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), + // Positive Z face: since forward (0,0,1) is parallel to global up, choose a different up (e.g. (0,1,0)) + lookAt(vec3(0.0f), vec3(0.0f, 0.0f, 1.0f), vec3(0.0f, 1.0f, 0.0f)), + // Negative Z face: similarly choose (0,1,0) for up. + lookAt(vec3(0.0f), vec3(0.0f, 0.0f, -1.0f), vec3(0.0f, 1.0f, 0.0f)), + }; + // Render for (size_t i = 0; i < 6; i++) { framebuffer->setLayer(i); @@ -68,7 +89,9 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { { _pipeline->begin(); { - // TODO + _pipeline->setMat4("uProjection", proj); + _pipeline->setMat4("uView", views[i]); + _pipeline->setImageGroup("imgGroup"); _pipeline->renderQuad(); } _pipeline->end(); From 6c9194a811b7610fc9929b832389bbd2e2f7551f Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 15:25:32 +0200 Subject: [PATCH 20/39] Fix: Vulkan samplerCube bindings --- resources/shaders/common/skybox.asl | 5 +++-- src/atta/graphics/apis/vulkan/pipeline.cpp | 20 ++++++++------------ src/atta/graphics/compute/equiToCubemap.cpp | 6 +++--- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/resources/shaders/common/skybox.asl b/resources/shaders/common/skybox.asl index ccd31034..7a53e8b5 100644 --- a/resources/shaders/common/skybox.asl +++ b/resources/shaders/common/skybox.asl @@ -1,6 +1,6 @@ perFrame mat4 uProjection; perFrame mat4 uView; -perFrame samplerCube uEnvironmentMap; +perFrame sampler2D uEnvironmentMap; perVertex vec3 vTexCoords; @@ -16,5 +16,6 @@ vec4 vertex(vec3 iVertex, vec3 iNormal, vec2 iUV) { void fragment(out vec4 color) { // Sample the cubemap using the normalized direction - color = texture(uEnvironmentMap, normalize(vTexCoords)); + //color = texture(uEnvironmentMap, normalize(vTexCoords)); + color = vec4(1,0,0,1); // Ensure alpha is 1.0 } diff --git a/src/atta/graphics/apis/vulkan/pipeline.cpp b/src/atta/graphics/apis/vulkan/pipeline.cpp index bb57ceec..d3aef427 100644 --- a/src/atta/graphics/apis/vulkan/pipeline.cpp +++ b/src/atta/graphics/apis/vulkan/pipeline.cpp @@ -129,11 +129,9 @@ Pipeline::Pipeline(const gfx::Pipeline::CreateInfo& info) : gfx::Pipeline(info), std::vector bindings; for (const auto& element : _shader->getPerFrameImageLayout().getElements()) { uint32_t bindingIdx = bindings.size(); - if (element.type == BufferLayout::Element::Type::SAMPLER_2D) + if (element.type == BufferLayout::Element::Type::SAMPLER_2D || element.type == BufferLayout::Element::Type::SAMPLER_CUBE) bindings.push_back( {bindingIdx, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT}); - else if (element.type == BufferLayout::Element::Type::SAMPLER_CUBE) - bindings.push_back({bindingIdx, 1, VK_DESCRIPTOR_TYPE_SAMPLER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT}); } if (bindings.size()) { _perFrameImageDescriptorSetLayout = std::make_shared(bindings); @@ -146,11 +144,9 @@ Pipeline::Pipeline(const gfx::Pipeline::CreateInfo& info) : gfx::Pipeline(info), std::vector bindings; for (const auto& element : _shader->getPerDrawImageLayout().getElements()) { uint32_t bindingIdx = bindings.size(); - if (element.type == BufferLayout::Element::Type::SAMPLER_2D) + if (element.type == BufferLayout::Element::Type::SAMPLER_2D || element.type == BufferLayout::Element::Type::SAMPLER_CUBE) bindings.push_back( {bindingIdx, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT}); - else if (element.type == BufferLayout::Element::Type::SAMPLER_CUBE) - bindings.push_back({bindingIdx, 1, VK_DESCRIPTOR_TYPE_SAMPLER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT}); } if (bindings.size()) { _perDrawImageDescriptorSetLayout = std::make_shared(bindings); @@ -169,8 +165,7 @@ Pipeline::Pipeline(const gfx::Pipeline::CreateInfo& info) : gfx::Pipeline(info), const uint32_t maxSets = 254; // Maximum number of image groups std::vector poolSizes = { {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1}, // Up to 1 uniform buffer - {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 8 * maxSets}, // Up to 8 sampler2D perDraw/perFrame - {VK_DESCRIPTOR_TYPE_SAMPLER, 8 * maxSets}, // Up to 8 samplerCube perDraw/perFrame + {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 8 * maxSets}, // Up to 8 sampler2D/samplerCube perDraw/perFrame }; _descriptorPool = std::make_shared(poolSizes, maxSets); _descriptorSets = std::make_shared(_descriptorPool, _uniformDescriptorSetLayout, _pipelineLayout, 1); @@ -335,13 +330,14 @@ void Pipeline::updateImageGroup(std::string name, ImageGroup imageGroup) { if (imageItem.name == element.name && imageItem.image) image = std::dynamic_pointer_cast(imageItem.image); - // Update descriptor + // Update descriptor: if no image, use a default pink image. if (image != nullptr) imageGroupInfo.descriptorSet->update(binding++, image); else { - // Bind pink - std::shared_ptr image = std::dynamic_pointer_cast(gfx::Manager::getInstance().getImage("atta::gfx::pink")); - imageGroupInfo.descriptorSet->update(binding++, image); + // Set default pink image to this binding + StringId imageSid = element.type == BufferLayout::Element::Type::SAMPLER_2D ? "atta::gfx::pink" : "atta::gfx::pinkCubemap"; + std::shared_ptr defaultImage = std::dynamic_pointer_cast(gfx::Manager::getInstance().getImage(imageSid)); + imageGroupInfo.descriptorSet->update(binding++, defaultImage); } } diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index c6bcbaf6..b9ae68e2 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -60,8 +60,8 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { _pipeline = graphics::create(pipelineInfo); // Create Image Group - _pipeline->createImageGroup(Pipeline::ImageGroupType::PER_DRAW, "imgGroup"); - _pipeline->updateImageGroup("imgGroup", {{"uEquirectangularMap", image}}); + _pipeline->createImageGroup(Pipeline::ImageGroupType::PER_FRAME, "equiImg"); + _pipeline->updateImageGroup("equiImg", {{"uEquirectangularMap", image}}); // Define projection and view matrices mat4 proj = perspective(M_PI, 1.0f, 0.1f, 10.0f); @@ -91,7 +91,7 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { { _pipeline->setMat4("uProjection", proj); _pipeline->setMat4("uView", views[i]); - _pipeline->setImageGroup("imgGroup"); + _pipeline->setImageGroup("equiImg"); _pipeline->renderQuad(); } _pipeline->end(); From 8165ae420abcf0eafd3413e5a9eaf8bb665a93f7 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 6 Apr 2025 15:30:14 +0200 Subject: [PATCH 21/39] Fix: Vulkan default cubemap image binding --- resources/shaders/common/skybox.asl | 5 ++--- src/atta/graphics/apis/vulkan/pipeline.cpp | 11 ++++++++--- src/atta/graphics/manager.cpp | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/resources/shaders/common/skybox.asl b/resources/shaders/common/skybox.asl index 7a53e8b5..ccd31034 100644 --- a/resources/shaders/common/skybox.asl +++ b/resources/shaders/common/skybox.asl @@ -1,6 +1,6 @@ perFrame mat4 uProjection; perFrame mat4 uView; -perFrame sampler2D uEnvironmentMap; +perFrame samplerCube uEnvironmentMap; perVertex vec3 vTexCoords; @@ -16,6 +16,5 @@ vec4 vertex(vec3 iVertex, vec3 iNormal, vec2 iUV) { void fragment(out vec4 color) { // Sample the cubemap using the normalized direction - //color = texture(uEnvironmentMap, normalize(vTexCoords)); - color = vec4(1,0,0,1); // Ensure alpha is 1.0 + color = texture(uEnvironmentMap, normalize(vTexCoords)); } diff --git a/src/atta/graphics/apis/vulkan/pipeline.cpp b/src/atta/graphics/apis/vulkan/pipeline.cpp index d3aef427..8b0feb2d 100644 --- a/src/atta/graphics/apis/vulkan/pipeline.cpp +++ b/src/atta/graphics/apis/vulkan/pipeline.cpp @@ -330,13 +330,18 @@ void Pipeline::updateImageGroup(std::string name, ImageGroup imageGroup) { if (imageItem.name == element.name && imageItem.image) image = std::dynamic_pointer_cast(imageItem.image); - // Update descriptor: if no image, use a default pink image. + // Update descriptor: if no image, use a default pink image if (image != nullptr) imageGroupInfo.descriptorSet->update(binding++, image); else { // Set default pink image to this binding - StringId imageSid = element.type == BufferLayout::Element::Type::SAMPLER_2D ? "atta::gfx::pink" : "atta::gfx::pinkCubemap"; - std::shared_ptr defaultImage = std::dynamic_pointer_cast(gfx::Manager::getInstance().getImage(imageSid)); + std::shared_ptr defaultImage; + if (element.type == BufferLayout::Element::Type::SAMPLER_2D) + defaultImage = std::dynamic_pointer_cast(gfx::Manager::getInstance().getImage("atta::gfx::pink")); + else if (element.type == BufferLayout::Element::Type::SAMPLER_CUBE) + defaultImage = std::dynamic_pointer_cast(gfx::Manager::getInstance().getCubemapImage("atta::gfx::pink")); + else + LOG_ERROR("gfx::vk::Pipeline", "Could not set default image for [w]$0[], unknown image type", element.name); imageGroupInfo.descriptorSet->update(binding++, defaultImage); } } diff --git a/src/atta/graphics/manager.cpp b/src/atta/graphics/manager.cpp index b0a6217f..0631e11b 100644 --- a/src/atta/graphics/manager.cpp +++ b/src/atta/graphics/manager.cpp @@ -83,7 +83,7 @@ void Manager::startUpImpl() { //----- Create shaders -----// _equiToCubemap = std::make_unique(); // Create pink default cubemap - _cubemapImages["atta::gfx::pinkCubemap"] = _equiToCubemap->createCubemap("atta::gfx::pink"); + _cubemapImages["atta::gfx::pink"] = _equiToCubemap->createCubemap("atta::gfx::pink"); //----- Resource sync -----// event::subscribe(BIND_EVENT_FUNC(Manager::onMeshLoadEvent)); From e749f5e49f32794217d36c5074044609b43c7276 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Mon, 7 Apr 2025 07:53:00 +0200 Subject: [PATCH 22/39] Feat: Write data to OpenGL/Vulkan cubemap images Feat: Create default cubemap images Refactor: gfx::Manager keep 2D and cube images together --- src/atta/graphics/apis/openGL/image.cpp | 21 +++- src/atta/graphics/apis/vulkan/image.cpp | 112 +++++++++++------- src/atta/graphics/apis/vulkan/pipeline.cpp | 10 +- src/atta/graphics/compute/equiToCubemap.cpp | 2 +- src/atta/graphics/manager.cpp | 98 ++++++++++++--- src/atta/graphics/manager.h | 3 - .../renderers/common/skyboxPipeline.cpp | 7 ++ 7 files changed, 182 insertions(+), 71 deletions(-) diff --git a/src/atta/graphics/apis/openGL/image.cpp b/src/atta/graphics/apis/openGL/image.cpp index 588a995c..cfac2633 100644 --- a/src/atta/graphics/apis/openGL/image.cpp +++ b/src/atta/graphics/apis/openGL/image.cpp @@ -17,16 +17,25 @@ Image::~Image() { } void Image::write(uint8_t* data) { - if (!_isCubemap) { - GLenum dataType = Image::convertDataType(_format); - GLenum internalFormat = Image::convertInternalFormat(_format); - GLenum format = Image::convertFormat(_format); + GLenum dataType = Image::convertDataType(_format); + GLenum internalFormat = Image::convertInternalFormat(_format); + GLenum format = Image::convertFormat(_format); + if (!_isCubemap) { glBindTexture(GL_TEXTURE_2D, _id); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _width, _height, format, dataType, data); glBindTexture(GL_TEXTURE_2D, 0); - } else - LOG_WARN("gfx::gl::Image", "Writing to cubemap image is not implemented yet. Image debug name: [w]$0[]", _debugName); + } else { + glBindTexture(GL_TEXTURE_CUBE_MAP, _id); + + // Calculate the size (in bytes) of one face + uint32_t faceSize = _width * _height * Image::getPixelSize(_format); + + // Update each of the six faces + for (unsigned int i = 0; i < 6; i++) + glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, _width, _height, format, dataType, data + (i * faceSize)); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + } } std::vector Image::read(vec2i offset, vec2i size) { diff --git a/src/atta/graphics/apis/vulkan/image.cpp b/src/atta/graphics/apis/vulkan/image.cpp index 428c8047..76dbef63 100644 --- a/src/atta/graphics/apis/vulkan/image.cpp +++ b/src/atta/graphics/apis/vulkan/image.cpp @@ -32,49 +32,61 @@ Image::Image(const gfx::Image::CreateInfo& info, std::shared_ptr device, Image::~Image() { destroy(); } void Image::write(uint8_t* data) { - if (!_isCubemap) { - uint8_t* finalData = data; - uint32_t finalSize = _width * _height * Image::getPixelSize(_format); - if (_format != _supportedFormat) { - uint32_t co = Image::getNumChannels(_format); // Num channels original - uint32_t cs = Image::getNumChannels(_supportedFormat); // Num channels supported - // Convert data from _format to _supportedFormat - if (_supportedFormat == Image::Format::RGBA) { - _supportedData.resize(_width * _height * cs); - for (size_t y = 0; y < _height; y++) - for (size_t x = 0; x < _width; x++) { - size_t idx = x + y * _width; - size_t idxo = idx * co; - size_t idxs = idx * cs; + // Determine number of faces: 6 for cubemaps, 1 for 2D textures + uint32_t numFaces = _isCubemap ? 6 : 1; + + uint8_t* finalData = data; + uint32_t finalSize = _width * _height * Image::getPixelSize(_format) * numFaces; + + // If the chosen format is not supported by the GPU, convert data to _supportedFormat + if (_format != _supportedFormat) { + // Get channel counts for original and supported formats + uint32_t co = Image::getNumChannels(_format); // Number of channels in original format + uint32_t cs = Image::getNumChannels(_supportedFormat); // Number of channels in supported format + + // Resize _supportedData to store the converted data + _supportedData.resize(_width * _height * Image::getPixelSize(_supportedFormat) * numFaces); + + // Process each face separately + for (uint32_t face = 0; face < numFaces; face++) { + // Loop over each pixel in the face + for (size_t y = 0; y < _height; y++) { + for (size_t x = 0; x < _width; x++) { + size_t pixelIdx = x + y * _width + face * _width * _height; + + // Compute source and destination indices for this pixel + size_t srcIdx = pixelIdx * Image::getPixelSize(_format); + size_t dstIdx = pixelIdx * Image::getPixelSize(_supportedFormat); + + // If converting to RGBA (8-bit) format + if (_format == Image::Format::RGB && _supportedFormat == Image::Format::RGBA) { for (size_t c = 0; c < cs; c++) - _supportedData[idxs + c] = c < co ? data[idxo + c] : 255; + _supportedData[dstIdx + c] = (c < co ? data[srcIdx + c] : 255); } - } else if (_supportedFormat == Image::Format::RGBA32F) { - _supportedData.resize(_width * _height * cs * sizeof(float)); - float* oDataF = (float*)data; - float* sDataF = (float*)_supportedData.data(); - for (size_t y = 0; y < _height; y++) - for (size_t x = 0; x < _width; x++) { - size_t idx = x + y * _width; - size_t idxo = idx * co; - size_t idxs = idx * cs; + // If converting to RGBA32F (float) format + else if (_format == Image::Format::RGB32F && _supportedFormat == Image::Format::RGBA32F) { + float* oDataF = reinterpret_cast(&data[srcIdx]); + float* sDataF = reinterpret_cast(&_supportedData[dstIdx]); for (size_t c = 0; c < cs; c++) - sDataF[idxs + c] = c < co ? oDataF[idxo + c] : 1.0f; + sDataF[c] = (c < co ? oDataF[c] : 1.0f); + } else { + LOG_ERROR("gfx::vk::Image", "Unknown format conversion when writing image. Image will not be written"); + return; } - } else { - LOG_ERROR("gfx::vk::Image", "Unknown format conversion when writing image. Image will not be written"); - return; + } } - - finalData = _supportedData.data(); - finalSize = _supportedData.size(); } - // Copy data to GPU - std::shared_ptr stagingBuffer = std::make_shared(finalData, finalSize); - VkCommandBuffer commandBuffer = common::getCommandPool()->beginSingleTimeCommands(); - { - transitionLayout(commandBuffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + finalData = _supportedData.data(); + finalSize = (uint32_t)_supportedData.size(); + } + + // Copy finalData to GPU using a staging buffer + std::shared_ptr stagingBuffer = std::make_shared(finalData, finalSize); + VkCommandBuffer commandBuffer = common::getCommandPool()->beginSingleTimeCommands(); + { + transitionLayout(commandBuffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + if (!_isCubemap) { VkBufferImageCopy region{}; region.bufferOffset = 0; region.bufferRowLength = 0; @@ -86,11 +98,27 @@ void Image::write(uint8_t* data) { region.imageOffset = {0, 0, 0}; region.imageExtent = {_width, _height, 1}; vkCmdCopyBufferToImage(commandBuffer, stagingBuffer->getHandle(), _image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); - transitionLayout(commandBuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } else { + // Prepare an array of 6 regions, one for each cubemap face + std::array regions{}; + uint32_t supportedPixelSize = Image::getPixelSize(_supportedFormat); + for (uint32_t i = 0; i < 6; i++) { + regions[i].bufferOffset = i * (_width * _height * supportedPixelSize); + regions[i].bufferRowLength = 0; + regions[i].bufferImageHeight = 0; + regions[i].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + regions[i].imageSubresource.mipLevel = 0; + regions[i].imageSubresource.baseArrayLayer = i; + regions[i].imageSubresource.layerCount = 1; + regions[i].imageOffset = {0, 0, 0}; + regions[i].imageExtent = {_width, _height, 1}; + } + vkCmdCopyBufferToImage(commandBuffer, stagingBuffer->getHandle(), _image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + static_cast(regions.size()), regions.data()); } - common::getCommandPool()->endSingleTimeCommands(commandBuffer); - } else - LOG_WARN("gfx::vk::Image", "Writing to cubemap image is not implemented yet. Image debug name: [w]$0[]", _debugName); + transitionLayout(commandBuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + common::getCommandPool()->endSingleTimeCommands(commandBuffer); } std::vector Image::read(vec2i offset, vec2i size) { @@ -417,8 +445,8 @@ void Image::allocMemory() { void Image::destroy() { vkDeviceWaitIdle(common::getDevice()->getHandle()); if (_imGuiDescriptorSet != VK_NULL_HANDLE) { - // XXX We would need to make sure this happens UI module is shut down, for now it is OK to not remove the texture because it is freed when the - // UI descroptor pool is freed. This will fail if a lot of images are created and destroyed while the UI is running + // XXX We would need to make sure this happens UI module is shut down, for now it is OK to not remove the texture because it is freed when + // the UI descroptor pool is freed. This will fail if a lot of images are created and destroyed while the UI is running // ImGui_ImplVulkan_RemoveTexture(_imGuiDescriptorSet); } if (_imageView != VK_NULL_HANDLE) diff --git a/src/atta/graphics/apis/vulkan/pipeline.cpp b/src/atta/graphics/apis/vulkan/pipeline.cpp index 8b0feb2d..060ca289 100644 --- a/src/atta/graphics/apis/vulkan/pipeline.cpp +++ b/src/atta/graphics/apis/vulkan/pipeline.cpp @@ -336,12 +336,16 @@ void Pipeline::updateImageGroup(std::string name, ImageGroup imageGroup) { else { // Set default pink image to this binding std::shared_ptr defaultImage; + StringId defaultImageSid{}; if (element.type == BufferLayout::Element::Type::SAMPLER_2D) - defaultImage = std::dynamic_pointer_cast(gfx::Manager::getInstance().getImage("atta::gfx::pink")); + defaultImageSid = "atta::gfx::pink"; else if (element.type == BufferLayout::Element::Type::SAMPLER_CUBE) - defaultImage = std::dynamic_pointer_cast(gfx::Manager::getInstance().getCubemapImage("atta::gfx::pink")); - else + defaultImageSid = "atta::gfx::pinkCubemap"; + else { LOG_ERROR("gfx::vk::Pipeline", "Could not set default image for [w]$0[], unknown image type", element.name); + continue; + } + defaultImage = std::dynamic_pointer_cast(gfx::Manager::getInstance().getImage(defaultImageSid)); imageGroupInfo.descriptorSet->update(binding++, defaultImage); } } diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index b9ae68e2..20090a1e 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -92,7 +92,7 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { _pipeline->setMat4("uProjection", proj); _pipeline->setMat4("uView", views[i]); _pipeline->setImageGroup("equiImg"); - _pipeline->renderQuad(); + _pipeline->renderCube(); } _pipeline->end(); } diff --git a/src/atta/graphics/manager.cpp b/src/atta/graphics/manager.cpp index 0631e11b..bfe4b5b2 100644 --- a/src/atta/graphics/manager.cpp +++ b/src/atta/graphics/manager.cpp @@ -82,8 +82,6 @@ void Manager::startUpImpl() { //----- Create shaders -----// _equiToCubemap = std::make_unique(); - // Create pink default cubemap - _cubemapImages["atta::gfx::pink"] = _equiToCubemap->createCubemap("atta::gfx::pink"); //----- Resource sync -----// event::subscribe(BIND_EVENT_FUNC(Manager::onMeshLoadEvent)); @@ -106,7 +104,6 @@ void Manager::shutDownImpl() { _equiToCubemap.reset(); _meshes.clear(); _images.clear(); - _cubemapImages.clear(); _graphicsAPI->shutDown(); _graphicsAPI.reset(); _window.reset(); @@ -193,7 +190,7 @@ gfx::Image::Format Manager::convertFormat(res::Image::Format format) const { } void Manager::createDefaultImages() { - //----- Create basic images -----// + //----- Create default 2D images -----// // White { static uint8_t white[] = {255, 255, 255, 255}; @@ -227,10 +224,81 @@ void Manager::createDefaultImages() { info.debugName = "Pink"; _images["atta::gfx::pink"] = create(info); } + + //----- Create default cubemap images -----// + // WhiteCubemap: each face has a slightly different white tone + { + static uint8_t whiteCubemap[] = {// +X face: pure white + 255, 255, 255, 255, + // -X face: slightly off white + 240, 240, 240, 255, + // +Y face: warm white + 255, 250, 240, 255, + // -Y face: cool white + 230, 240, 255, 255, + // +Z face: bright white + 255, 245, 245, 255, + // -Z face: neutral white + 245, 245, 245, 255}; + Image::CreateInfo info{}; + info.format = Image::Format::RGBA; + info.width = 1; // Each face is 1x1 pixel + info.height = 1; + info.data = whiteCubemap; + info.debugName = "WhiteCubemap"; + info.isCubemap = true; + _images["atta::gfx::whiteCubemap"] = create(info); + } + // BlackCubemap: each face uses a different dark shade + { + static uint8_t blackCubemap[] = {// +X face: absolute black + 0, 0, 0, 255, + // -X face: very dark gray + 10, 10, 10, 255, + // +Y face: dark gray + 20, 20, 20, 255, + // -Y face: slightly lighter dark gray + 30, 30, 30, 255, + // +Z face: medium-dark gray + 40, 40, 40, 255, + // -Z face: light dark gray + 50, 50, 50, 255}; + Image::CreateInfo info{}; + info.format = Image::Format::RGBA; + info.width = 1; + info.height = 1; + info.data = blackCubemap; + info.debugName = "BlackCubemap"; + info.isCubemap = true; + _images["atta::gfx::blackCubemap"] = create(info); + } + // PinkCubemap: each face with a unique pink tone + { + static uint8_t pinkCubemap[] = {// +X face: vivid pink + 255, 0, 255, 255, + // -X face: deep magenta + 200, 0, 200, 255, + // +Y face: soft pink + 255, 150, 255, 255, + // -Y face: muted pink + 180, 0, 180, 255, + // +Z face: hot pink + 255, 0, 230, 255, + // -Z face: pastel pink + 240, 0, 240, 255}; + Image::CreateInfo info{}; + info.format = Image::Format::RGBA; + info.width = 1; + info.height = 1; + info.data = pinkCubemap; + info.debugName = "PinkCubemap"; + info.isCubemap = true; + _images["atta::gfx::pinkCubemap"] = create(info); + } } void Manager::createDefaultMeshes() { - //----- Create basic meshes -----// + //----- Create default meshes -----// // Quad { static float vertices[] = { @@ -463,18 +531,21 @@ void Manager::createImage(StringId sid) { if (image == nullptr) LOG_WARN("gfx::Manager", "Could not initialize gfx::Image from [w]$0[]", sid); - // Create 2D image Image::CreateInfo info{}; info.width = image->getWidth(); info.height = image->getHeight(); info.data = image->getData(); info.format = convertFormat(image->getFormat()); info.debugName = sid; - _images[sid] = create(info); - - // If it is HDR image, also create cubemap image - if (sid.getString().find(".hdr") != std::string::npos) - _cubemapImages[sid] = _equiToCubemap->createCubemap(sid); + if (sid.getString().find(".hdr") == std::string::npos) { + // If it is not HDR image, just create the 2D image + _images[sid] = create(info); + } else { + // If it is HDR image, create both 2D image and cubemap image + StringId sid2D = sid.getString() + "::2D"; + _images[sid2D] = create(info); + _images[sid] = _equiToCubemap->createCubemap(sid2D); + } } std::shared_ptr Manager::getMesh(StringId sid) const { @@ -485,13 +556,8 @@ std::shared_ptr Manager::getImage(StringId sid) const { auto it = _images.find(sid); return it != _images.end() ? it->second : nullptr; } -std::shared_ptr Manager::getCubemapImage(StringId sid) const { - auto it = _cubemapImages.find(sid); - return it != _cubemapImages.end() ? it->second : nullptr; -} const std::unordered_map>& Manager::getMeshes() const { return _meshes; } const std::unordered_map>& Manager::getImages() const { return _images; } -const std::unordered_map>& Manager::getCubemapImages() const { return _cubemapImages; } //---------- Register API specific implementations ----------// // For each type, will return the OpenGL, Vulkan, ... implementation based on the current active graphicsAPI diff --git a/src/atta/graphics/manager.h b/src/atta/graphics/manager.h index 2d21ef10..8443ad44 100644 --- a/src/atta/graphics/manager.h +++ b/src/atta/graphics/manager.h @@ -50,10 +50,8 @@ class Manager final { std::shared_ptr getMesh(StringId sid) const; std::shared_ptr getImage(StringId sid) const; - std::shared_ptr getCubemapImage(StringId sid) const; const std::unordered_map>& getMeshes() const; const std::unordered_map>& getImages() const; - const std::unordered_map>& getCubemapImages() const; private: void startUpImpl(); @@ -101,7 +99,6 @@ class Manager final { // Resource binding std::unordered_map> _meshes; std::unordered_map> _images; - std::unordered_map> _cubemapImages; // UI std::function _uiRenderViewportsFunc; diff --git a/src/atta/graphics/renderers/common/skyboxPipeline.cpp b/src/atta/graphics/renderers/common/skyboxPipeline.cpp index 5fef1376..83c2afc6 100644 --- a/src/atta/graphics/renderers/common/skyboxPipeline.cpp +++ b/src/atta/graphics/renderers/common/skyboxPipeline.cpp @@ -16,11 +16,18 @@ SkyboxPipeline::SkyboxPipeline(std::shared_ptr renderPass) { pipelineInfo.enableDepthTest = false; // Disable depth testing for the skybox pipelineInfo.debugName = StringId("Skybox Pipeline"); _pipeline = gfx::create(pipelineInfo); + + // Create image group + _pipeline->createImageGroup(Pipeline::ImageGroupType::PER_FRAME, "envImg"); } void SkyboxPipeline::render(std::shared_ptr camera, StringId envImg) { + // Update environment map image + _pipeline->updateImageGroup("envImg", {{"uEnvironmentMap", "atta::gfx::blackCubemap"}}); + _pipeline->begin(); { + _pipeline->setImageGroup("envImg"); _pipeline->setMat4("uProjection", camera->getProj()); _pipeline->setMat4("uView", camera->getView()); _pipeline->renderCube(); From 3907a42ee91cec2a541b30b98e5cdc6886e5f714 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Mon, 7 Apr 2025 08:13:36 +0200 Subject: [PATCH 23/39] Fix: OpenGL cubemap image creation with data Feat: Enable OpenGL cubemap seamless when supported --- src/atta/graphics/apis/openGL/image.cpp | 9 ++++++++- src/atta/graphics/apis/openGL/openGLAPI.cpp | 4 ++++ src/atta/graphics/apis/openGL/shader.cpp | 6 +++++- src/atta/graphics/renderers/common/skyboxPipeline.cpp | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/atta/graphics/apis/openGL/image.cpp b/src/atta/graphics/apis/openGL/image.cpp index cfac2633..3273120a 100644 --- a/src/atta/graphics/apis/openGL/image.cpp +++ b/src/atta/graphics/apis/openGL/image.cpp @@ -24,6 +24,8 @@ void Image::write(uint8_t* data) { if (!_isCubemap) { glBindTexture(GL_TEXTURE_2D, _id); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _width, _height, format, dataType, data); + if (_mipLevels > 1) + glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } else { glBindTexture(GL_TEXTURE_CUBE_MAP, _id); @@ -34,6 +36,8 @@ void Image::write(uint8_t* data) { // Update each of the six faces for (unsigned int i = 0; i < 6; i++) glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, _width, _height, format, dataType, data + (i * faceSize)); + if (_mipLevels > 1) + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); glBindTexture(GL_TEXTURE_CUBE_MAP, 0); } } @@ -86,7 +90,6 @@ void Image::resize(uint32_t width, uint32_t height, bool forceRecreate) { for (unsigned int i = 0; i < 6; i++) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internalFormat, _width, _height, 0, format, dataType, _data); - // glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT, _width, _height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, wrapMode); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, wrapMode); @@ -97,6 +100,10 @@ void Image::resize(uint32_t width, uint32_t height, bool forceRecreate) { if (_mipLevels > 1) glGenerateMipmap(GL_TEXTURE_CUBE_MAP); } + + // Update the texture data if _data is set + if (_data) + write(_data); } void Image::setFramebufferRead(std::function(vec2i, vec2i)> framebufferRead) { _framebufferRead = framebufferRead; } diff --git a/src/atta/graphics/apis/openGL/openGLAPI.cpp b/src/atta/graphics/apis/openGL/openGLAPI.cpp index ce1cce04..318427ef 100644 --- a/src/atta/graphics/apis/openGL/openGLAPI.cpp +++ b/src/atta/graphics/apis/openGL/openGLAPI.cpp @@ -72,6 +72,10 @@ void OpenGLAPI::startUp() { nullptr); #endif // ATTA_DEBUG_BUILD + // Enable cubemap seamless if supported + if (_apiVersion >= 320) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + glEnable(GL_DEPTH_TEST); } diff --git a/src/atta/graphics/apis/openGL/shader.cpp b/src/atta/graphics/apis/openGL/shader.cpp index ae51425b..f5406e61 100644 --- a/src/atta/graphics/apis/openGL/shader.cpp +++ b/src/atta/graphics/apis/openGL/shader.cpp @@ -194,7 +194,11 @@ void Shader::setImage(const char* name, std::shared_ptr inImage) { // Activate texture unit glActiveTexture(GL_TEXTURE0 + imgUnit); - glBindTexture(GL_TEXTURE_2D, image->getHandle()); + // Bind the texture with the proper target + if (image->isCubemap()) + glBindTexture(GL_TEXTURE_CUBE_MAP, image->getHandle()); + else + glBindTexture(GL_TEXTURE_2D, image->getHandle()); } void Shader::setCubemap(const char* name, std::shared_ptr inImage) { diff --git a/src/atta/graphics/renderers/common/skyboxPipeline.cpp b/src/atta/graphics/renderers/common/skyboxPipeline.cpp index 83c2afc6..719be3e8 100644 --- a/src/atta/graphics/renderers/common/skyboxPipeline.cpp +++ b/src/atta/graphics/renderers/common/skyboxPipeline.cpp @@ -23,7 +23,7 @@ SkyboxPipeline::SkyboxPipeline(std::shared_ptr renderPass) { void SkyboxPipeline::render(std::shared_ptr camera, StringId envImg) { // Update environment map image - _pipeline->updateImageGroup("envImg", {{"uEnvironmentMap", "atta::gfx::blackCubemap"}}); + _pipeline->updateImageGroup("envImg", {{"uEnvironmentMap", "atta::gfx::pinkCubemap"}}); _pipeline->begin(); { From 77c4712d66fdef47ee72144560cc1cbfaafd7f77 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 8 Mar 2026 15:46:34 +0100 Subject: [PATCH 24/39] Feat: Drawer and grid shaders --- resources/shaders/drawer/line.asl | 12 ++++++++++++ resources/shaders/drawer/point.asl | 14 ++++++++++++++ resources/shaders/grid/grid.asl | 28 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 resources/shaders/drawer/line.asl create mode 100644 resources/shaders/drawer/point.asl create mode 100644 resources/shaders/grid/grid.asl diff --git a/resources/shaders/drawer/line.asl b/resources/shaders/drawer/line.asl new file mode 100644 index 00000000..1c96d768 --- /dev/null +++ b/resources/shaders/drawer/line.asl @@ -0,0 +1,12 @@ +perFrame mat4 uView; +perFrame mat4 uProjection; + +perVertex vec4 vColor; + +vec4 vertex(vec3 iPos, vec4 iColor) { + vec4 pos = uProjection * uView * vec4(iPos, 1.0); + vColor = iColor; + return vec4(pos.xyz, pos.w); +} + +void fragment(out vec4 color) { color = vColor; } diff --git a/resources/shaders/drawer/point.asl b/resources/shaders/drawer/point.asl new file mode 100644 index 00000000..7c11932e --- /dev/null +++ b/resources/shaders/drawer/point.asl @@ -0,0 +1,14 @@ +perFrame mat4 uView; +perFrame mat4 uProjection; + +perVertex vec4 vColor; + +vec4 vertex(vec3 iPos, vec4 iColor) { + vec4 pos = uProjection * uView * vec4(iPos, 1.0); + vColor = iColor; + gl_PointSize = 5.0; + return pos; +} + +void fragment(out vec4 color) { color = vColor; } + diff --git a/resources/shaders/grid/grid.asl b/resources/shaders/grid/grid.asl new file mode 100644 index 00000000..2a5939fb --- /dev/null +++ b/resources/shaders/grid/grid.asl @@ -0,0 +1,28 @@ +perFrame mat4 uView; +perFrame mat4 uProjection; +perFrame vec3 uCamPos; + +perVertex vec4 vColor; +perVertex vec3 vFragPos; + +vec4 vertex(vec3 iPos, vec4 iColor, float iWidth) { + vec4 pos = uProjection * uView * vec4(iPos, 1.0); + vColor = iColor; + vFragPos = iPos; + return vec4(pos.xyz, pos.w); +} + +void fragment(out vec4 color) { + float dist = length(uCamPos - vFragPos) + 0.0001; + color = vColor; + + // Parameters for controlling the fade effect + float fadeStart = 20.0; // Distance where the fading starts + float fadeEnd = 50.0; // Distance where the grid is fully faded/transparent + + // Clamped ratio between where fading starts and where it ends + float fadeRatio = clamp((dist - fadeStart) / (fadeEnd - fadeStart), 0.0, 1.0); + + // Apply fade falloff + color.a *= 1.0 - fadeRatio; +} From 09d714e937e82c3a6aa8caa2382513821c0732b7 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sun, 8 Mar 2026 16:01:07 +0100 Subject: [PATCH 25/39] Fix: Vulkan transition error --- resources/shaders/compute/equiToCubemap.asl | 2 ++ src/atta/graphics/apis/vulkan/image.h | 9 ++++++++- src/atta/graphics/apis/vulkan/renderPass.cpp | 2 +- src/atta/graphics/compute/equiToCubemap.cpp | 2 +- src/atta/graphics/renderers/common/skyboxPipeline.cpp | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/resources/shaders/compute/equiToCubemap.asl b/resources/shaders/compute/equiToCubemap.asl index 85383240..be532b21 100644 --- a/resources/shaders/compute/equiToCubemap.asl +++ b/resources/shaders/compute/equiToCubemap.asl @@ -6,6 +6,8 @@ perVertex vec3 localPos; vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { localPos = iPos; + // Add small dependency between vertex and normal/uv to avoid compilation error + localPos += iNormal / 10000.0f + vec3(iUV,0) / 10000.0f; return uProjection * uView * vec4(localPos, 1.0); } diff --git a/src/atta/graphics/apis/vulkan/image.h b/src/atta/graphics/apis/vulkan/image.h index 8e41a36d..87350d89 100644 --- a/src/atta/graphics/apis/vulkan/image.h +++ b/src/atta/graphics/apis/vulkan/image.h @@ -55,6 +55,14 @@ class Image final : public gfx::Image { */ void setLayout(VkImageLayout layout); + /** + * @brief Transition image layout + * + * Transition image layout to a new layout. A pipeline barrier will be created to perform the transition. This can be used to ensure + * the image is in the correct layout to be bind to a shader. + */ + void transitionLayout(VkCommandBuffer commandBuffer, VkImageLayout newLayout); + /** * @brief Convert format to supported vulkan format * @@ -83,7 +91,6 @@ class Image final : public gfx::Image { void allocMemory(); void destroy(); uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties); - void transitionLayout(VkCommandBuffer commandBuffer, VkImageLayout newLayout); /** * @brief Supported format by the GPU diff --git a/src/atta/graphics/apis/vulkan/renderPass.cpp b/src/atta/graphics/apis/vulkan/renderPass.cpp index bb5b038c..25175367 100644 --- a/src/atta/graphics/apis/vulkan/renderPass.cpp +++ b/src/atta/graphics/apis/vulkan/renderPass.cpp @@ -23,7 +23,7 @@ RenderPass::RenderPass(const graphics::RenderPass::CreateInfo& info) : graphics: colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;// VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // attachments.push_back(colorAttachment); } else if (Image::isDepthFormat(format) || Image::isStencilFormat(format)) { VkAttachmentDescription depthAttachment{}; diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index 20090a1e..7cabcc6d 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -41,7 +41,7 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { framebufferInfo.attachments.push_back(attachment); framebufferInfo.width = _width; framebufferInfo.height = _height; - framebufferInfo.clearColor = {0, 0, 0, 1}; + framebufferInfo.clearColor = {0, 100, 0, 255}; framebufferInfo.debugName = StringId("EquiToCubemap Framebuffer"); std::shared_ptr framebuffer = graphics::create(framebufferInfo); diff --git a/src/atta/graphics/renderers/common/skyboxPipeline.cpp b/src/atta/graphics/renderers/common/skyboxPipeline.cpp index 719be3e8..9746bedf 100644 --- a/src/atta/graphics/renderers/common/skyboxPipeline.cpp +++ b/src/atta/graphics/renderers/common/skyboxPipeline.cpp @@ -23,7 +23,7 @@ SkyboxPipeline::SkyboxPipeline(std::shared_ptr renderPass) { void SkyboxPipeline::render(std::shared_ptr camera, StringId envImg) { // Update environment map image - _pipeline->updateImageGroup("envImg", {{"uEnvironmentMap", "atta::gfx::pinkCubemap"}}); + _pipeline->updateImageGroup("envImg", {{"uEnvironmentMap", envImg}}); _pipeline->begin(); { From 08b84ada59bbf0c5ecfd4ade4d0c316aadab31e2 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Mon, 9 Mar 2026 15:42:14 +0100 Subject: [PATCH 26/39] Fix: Render skybox from HDR image --- .../material-resource/material-resource.atta | 7 +++++- resources/shaders/compute/equiToCubemap.asl | 13 +++++----- src/atta/graphics/compute/equiToCubemap.cpp | 24 +++++++------------ 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/examples/tutorials/material-resource/material-resource.atta b/examples/tutorials/material-resource/material-resource.atta index 52ec5caf..f7ca4859 100644 --- a/examples/tutorials/material-resource/material-resource.atta +++ b/examples/tutorials/material-resource/material-resource.atta @@ -1,5 +1,5 @@ [project] -attaVersion = "0.3.7" +attaVersion = "0.3.10" name = "material-resource" [config] @@ -166,3 +166,8 @@ transform.position = vec3(0.000000, 0.000000, 2.548024) transform.orientation = quat(0.000000, 0.000000, 0.000000, 1.000000) transform.scale = vec3(1.000000, 1.000000, 1.000000) +[node] +id = 9 +environmentLight.sid = "environment/suburban_soccer_park_2k.hdr" +name.name = "Environment light" + diff --git a/resources/shaders/compute/equiToCubemap.asl b/resources/shaders/compute/equiToCubemap.asl index be532b21..115ce2ad 100644 --- a/resources/shaders/compute/equiToCubemap.asl +++ b/resources/shaders/compute/equiToCubemap.asl @@ -11,15 +11,16 @@ vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { return uProjection * uView * vec4(localPos, 1.0); } -vec4 fragment() { - vec2 uv = sampleSphericalMap(normalize(localPos)); - return vec4(texture(uEquirectangularMap, uv).rgb, 1.0); -} - const vec2 invAtan = vec2(0.1591, 0.3183); vec2 sampleSphericalMap(vec3 v) { - vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + // Z-up convention: azimuth in XY plane, elevation along Z axis + vec2 uv = vec2(atan(v.y, v.x), asin(v.z)); uv *= invAtan; uv += 0.5; return uv; } + +void fragment(out vec4 color) { + vec2 uv = sampleSphericalMap(normalize(localPos)); + color = vec4(texture(uEquirectangularMap, uv).rgb, 1.0); +} diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index 7cabcc6d..9d928a70 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -41,7 +41,7 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { framebufferInfo.attachments.push_back(attachment); framebufferInfo.width = _width; framebufferInfo.height = _height; - framebufferInfo.clearColor = {0, 100, 0, 255}; + framebufferInfo.clearColor = {255, 0, 255, 255}; framebufferInfo.debugName = StringId("EquiToCubemap Framebuffer"); std::shared_ptr framebuffer = graphics::create(framebufferInfo); @@ -63,21 +63,15 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { _pipeline->createImageGroup(Pipeline::ImageGroupType::PER_FRAME, "equiImg"); _pipeline->updateImageGroup("equiImg", {{"uEquirectangularMap", image}}); - // Define projection and view matrices - mat4 proj = perspective(M_PI, 1.0f, 0.1f, 10.0f); + // Define projection and view matrices. + mat4 proj = perspective(M_PI / 2.0f, 1.0f, 0.1f, 10.0f); std::array views = { - // Positive X face: look toward +X, with Z as up. - lookAt(vec3(0.0f), vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), - // Negative X face: look toward -X, with Z as up. - lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), - // Positive Y face: look toward +Y, with Z as up. - lookAt(vec3(0.0f), vec3(0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), - // Negative Y face: look toward -Y, with Z as up. - lookAt(vec3(0.0f), vec3(0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), - // Positive Z face: since forward (0,0,1) is parallel to global up, choose a different up (e.g. (0,1,0)) - lookAt(vec3(0.0f), vec3(0.0f, 0.0f, 1.0f), vec3(0.0f, 1.0f, 0.0f)), - // Negative Z face: similarly choose (0,1,0) for up. - lookAt(vec3(0.0f), vec3(0.0f, 0.0f, -1.0f), vec3(0.0f, 1.0f, 0.0f)), + lookAt(vec3(0.0f), vec3( 1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // +X face + lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // -X face + lookAt(vec3(0.0f), vec3( 0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), // +Y face + lookAt(vec3(0.0f), vec3( 0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f)), // -Y face + lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, 1.0f), vec3(0.0f, -1.0f, 0.0f)), // +Z face + lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f)), // -Z face }; // Render From 1f564c958bcdf253f1dcdcc9d9f5d0bfa790e2e8 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Mon, 9 Mar 2026 16:26:15 +0100 Subject: [PATCH 27/39] Fix: Vulkan warnings --- src/atta/graphics/apis/openGL/image.h | 1 + src/atta/graphics/apis/vulkan/framebuffer.cpp | 3 ++- src/atta/graphics/apis/vulkan/image.cpp | 10 ++++++++++ src/atta/graphics/apis/vulkan/image.h | 1 + src/atta/graphics/apis/vulkan/renderPass.cpp | 2 +- src/atta/graphics/apis/vulkan/vulkanAPI.cpp | 1 + src/atta/graphics/compute/equiToCubemap.cpp | 2 ++ src/atta/graphics/framebuffer.cpp | 2 +- src/atta/graphics/framebuffer.h | 3 +++ src/atta/graphics/image.h | 1 + 10 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/atta/graphics/apis/openGL/image.h b/src/atta/graphics/apis/openGL/image.h index 792bd2b9..343e30e3 100644 --- a/src/atta/graphics/apis/openGL/image.h +++ b/src/atta/graphics/apis/openGL/image.h @@ -21,6 +21,7 @@ class Image final : public gfx::Image { void write(uint8_t* data) override; std::vector read(vec2i offset = {0, 0}, vec2i size = {0, 0}) override; void resize(uint32_t width, uint32_t height, bool forceRecreate = false) override; + void prepareForSampling() override {} // No-op: OpenGL has no explicit layout transitions OpenGLId getHandle() const { return _id; } diff --git a/src/atta/graphics/apis/vulkan/framebuffer.cpp b/src/atta/graphics/apis/vulkan/framebuffer.cpp index 5b7a0c92..9e19c5d5 100644 --- a/src/atta/graphics/apis/vulkan/framebuffer.cpp +++ b/src/atta/graphics/apis/vulkan/framebuffer.cpp @@ -20,8 +20,9 @@ void Framebuffer::bind(bool clear) { } } void Framebuffer::unbind() { + VkImageLayout layout = _isSwapchain ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; for (size_t i = 0; i < _images.size(); i++) - std::dynamic_pointer_cast(_images[i])->setLayout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + std::dynamic_pointer_cast(_images[i])->setLayout(layout); } void Framebuffer::resize(uint32_t width, uint32_t height, bool forceRecreate) { diff --git a/src/atta/graphics/apis/vulkan/image.cpp b/src/atta/graphics/apis/vulkan/image.cpp index 89348e2e..d076539c 100644 --- a/src/atta/graphics/apis/vulkan/image.cpp +++ b/src/atta/graphics/apis/vulkan/image.cpp @@ -176,6 +176,12 @@ void Image::resize(uint32_t width, uint32_t height, bool forceRecreate) { write(_data); } +void Image::prepareForSampling() { + VkCommandBuffer commandBuffer = common::getCommandPool()->beginSingleTimeCommands(); + transitionLayout(commandBuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + common::getCommandPool()->endSingleTimeCommands(commandBuffer); +} + void* Image::getImGuiImage() { // Make sure image is readable VkCommandBuffer commandBuffer = common::getCommandPool()->beginSingleTimeCommands(); @@ -480,6 +486,10 @@ void populateLayoutStage(VkImageLayout layout, VkPipelineStageFlags* stage, VkAc *access = 0; *stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; break; + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + *access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + *stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + break; case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: *access = VK_ACCESS_SHADER_READ_BIT; *stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; diff --git a/src/atta/graphics/apis/vulkan/image.h b/src/atta/graphics/apis/vulkan/image.h index 87350d89..d530b68c 100644 --- a/src/atta/graphics/apis/vulkan/image.h +++ b/src/atta/graphics/apis/vulkan/image.h @@ -37,6 +37,7 @@ class Image final : public gfx::Image { void write(uint8_t* data) override; std::vector read(vec2i offset = {0, 0}, vec2i size = {0, 0}) override; void resize(uint32_t width, uint32_t height, bool forceRecreate = false) override; + void prepareForSampling() override; void* getImGuiImage() override; VkImage getImageHandle() const; diff --git a/src/atta/graphics/apis/vulkan/renderPass.cpp b/src/atta/graphics/apis/vulkan/renderPass.cpp index 25175367..eb902bc5 100644 --- a/src/atta/graphics/apis/vulkan/renderPass.cpp +++ b/src/atta/graphics/apis/vulkan/renderPass.cpp @@ -23,7 +23,7 @@ RenderPass::RenderPass(const graphics::RenderPass::CreateInfo& info) : graphics: colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;// VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // + colorAttachment.finalLayout = _framebuffer->isSwapchain() ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachments.push_back(colorAttachment); } else if (Image::isDepthFormat(format) || Image::isStencilFormat(format)) { VkAttachmentDescription depthAttachment{}; diff --git a/src/atta/graphics/apis/vulkan/vulkanAPI.cpp b/src/atta/graphics/apis/vulkan/vulkanAPI.cpp index 758947a2..fcc54cae 100644 --- a/src/atta/graphics/apis/vulkan/vulkanAPI.cpp +++ b/src/atta/graphics/apis/vulkan/vulkanAPI.cpp @@ -57,6 +57,7 @@ void VulkanAPI::startUp() { gfx::Framebuffer::CreateInfo framebufferInfo{}; framebufferInfo.width = _swapChain->getImages()[0]->getWidth(); framebufferInfo.height = _swapChain->getImages()[0]->getHeight(); + framebufferInfo.isSwapchain = true; size_t i = 0; for (std::shared_ptr image : _swapChain->getImages()) { gfx::Framebuffer::Attachment attachment{}; diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index 9d928a70..55d15c02 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -95,6 +95,8 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { _renderQueue->end(); } + cubemapImage->prepareForSampling(); + return cubemapImage; } diff --git a/src/atta/graphics/framebuffer.cpp b/src/atta/graphics/framebuffer.cpp index 2683cd01..0e4929d0 100644 --- a/src/atta/graphics/framebuffer.cpp +++ b/src/atta/graphics/framebuffer.cpp @@ -5,7 +5,7 @@ namespace atta::graphics { Framebuffer::Framebuffer(const CreateInfo& info) - : _width(info.width), _height(info.height), _clearColor(info.clearColor), _attachments(info.attachments), _debugName(info.debugName) { + : _width(info.width), _height(info.height), _clearColor(info.clearColor), _isSwapchain(info.isSwapchain), _attachments(info.attachments), _debugName(info.debugName) { DASSERT(_attachments.size() > 0, "It is not possible to create framebuffer with no attachments"); // Check consistency and populate color and depth attachments diff --git a/src/atta/graphics/framebuffer.h b/src/atta/graphics/framebuffer.h index 866031a5..120ea28b 100644 --- a/src/atta/graphics/framebuffer.h +++ b/src/atta/graphics/framebuffer.h @@ -30,6 +30,7 @@ class Framebuffer { uint32_t width; ///< Framebuffer width uint32_t height; ///< Framebuffer height vec4 clearColor = {0.0f, 0.0f, 0.0f, 0.0f}; ///< Color to be used when cleaning the color attachments + bool isSwapchain = false; ///< Whether this framebuffer wraps a swapchain image std::vector attachments = {}; @@ -53,6 +54,7 @@ class Framebuffer { uint32_t getHeight() const { return _height; } vec4 getClearColor() const { return _clearColor; } StringId getDebugName() const { return _debugName; } + bool isSwapchain() const { return _isSwapchain; } bool hasColorAttachment() const; bool hasDepthAttachment() const; @@ -65,6 +67,7 @@ class Framebuffer { uint32_t _width; uint32_t _height; vec4 _clearColor; + bool _isSwapchain; std::vector _attachments; int _colorAttachmentIndex; diff --git a/src/atta/graphics/image.h b/src/atta/graphics/image.h index d29798ab..0b8016d6 100644 --- a/src/atta/graphics/image.h +++ b/src/atta/graphics/image.h @@ -51,6 +51,7 @@ class Image { virtual void write(uint8_t* data) = 0; virtual std::vector read(vec2i offset = {0, 0}, vec2i size = {0, 0}) = 0; virtual void resize(uint32_t width, uint32_t height, bool forceRecreate = false) = 0; + virtual void prepareForSampling() = 0; /* Get format * From a8548dc27d2b6ba79371fde7da4963fd5562b0ad Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Mon, 9 Mar 2026 18:08:14 +0100 Subject: [PATCH 28/39] Feat: Convert PBR shaders into ASL --- resources/shaders/compute/brdf.asl | 92 +++++++++++++++++ resources/shaders/compute/brdf.frag | 120 ---------------------- resources/shaders/compute/brdf.vert | 16 --- resources/shaders/compute/irradiance.asl | 38 +++++++ resources/shaders/compute/irradiance.frag | 44 -------- resources/shaders/compute/irradiance.vert | 13 --- resources/shaders/compute/prefilter.asl | 70 +++++++++++++ resources/shaders/compute/prefilter.frag | 103 ------------------- resources/shaders/compute/prefilter.vert | 13 --- 9 files changed, 200 insertions(+), 309 deletions(-) create mode 100644 resources/shaders/compute/brdf.asl delete mode 100644 resources/shaders/compute/brdf.frag delete mode 100644 resources/shaders/compute/brdf.vert create mode 100644 resources/shaders/compute/irradiance.asl delete mode 100644 resources/shaders/compute/irradiance.frag delete mode 100644 resources/shaders/compute/irradiance.vert create mode 100644 resources/shaders/compute/prefilter.asl delete mode 100644 resources/shaders/compute/prefilter.frag delete mode 100644 resources/shaders/compute/prefilter.vert diff --git a/resources/shaders/compute/brdf.asl b/resources/shaders/compute/brdf.asl new file mode 100644 index 00000000..e59f5050 --- /dev/null +++ b/resources/shaders/compute/brdf.asl @@ -0,0 +1,92 @@ +perVertex vec2 uv; + +vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { + uv = iUV; + return vec4(iPos, 1.0); +} + +const float PI = 3.14159265359; + +float RadicalInverse_VdC(uint bits) { + bits = (bits << 16u) | (bits >> 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); + return float(bits) * 2.3283064365386963e-10; +} + +vec2 Hammersley(uint i, uint N) { + return vec2(float(i) / float(N), RadicalInverse_VdC(i)); +} + +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) { + float a = roughness * roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + return normalize(tangent * H.x + bitangent * H.y + N * H.z); +} + +float GeometrySchlickGGX(float NdotV, float roughness) { + float a = roughness; + float k = (a * a) / 2.0; + return NdotV / (NdotV * (1.0 - k) + k); +} + +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + return GeometrySchlickGGX(NdotV, roughness) * GeometrySchlickGGX(NdotL, roughness); +} + +vec2 IntegrateBRDF(float NdotV, float roughness) { + vec3 V; + V.x = sqrt(1.0 - NdotV * NdotV); + V.y = 0.0; + V.z = NdotV; + + float A = 0.0; + float B = 0.0; + + vec3 N = vec3(0.0, 0.0, 1.0); + + const uint SAMPLE_COUNT = 1024u; + for (uint i = 0u; i < SAMPLE_COUNT; ++i) { + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, roughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(L.z, 0.0); + float NdotH = max(H.z, 0.0); + float VdotH = max(dot(V, H), 0.0); + + if (NdotL > 0.0) { + float G = GeometrySmith(N, V, L, roughness); + float G_Vis = (G * VdotH) / (NdotH * NdotV); + float Fc = pow(1.0 - VdotH, 5.0); + + A += (1.0 - Fc) * G_Vis; + B += Fc * G_Vis; + } + } + A /= float(SAMPLE_COUNT); + B /= float(SAMPLE_COUNT); + return vec2(A, B); +} + +void fragment(out vec4 color) { + vec2 integratedBRDF = IntegrateBRDF(uv.x, uv.y); + color = vec4(integratedBRDF, 0.0, 1.0); +} diff --git a/resources/shaders/compute/brdf.frag b/resources/shaders/compute/brdf.frag deleted file mode 100644 index 2bf8801b..00000000 --- a/resources/shaders/compute/brdf.frag +++ /dev/null @@ -1,120 +0,0 @@ -#version 300 es -precision mediump float; - -in vec2 TexCoords; -in vec3 Pos; - -out vec2 FragColor; - -const float PI = 3.14159265359; -// ---------------------------------------------------------------------------- -// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html -// efficient VanDerCorpus calculation. -float RadicalInverse_VdC(uint bits) -{ - bits = (bits << 16u) | (bits >> 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); - return float(bits) * 2.3283064365386963e-10; // / 0x100000000 -} -// ---------------------------------------------------------------------------- -vec2 Hammersley(uint i, uint N) -{ - return vec2(float(i)/float(N), RadicalInverse_VdC(i)); -} -// ---------------------------------------------------------------------------- -vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) -{ - float a = roughness*roughness; - - float phi = 2.0 * PI * Xi.x; - float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); - float sinTheta = sqrt(1.0 - cosTheta*cosTheta); - - // from spherical coordinates to cartesian coordinates - halfway vector - vec3 H; - H.x = cos(phi) * sinTheta; - H.y = sin(phi) * sinTheta; - H.z = cosTheta; - - // from tangent-space H vector to world-space sample vector - vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, N)); - vec3 bitangent = cross(N, tangent); - - vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; - return normalize(sampleVec); -} -// ---------------------------------------------------------------------------- -float GeometrySchlickGGX(float NdotV, float roughness) -{ - // note that we use a different k for IBL - float a = roughness; - float k = (a * a) / 2.0; - - float nom = NdotV; - float denom = NdotV * (1.0 - k) + k; - - return nom / denom; -} -// ---------------------------------------------------------------------------- -float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) -{ - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = GeometrySchlickGGX(NdotV, roughness); - float ggx1 = GeometrySchlickGGX(NdotL, roughness); - - return ggx1 * ggx2; -} -// ---------------------------------------------------------------------------- -vec2 IntegrateBRDF(float NdotV, float roughness) -{ - vec3 V; - V.x = sqrt(1.0 - NdotV*NdotV); - V.y = 0.0; - V.z = NdotV; - - float A = 0.0; - float B = 0.0; - - vec3 N = vec3(0.0, 0.0, 1.0); - - const uint SAMPLE_COUNT = 1024u; - for(uint i = 0u; i < SAMPLE_COUNT; ++i) - { - // generates a sample vector that's biased towards the - // preferred alignment direction (importance sampling). - vec2 Xi = Hammersley(i, SAMPLE_COUNT); - vec3 H = ImportanceSampleGGX(Xi, N, roughness); - vec3 L = normalize(2.0 * dot(V, H) * H - V); - - float NdotL = max(L.z, 0.0); - float NdotH = max(H.z, 0.0); - float VdotH = max(dot(V, H), 0.0); - - if(NdotL > 0.0) - { - float G = GeometrySmith(N, V, L, roughness); - float G_Vis = (G * VdotH) / (NdotH * NdotV); - float Fc = pow(1.0 - VdotH, 5.0); - - A += (1.0 - Fc) * G_Vis; - B += Fc * G_Vis; - } - } - A /= float(SAMPLE_COUNT); - B /= float(SAMPLE_COUNT); - return vec2(A, B); -} -// ---------------------------------------------------------------------------- -void main() -{ - // TODO Should work using the texture coordinates - float NdotV = (Pos.x+1.0f)/2.0f; - float roughness = (Pos.y+1.0f)/2.0f; - vec2 integratedBRDF = IntegrateBRDF(NdotV, roughness); - FragColor = integratedBRDF; -} diff --git a/resources/shaders/compute/brdf.vert b/resources/shaders/compute/brdf.vert deleted file mode 100644 index 4d07585b..00000000 --- a/resources/shaders/compute/brdf.vert +++ /dev/null @@ -1,16 +0,0 @@ -#version 300 es -//in vec3 aPos; -//in vec2 aTexCoords; -in vec3 inPos; -in vec3 inNormal; -in vec2 inTexCoords; - -out vec2 TexCoords; -out vec3 Pos; - -void main() -{ - TexCoords = inTexCoords; - Pos = inPos; - gl_Position = vec4(inPos, 1.0); -} diff --git a/resources/shaders/compute/irradiance.asl b/resources/shaders/compute/irradiance.asl new file mode 100644 index 00000000..a660600e --- /dev/null +++ b/resources/shaders/compute/irradiance.asl @@ -0,0 +1,38 @@ +perFrame mat4 uProjection; +perFrame mat4 uView; +perFrame samplerCube uEnvironmentMap; + +perVertex vec3 localPos; + +vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { + localPos = iPos; + localPos += iNormal / 10000.0f + vec3(iUV, 0) / 10000.0f; + return uProjection * uView * vec4(localPos, 1.0); +} + +const float PI = 3.14159265359; + +void fragment(out vec4 color) { + vec3 N = normalize(localPos); + + vec3 irradiance = vec3(0.0); + + vec3 up = vec3(0.0, 1.0, 0.0); + vec3 right = normalize(cross(up, N)); + up = normalize(cross(N, right)); + + float sampleDelta = 0.025; + float nrSamples = 0.0; + for (float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) { + for (float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) { + vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); + vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; + + irradiance += texture(uEnvironmentMap, sampleVec).rgb * cos(theta) * sin(theta); + nrSamples++; + } + } + irradiance = PI * irradiance * (1.0 / float(nrSamples)); + + color = vec4(irradiance, 1.0); +} diff --git a/resources/shaders/compute/irradiance.frag b/resources/shaders/compute/irradiance.frag deleted file mode 100644 index 51b511f4..00000000 --- a/resources/shaders/compute/irradiance.frag +++ /dev/null @@ -1,44 +0,0 @@ -#version 300 es -precision mediump float; -out vec4 FragColor; -in vec3 WorldPos; - -uniform samplerCube environmentMap; - -const float PI = 3.14159265359; - -void main() -{ - // The world vector acts as the normal of a tangent surface - // from the origin, aligned to WorldPos. Given this normal, calculate all - // incoming radiance of the environment. The result of this radiance - // is the radiance of light coming from -Normal direction, which is what - // we use in the PBR shader to sample irradiance. - vec3 N = normalize(WorldPos); - - vec3 irradiance = vec3(0.0); - - // tangent space calculation from origin point - vec3 up = vec3(0.0, 1.0, 0.0); - vec3 right = normalize(cross(up, N)); - up = normalize(cross(N, right)); - - float sampleDelta = 0.025; - float nrSamples = 0.0; - for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) - { - for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) - { - // spherical to cartesian (in tangent space) - vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); - // tangent space to world - vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; - - irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta); - nrSamples++; - } - } - irradiance = PI * irradiance * (1.0 / float(nrSamples)); - - FragColor = vec4(irradiance, 1.0); -} diff --git a/resources/shaders/compute/irradiance.vert b/resources/shaders/compute/irradiance.vert deleted file mode 100644 index 21d015e1..00000000 --- a/resources/shaders/compute/irradiance.vert +++ /dev/null @@ -1,13 +0,0 @@ -#version 300 es -in vec3 aPos; - -out vec3 WorldPos; - -uniform mat4 projection; -uniform mat4 view; - -void main() -{ - WorldPos = aPos; - gl_Position = projection * view * vec4(WorldPos, 1.0); -} diff --git a/resources/shaders/compute/prefilter.asl b/resources/shaders/compute/prefilter.asl new file mode 100644 index 00000000..149a24bf --- /dev/null +++ b/resources/shaders/compute/prefilter.asl @@ -0,0 +1,70 @@ +perFrame mat4 uProjection; +perFrame mat4 uView; +perFrame samplerCube uEnvironmentMap; +perFrame float uRoughness; + +perVertex vec3 localPos; + +vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { + localPos = iPos; + localPos += iNormal / 10000.0f + vec3(iUV, 0) / 10000.0f; + return uProjection * uView * vec4(localPos, 1.0); +} + +const float PI = 3.14159265359; + +float RadicalInverse_VdC(uint bits) { + bits = (bits << 16u) | (bits >> 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); + return float(bits) * 2.3283064365386963e-10; +} + +vec2 Hammersley(uint i, uint N) { + return vec2(float(i) / float(N), RadicalInverse_VdC(i)); +} + +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) { + float a = roughness * roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + return normalize(tangent * H.x + bitangent * H.y + N * H.z); +} + +void fragment(out vec4 color) { + vec3 N = normalize(localPos); + vec3 R = N; + vec3 V = R; + + const uint SAMPLE_COUNT = 1024u; + float totalWeight = 0.0; + vec3 prefilteredColor = vec3(0.0); + for (uint i = 0u; i < SAMPLE_COUNT; ++i) { + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, uRoughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(dot(N, L), 0.0); + if (NdotL > 0.0) { + prefilteredColor += texture(uEnvironmentMap, L).rgb * NdotL; + totalWeight += NdotL; + } + } + prefilteredColor = prefilteredColor / totalWeight; + + color = vec4(prefilteredColor, 1.0); +} diff --git a/resources/shaders/compute/prefilter.frag b/resources/shaders/compute/prefilter.frag deleted file mode 100644 index 5ea009f6..00000000 --- a/resources/shaders/compute/prefilter.frag +++ /dev/null @@ -1,103 +0,0 @@ -#version 300 es -precision mediump float; -out vec4 FragColor; -in vec3 localPos; - -uniform samplerCube environmentMap; -uniform float roughness; - -const float PI = 3.14159265359; - -float RadicalInverse_VdC(uint bits); -vec2 Hammersley(uint i, uint N); -vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness); - -void main() -{ - vec3 N = normalize(localPos); - vec3 R = N; - vec3 V = R; - - const uint SAMPLE_COUNT = 1024u; - float totalWeight = 0.0; - vec3 prefilteredColor = vec3(0.0); - for(uint i = 0u; i < SAMPLE_COUNT; ++i) - { - vec2 Xi = Hammersley(i, SAMPLE_COUNT); - vec3 H = ImportanceSampleGGX(Xi, N, roughness); - vec3 L = normalize(2.0 * dot(V, H) * H - V); - - float NdotL = max(dot(N, L), 0.0); - if(NdotL > 0.0) - { - prefilteredColor += texture(environmentMap, L).rgb * NdotL; - totalWeight += NdotL; - } - } - prefilteredColor = prefilteredColor / totalWeight; - - FragColor = vec4(prefilteredColor, 1.0); -} - -float VanDerCorput(uint n, uint base) -{ - float invBase = 1.0 / float(base); - float denom = 1.0; - float result = 0.0; - - for(uint i = 0u; i < 32u; ++i) - { - if(n > 0u) - { - denom = mod(float(n), 2.0); - result += denom * invBase; - invBase = invBase / 2.0; - n = uint(float(n) / 2.0); - } - } - - return result; -} -// ---------------------------------------------------------------------------- -vec2 HammersleyNoBitOps(uint i, uint N) -{ - return vec2(float(i)/float(N), VanDerCorput(i, 2u)); -} - -vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) -{ - float a = roughness*roughness; - - float phi = 2.0 * PI * Xi.x; - float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); - float sinTheta = sqrt(1.0 - cosTheta*cosTheta); - - // from spherical coordinates to cartesian coordinates - vec3 H; - H.x = cos(phi) * sinTheta; - H.y = sin(phi) * sinTheta; - H.z = cosTheta; - - // from tangent-space vector to world-space sample vector - vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, N)); - vec3 bitangent = cross(N, tangent); - - vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; - return normalize(sampleVec); -} - -float RadicalInverse_VdC(uint bits) -{ - bits = (bits << 16u) | (bits >> 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); - return float(bits) * 2.3283064365386963e-10; // / 0x100000000 -} -// ---------------------------------------------------------------------------- -vec2 Hammersley(uint i, uint N) -{ - return vec2(float(i)/float(N), RadicalInverse_VdC(i)); -} diff --git a/resources/shaders/compute/prefilter.vert b/resources/shaders/compute/prefilter.vert deleted file mode 100644 index f1626ca8..00000000 --- a/resources/shaders/compute/prefilter.vert +++ /dev/null @@ -1,13 +0,0 @@ -#version 300 es -in vec3 aPos; - -out vec3 localPos; - -uniform mat4 projection; -uniform mat4 view; - -void main() -{ - localPos = aPos; - gl_Position = projection * view * vec4(localPos, 1.0); -} From e8e83f407e790fda732245550859ac7a01654857 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Mon, 9 Mar 2026 19:13:55 +0100 Subject: [PATCH 29/39] Feat: PBR shader with irradiance map --- resources/shaders/pbrRenderer/pbrRenderer.asl | 6 +- src/atta/graphics/CMakeLists.txt | 1 + src/atta/graphics/compute/irradiance.cpp | 94 +++++++++++++++++++ src/atta/graphics/compute/irradiance.h | 35 +++++++ .../renderers/common/skyboxPipeline.cpp | 14 ++- .../renderers/common/skyboxPipeline.h | 1 + src/atta/graphics/renderers/pbrRenderer.cpp | 42 ++------- src/atta/graphics/renderers/pbrRenderer.h | 5 +- 8 files changed, 160 insertions(+), 38 deletions(-) create mode 100644 src/atta/graphics/compute/irradiance.cpp create mode 100644 src/atta/graphics/compute/irradiance.h diff --git a/resources/shaders/pbrRenderer/pbrRenderer.asl b/resources/shaders/pbrRenderer/pbrRenderer.asl index 5e6d2d95..c1eb37c0 100644 --- a/resources/shaders/pbrRenderer/pbrRenderer.asl +++ b/resources/shaders/pbrRenderer/pbrRenderer.asl @@ -38,7 +38,7 @@ perFrame float omniFarPlane; // Environment light - IBL perFrame mat3 environmentLightOri; // perFrame samplerCube prefilterMap; -// perFrame samplerCube irradianceMap; +perFrame samplerCube irradianceMap; // perFrame sampler2D brdfLUT; //----- Per Draw -----// @@ -145,13 +145,13 @@ void fragment(out vec4 outColor) { vec3 kS = F; vec3 kD = 1.0 - kS; kD *= 1.0 - metallic; - vec3 irradiance = vec3(1, 1, 1); // texture(irradianceMap, environmentLightOri * N).rgb; + vec3 irradiance = texture(irradianceMap, environmentLightOri * N).rgb; vec3 diffuse = irradiance * albedo; // Specular IBL const float MAX_REFLECTION_LOD = 4.0; vec3 R = reflect(-V, N); - vec3 prefilteredColor = vec3(1, 1, 1); // textureLod(prefilterMap, environmentLightOri * R, roughness * MAX_REFLECTION_LOD).rgb; + vec3 prefilteredColor = vec3(1, 0, 0); // textureLod(prefilterMap, environmentLightOri * R, roughness * MAX_REFLECTION_LOD).rgb; vec2 brdf = vec2(0, 0); // texture(brdfLUT, vec2(max(dot(environmentLightOri * N, V), 0.0), roughness)).rg; vec3 specular = prefilteredColor * (F * brdf.x + brdf.y); diff --git a/src/atta/graphics/CMakeLists.txt b/src/atta/graphics/CMakeLists.txt index 050a34eb..18564585 100644 --- a/src/atta/graphics/CMakeLists.txt +++ b/src/atta/graphics/CMakeLists.txt @@ -19,6 +19,7 @@ set(ATTA_GRAPHICS_MODULE_SOURCE compute/entityClick.cpp compute/equiToCubemap.cpp + compute/irradiance.cpp renderers/common/drawerPipeline.cpp renderers/common/gridPipeline.cpp diff --git a/src/atta/graphics/compute/irradiance.cpp b/src/atta/graphics/compute/irradiance.cpp new file mode 100644 index 00000000..3d273e79 --- /dev/null +++ b/src/atta/graphics/compute/irradiance.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2020-2026 Breno Cunha Queiroz +#include +#include + +namespace atta::graphics { + +Irradiance::Irradiance() : _width(128), _height(128) {} + +std::shared_ptr Irradiance::createIrradianceCubemap(StringId environmentCubemapSid) { + LOG_VERBOSE("gfx::Irradiance", "Create irradiance cubemap from [w]$0[]", environmentCubemapSid); + + std::shared_ptr environmentCubemap = gfx::Manager::getInstance().getImage(environmentCubemapSid); + if (environmentCubemap == nullptr) { + LOG_ERROR("gfx::Irradiance", "Could not create irradiance cubemap, image [w]$0[] does not exist", environmentCubemapSid); + return nullptr; + } + + // Create irradiance cubemap image + gfx::Image::CreateInfo imageInfo{}; + imageInfo.format = environmentCubemap->getFormat(); + imageInfo.width = _width; + imageInfo.height = _height; + imageInfo.isCubemap = true; + imageInfo.debugName = environmentCubemapSid.getString() + " Irradiance"; + std::shared_ptr irradianceImage = graphics::create(imageInfo); + + // Create Render Queue + _renderQueue = graphics::create(); + + // Create Framebuffer + Framebuffer::Attachment attachment{}; + attachment.image = irradianceImage; + Framebuffer::CreateInfo framebufferInfo{}; + framebufferInfo.attachments.push_back(attachment); + framebufferInfo.width = _width; + framebufferInfo.height = _height; + framebufferInfo.clearColor = {255, 0, 255, 255}; + framebufferInfo.debugName = StringId("Irradiance Framebuffer"); + std::shared_ptr framebuffer = graphics::create(framebufferInfo); + + // Create Render Pass + RenderPass::CreateInfo renderPassInfo{}; + renderPassInfo.framebuffer = framebuffer; + renderPassInfo.debugName = StringId("Irradiance Render Pass"); + _renderPass = graphics::create(renderPassInfo); + + // Create Pipeline + Pipeline::CreateInfo pipelineInfo{}; + pipelineInfo.shader = graphics::create("shaders/compute/irradiance.asl"); + pipelineInfo.renderPass = _renderPass; + _pipeline = graphics::create(pipelineInfo); + + // Bind environment cubemap + _pipeline->createImageGroup(Pipeline::ImageGroupType::PER_FRAME, "envImg"); + _pipeline->updateImageGroup("envImg", {{"uEnvironmentMap", environmentCubemap}}); + + // Same face orientations as equiToCubemap (learnopengl convention, no Y-flip) + mat4 proj = perspective(M_PI / 2.0f, 1.0f, 0.1f, 10.0f); + std::array views = { + lookAt(vec3(0.0f), vec3( 1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // +X face + lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // -X face + lookAt(vec3(0.0f), vec3( 0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), // +Y face + lookAt(vec3(0.0f), vec3( 0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f)), // -Y face + lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, 1.0f), vec3(0.0f, -1.0f, 0.0f)), // +Z face + lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f)), // -Z face + }; + + for (size_t i = 0; i < 6; i++) { + framebuffer->setLayer(i); + _renderQueue->begin(); + { + _renderPass->begin(_renderQueue); + { + _pipeline->begin(); + { + _pipeline->setMat4("uProjection", proj); + _pipeline->setMat4("uView", views[i]); + _pipeline->setImageGroup("envImg"); + _pipeline->renderCube(); + } + _pipeline->end(); + } + _renderPass->end(); + } + _renderQueue->end(); + } + + irradianceImage->prepareForSampling(); + + return irradianceImage; +} + +} // namespace atta::graphics diff --git a/src/atta/graphics/compute/irradiance.h b/src/atta/graphics/compute/irradiance.h new file mode 100644 index 00000000..80f99e32 --- /dev/null +++ b/src/atta/graphics/compute/irradiance.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2020-2026 Breno Cunha Queiroz +#pragma once + +#include +#include +#include + +namespace atta::graphics { + +class Irradiance { + public: + Irradiance(); + + /** + * @brief Create an irradiance cubemap from an environment cubemap + * + * The resulting cubemap encodes the pre-integrated diffuse irradiance for every + * possible surface normal direction, enabling real-time IBL diffuse lighting. + * + * @param environmentCubemapSid Name of the environment cubemap gfx::Image to convolve + * @return The irradiance cubemap image, already transitioned to shader-read layout + */ + std::shared_ptr createIrradianceCubemap(StringId environmentCubemapSid); + + private: + uint32_t _width; + uint32_t _height; + + std::shared_ptr _renderQueue; + std::shared_ptr _renderPass; + std::shared_ptr _pipeline; +}; + +} // namespace atta::graphics diff --git a/src/atta/graphics/renderers/common/skyboxPipeline.cpp b/src/atta/graphics/renderers/common/skyboxPipeline.cpp index 9746bedf..46dbca2b 100644 --- a/src/atta/graphics/renderers/common/skyboxPipeline.cpp +++ b/src/atta/graphics/renderers/common/skyboxPipeline.cpp @@ -22,7 +22,19 @@ SkyboxPipeline::SkyboxPipeline(std::shared_ptr renderPass) { } void SkyboxPipeline::render(std::shared_ptr camera, StringId envImg) { - // Update environment map image + _pipeline->updateImageGroup("envImg", {{"uEnvironmentMap", envImg}}); + + _pipeline->begin(); + { + _pipeline->setImageGroup("envImg"); + _pipeline->setMat4("uProjection", camera->getProj()); + _pipeline->setMat4("uView", camera->getView()); + _pipeline->renderCube(); + } + _pipeline->end(); +} + +void SkyboxPipeline::render(std::shared_ptr camera, std::shared_ptr envImg) { _pipeline->updateImageGroup("envImg", {{"uEnvironmentMap", envImg}}); _pipeline->begin(); diff --git a/src/atta/graphics/renderers/common/skyboxPipeline.h b/src/atta/graphics/renderers/common/skyboxPipeline.h index 6a987350..28ce7e94 100644 --- a/src/atta/graphics/renderers/common/skyboxPipeline.h +++ b/src/atta/graphics/renderers/common/skyboxPipeline.h @@ -17,6 +17,7 @@ class SkyboxPipeline final { /// Render skybox void render(std::shared_ptr camera, StringId envImg); + void render(std::shared_ptr camera, std::shared_ptr envImg); private: std::shared_ptr _pipeline; diff --git a/src/atta/graphics/renderers/pbrRenderer.cpp b/src/atta/graphics/renderers/pbrRenderer.cpp index 9b996fc0..623e6d12 100644 --- a/src/atta/graphics/renderers/pbrRenderer.cpp +++ b/src/atta/graphics/renderers/pbrRenderer.cpp @@ -21,6 +21,8 @@ namespace atta::graphics { PbrRenderer::PbrRenderer() : Renderer("PbrRenderer"), _firstRender(true), _wasResized(false), _lastEnvironmentImg(StringId()) { + _irradiance = std::make_unique(); + // Render Queue _renderQueue = graphics::create(); @@ -48,6 +50,7 @@ PbrRenderer::PbrRenderer() : Renderer("PbrRenderer"), _firstRender(true), _wasRe geometryPipelineInfo.renderPass = _geometryRenderPass; geometryPipelineInfo.debugName = StringId("PbrRenderer Pipeline"); _geometryPipeline = graphics::create(geometryPipelineInfo); + _geometryPipeline->createImageGroup(Pipeline::ImageGroupType::PER_FRAME, "iblImg"); } //---------- Common pipelines ----------// @@ -154,7 +157,9 @@ void PbrRenderer::render(std::shared_ptr camera) { if (currEnvironmentImg != _lastEnvironmentImg) { _lastEnvironmentImg = currEnvironmentImg; if (_lastEnvironmentImg != StringId()) { - // irradianceCubemap(); + _irradianceImage = _irradiance->createIrradianceCubemap(_lastEnvironmentImg); + if (_irradianceImage) + _geometryPipeline->updateImageGroup("iblImg", {{"irradianceMap", _irradianceImage}}); // prefilterCubemap(); } } @@ -325,17 +330,9 @@ void PbrRenderer::geometryPass(std::shared_ptr camera) { // _geometryPipeline->setCubemap("omniShadowMap", _omnidirectionalShadowMap); _geometryPipeline->setFloat("omniFarPlane", 25.0f); - // Always set environment textures (if there is no environment map, use white texture) - // _geometryPipeline->setCubemap("irradianceMap", "PbrRenderer::irradiance"); - // _geometryPipeline->setCubemap("prefilterMap", "PbrRenderer::prefilter"); - //_geometryPipeline->setImage("brdfLUT", "PbrRenderer::brdfLUT"); - - // if (_lastEnvironmentImg != "Not defined"_sid) - // _geometryPipeline->setInt("numEnvironmentLights", 1); - // else { - // LOG_WARN("graphics::PbrRenderer", "Number of environment light should always be 1 (white texture if not defined)"); - // _geometryPipeline->setInt("numEnvironmentLights", 0); - // } + // Bind IBL textures + _geometryPipeline->setImageGroup("iblImg"); + _geometryPipeline->setInt("numEnvironmentLights", _irradianceImage ? 1 : 0); _geometryPipeline->setMat3("environmentLightOri", mat3(1.0f)); //----- Lighting -----// @@ -557,27 +554,6 @@ void PbrRenderer::geometryPass(std::shared_ptr camera) { //_geometryPipeline->end(); } -void PbrRenderer::irradianceCubemap() { - //// Create shader - // std::shared_ptr shader = graphics::create("shaders/compute/irradiance.asl"); - - //// Generate irradiance cubemap - // GraphicsAPI::GenerateProcessedCubemapInfo info; - // info.cubemapSid = StringId("PbrRenderer::irradiance"); - // info.shader = shader; - // info.width = 128; - // info.height = 128; - // info.numMipLevels = 1; - // info.func = [&](std::shared_ptr shader, mat4 proj, mat4 view, int face, int mipLevel) { - // if (mipLevel == 0 && face == 0) { - // // TODO shader->setCubemap("environmentMap", _lastEnvironmentImg); - // shader->setMat4("projection", transpose(proj)); - // } - // shader->setMat4("view", transpose(view)); - // }; - // graphics::getGraphicsAPI()->generateProcessedCubemap(info); -} - void PbrRenderer::prefilterCubemap() { //// Create shader // std::shared_ptr shader = graphics::create("shaders/compute/asl.vert"); diff --git a/src/atta/graphics/renderers/pbrRenderer.h b/src/atta/graphics/renderers/pbrRenderer.h index 07dbeacf..399c5d25 100644 --- a/src/atta/graphics/renderers/pbrRenderer.h +++ b/src/atta/graphics/renderers/pbrRenderer.h @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2020-2026 Breno Cunha Queiroz #pragma once +#include #include #include #include @@ -27,7 +28,6 @@ class PbrRenderer final : public Renderer { void shadowPass(); void geometryPass(std::shared_ptr camera); - void irradianceCubemap(); void prefilterCubemap(); void brdfLUT(); @@ -50,6 +50,9 @@ class PbrRenderer final : public Renderer { // Environment light StringId _lastEnvironmentImg; mat3 _environmentMapOri; + // Irradiance image + std::unique_ptr _irradiance; + std::shared_ptr _irradianceImage; // Shadow mapping std::shared_ptr _shadowMapPipeline; From caa5dfd8492335ed156fc5a5ee17270207dc7c08 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Mon, 9 Mar 2026 19:39:33 +0100 Subject: [PATCH 30/39] Feat: PBR shader with BRDF LUT --- resources/shaders/compute/brdf.asl | 4 +- resources/shaders/pbrRenderer/pbrRenderer.asl | 4 +- src/atta/graphics/CMakeLists.txt | 1 + src/atta/graphics/apis/vulkan/pipeline.cpp | 3 +- src/atta/graphics/apis/vulkan/shader.cpp | 12 +++- src/atta/graphics/compute/brdfLut.cpp | 68 +++++++++++++++++++ src/atta/graphics/compute/brdfLut.h | 35 ++++++++++ src/atta/graphics/renderers/pbrRenderer.cpp | 22 ++++-- src/atta/graphics/renderers/pbrRenderer.h | 4 ++ 9 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 src/atta/graphics/compute/brdfLut.cpp create mode 100644 src/atta/graphics/compute/brdfLut.h diff --git a/resources/shaders/compute/brdf.asl b/resources/shaders/compute/brdf.asl index e59f5050..f196c8fc 100644 --- a/resources/shaders/compute/brdf.asl +++ b/resources/shaders/compute/brdf.asl @@ -1,8 +1,8 @@ perVertex vec2 uv; -vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { +vec4 vertex(vec2 iVertex, vec2 iUV) { uv = iUV; - return vec4(iPos, 1.0); + return vec4(iVertex, 0.0, 1.0); } const float PI = 3.14159265359; diff --git a/resources/shaders/pbrRenderer/pbrRenderer.asl b/resources/shaders/pbrRenderer/pbrRenderer.asl index c1eb37c0..de1eff1b 100644 --- a/resources/shaders/pbrRenderer/pbrRenderer.asl +++ b/resources/shaders/pbrRenderer/pbrRenderer.asl @@ -39,7 +39,7 @@ perFrame float omniFarPlane; perFrame mat3 environmentLightOri; // perFrame samplerCube prefilterMap; perFrame samplerCube irradianceMap; -// perFrame sampler2D brdfLUT; +perFrame sampler2D brdfLUT; //----- Per Draw -----// perDraw mat4 model; @@ -152,7 +152,7 @@ void fragment(out vec4 outColor) { const float MAX_REFLECTION_LOD = 4.0; vec3 R = reflect(-V, N); vec3 prefilteredColor = vec3(1, 0, 0); // textureLod(prefilterMap, environmentLightOri * R, roughness * MAX_REFLECTION_LOD).rgb; - vec2 brdf = vec2(0, 0); // texture(brdfLUT, vec2(max(dot(environmentLightOri * N, V), 0.0), roughness)).rg; + vec2 brdf = texture(brdfLUT, vec2(max(dot(environmentLightOri * N, V), 0.0), roughness)).rg; vec3 specular = prefilteredColor * (F * brdf.x + brdf.y); ambient = (kD * diffuse + specular) * ao; diff --git a/src/atta/graphics/CMakeLists.txt b/src/atta/graphics/CMakeLists.txt index 18564585..21f22d28 100644 --- a/src/atta/graphics/CMakeLists.txt +++ b/src/atta/graphics/CMakeLists.txt @@ -17,6 +17,7 @@ set(ATTA_GRAPHICS_MODULE_SOURCE cameras/perspectiveCamera.cpp cameras/orthographicCamera.cpp + compute/brdfLut.cpp compute/entityClick.cpp compute/equiToCubemap.cpp compute/irradiance.cpp diff --git a/src/atta/graphics/apis/vulkan/pipeline.cpp b/src/atta/graphics/apis/vulkan/pipeline.cpp index 734aa1de..c20ce067 100644 --- a/src/atta/graphics/apis/vulkan/pipeline.cpp +++ b/src/atta/graphics/apis/vulkan/pipeline.cpp @@ -165,7 +165,8 @@ Pipeline::Pipeline(const gfx::Pipeline::CreateInfo& info) : gfx::Pipeline(info), }; _descriptorPool = std::make_shared(poolSizes, maxSets); _descriptorSets = std::make_shared(_descriptorPool, _uniformDescriptorSetLayout, _pipelineLayout, 1); - _descriptorSets->update(0, std::dynamic_pointer_cast(_shader)->getUniformBuffer()); + if (auto uniformBuffer = std::dynamic_pointer_cast(_shader)->getUniformBuffer()) + _descriptorSets->update(0, uniformBuffer); // Create default image group descriptor sets (pink images) if (createDefaultImageGroupPerFrame) diff --git a/src/atta/graphics/apis/vulkan/shader.cpp b/src/atta/graphics/apis/vulkan/shader.cpp index 32de5f58..098936f5 100644 --- a/src/atta/graphics/apis/vulkan/shader.cpp +++ b/src/atta/graphics/apis/vulkan/shader.cpp @@ -14,8 +14,11 @@ Shader::Shader(const fs::path& file) : gfx::Shader(file), _device(common::getDev shaderCode.apiCode = generateApiCode(type, shaderCode.iCode); compile(); - // Create uniform buffer - _uniformBuffer = std::make_shared(_perFrameLayout.getStride()); + // Create uniform buffer (only if shader has perFrame non-image uniforms) + if (_perFrameLayout.getStride() > 0) + _uniformBuffer = std::make_shared(_perFrameLayout.getStride()); + else + _uniformBuffer = nullptr; // Create push constant if (_perDrawLayout.getStride() > 0) @@ -278,7 +281,10 @@ void Shader::unbind() { _uniformImages.clear(); } -void Shader::pushUniformBuffer() { _uniformBuffer->writeInstance(_uniformBufferData); } +void Shader::pushUniformBuffer() { + if (_uniformBuffer) + _uniformBuffer->writeInstance(_uniformBufferData); +} void Shader::pushConstants(VkCommandBuffer commandBuffer, std::shared_ptr pipelineLayout) { if (!_pushConstantData.empty()) diff --git a/src/atta/graphics/compute/brdfLut.cpp b/src/atta/graphics/compute/brdfLut.cpp new file mode 100644 index 00000000..cf845f80 --- /dev/null +++ b/src/atta/graphics/compute/brdfLut.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2020-2026 Breno Cunha Queiroz +#include +#include + +namespace atta::graphics { + +BrdfLut::BrdfLut() : _width(512), _height(512) {} + +std::shared_ptr BrdfLut::generate() { + LOG_VERBOSE("gfx::BrdfLut", "Generating BRDF LUT"); + + // Create output image (RG16F: R=scale, G=bias) + gfx::Image::CreateInfo imageInfo{}; + imageInfo.format = gfx::Image::Format::RG16F; + imageInfo.width = _width; + imageInfo.height = _height; + imageInfo.isCubemap = false; + imageInfo.debugName = "BRDF LUT"; + std::shared_ptr lutImage = graphics::create(imageInfo); + + // Create Render Queue + _renderQueue = graphics::create(); + + // Create Framebuffer + Framebuffer::Attachment attachment{}; + attachment.image = lutImage; + Framebuffer::CreateInfo framebufferInfo{}; + framebufferInfo.attachments.push_back(attachment); + framebufferInfo.width = _width; + framebufferInfo.height = _height; + framebufferInfo.clearColor = {0, 0, 0, 0}; + framebufferInfo.debugName = StringId("BrdfLut Framebuffer"); + std::shared_ptr framebuffer = graphics::create(framebufferInfo); + + // Create Render Pass + RenderPass::CreateInfo renderPassInfo{}; + renderPassInfo.framebuffer = framebuffer; + renderPassInfo.debugName = StringId("BrdfLut Render Pass"); + _renderPass = graphics::create(renderPassInfo); + + // Create Pipeline + Pipeline::CreateInfo pipelineInfo{}; + pipelineInfo.shader = graphics::create("shaders/compute/brdf.asl"); + pipelineInfo.renderPass = _renderPass; + _pipeline = graphics::create(pipelineInfo); + + // Render a full-screen quad — UV (0,0)-(1,1) maps to (NdotV, roughness) + _renderQueue->begin(); + { + _renderPass->begin(_renderQueue); + { + _pipeline->begin(); + { + _pipeline->renderQuad(); + } + _pipeline->end(); + } + _renderPass->end(); + } + _renderQueue->end(); + + lutImage->prepareForSampling(); + + return lutImage; +} + +} // namespace atta::graphics diff --git a/src/atta/graphics/compute/brdfLut.h b/src/atta/graphics/compute/brdfLut.h new file mode 100644 index 00000000..0cb985e2 --- /dev/null +++ b/src/atta/graphics/compute/brdfLut.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2020-2026 Breno Cunha Queiroz +#pragma once + +#include +#include +#include + +namespace atta::graphics { + +class BrdfLut { + public: + BrdfLut(); + + /** + * @brief Generate the BRDF lookup table + * + * Pre-integrates the Cook-Torrance BRDF for all (NdotV, roughness) pairs. + * The result is stored in a 2D texture: R = scale (A), G = bias (B). + * Used in the split-sum specular IBL approximation. + * + * @return The BRDF LUT image, already transitioned to shader-read layout + */ + std::shared_ptr generate(); + + private: + uint32_t _width; + uint32_t _height; + + std::shared_ptr _renderQueue; + std::shared_ptr _renderPass; + std::shared_ptr _pipeline; +}; + +} // namespace atta::graphics diff --git a/src/atta/graphics/renderers/pbrRenderer.cpp b/src/atta/graphics/renderers/pbrRenderer.cpp index 623e6d12..7af81d48 100644 --- a/src/atta/graphics/renderers/pbrRenderer.cpp +++ b/src/atta/graphics/renderers/pbrRenderer.cpp @@ -22,6 +22,7 @@ namespace atta::graphics { PbrRenderer::PbrRenderer() : Renderer("PbrRenderer"), _firstRender(true), _wasResized(false), _lastEnvironmentImg(StringId()) { _irradiance = std::make_unique(); + _brdfLut = std::make_unique(); // Render Queue _renderQueue = graphics::create(); @@ -139,10 +140,13 @@ void PbrRenderer::render(std::shared_ptr camera) { _geometryPipeline->resize(_width, _height); _wasResized = false; } - // if (_firstRender) - // brdfLUT(); + if (_firstRender) { + _brdfLutImage = _brdfLut->generate(); + updateIblImageGroup(); + } + + // Check current environment map - // Check current envinroment map std::vector entities = component::getNoPrototypeView(); StringId currEnvironmentImg{}; for (auto entity : entities) { @@ -158,10 +162,9 @@ void PbrRenderer::render(std::shared_ptr camera) { _lastEnvironmentImg = currEnvironmentImg; if (_lastEnvironmentImg != StringId()) { _irradianceImage = _irradiance->createIrradianceCubemap(_lastEnvironmentImg); - if (_irradianceImage) - _geometryPipeline->updateImageGroup("iblImg", {{"irradianceMap", _irradianceImage}}); // prefilterCubemap(); } + updateIblImageGroup(); } // shadowPass(); @@ -579,6 +582,15 @@ void PbrRenderer::prefilterCubemap() { // graphics::getGraphicsAPI()->generateProcessedCubemap(info); } +void PbrRenderer::updateIblImageGroup() { + Pipeline::ImageGroup iblImages; + if (_irradianceImage) + iblImages.emplace_back("irradianceMap", _irradianceImage); + if (_brdfLutImage) + iblImages.emplace_back("brdfLUT", _brdfLutImage); + _geometryPipeline->updateImageGroup("iblImg", iblImages); +} + void PbrRenderer::brdfLUT() { // std::shared_ptr shader = graphics::create("shaders/compute/brdf.asl"); diff --git a/src/atta/graphics/renderers/pbrRenderer.h b/src/atta/graphics/renderers/pbrRenderer.h index 399c5d25..a91dcc90 100644 --- a/src/atta/graphics/renderers/pbrRenderer.h +++ b/src/atta/graphics/renderers/pbrRenderer.h @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2020-2026 Breno Cunha Queiroz #pragma once +#include #include #include #include @@ -30,6 +31,7 @@ class PbrRenderer final : public Renderer { void prefilterCubemap(); void brdfLUT(); + void updateIblImageGroup(); std::shared_ptr _renderQueue; std::shared_ptr _geometryRenderPass; @@ -53,6 +55,8 @@ class PbrRenderer final : public Renderer { // Irradiance image std::unique_ptr _irradiance; std::shared_ptr _irradianceImage; + std::unique_ptr _brdfLut; + std::shared_ptr _brdfLutImage; // Shadow mapping std::shared_ptr _shadowMapPipeline; From d0688c47ebc17c74789bb8f46655112f225ca674 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Mon, 9 Mar 2026 20:04:38 +0100 Subject: [PATCH 31/39] Feat: Full IBL pipeline implemented --- resources/shaders/pbrRenderer/pbrRenderer.asl | 4 +- src/atta/graphics/CMakeLists.txt | 1 + src/atta/graphics/apis/openGL/framebuffer.cpp | 6 +- src/atta/graphics/apis/openGL/framebuffer.h | 1 + src/atta/graphics/apis/vulkan/framebuffer.cpp | 53 ++++- src/atta/graphics/apis/vulkan/framebuffer.h | 1 + src/atta/graphics/apis/vulkan/image.cpp | 34 +++- src/atta/graphics/apis/vulkan/image.h | 7 +- src/atta/graphics/compute/prefilter.cpp | 103 ++++++++++ src/atta/graphics/compute/prefilter.h | 32 +++ src/atta/graphics/framebuffer.h | 2 + src/atta/graphics/renderers/pbrRenderer.cpp | 183 +----------------- src/atta/graphics/renderers/pbrRenderer.h | 5 +- 13 files changed, 240 insertions(+), 192 deletions(-) create mode 100644 src/atta/graphics/compute/prefilter.cpp create mode 100644 src/atta/graphics/compute/prefilter.h diff --git a/resources/shaders/pbrRenderer/pbrRenderer.asl b/resources/shaders/pbrRenderer/pbrRenderer.asl index de1eff1b..cf230da1 100644 --- a/resources/shaders/pbrRenderer/pbrRenderer.asl +++ b/resources/shaders/pbrRenderer/pbrRenderer.asl @@ -37,7 +37,7 @@ perFrame float omniFarPlane; // Environment light - IBL perFrame mat3 environmentLightOri; -// perFrame samplerCube prefilterMap; +perFrame samplerCube prefilterMap; perFrame samplerCube irradianceMap; perFrame sampler2D brdfLUT; @@ -151,7 +151,7 @@ void fragment(out vec4 outColor) { // Specular IBL const float MAX_REFLECTION_LOD = 4.0; vec3 R = reflect(-V, N); - vec3 prefilteredColor = vec3(1, 0, 0); // textureLod(prefilterMap, environmentLightOri * R, roughness * MAX_REFLECTION_LOD).rgb; + vec3 prefilteredColor = textureLod(prefilterMap, environmentLightOri * R, roughness * MAX_REFLECTION_LOD).rgb; vec2 brdf = texture(brdfLUT, vec2(max(dot(environmentLightOri * N, V), 0.0), roughness)).rg; vec3 specular = prefilteredColor * (F * brdf.x + brdf.y); diff --git a/src/atta/graphics/CMakeLists.txt b/src/atta/graphics/CMakeLists.txt index 21f22d28..59442110 100644 --- a/src/atta/graphics/CMakeLists.txt +++ b/src/atta/graphics/CMakeLists.txt @@ -21,6 +21,7 @@ set(ATTA_GRAPHICS_MODULE_SOURCE compute/entityClick.cpp compute/equiToCubemap.cpp compute/irradiance.cpp + compute/prefilter.cpp renderers/common/drawerPipeline.cpp renderers/common/gridPipeline.cpp diff --git a/src/atta/graphics/apis/openGL/framebuffer.cpp b/src/atta/graphics/apis/openGL/framebuffer.cpp index 506615e1..b3fef2e0 100644 --- a/src/atta/graphics/apis/openGL/framebuffer.cpp +++ b/src/atta/graphics/apis/openGL/framebuffer.cpp @@ -143,11 +143,13 @@ void Framebuffer::setLayer(uint32_t layer) { std::shared_ptr image = std::dynamic_pointer_cast(_images[0]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer, image->getHandle(), 0); - // Unbind framebuffer + // Unbind framebuffer glBindFramebuffer(GL_FRAMEBUFFER, 0); } -//------------------------------------------------// +void Framebuffer::setLayerAndMip(uint32_t layer, uint32_t mipLevel) { + LOG_WARN("gfx::gl::Framebuffer", "setLayerAndMip is not implemented for OpenGL"); +} //---------- Atta to OpenGL conversions ----------// //------------------------------------------------// GLenum Framebuffer::convertDepthAttachmentType(Image::Format format) { diff --git a/src/atta/graphics/apis/openGL/framebuffer.h b/src/atta/graphics/apis/openGL/framebuffer.h index e2dda7b0..1e6a1eb3 100644 --- a/src/atta/graphics/apis/openGL/framebuffer.h +++ b/src/atta/graphics/apis/openGL/framebuffer.h @@ -18,6 +18,7 @@ class Framebuffer final : public gfx::Framebuffer { void resize(uint32_t width, uint32_t height, bool forceRecreate = false) override; void setLayer(uint32_t layer) override; + void setLayerAndMip(uint32_t layer, uint32_t mipLevel) override; static GLenum convertDepthAttachmentType(Image::Format format); diff --git a/src/atta/graphics/apis/vulkan/framebuffer.cpp b/src/atta/graphics/apis/vulkan/framebuffer.cpp index 9e19c5d5..d03d5d3a 100644 --- a/src/atta/graphics/apis/vulkan/framebuffer.cpp +++ b/src/atta/graphics/apis/vulkan/framebuffer.cpp @@ -94,6 +94,46 @@ void Framebuffer::setLayer(uint32_t layer) { } } +void Framebuffer::setLayerAndMip(uint32_t layer, uint32_t mipLevel) { + if (_images.size() != 1) { + LOG_ERROR("gfx::vk::Framebuffer", "setLayerAndMip is only supported for framebuffers with one attachment, not for framebuffer [w]$0[]", _debugName); + return; + } + + std::shared_ptr vkImage = std::dynamic_pointer_cast(_images[0]); + if (!vkImage->isCubemap()) { + LOG_ERROR("gfx::vk::Framebuffer", "setLayerAndMip called on a non-cubemap image for framebuffer [w]$0[]", _debugName); + return; + } + + if (_framebuffer != VK_NULL_HANDLE) { + vkDestroyFramebuffer(_device->getHandle(), _framebuffer, nullptr); + _framebuffer = VK_NULL_HANDLE; + } + + VkImageView faceView = vkImage->getCubemapFaceMipImageViewHandle(layer, mipLevel); + if (faceView == VK_NULL_HANDLE) { + LOG_ERROR("gfx::vk::Framebuffer", "Failed to get cubemap face+mip image view for layer $0 mip $1", layer, mipLevel); + return; + } + + std::vector attachments = {faceView}; + + VkFramebufferCreateInfo fbInfo{}; + fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + fbInfo.renderPass = _renderPass->getHandle(); + fbInfo.attachmentCount = static_cast(attachments.size()); + fbInfo.pAttachments = attachments.data(); + fbInfo.width = _width; + fbInfo.height = _height; + fbInfo.layers = 1; + + if (vkCreateFramebuffer(_device->getHandle(), &fbInfo, nullptr, &_framebuffer) != VK_SUCCESS) { + LOG_ERROR("gfx::vk::Framebuffer", "Failed to create framebuffer for cubemap layer $0 mip $1", layer, mipLevel); + return; + } +} + VkFramebuffer Framebuffer::getHandle() const { return _framebuffer; } std::shared_ptr Framebuffer::getDevice() const { return _device; } std::shared_ptr Framebuffer::getRenderPass() const { return _renderPass; } @@ -105,10 +145,17 @@ void Framebuffer::create(std::shared_ptr renderPass) { if (_framebuffer != VK_NULL_HANDLE) vkDestroyFramebuffer(_device->getHandle(), _framebuffer, nullptr); - // Get image views + // Get image views (framebuffer attachments must have levelCount == 1) std::vector attachments; - for (std::shared_ptr image : _images) - attachments.push_back(std::dynamic_pointer_cast(image)->getImageViewHandle()); + for (std::shared_ptr image : _images) { + auto vkImage = std::dynamic_pointer_cast(image); + // Cubemap images: use face 0 mip 0 as a valid single-mip placeholder. + // setLayer() or setLayerAndMip() will replace it with the correct view before rendering. + if (vkImage->isCubemap()) + attachments.push_back(vkImage->getCubemapFaceMipImageViewHandle(0, 0)); + else + attachments.push_back(vkImage->getImageViewHandle()); + } // Create framebuffer VkFramebufferCreateInfo framebufferInfo{}; diff --git a/src/atta/graphics/apis/vulkan/framebuffer.h b/src/atta/graphics/apis/vulkan/framebuffer.h index 1d9e59a2..30b144fe 100644 --- a/src/atta/graphics/apis/vulkan/framebuffer.h +++ b/src/atta/graphics/apis/vulkan/framebuffer.h @@ -22,6 +22,7 @@ class Framebuffer final : public gfx::Framebuffer { void resize(uint32_t width, uint32_t height, bool forceRecreate = false) override; void setLayer(uint32_t layer) override; + void setLayerAndMip(uint32_t layer, uint32_t mipLevel) override; VkFramebuffer getHandle() const; std::shared_ptr getDevice() const; diff --git a/src/atta/graphics/apis/vulkan/image.cpp b/src/atta/graphics/apis/vulkan/image.cpp index d076539c..21fe4550 100644 --- a/src/atta/graphics/apis/vulkan/image.cpp +++ b/src/atta/graphics/apis/vulkan/image.cpp @@ -206,6 +206,32 @@ VkImageView Image::getCubemapImageViewHandle(uint32_t layer) const { return _cubemapImageViews[layer]; } +VkImageView Image::getCubemapFaceMipImageViewHandle(uint32_t layer, uint32_t mipLevel) { + uint32_t key = layer * _mipLevels + mipLevel; + auto it = _cubemapFaceMipImageViews.find(key); + if (it != _cubemapFaceMipImageViews.end()) + return it->second; + + VkImageViewCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = _image; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = convertFormat(_supportedFormat); + info.subresourceRange.aspectMask = convertAspectFlags(_supportedFormat); + info.subresourceRange.baseMipLevel = mipLevel; + info.subresourceRange.levelCount = 1; + info.subresourceRange.baseArrayLayer = layer; + info.subresourceRange.layerCount = 1; + + VkImageView view; + if (vkCreateImageView(_device->getHandle(), &info, nullptr, &view) != VK_SUCCESS) { + LOG_ERROR("gfx::vk::Image", "Failed to create cubemap face+mip image view (layer=$0 mip=$1) for [w]$2[]", layer, mipLevel, _debugName); + return VK_NULL_HANDLE; + } + _cubemapFaceMipImageViews[key] = view; + return view; +} + VkSampler Image::getSamplerHandle() const { return _sampler; } std::shared_ptr Image::getDevice() const { return _device; } @@ -342,6 +368,7 @@ VkImageAspectFlags Image::convertAspectFlags(Image::Format format) { return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; } ASSERT(false, "Could not convert atta format to vulkan aspect flags. Unknown image format"); + return VK_IMAGE_ASPECT_COLOR_BIT; } void Image::createImage() { @@ -349,7 +376,7 @@ void Image::createImage() { info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; info.imageType = VK_IMAGE_TYPE_2D; info.extent = {_width, _height, 1}; - info.mipLevels = 1; + info.mipLevels = _mipLevels; // If this is a cubemap, use 6 layers, else 1. if (_isCubemap) { info.arrayLayers = 6; @@ -456,6 +483,9 @@ void Image::destroy() { for (const VkImageView& imageView : _cubemapImageViews) if (imageView != VK_NULL_HANDLE) vkDestroyImageView(_device->getHandle(), imageView, nullptr); + for (auto& [key, imageView] : _cubemapFaceMipImageViews) + if (imageView != VK_NULL_HANDLE) + vkDestroyImageView(_device->getHandle(), imageView, nullptr); if (_sampler != VK_NULL_HANDLE) vkDestroySampler(_device->getHandle(), _sampler, nullptr); if (_image != VK_NULL_HANDLE && _destroyImage) @@ -521,7 +551,7 @@ void Image::transitionLayout(VkCommandBuffer commandBuffer, VkImageLayout newLay barrier.image = _image; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.levelCount = _mipLevels; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = _isCubemap ? 6 : 1; diff --git a/src/atta/graphics/apis/vulkan/image.h b/src/atta/graphics/apis/vulkan/image.h index d530b68c..d41879a9 100644 --- a/src/atta/graphics/apis/vulkan/image.h +++ b/src/atta/graphics/apis/vulkan/image.h @@ -6,6 +6,7 @@ #include #include #include +#include namespace atta::graphics::vk { @@ -43,6 +44,7 @@ class Image final : public gfx::Image { VkImage getImageHandle() const; VkImageView getImageViewHandle() const; VkImageView getCubemapImageViewHandle(uint32_t layer) const; + VkImageView getCubemapFaceMipImageViewHandle(uint32_t layer, uint32_t mipLevel); VkSampler getSamplerHandle() const; std::shared_ptr getDevice() const; @@ -102,8 +104,9 @@ class Image final : public gfx::Image { std::vector _supportedData; ///< Converted _data to _supportedFormat VkImage _image; - VkImageView _imageView; // Image view for the whole image (all layers) - std::array _cubemapImageViews{}; // Image view for each face of a cubemap image + VkImageView _imageView; // Image view for the whole image (all layers) + std::array _cubemapImageViews{}; // Image view for each face of a cubemap image + std::unordered_map _cubemapFaceMipImageViews; // Image view for each face+mip of a cubemap image VkSampler _sampler; VkDeviceMemory _memory; VkImageLayout _layout; diff --git a/src/atta/graphics/compute/prefilter.cpp b/src/atta/graphics/compute/prefilter.cpp new file mode 100644 index 00000000..c4be544e --- /dev/null +++ b/src/atta/graphics/compute/prefilter.cpp @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2020-2026 Breno Cunha Queiroz +#include +#include + +namespace atta::graphics { + +Prefilter::Prefilter() {} + +std::shared_ptr Prefilter::createPrefilterCubemap(StringId environmentCubemapSid) { + LOG_VERBOSE("gfx::Prefilter", "Create prefilter cubemap from [w]$0[]", environmentCubemapSid); + + std::shared_ptr environmentCubemap = gfx::Manager::getInstance().getImage(environmentCubemapSid); + if (environmentCubemap == nullptr) { + LOG_ERROR("gfx::Prefilter", "Could not create prefilter cubemap, image [w]$0[] does not exist", environmentCubemapSid); + return nullptr; + } + + // Create prefilter cubemap image with mip levels + gfx::Image::CreateInfo imageInfo{}; + imageInfo.format = environmentCubemap->getFormat(); + imageInfo.width = _baseSize; + imageInfo.height = _baseSize; + imageInfo.mipLevels = _numMips; + imageInfo.isCubemap = true; + imageInfo.debugName = environmentCubemapSid.getString() + " Prefilter"; + std::shared_ptr prefilterImage = graphics::create(imageInfo); + + // Create render queue (reused for all passes) + auto renderQueue = graphics::create(); + + // Same face orientations as equiToCubemap/irradiance (learnopengl convention, no Y-flip) + mat4 proj = perspective(M_PI / 2.0f, 1.0f, 0.1f, 10.0f); + std::array views = { + lookAt(vec3(0.0f), vec3( 1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // +X face + lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // -X face + lookAt(vec3(0.0f), vec3( 0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), // +Y face + lookAt(vec3(0.0f), vec3( 0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f)), // -Y face + lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, 1.0f), vec3(0.0f, -1.0f, 0.0f)), // +Z face + lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f)), // -Z face + }; + + // One framebuffer + renderpass + pipeline per mip level (different dimensions) + for (uint32_t mip = 0; mip < _numMips; mip++) { + uint32_t mipSize = std::max(1u, _baseSize >> mip); + float roughness = static_cast(mip) / static_cast(_numMips - 1); + + // Create framebuffer at this mip's resolution + Framebuffer::Attachment attachment{}; + attachment.image = prefilterImage; + Framebuffer::CreateInfo framebufferInfo{}; + framebufferInfo.attachments.push_back(attachment); + framebufferInfo.width = mipSize; + framebufferInfo.height = mipSize; + framebufferInfo.clearColor = {0, 0, 0, 0}; + framebufferInfo.debugName = StringId("Prefilter Framebuffer mip" + std::to_string(mip)); + auto framebuffer = graphics::create(framebufferInfo); + + // Create render pass + RenderPass::CreateInfo renderPassInfo{}; + renderPassInfo.framebuffer = framebuffer; + renderPassInfo.debugName = StringId("Prefilter Render Pass mip" + std::to_string(mip)); + auto renderPass = graphics::create(renderPassInfo); + + // Create pipeline (reuses same shader, different renderpass/framebuffer per mip) + Pipeline::CreateInfo pipelineInfo{}; + pipelineInfo.shader = graphics::create("shaders/compute/prefilter.asl"); + pipelineInfo.renderPass = renderPass; + auto pipeline = graphics::create(pipelineInfo); + + // Bind environment cubemap + pipeline->createImageGroup(Pipeline::ImageGroupType::PER_FRAME, "envImg"); + pipeline->updateImageGroup("envImg", {{"uEnvironmentMap", environmentCubemap}}); + + // Render 6 faces at this mip level + for (uint32_t face = 0; face < 6; face++) { + framebuffer->setLayerAndMip(face, mip); + renderQueue->begin(); + { + renderPass->begin(renderQueue); + { + pipeline->begin(); + { + pipeline->setMat4("uProjection", proj); + pipeline->setMat4("uView", views[face]); + pipeline->setFloat("uRoughness", roughness); + pipeline->setImageGroup("envImg"); + pipeline->renderCube(); + } + pipeline->end(); + } + renderPass->end(); + } + renderQueue->end(); + } + } + + prefilterImage->prepareForSampling(); + + return prefilterImage; +} + +} // namespace atta::graphics diff --git a/src/atta/graphics/compute/prefilter.h b/src/atta/graphics/compute/prefilter.h new file mode 100644 index 00000000..97fc956d --- /dev/null +++ b/src/atta/graphics/compute/prefilter.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2020-2026 Breno Cunha Queiroz +#pragma once + +#include +#include +#include + +namespace atta::graphics { + +class Prefilter { + public: + Prefilter(); + + /** + * @brief Create a prefiltered specular cubemap from an environment cubemap + * + * Generates a cubemap with multiple mip levels, each representing the environment + * convolved with a GGX lobe of increasing roughness (mip 0 = mirror-like, mip 4 = fully rough). + * Used for the specular IBL split-sum approximation. + * + * @param environmentCubemapSid Name of the environment cubemap gfx::Image to convolve + * @return The prefiltered cubemap image with mip levels, already transitioned to shader-read layout + */ + std::shared_ptr createPrefilterCubemap(StringId environmentCubemapSid); + + private: + static constexpr uint32_t _baseSize = 128; + static constexpr uint32_t _numMips = 5; +}; + +} // namespace atta::graphics diff --git a/src/atta/graphics/framebuffer.h b/src/atta/graphics/framebuffer.h index 120ea28b..cee7be77 100644 --- a/src/atta/graphics/framebuffer.h +++ b/src/atta/graphics/framebuffer.h @@ -47,6 +47,8 @@ class Framebuffer { // When the attachment is a cubemap image, you can use this method set the layer to be used virtual void setLayer(uint32_t layer) = 0; + // When the attachment is a cubemap image with multiple mip levels, you can use this method to set the layer and mip level + virtual void setLayerAndMip(uint32_t layer, uint32_t mipLevel) = 0; std::vector> getImages() const; std::shared_ptr getImage(uint32_t attachment = 0); diff --git a/src/atta/graphics/renderers/pbrRenderer.cpp b/src/atta/graphics/renderers/pbrRenderer.cpp index 7af81d48..10f26762 100644 --- a/src/atta/graphics/renderers/pbrRenderer.cpp +++ b/src/atta/graphics/renderers/pbrRenderer.cpp @@ -22,6 +22,7 @@ namespace atta::graphics { PbrRenderer::PbrRenderer() : Renderer("PbrRenderer"), _firstRender(true), _wasResized(false), _lastEnvironmentImg(StringId()) { _irradiance = std::make_unique(); + _prefilter = std::make_unique(); _brdfLut = std::make_unique(); // Render Queue @@ -162,7 +163,7 @@ void PbrRenderer::render(std::shared_ptr camera) { _lastEnvironmentImg = currEnvironmentImg; if (_lastEnvironmentImg != StringId()) { _irradianceImage = _irradiance->createIrradianceCubemap(_lastEnvironmentImg); - // prefilterCubemap(); + _prefilterImage = _prefilter->createPrefilterCubemap(_lastEnvironmentImg); } updateIblImageGroup(); } @@ -419,193 +420,17 @@ void PbrRenderer::geometryPass(std::shared_ptr camera) { _geometryRenderPass->end(); } _renderQueue->end(); - //_geometryPipeline->begin(); - //{ - // //---------- PBR shader ----------// - // std::shared_ptr shader = _geometryPipeline->getShader(); - // shader->bind(); - - // shader->setMat4("projection", transpose(camera->getProj())); - // shader->setMat4("view", transpose(camera->getView())); - // shader->setVec3("camPos", camera->getPosition()); - // shader->setMat4("directionalLightMatrix", transpose(_directionalLightMatrix)); - // shader->setImage("directionalShadowMap", _shadowMapPipeline->getRenderPass()->getFramebuffer()->getImage()); - // shader->setCubemap("omniShadowMap", _omnidirectionalShadowMap); - // shader->setFloat("omniFarPlane", 25.0f); - - // // Always set environment textures (if there is no environment map, use white texture) - // shader->setCubemap("irradianceMap", "PbrRenderer::irradiance"); - // shader->setCubemap("prefilterMap", "PbrRenderer::prefilter"); - // shader->setImage("brdfLUT", "PbrRenderer::brdfLUT"); - - // if (_lastEnvironmentImg != "Not defined"_sid) - // shader->setInt("numEnvironmentLights", 1); - // else { - // LOG_WARN("graphics::PbrRenderer", "Number of environment light should always be 1 (white texture if not defined)"); - // shader->setInt("numEnvironmentLights", 0); - // } - // shader->setMat3("environmentLightOri", mat3(1.0f)); - - // //----- Lighting -----// - // _environmentMapOri = mat3(1.0f); - // int numPointLights = 0; - // int numDirectionalLights = 0; - // for (auto entity : entities) { - // component::Transform* transform = component::getComponent(entity); - // component::PointLight* pl = component::getComponent(entity); - // component::DirectionalLight* dl = component::getComponent(entity); - // component::EnvironmentLight* el = component::getComponent(entity); - - // if (transform && (pl || dl || el)) { - // if (pl && numPointLights < 10) { - // vec3 position = transform->getWorldTransformMatrix(entity).getPosition(); - // int i = numPointLights++; - // shader->setVec3(("pointLights[" + std::to_string(i) + "].position").c_str(), position); - // shader->setVec3(("pointLights[" + std::to_string(i) + "].intensity").c_str(), pl->intensity); - // } - // if (dl) { - // vec3 base = {0.0f, 0.0f, -1.0f}; - // mat4 rotation; - // rotation.setPosOri({0, 0, 0}, transform->orientation); - // base = transpose(mat3(rotation)) * base; - // shader->setVec3("directionalLight.direction", base); - // shader->setVec3("directionalLight.intensity", dl->intensity); - // numDirectionalLights++; - // } - // if (el) { - // mat4 ori; - // ori.setPosOri(vec3(0.0f), transform->orientation); - // _environmentMapOri = mat3(ori); - // shader->setMat3("environmentLightOri", transpose(_environmentMapOri)); - // } - // if (numPointLights++ == 10) - // LOG_WARN("graphics::PbrRenderer", "Maximum number of point lights reached, 10 lights"); - // } - // } - // shader->setInt("numPointLights", numPointLights); - // shader->setInt("numDirectionalLights", numDirectionalLights ? 1 : 0); - - // //----- Entities -----// - // for (auto entity : entities) { - // component::Mesh* mesh = component::getComponent(entity); - // component::Transform* transform = component::getComponent(entity); - // component::Material* compMat = component::getComponent(entity); - // resource::Material* material = compMat ? compMat->getResource() : nullptr; - - // if (mesh && transform) { - // mat4 model = transpose(transform->getWorldTransformMatrix(entity)); - // mat4 invModel = inverse(model); - // shader->setMat4("model", model); - // shader->setMat4("invModel", invModel); - - // if (material) { - // if (material->colorIsImage()) { - // shader->setImage("albedoTexture", material->colorImage); - // shader->setVec3("material.albedo", {-1, -1, -1}); - // } else - // shader->setVec3("material.albedo", material->color); - - // if (material->metallicIsImage()) { - // shader->setImage("metallicTexture", material->metallicImage); - // shader->setFloat("material.metallic", -1); - // } else - // shader->setFloat("material.metallic", material->metallic); - - // if (material->roughnessIsImage()) { - // shader->setImage("roughnessTexture", material->roughnessImage); - // shader->setFloat("material.roughness", -1); - // } else - // shader->setFloat("material.roughness", material->roughness); - - // if (material->aoIsImage()) { - // shader->setImage("aoTexture", material->aoImage); - // shader->setFloat("material.ao", -1); - // } else - // shader->setFloat("material.ao", material->ao); - - // if (material->hasNormalImage()) { - // shader->setImage("normalTexture", material->normalImage); - // shader->setInt("material.hasNormalTexture", 1); - // } else - // shader->setInt("material.hasNormalTexture", 0); - // } else { - // resource::Material::CreateInfo defaultMaterial{}; - // shader->setVec3("material.albedo", defaultMaterial.color); - // shader->setFloat("material.metallic", defaultMaterial.metallic); - // shader->setFloat("material.roughness", defaultMaterial.roughness); - // shader->setFloat("material.ao", defaultMaterial.ao); - // shader->setInt("material.hasNormalTexture", 0); - // } - - // graphics::getGraphicsAPI()->renderMesh(mesh->sid); - // } - // } - - // //---------- Background shader ----------// - // if (_lastEnvironmentImg != "Not defined"_sid) { - // _backgroundShader->bind(); - // _backgroundShader->setMat4("projection", transpose(camera->getProj())); - // _backgroundShader->setMat4("view", transpose(camera->getView())); - // _backgroundShader->setCubemap("environmentMap", _lastEnvironmentImg); - // //_backgroundShader->setCubemap("environmentMap", _omnidirectionalShadowMap); - // //_backgroundShader->setCubemap("environmentMap", "PbrRenderer::irradiance"); - // _backgroundShader->setMat3("environmentMapOri", _environmentMapOri); - - // graphics::getGraphicsAPI()->renderCube(); - // } - //} - //_geometryPipeline->end(); -} - -void PbrRenderer::prefilterCubemap() { - //// Create shader - // std::shared_ptr shader = graphics::create("shaders/compute/asl.vert"); - - //// Generate prefilter cubemap - // GraphicsAPI::GenerateProcessedCubemapInfo info; - // info.cubemapSid = StringId("PbrRenderer::prefilter"); - // info.shader = shader; - // info.width = 512; - // info.height = 512; - // info.numMipLevels = 5; - // info.func = [&](std::shared_ptr shader, mat4 proj, mat4 view, int face, int mipLevel) { - // if (mipLevel == 0 && face == 0) { - // // TODO shader->setCubemap("environmentMap", _lastEnvironmentImg); - // shader->setMat4("projection", transpose(proj)); - // } - - // shader->setMat4("view", transpose(view)); - - // float roughness = (float)mipLevel / (float)(info.numMipLevels - 1); - // shader->setFloat("roughness", roughness); - //}; - // graphics::getGraphicsAPI()->generateProcessedCubemap(info); } void PbrRenderer::updateIblImageGroup() { Pipeline::ImageGroup iblImages; if (_irradianceImage) iblImages.emplace_back("irradianceMap", _irradianceImage); + if (_prefilterImage) + iblImages.emplace_back("prefilterMap", _prefilterImage); if (_brdfLutImage) iblImages.emplace_back("brdfLUT", _brdfLutImage); _geometryPipeline->updateImageGroup("iblImg", iblImages); } -void PbrRenderer::brdfLUT() { - // std::shared_ptr shader = graphics::create("shaders/compute/brdf.asl"); - - //// Generate brdf LUT - // GraphicsAPI::GenerateProcessedTextureInfo info; - // info.textureSid = StringId("PbrRenderer::brdfLUT"); - // info.shader = shader; - // info.imageInfo.format = Image::Format::RG16F; - // info.imageInfo.samplerWrap = Image::Wrap::REPEAT; - // info.imageInfo.width = 512; - // info.imageInfo.height = 512; - // info.imageInfo.mipLevels = 1; - // info.imageInfo.debugName = StringId("PbrRenderer brdfLUT image"); - - // graphics::getGraphicsAPI()->generateProcessedTexture(info); -} - } // namespace atta::graphics diff --git a/src/atta/graphics/renderers/pbrRenderer.h b/src/atta/graphics/renderers/pbrRenderer.h index a91dcc90..f9e8ff39 100644 --- a/src/atta/graphics/renderers/pbrRenderer.h +++ b/src/atta/graphics/renderers/pbrRenderer.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -29,8 +30,6 @@ class PbrRenderer final : public Renderer { void shadowPass(); void geometryPass(std::shared_ptr camera); - void prefilterCubemap(); - void brdfLUT(); void updateIblImageGroup(); std::shared_ptr _renderQueue; @@ -55,6 +54,8 @@ class PbrRenderer final : public Renderer { // Irradiance image std::unique_ptr _irradiance; std::shared_ptr _irradianceImage; + std::unique_ptr _prefilter; + std::shared_ptr _prefilterImage; std::unique_ptr _brdfLut; std::shared_ptr _brdfLutImage; From 5bf67fa9ad4d9792f5afbab289f98a9ff3a9f2a4 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Tue, 10 Mar 2026 06:20:18 +0100 Subject: [PATCH 32/39] Feat: Directional light shadows --- resources/shaders/pbrRenderer/pbrRenderer.asl | 30 ++-- resources/shaders/pbrRenderer/shadow.asl | 10 ++ src/atta/graphics/apis/vulkan/framebuffer.cpp | 12 +- src/atta/graphics/apis/vulkan/image.cpp | 29 +++- src/atta/graphics/apis/vulkan/pipeline.cpp | 23 +-- src/atta/graphics/apis/vulkan/renderPass.cpp | 2 +- src/atta/graphics/renderers/pbrRenderer.cpp | 160 +++++++++--------- src/atta/graphics/renderers/pbrRenderer.h | 6 +- 8 files changed, 158 insertions(+), 114 deletions(-) create mode 100644 resources/shaders/pbrRenderer/shadow.asl diff --git a/resources/shaders/pbrRenderer/pbrRenderer.asl b/resources/shaders/pbrRenderer/pbrRenderer.asl index cf230da1..872203d1 100644 --- a/resources/shaders/pbrRenderer/pbrRenderer.asl +++ b/resources/shaders/pbrRenderer/pbrRenderer.asl @@ -31,8 +31,8 @@ perFrame PointLight pointLights[MAX_NUM_POINT_LIGHTS]; // Directional light - Shadow map perFrame DirectionalLight directionalLight; -// perFrame sampler2D directionalShadowMap; // perFrame samplerCube omniShadowMap; +perFrame sampler2D directionalShadowMap; perFrame float omniFarPlane; // Environment light - IBL @@ -129,11 +129,12 @@ void fragment(out vec4 outColor) { } //----- Directional light -----// + float shadow = 0.0f; if (numDirectionalLights == 1) { vec3 L = -directionalLight.direction; vec3 radiance = directionalLight.intensity; - float shadow = 0.0f; // directionalShadowCalculation(posDirectionalLightSpace, N, L); + shadow = directionalShadowCalculation(posDirectionalLightSpace, N, L); Lo += calcLightContribution(L, N, V, radiance, albedo, metallic, roughness, F0) * (1.0f - shadow); } @@ -156,8 +157,10 @@ void fragment(out vec4 outColor) { vec3 specular = prefilteredColor * (F * brdf.x + brdf.y); ambient = (kD * diffuse + specular) * ao; + // Partially darken ambient in shadowed areas (indirect light is reduced but not eliminated) + ambient *= (1.0 - shadow * 0.5); } else if (numEnvironmentLights == 0) { - ambient = vec3(0.03) * albedo * ao; + ambient = vec3(0.03) * albedo * ao * (1.0 - shadow * 0.5); } vec3 color = ambient + Lo; @@ -192,27 +195,24 @@ vec3 calcLightContribution(vec3 L, vec3 N, vec3 V, vec3 radiance, vec3 albedo, f } float directionalShadowCalculation(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) { - // perform perspective divide + // Perspective divide (no-op for orthographic, but correct for general use) vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; - // transform to [0,1] range - projCoords = projCoords * 0.5 + 0.5; - // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) - float closestDepth = 0.0f; // texture(directionalShadowMap, projCoords.xy).r; - // get depth of current fragment from light's perspective - float currentDepth = projCoords.z; - // check whether current frag pos is in shadow + // Remap XY from [-1,1] to [0,1] UV range; Z is already in [0,1] (Vulkan NDC) + projCoords.xy = projCoords.xy * 0.5 + 0.5; + // Bias to reduce shadow acne (scale by surface angle to light) float bias = max(0.01 * (1.0 - dot(normal, lightDir)), 0.0001); float shadow = 0.0; - ivec2 texSize = ivec2(0, 0); // textureSize(directionalShadowMap, 0); - vec2 texelSize = 1.0f / vec2(texSize); + vec2 texelSize = 1.0 / vec2(textureSize(directionalShadowMap, 0)); + // 5x5 PCF kernel for (int x = -2; x <= 2; ++x) { for (int y = -2; y <= 2; ++y) { - float pcfDepth = 0.0f; // texture(directionalShadowMap, projCoords.xy + vec2(x, y) * texelSize).r; - shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; + float pcfDepth = texture(directionalShadowMap, projCoords.xy + vec2(x, y) * texelSize).r; + shadow += projCoords.z - bias > pcfDepth ? 1.0 : 0.0; } } shadow /= 25.0; + // No shadow outside the light frustum if (projCoords.z > 1.0) shadow = 0.0; diff --git a/resources/shaders/pbrRenderer/shadow.asl b/resources/shaders/pbrRenderer/shadow.asl new file mode 100644 index 00000000..5c920a4d --- /dev/null +++ b/resources/shaders/pbrRenderer/shadow.asl @@ -0,0 +1,10 @@ +perFrame mat4 lightSpaceMatrix; +perDraw mat4 model; + +vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { + return lightSpaceMatrix * model * vec4(iPos, 1.0); +} + +// Depth-only pass: no color output needed. +// gl_FragCoord.z is automatically written to the depth buffer by the rasterizer. +void fragment() {} diff --git a/src/atta/graphics/apis/vulkan/framebuffer.cpp b/src/atta/graphics/apis/vulkan/framebuffer.cpp index d03d5d3a..c32c7990 100644 --- a/src/atta/graphics/apis/vulkan/framebuffer.cpp +++ b/src/atta/graphics/apis/vulkan/framebuffer.cpp @@ -20,9 +20,15 @@ void Framebuffer::bind(bool clear) { } } void Framebuffer::unbind() { - VkImageLayout layout = _isSwapchain ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - for (size_t i = 0; i < _images.size(); i++) - std::dynamic_pointer_cast(_images[i])->setLayout(layout); + for (size_t i = 0; i < _images.size(); i++) { + auto vkImage = std::dynamic_pointer_cast(_images[i]); + VkImageLayout layout; + if (gfx::Image::isDepthFormat(vkImage->getSupportedFormat()) || gfx::Image::isStencilFormat(vkImage->getSupportedFormat())) + layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + else + layout = _isSwapchain ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + vkImage->setLayout(layout); + } } void Framebuffer::resize(uint32_t width, uint32_t height, bool forceRecreate) { diff --git a/src/atta/graphics/apis/vulkan/image.cpp b/src/atta/graphics/apis/vulkan/image.cpp index 21fe4550..eadf496f 100644 --- a/src/atta/graphics/apis/vulkan/image.cpp +++ b/src/atta/graphics/apis/vulkan/image.cpp @@ -168,7 +168,7 @@ void Image::resize(uint32_t width, uint32_t height, bool forceRecreate) { createImage(); allocMemory(); createImageViews(); - if (isColorFormat(_supportedFormat)) + if (isColorFormat(_supportedFormat) || isDepthFormat(_supportedFormat)) createSampler(); // Transfer data if specified @@ -392,7 +392,7 @@ void Image::createImage() { info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; else if (isDepthFormat(_supportedFormat)) - info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; info.samples = VK_SAMPLE_COUNT_1_BIT; if (vkCreateImage(_device->getHandle(), &info, nullptr, &_image) != VK_SUCCESS) @@ -428,15 +428,26 @@ void Image::createImageViews() { } void Image::createSampler() { + VkSamplerAddressMode addressMode; + switch (_samplerWrap) { + case Wrap::CLAMP: + addressMode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + break; + case Wrap::REPEAT: + default: + addressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT; + break; + } + VkSamplerCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; // Mag/min filters info.magFilter = VK_FILTER_LINEAR; info.minFilter = VK_FILTER_LINEAR; // Address mode - info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; - info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; - info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeU = addressMode; + info.addressModeV = addressMode; + info.addressModeW = addressMode; info.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; // Anisotropy info.anisotropyEnable = VK_TRUE; @@ -452,7 +463,7 @@ void Image::createSampler() { info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; info.mipLodBias = 0.0f; info.minLod = 0.0f; - info.maxLod = 0.0f; + info.maxLod = static_cast(_mipLevels - 1); if (vkCreateSampler(_device->getHandle(), &info, nullptr, &_sampler) != VK_SUCCESS) LOG_ERROR("gfx::vk::Image", "Failed to create texture sampler"); @@ -520,6 +531,10 @@ void populateLayoutStage(VkImageLayout layout, VkPipelineStageFlags* stage, VkAc *access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; *stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; break; + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + *access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + *stage = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + break; case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: *access = VK_ACCESS_SHADER_READ_BIT; *stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; @@ -549,7 +564,7 @@ void Image::transitionLayout(VkCommandBuffer commandBuffer, VkImageLayout newLay barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = _image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.aspectMask = convertAspectFlags(_supportedFormat); barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = _mipLevels; barrier.subresourceRange.baseArrayLayer = 0; diff --git a/src/atta/graphics/apis/vulkan/pipeline.cpp b/src/atta/graphics/apis/vulkan/pipeline.cpp index c20ce067..ece36470 100644 --- a/src/atta/graphics/apis/vulkan/pipeline.cpp +++ b/src/atta/graphics/apis/vulkan/pipeline.cpp @@ -86,21 +86,24 @@ Pipeline::Pipeline(const gfx::Pipeline::CreateInfo& info) : gfx::Pipeline(info), VkPipelineColorBlendAttachmentState colorBlendAttachment{}; colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; colorBlendAttachment.blendEnable = VK_FALSE; - if (gfx::Image::getNumChannels(_framebuffer->getImage(0)->getFormat()) == 4) { - colorBlendAttachment.blendEnable = VK_TRUE; - colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; - colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; - colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; - colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE; - colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + if (_framebuffer->hasColorAttachment()) { + int colorIdx = _framebuffer->getColorAttachmentIndex(); + if (gfx::Image::getNumChannels(_framebuffer->getImage(colorIdx)->getFormat()) == 4) { + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + } } VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.attachmentCount = _framebuffer->hasColorAttachment() ? 1 : 0; + colorBlending.pAttachments = _framebuffer->hasColorAttachment() ? &colorBlendAttachment : nullptr; // Dynamic state std::vector dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; diff --git a/src/atta/graphics/apis/vulkan/renderPass.cpp b/src/atta/graphics/apis/vulkan/renderPass.cpp index eb902bc5..99b0ad38 100644 --- a/src/atta/graphics/apis/vulkan/renderPass.cpp +++ b/src/atta/graphics/apis/vulkan/renderPass.cpp @@ -30,7 +30,7 @@ RenderPass::RenderPass(const graphics::RenderPass::CreateInfo& info) : graphics: depthAttachment.format = vk::Image::convertFormat(format); depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; diff --git a/src/atta/graphics/renderers/pbrRenderer.cpp b/src/atta/graphics/renderers/pbrRenderer.cpp index 10f26762..07906e33 100644 --- a/src/atta/graphics/renderers/pbrRenderer.cpp +++ b/src/atta/graphics/renderers/pbrRenderer.cpp @@ -64,40 +64,35 @@ PbrRenderer::PbrRenderer() : Renderer("PbrRenderer"), _firstRender(true), _wasRe ////---------- Create background shader ----------// //{ _backgroundShader = graphics::create("shaders/pbrRenderer/background.asl"); } - ////---------- Directional shadow mapping ----------// - //{ - // // Framebuffer - // Image::CreateInfo imageInfo; - // imageInfo.format = Image::Format::DEPTH32F; - // imageInfo.samplerWrap = Image::Wrap::BORDER; - // imageInfo.borderColor = vec4(1.0f); - // imageInfo.width = 1024; - // imageInfo.height = 1024; - // imageInfo.debugName = StringId("PbrRenderer::dirShadowMap::image"); - // std::shared_ptr depthImage = graphics::create(imageInfo); - - // Framebuffer::CreateInfo framebufferInfo{}; - // framebufferInfo.attachments.push_back({Image::Format::NONE, depthImage}); - // framebufferInfo.width = 1024; - // framebufferInfo.height = 1024; - // framebufferInfo.debugName = StringId("PbrRenderer::shadowMap::framebuffer"); - // std::shared_ptr framebuffer = graphics::create(framebufferInfo); - - // // Shader - // std::shared_ptr shader = graphics::create("shaders/pbrRenderer/shadow.asl"); - - // // Render Pass - // RenderPass::CreateInfo renderPassInfo{}; - // renderPassInfo.framebuffer = framebuffer; - // renderPassInfo.debugName = StringId("PbrRenderer::shadowMap::renderPass"); - // std::shared_ptr renderPass = graphics::create(renderPassInfo); + //---------- Directional shadow mapping ----------// + { + Image::CreateInfo imageInfo{}; + imageInfo.format = Image::Format::DEPTH32F; + imageInfo.samplerWrap = Image::Wrap::CLAMP; + imageInfo.width = 1024; + imageInfo.height = 1024; + imageInfo.debugName = StringId("PbrRenderer::dirShadowMap"); + _directionalShadowMap = graphics::create(imageInfo); + + Framebuffer::Attachment attachment{}; + attachment.image = _directionalShadowMap; + Framebuffer::CreateInfo framebufferInfo{}; + framebufferInfo.attachments.push_back(attachment); + framebufferInfo.width = 1024; + framebufferInfo.height = 1024; + framebufferInfo.debugName = StringId("PbrRenderer::shadowMap::framebuffer"); + _shadowMapFramebuffer = graphics::create(framebufferInfo); - // // Pipeline - // Pipeline::CreateInfo pipelineInfo{}; - // pipelineInfo.shader = shader; - // pipelineInfo.renderPass = renderPass; - // _shadowMapPipeline = graphics::create(pipelineInfo); - //} + RenderPass::CreateInfo renderPassInfo{}; + renderPassInfo.framebuffer = _shadowMapFramebuffer; + renderPassInfo.debugName = StringId("PbrRenderer::shadowMap::renderPass"); + _shadowMapRenderPass = graphics::create(renderPassInfo); + + Pipeline::CreateInfo pipelineInfo{}; + pipelineInfo.shader = graphics::create("shaders/pbrRenderer/shadow.asl"); + pipelineInfo.renderPass = _shadowMapRenderPass; + _shadowMapPipeline = graphics::create(pipelineInfo); + } ////---------- Omnidirectional shadow mapping ----------// //{ @@ -168,7 +163,7 @@ void PbrRenderer::render(std::shared_ptr camera) { updateIblImageGroup(); } - // shadowPass(); + shadowPass(); geometryPass(camera); // if (_renderSelected) @@ -188,50 +183,61 @@ void PbrRenderer::resize(uint32_t width, uint32_t height) { } void PbrRenderer::shadowPass() { - // std::vector entities = component::getNoPrototypeView(); - - ////----- Directional shadow mapping -----// - // component::EntityId directionalLightEntity = -1; - // for (auto entity : entities) { - // component::DirectionalLight* dl = component::getComponent(entity); - // component::Transform* t = component::getComponent(entity); - // if (dl && t) { - // directionalLightEntity = entity; - // break; - // } - // } - - // if (directionalLightEntity != -1) { - // _shadowMapPipeline->begin(); - // { - // std::shared_ptr shader = _shadowMapPipeline->getShader(); - // shader->bind(); - - // // Create light matrix - // component::Transform* t = component::getComponent(directionalLightEntity); - - // float height = 10.0f; - // float ratio = 1.0f; - // float far = 25.0f; - // mat4 proj = orthographic(height, ratio, far); - // mat4 view; - // view.setPosOri(vec3(), t->orientation); - // _directionalLightMatrix = proj * view; - // shader->setMat4("lightSpaceMatrix", transpose(_directionalLightMatrix)); + std::vector entities = component::getNoPrototypeView(); - // // Fill shadow map rendering the scene - // for (auto entity : entities) { - // component::Mesh* mesh = component::getComponent(entity); - // component::Transform* transform = component::getComponent(entity); + //----- Directional shadow mapping -----// + component::EntityId directionalLightEntity = -1; + for (auto entity : entities) { + component::DirectionalLight* dl = component::getComponent(entity); + component::Transform* t = component::getComponent(entity); + if (dl && t) { + directionalLightEntity = entity; + break; + } + } - // if (mesh && transform) { - // shader->setMat4("model", transpose(transform->getWorldTransformMatrix(entity))); - // graphics::getGraphicsAPI()->renderMesh(mesh->sid); - // } - // } - // } - // _shadowMapPipeline->end(); - //} + // Always run the shadow pass to keep the depth image in a valid layout for sampling. + // When a directional light exists, render scene geometry into the shadow map. + _renderQueue->begin(); + { + _shadowMapRenderPass->begin(_renderQueue); + { + if (directionalLightEntity != -1) { + component::Transform* t = component::getComponent(directionalLightEntity); + + // Compute light direction (forward is -Z in atta's Z-up coordinate system) + mat4 rotation; + rotation.setPosOri({0, 0, 0}, t->orientation); + vec3 lightDir = normalize(mat3(rotation) * vec3(0.0f, 0.0f, -1.0f)); + + // Position the light camera behind the scene along the incoming light direction. + // Use Z as up; if lightDir is nearly parallel to Z, fall back to Y. + vec3 lightPos = -lightDir * 20.0f; + vec3 up = (std::abs(lightDir.z) < 0.99f) ? vec3(0.0f, 0.0f, 1.0f) : vec3(0.0f, 1.0f, 0.0f); + mat4 lightView = lookAt(lightPos, vec3(0.0f, 0.0f, 0.0f), up); + mat4 lightProj = orthographic(20.0f, 1.0f, 40.0f); + _directionalLightMatrix = lightProj * lightView; + + _shadowMapPipeline->begin(); + { + _shadowMapPipeline->setMat4("lightSpaceMatrix", _directionalLightMatrix); + + for (auto entity : entities) { + component::Mesh* mesh = component::getComponent(entity); + component::Transform* transform = component::getComponent(entity); + if (mesh && transform) { + _shadowMapPipeline->setMat4("model", transform->getWorldTransformMatrix(entity)); + _shadowMapPipeline->renderMesh(mesh->sid); + } + } + } + _shadowMapPipeline->end(); + } + } + _shadowMapRenderPass->end(); + } + _renderQueue->end(); + _directionalShadowMap->prepareForSampling(); ////----- Omnidirectional shadow mapping -----// // component::EntityId pointLightEntity = -1; @@ -430,6 +436,8 @@ void PbrRenderer::updateIblImageGroup() { iblImages.emplace_back("prefilterMap", _prefilterImage); if (_brdfLutImage) iblImages.emplace_back("brdfLUT", _brdfLutImage); + if (_directionalShadowMap) + iblImages.emplace_back("directionalShadowMap", _directionalShadowMap); _geometryPipeline->updateImageGroup("iblImg", iblImages); } diff --git a/src/atta/graphics/renderers/pbrRenderer.h b/src/atta/graphics/renderers/pbrRenderer.h index f9e8ff39..144fee06 100644 --- a/src/atta/graphics/renderers/pbrRenderer.h +++ b/src/atta/graphics/renderers/pbrRenderer.h @@ -46,8 +46,6 @@ class PbrRenderer final : public Renderer { bool _wasResized; //----- Lighting -----// - // Directional light - mat3 _directionalLightMatrix; // Environment light StringId _lastEnvironmentImg; mat3 _environmentMapOri; @@ -61,6 +59,10 @@ class PbrRenderer final : public Renderer { // Shadow mapping std::shared_ptr _shadowMapPipeline; + std::shared_ptr _shadowMapRenderPass; + std::shared_ptr _shadowMapFramebuffer; + std::shared_ptr _directionalShadowMap; + mat4 _directionalLightMatrix; std::shared_ptr _omniShadowMapPipeline; std::shared_ptr _omnidirectionalShadowMap; }; From 51c4b3a4b8e5bb6a6094efb174fb0565f8535dd2 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Tue, 10 Mar 2026 06:47:02 +0100 Subject: [PATCH 33/39] Fix: Specular IBL with OpenGL API --- resources/shaders/pbrRenderer/background.frag | 17 ----------------- resources/shaders/pbrRenderer/background.vert | 18 ------------------ resources/shaders/pbrRenderer/shadow.frag | 12 ------------ resources/shaders/pbrRenderer/shadow.vert | 19 ------------------- src/atta/graphics/apis/openGL/framebuffer.cpp | 17 ++++++++++++++--- 5 files changed, 14 insertions(+), 69 deletions(-) delete mode 100644 resources/shaders/pbrRenderer/background.frag delete mode 100644 resources/shaders/pbrRenderer/background.vert delete mode 100644 resources/shaders/pbrRenderer/shadow.frag delete mode 100644 resources/shaders/pbrRenderer/shadow.vert diff --git a/resources/shaders/pbrRenderer/background.frag b/resources/shaders/pbrRenderer/background.frag deleted file mode 100644 index b3b643d0..00000000 --- a/resources/shaders/pbrRenderer/background.frag +++ /dev/null @@ -1,17 +0,0 @@ -#version 300 es -precision mediump float; -out vec4 FragColor; -in vec3 WorldPos; - -uniform samplerCube environmentMap; - -void main() -{ - vec3 envColor = texture(environmentMap, WorldPos).rgb; - - // HDR tonemap and gamma correct - envColor = envColor / (envColor + vec3(1.0)); - envColor = pow(envColor, vec3(1.0/2.2)); - - FragColor = vec4(envColor, 1.0); -} diff --git a/resources/shaders/pbrRenderer/background.vert b/resources/shaders/pbrRenderer/background.vert deleted file mode 100644 index 6546655e..00000000 --- a/resources/shaders/pbrRenderer/background.vert +++ /dev/null @@ -1,18 +0,0 @@ -#version 300 es -in vec3 aPos; - -uniform mat4 projection; -uniform mat4 view; -uniform mat3 environmentMapOri; - -out vec3 WorldPos; - -void main() -{ - WorldPos = aPos; - - mat4 rotView = mat4(mat3(view)*environmentMapOri); - vec4 clipPos = projection * rotView * vec4(WorldPos, 1.0); - - gl_Position = clipPos.xyww; -} diff --git a/resources/shaders/pbrRenderer/shadow.frag b/resources/shaders/pbrRenderer/shadow.frag deleted file mode 100644 index 377b8264..00000000 --- a/resources/shaders/pbrRenderer/shadow.frag +++ /dev/null @@ -1,12 +0,0 @@ -#version 300 es -//-------------------------------------------------- -// Atta Shaders -// shadow.frag -// Date: 2022-01-04 -// By Breno Cunha Queiroz -//-------------------------------------------------- - -void main() -{ - // gl_FragDepth = gl_FragCoord.z; -} diff --git a/resources/shaders/pbrRenderer/shadow.vert b/resources/shaders/pbrRenderer/shadow.vert deleted file mode 100644 index 9a165f56..00000000 --- a/resources/shaders/pbrRenderer/shadow.vert +++ /dev/null @@ -1,19 +0,0 @@ -#version 300 es -//-------------------------------------------------- -// Atta Shaders -// shadow.vert -// Date: 2022-01-04 -// By Breno Cunha Queiroz -//-------------------------------------------------- - -in vec3 inPosition; -in vec3 inNormal; -in vec2 inTexCoord; - -uniform mat4 lightSpaceMatrix; -uniform mat4 model; - -void main() -{ - gl_Position = lightSpaceMatrix * model * vec4(inPosition, 1.0); -} diff --git a/src/atta/graphics/apis/openGL/framebuffer.cpp b/src/atta/graphics/apis/openGL/framebuffer.cpp index b3fef2e0..6eee8bd6 100644 --- a/src/atta/graphics/apis/openGL/framebuffer.cpp +++ b/src/atta/graphics/apis/openGL/framebuffer.cpp @@ -113,9 +113,10 @@ void Framebuffer::resize(uint32_t width, uint32_t height, bool forceRecreate) { glBindFramebuffer(GL_FRAMEBUFFER, _id); //----- Update attachment images -----// - // Resize attachments + // Only resize framebuffer-owned images (not pre-created ones passed via attachment.image) for (unsigned i = 0; i < _attachments.size(); i++) - _images[i]->resize(width, height, forceRecreate); + if (!_attachments[i].image) + _images[i]->resize(width, height, forceRecreate); // Bind attachments bindAttachments(); @@ -148,7 +149,17 @@ void Framebuffer::setLayer(uint32_t layer) { } void Framebuffer::setLayerAndMip(uint32_t layer, uint32_t mipLevel) { - LOG_WARN("gfx::gl::Framebuffer", "setLayerAndMip is not implemented for OpenGL"); + if (_images.size() != 1 || _colorAttachmentIndex == -1) { + LOG_ERROR( + "gfx::gl::Framebuffer", + "Error when setting layer+mip for framebuffer [w]$0[]. It is only possible to set layer+mip for a framebuffer with one cubemap color attachment", + _debugName); + return; + } + glBindFramebuffer(GL_FRAMEBUFFER, _id); + std::shared_ptr image = std::dynamic_pointer_cast(_images[0]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer, image->getHandle(), mipLevel); + glBindFramebuffer(GL_FRAMEBUFFER, 0); } //---------- Atta to OpenGL conversions ----------// //------------------------------------------------// From 8c43eae3e641ad8e6301104d7b10de549a1ba997 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Tue, 10 Mar 2026 06:53:02 +0100 Subject: [PATCH 34/39] Fix: Directional light shadows with OpenGL API --- resources/shaders/pbrRenderer/shadow.asl | 10 +++++++++- src/atta/graphics/apis/openGL/shader.cpp | 1 + src/atta/graphics/apis/vulkan/shader.cpp | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/resources/shaders/pbrRenderer/shadow.asl b/resources/shaders/pbrRenderer/shadow.asl index 5c920a4d..8d8d9c74 100644 --- a/resources/shaders/pbrRenderer/shadow.asl +++ b/resources/shaders/pbrRenderer/shadow.asl @@ -2,7 +2,15 @@ perFrame mat4 lightSpaceMatrix; perDraw mat4 model; vec4 vertex(vec3 iPos, vec3 iNormal, vec2 iUV) { - return lightSpaceMatrix * model * vec4(iPos, 1.0); + vec4 pos = lightSpaceMatrix * model * vec4(iPos, 1.0); +#ifdef ATTA_OPENGL + // atta's orthographic outputs Z in [0,1] (Vulkan-style). + // OpenGL expects Z_ndc in [-1,1] and remaps to depth as d=(z+1)/2. + // Remap so the depth buffer stores the same [0,1] range that + // pbrRenderer.asl reads back via posDirectionalLightSpace.z. + pos.z = pos.z * 2.0 - 1.0; +#endif + return pos; } // Depth-only pass: no color output needed. diff --git a/src/atta/graphics/apis/openGL/shader.cpp b/src/atta/graphics/apis/openGL/shader.cpp index 1b765331..d12e35b8 100644 --- a/src/atta/graphics/apis/openGL/shader.cpp +++ b/src/atta/graphics/apis/openGL/shader.cpp @@ -38,6 +38,7 @@ std::string Shader::generateApiCode(ShaderType type, std::string iCode) { LOG_ERROR("gfx::gl::Shader", "Compiling shaders for OpenGL version [w]$0[] is not supported", openGLVersion); return ""; } + apiCode += "#define ATTA_OPENGL 1\n"; apiCode += iCode; // Replace perFrame/perDraw diff --git a/src/atta/graphics/apis/vulkan/shader.cpp b/src/atta/graphics/apis/vulkan/shader.cpp index 098936f5..c724cfeb 100644 --- a/src/atta/graphics/apis/vulkan/shader.cpp +++ b/src/atta/graphics/apis/vulkan/shader.cpp @@ -112,6 +112,7 @@ std::string Shader::generateApiCode(ShaderType type, std::string iCode) { // GLSL version apiCode += "#version 450\n"; + apiCode += "#define ATTA_VULKAN 1\n"; // Move custom type definitions to the beginning std::regex structRegex(R"(struct\s+\w+\s*\{([^\}]*)\};)"); From 57ea44c5e6d6d9189b4612e01891832c6131fcac Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Tue, 10 Mar 2026 07:58:38 +0100 Subject: [PATCH 35/39] Refactor: Remove omni shadow mapping --- resources/shaders/pbrRenderer/omniShadow.frag | 24 ----- resources/shaders/pbrRenderer/omniShadow.geom | 29 ------ resources/shaders/pbrRenderer/omniShadow.vert | 18 ---- resources/shaders/pbrRenderer/pbrRenderer.asl | 37 ------- src/atta/graphics/renderers/pbrRenderer.cpp | 98 ------------------- 5 files changed, 206 deletions(-) delete mode 100644 resources/shaders/pbrRenderer/omniShadow.frag delete mode 100644 resources/shaders/pbrRenderer/omniShadow.geom delete mode 100644 resources/shaders/pbrRenderer/omniShadow.vert diff --git a/resources/shaders/pbrRenderer/omniShadow.frag b/resources/shaders/pbrRenderer/omniShadow.frag deleted file mode 100644 index fa6324b7..00000000 --- a/resources/shaders/pbrRenderer/omniShadow.frag +++ /dev/null @@ -1,24 +0,0 @@ -#version 320 es -//-------------------------------------------------- -// Atta Shaders -// omniShadow.frag -// Date: 2022-01-08 -// By Breno Cunha Queiroz -//-------------------------------------------------- -precision mediump float; -in vec4 FragPos; - -uniform vec3 lightPos; -uniform float far_plane; - -void main() -{ - // get distance between fragment and light source - float lightDistance = length(FragPos.xyz - lightPos); - - // map to [0;1] range by dividing by far_plane - lightDistance = lightDistance / far_plane; - - // write this as modified depth - gl_FragDepth = lightDistance; -} diff --git a/resources/shaders/pbrRenderer/omniShadow.geom b/resources/shaders/pbrRenderer/omniShadow.geom deleted file mode 100644 index 369326e8..00000000 --- a/resources/shaders/pbrRenderer/omniShadow.geom +++ /dev/null @@ -1,29 +0,0 @@ -#version 320 es -//-------------------------------------------------- -// Atta Shaders -// omniShadow.geom -// Date: 2022-01-08 -// By Breno Cunha Queiroz -//-------------------------------------------------- - -layout (triangles) in; -layout (triangle_strip, max_vertices=18) out; - -uniform mat4 shadowMatrices[6]; - -out vec4 FragPos; // FragPos from GS (output per emitvertex) - -void main() -{ - for(int face = 0; face < 6; face++) - { - gl_Layer = face; // built-in variable that specifies to which face we render. - for(int i = 0; i < 3; i++) // for each triangle vertex - { - FragPos = gl_in[i].gl_Position; - gl_Position = shadowMatrices[face] * FragPos; - EmitVertex(); - } - EndPrimitive(); - } -} diff --git a/resources/shaders/pbrRenderer/omniShadow.vert b/resources/shaders/pbrRenderer/omniShadow.vert deleted file mode 100644 index 2fda870d..00000000 --- a/resources/shaders/pbrRenderer/omniShadow.vert +++ /dev/null @@ -1,18 +0,0 @@ -#version 320 es -//-------------------------------------------------- -// Atta Shaders -// omniShadow.vert -// Date: 2022-01-08 -// By Breno Cunha Queiroz -//-------------------------------------------------- - -in vec3 inPosition; -in vec3 inNormal; -in vec2 inTexCoord; - -uniform mat4 model; - -void main() -{ - gl_Position = model * vec4(inPosition, 1.0); -} diff --git a/resources/shaders/pbrRenderer/pbrRenderer.asl b/resources/shaders/pbrRenderer/pbrRenderer.asl index 872203d1..6eda2c7a 100644 --- a/resources/shaders/pbrRenderer/pbrRenderer.asl +++ b/resources/shaders/pbrRenderer/pbrRenderer.asl @@ -31,9 +31,7 @@ perFrame PointLight pointLights[MAX_NUM_POINT_LIGHTS]; // Directional light - Shadow map perFrame DirectionalLight directionalLight; -// perFrame samplerCube omniShadowMap; perFrame sampler2D directionalShadowMap; -perFrame float omniFarPlane; // Environment light - IBL perFrame mat3 environmentLightOri; @@ -80,7 +78,6 @@ vec4 vertex(vec3 inPosition, vec3 inNormal, vec2 inTexCoord) { //----- Fragment Shader -----// vec3 calcLightContribution(vec3 L, vec3 N, vec3 V, vec3 radiance, vec3 albedo, float metallic, float roughness, vec3 F0); float directionalShadowCalculation(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir); -float omniShadowCalculation(vec3 lightPos, vec3 normal); float distributionGGX(vec3 N, vec3 H, float roughness); // Distribution of microsurface normals float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness); // Microsurface shadowing vec3 fresnelSchlick(float cosTheta, vec3 F0); // Reflect-refract ratio @@ -124,7 +121,6 @@ void fragment(out vec4 outColor) { vec3 radiance = pointLights[i].intensity * attenuation; float shadow = 0.0f; - // shadow = omniShadowCalculation(pointLights[i].position, N); Lo += calcLightContribution(L, N, V, radiance, albedo, metallic, roughness, F0) * (1.0f - shadow); } @@ -219,39 +215,6 @@ float directionalShadowCalculation(vec4 fragPosLightSpace, vec3 normal, vec3 lig return shadow; } -vec3 sampleOffsetDirections[20] = - vec3[](vec3(1, 1, 1), vec3(1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1), vec3(1, 1, -1), vec3(1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), - vec3(1, 1, 0), vec3(1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), vec3(1, 0, 1), vec3(-1, 0, 1), vec3(1, 0, -1), vec3(-1, 0, -1), - vec3(0, 1, 1), vec3(0, -1, 1), vec3(0, -1, -1), vec3(0, 1, -1)); - -float omniShadowCalculation(vec3 lightPos, vec3 normal) { - // get vector between fragment position and light position - vec3 fragToLight = worldPos - lightPos; - // use the light to fragment vector to sample from the depth map - float closestDepth = 0.0f; // texture(omniShadowMap, fragToLight).r; - if (closestDepth == 1.0f) - return 0.0f; - // it is currently in linear range between [0,1]. Re-transform back to original value - closestDepth *= omniFarPlane; - // now get current linear depth as the length between the fragment and light position - float currentDepth = length(fragToLight); - // now test for shadows - float shadow = 0.0; - float bias = 0.15; - int samples = 20; - float viewDistance = length(camPos - worldPos); - float diskRadius = (1.0 + (viewDistance / omniFarPlane)) / 25.0; - for (int i = 0; i < samples; ++i) { - float closestDepth = 0.0f; // texture(omniShadowMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r; - closestDepth *= omniFarPlane; // undo mapping [0;1] - if (currentDepth - bias > closestDepth) - shadow += 1.0; - } - shadow /= float(samples); - - return shadow; -} - float distributionGGX(vec3 N, vec3 H, float roughness) { // Trowbridge-Reitz GGX normal distribution function float a = roughness * roughness; diff --git a/src/atta/graphics/renderers/pbrRenderer.cpp b/src/atta/graphics/renderers/pbrRenderer.cpp index 07906e33..16d528e4 100644 --- a/src/atta/graphics/renderers/pbrRenderer.cpp +++ b/src/atta/graphics/renderers/pbrRenderer.cpp @@ -61,9 +61,6 @@ PbrRenderer::PbrRenderer() : Renderer("PbrRenderer"), _firstRender(true), _wasRe _gridPipeline = std::make_unique(_geometryRenderPass); _drawerPipeline = std::make_unique(_geometryRenderPass); - ////---------- Create background shader ----------// - //{ _backgroundShader = graphics::create("shaders/pbrRenderer/background.asl"); } - //---------- Directional shadow mapping ----------// { Image::CreateInfo imageInfo{}; @@ -93,40 +90,6 @@ PbrRenderer::PbrRenderer() : Renderer("PbrRenderer"), _firstRender(true), _wasRe pipelineInfo.renderPass = _shadowMapRenderPass; _shadowMapPipeline = graphics::create(pipelineInfo); } - - ////---------- Omnidirectional shadow mapping ----------// - //{ - // // Framebuffer - // Image::CreateInfo imageInfo; - // imageInfo.format = Image::Format::DEPTH32F; - // imageInfo.width = 1024; - // imageInfo.height = 1024; - // imageInfo.isCubemap = true; - // imageInfo.debugName = StringId("PbrRenderer::omniShadowMap::image"); - // _omnidirectionalShadowMap = graphics::create(imageInfo); - - // Framebuffer::CreateInfo framebufferInfo{}; - // framebufferInfo.attachments.push_back({Image::Format::NONE, _omnidirectionalShadowMap}); - // framebufferInfo.width = 1024; - // framebufferInfo.height = 1024; - // framebufferInfo.debugName = StringId("PbrRenderer::omniShadowMap::framebuffer"); - // std::shared_ptr framebuffer = graphics::create(framebufferInfo); - - // // Shader - // std::shared_ptr shader = graphics::create("shaders/pbrRenderer/omniShadow.asl"); - - // // Render Pass - // RenderPass::CreateInfo renderPassInfo{}; - // renderPassInfo.framebuffer = framebuffer; - // renderPassInfo.debugName = StringId("PbrRenderer::omniShadowMap::renderPass"); - // std::shared_ptr renderPass = graphics::create(renderPassInfo); - - // // Pipeline - // Pipeline::CreateInfo pipelineInfo{}; - // pipelineInfo.shader = shader; - // pipelineInfo.renderPass = renderPass; - // _omniShadowMapPipeline = graphics::create(pipelineInfo); - //} } PbrRenderer::~PbrRenderer() {} @@ -142,7 +105,6 @@ void PbrRenderer::render(std::shared_ptr camera) { } // Check current environment map - std::vector entities = component::getNoPrototypeView(); StringId currEnvironmentImg{}; for (auto entity : entities) { @@ -238,63 +200,6 @@ void PbrRenderer::shadowPass() { } _renderQueue->end(); _directionalShadowMap->prepareForSampling(); - - ////----- Omnidirectional shadow mapping -----// - // component::EntityId pointLightEntity = -1; - // for (auto entity : entities) { - // component::PointLight* pl = component::getComponent(entity); - // component::Transform* t = component::getComponent(entity); - // if (pl && t) { - // pointLightEntity = entity; - // break; - // } - // } - - // if (pointLightEntity != -1) { - // _omniShadowMapPipeline->begin(); - // { - // std::shared_ptr shader = _omniShadowMapPipeline->getShader(); - // shader->bind(); - - // // Create light matrix - // component::Transform* t = component::getComponent(pointLightEntity); - - // // TODO world position - // float fov = radians(90.0f); - // float ratio = 1.0f; - // float near = 0.01f; - // float far = 25.0f; - // mat4 proj = perspective(fov, ratio, near, far); - // std::vector shadowMatrices = {proj * lookAt(t->position, t->position + vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), - // proj * lookAt(t->position, t->position + vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), - // proj * lookAt(t->position, t->position + vec3(0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), - // proj * lookAt(t->position, t->position + vec3(0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f)), - // proj * lookAt(t->position, t->position + vec3(0.0f, 0.0f, 1.0f), vec3(0.0f, -1.0f, 0.0f)), - // proj * lookAt(t->position, t->position + vec3(0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f))}; - - // shader->setMat4("shadowMatrices[0]", transpose(shadowMatrices[0])); - // shader->setMat4("shadowMatrices[1]", transpose(shadowMatrices[1])); - // shader->setMat4("shadowMatrices[2]", transpose(shadowMatrices[2])); - // shader->setMat4("shadowMatrices[3]", transpose(shadowMatrices[3])); - // shader->setMat4("shadowMatrices[4]", transpose(shadowMatrices[4])); - // shader->setMat4("shadowMatrices[5]", transpose(shadowMatrices[5])); - // shader->setMat4("model", transpose(t->getWorldTransformMatrix(pointLightEntity))); - // shader->setVec3("lightPos", t->position); - // shader->setFloat("far_plane", far); - - // // Fill shadow map rendering the scene - // for (auto entity : entities) { - // component::Mesh* mesh = component::getComponent(entity); - // component::Transform* transform = component::getComponent(entity); - - // if (mesh && transform) { - // shader->setMat4("model", transpose(transform->getWorldTransformMatrix(entity))); - // graphics::getGraphicsAPI()->renderMesh(mesh->sid); - // } - // } - // } - // _omniShadowMapPipeline->end(); - //} } void PbrRenderer::geometryPass(std::shared_ptr camera) { @@ -336,9 +241,6 @@ void PbrRenderer::geometryPass(std::shared_ptr camera) { _geometryPipeline->setMat4("view", camera->getView()); _geometryPipeline->setVec3("camPos", camera->getPosition()); _geometryPipeline->setMat4("directionalLightMatrix", _directionalLightMatrix); - // _geometryPipeline->setImage("directionalShadowMap", _shadowMapPipeline->getRenderPass()->getFramebuffer()->getImage()); - // _geometryPipeline->setCubemap("omniShadowMap", _omnidirectionalShadowMap); - _geometryPipeline->setFloat("omniFarPlane", 25.0f); // Bind IBL textures _geometryPipeline->setImageGroup("iblImg"); From 8a44a1c851dceae3dc1f3561047737e6f115c5e0 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Tue, 10 Mar 2026 08:39:55 +0100 Subject: [PATCH 36/39] Fix: Wrong phong renderer mesh transform --- src/atta/graphics/apis/openGL/openGLAPI.cpp | 4 +++- src/atta/graphics/apis/openGL/shader.cpp | 8 +++++++- src/atta/graphics/renderers/phongRenderer.cpp | 4 ++-- src/atta/graphics/shader.cpp | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/atta/graphics/apis/openGL/openGLAPI.cpp b/src/atta/graphics/apis/openGL/openGLAPI.cpp index c3b71b26..4a9bf64f 100644 --- a/src/atta/graphics/apis/openGL/openGLAPI.cpp +++ b/src/atta/graphics/apis/openGL/openGLAPI.cpp @@ -68,9 +68,11 @@ void OpenGLAPI::startUp() { nullptr); #endif // ATTA_DEBUG_BUILD - // Enable cubemap seamless if supported + // Enable cubemap seamless if supported (desktop OpenGL only, not available in WebGL/GLES) +#ifndef ATTA_OS_WEB if (_apiVersion >= 320) glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); +#endif glEnable(GL_DEPTH_TEST); } diff --git a/src/atta/graphics/apis/openGL/shader.cpp b/src/atta/graphics/apis/openGL/shader.cpp index d12e35b8..8e3ccca6 100644 --- a/src/atta/graphics/apis/openGL/shader.cpp +++ b/src/atta/graphics/apis/openGL/shader.cpp @@ -33,7 +33,8 @@ std::string Shader::generateApiCode(ShaderType type, std::string iCode) { apiCode = "#version 330 core\n"; } else if (openGLVersion >= 300) { apiCode = "#version 300 es\n" - "precision mediump float;\n"; + "precision highp float;\n" + "precision highp int;\n"; } else { LOG_ERROR("gfx::gl::Shader", "Compiling shaders for OpenGL version [w]$0[] is not supported", openGLVersion); return ""; @@ -71,6 +72,11 @@ std::string Shader::generateApiCode(ShaderType type, std::string iCode) { // Move to the next match start = match.suffix().first; } + + // Strip f/F suffix from float literals for GLSL ES (e.g. 1.0f -> 1.0) + if (openGLVersion < 330) + apiCode = std::regex_replace(apiCode, std::regex(R"((\d+\.\d*)[fF]\b)"), "$1"); + // LOG_DEBUG("gfx::gl::Shader", "Generated API code for shader [g]$1[]\n[w]$0[]", apiCode, _file.string()); return apiCode; diff --git a/src/atta/graphics/renderers/phongRenderer.cpp b/src/atta/graphics/renderers/phongRenderer.cpp index ada7f8eb..b4d9a8ca 100644 --- a/src/atta/graphics/renderers/phongRenderer.cpp +++ b/src/atta/graphics/renderers/phongRenderer.cpp @@ -110,6 +110,8 @@ void PhongRenderer::render(std::shared_ptr camera) { int i = numPointLights++; _geometryPipeline->setVec3(("uPointLights[" + std::to_string(i) + "].position").c_str(), position); _geometryPipeline->setVec3(("uPointLights[" + std::to_string(i) + "].intensity").c_str(), pl->intensity); + } else if (pl) { + LOG_WARN("gfx::PhongRenderer", "Maximum number of point lights reached, 10 lights"); } if (dl) { hasDirectionalLight = true; @@ -118,8 +120,6 @@ void PhongRenderer::render(std::shared_ptr camera) { _geometryPipeline->setVec3("uDirectionalLight.direction", direction); _geometryPipeline->setVec3("uDirectionalLight.intensity", dl->intensity); } - if (numPointLights++ == 10) - LOG_WARN("gfx::PhongRenderer", "Maximum number of point lights reached, 10 lights"); } } _geometryPipeline->setInt("uNumPointLights", numPointLights); diff --git a/src/atta/graphics/shader.cpp b/src/atta/graphics/shader.cpp index a932eb06..cdc411cd 100644 --- a/src/atta/graphics/shader.cpp +++ b/src/atta/graphics/shader.cpp @@ -175,7 +175,7 @@ std::string Shader::generateICode(ShaderType type, std::string aslCode) { for (size_t i = 0; i < _vertexLayout.getElements().size(); i++) { BufferLayout::Element element = _vertexLayout.getElements()[i]; std::string typeStr = BufferLayout::Element::typeToString(element.type); - input += "in " + typeStr + " " + element.name + ";\n"; + input += "layout(location = " + std::to_string(i) + ") in " + typeStr + " " + element.name + ";\n"; params += element.name; if (i != _vertexLayout.getElements().size() - 1) params += ", "; From df5097a490976ffb23ca99b2c01c2743a746b567 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Tue, 10 Mar 2026 09:24:11 +0100 Subject: [PATCH 37/39] Fix: Phong renderer materials too white --- resources/shaders/phongRenderer/phongRenderer.asl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/shaders/phongRenderer/phongRenderer.asl b/resources/shaders/phongRenderer/phongRenderer.asl index e4d73914..f09d4bec 100644 --- a/resources/shaders/phongRenderer/phongRenderer.asl +++ b/resources/shaders/phongRenderer/phongRenderer.asl @@ -113,8 +113,8 @@ vec3 calcDirectionalLight(DirectionalLight light, vec3 normal, vec3 viewDir) { vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), 3.0f); // combine results - float diffuse = uDiffuseStrength * diff * roughness; - float specular = uSpecularStrength * spec * metallic; + vec3 diffuse = albedo * uDiffuseStrength * diff; + vec3 specular = vec3(uSpecularStrength * spec * metallic); return light.intensity * (diffuse + specular); } @@ -129,7 +129,7 @@ vec3 calcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) { float dist = length(light.position - fragPos); float attenuation = 1.0 / (2.0f + 2.0f * dist + 2.0 * (dist * dist)); // combine results - float diffuse = uDiffuseStrength * diff * roughness; - float specular = uSpecularStrength * spec * metallic; + vec3 diffuse = albedo * uDiffuseStrength * diff; + vec3 specular = vec3(uSpecularStrength * spec * metallic); return light.intensity * attenuation * (diffuse + specular); } From 3e5c5ca3164454c8258c81a33dd7088ed38a6e7f Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Tue, 10 Mar 2026 09:28:03 +0100 Subject: [PATCH 38/39] Chore: Apply clang-format --- src/atta/event/events/uiCameraComponent.h | 2 +- src/atta/graphics/apis/openGL/framebuffer.cpp | 10 +++++----- src/atta/graphics/apis/vulkan/framebuffer.cpp | 3 ++- src/atta/graphics/apis/vulkan/image.h | 4 ++-- src/atta/graphics/compute/equiToCubemap.cpp | 12 ++++++------ src/atta/graphics/compute/irradiance.cpp | 12 ++++++------ src/atta/graphics/compute/prefilter.cpp | 12 ++++++------ src/atta/graphics/framebuffer.cpp | 3 ++- 8 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/atta/event/events/uiCameraComponent.h b/src/atta/event/events/uiCameraComponent.h index b0408fae..76b9872c 100644 --- a/src/atta/event/events/uiCameraComponent.h +++ b/src/atta/event/events/uiCameraComponent.h @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2020-2026 Breno Cunha Queiroz #pragma once -#include #include +#include namespace atta::event { diff --git a/src/atta/graphics/apis/openGL/framebuffer.cpp b/src/atta/graphics/apis/openGL/framebuffer.cpp index 6eee8bd6..b99c8026 100644 --- a/src/atta/graphics/apis/openGL/framebuffer.cpp +++ b/src/atta/graphics/apis/openGL/framebuffer.cpp @@ -144,16 +144,16 @@ void Framebuffer::setLayer(uint32_t layer) { std::shared_ptr image = std::dynamic_pointer_cast(_images[0]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer, image->getHandle(), 0); - // Unbind framebuffer + // Unbind framebuffer glBindFramebuffer(GL_FRAMEBUFFER, 0); } void Framebuffer::setLayerAndMip(uint32_t layer, uint32_t mipLevel) { if (_images.size() != 1 || _colorAttachmentIndex == -1) { - LOG_ERROR( - "gfx::gl::Framebuffer", - "Error when setting layer+mip for framebuffer [w]$0[]. It is only possible to set layer+mip for a framebuffer with one cubemap color attachment", - _debugName); + LOG_ERROR("gfx::gl::Framebuffer", + "Error when setting layer+mip for framebuffer [w]$0[]. It is only possible to set layer+mip for a framebuffer with one cubemap " + "color attachment", + _debugName); return; } glBindFramebuffer(GL_FRAMEBUFFER, _id); diff --git a/src/atta/graphics/apis/vulkan/framebuffer.cpp b/src/atta/graphics/apis/vulkan/framebuffer.cpp index c32c7990..ed73d06d 100644 --- a/src/atta/graphics/apis/vulkan/framebuffer.cpp +++ b/src/atta/graphics/apis/vulkan/framebuffer.cpp @@ -102,7 +102,8 @@ void Framebuffer::setLayer(uint32_t layer) { void Framebuffer::setLayerAndMip(uint32_t layer, uint32_t mipLevel) { if (_images.size() != 1) { - LOG_ERROR("gfx::vk::Framebuffer", "setLayerAndMip is only supported for framebuffers with one attachment, not for framebuffer [w]$0[]", _debugName); + LOG_ERROR("gfx::vk::Framebuffer", "setLayerAndMip is only supported for framebuffers with one attachment, not for framebuffer [w]$0[]", + _debugName); return; } diff --git a/src/atta/graphics/apis/vulkan/image.h b/src/atta/graphics/apis/vulkan/image.h index d41879a9..8f4d3e30 100644 --- a/src/atta/graphics/apis/vulkan/image.h +++ b/src/atta/graphics/apis/vulkan/image.h @@ -104,8 +104,8 @@ class Image final : public gfx::Image { std::vector _supportedData; ///< Converted _data to _supportedFormat VkImage _image; - VkImageView _imageView; // Image view for the whole image (all layers) - std::array _cubemapImageViews{}; // Image view for each face of a cubemap image + VkImageView _imageView; // Image view for the whole image (all layers) + std::array _cubemapImageViews{}; // Image view for each face of a cubemap image std::unordered_map _cubemapFaceMipImageViews; // Image view for each face+mip of a cubemap image VkSampler _sampler; VkDeviceMemory _memory; diff --git a/src/atta/graphics/compute/equiToCubemap.cpp b/src/atta/graphics/compute/equiToCubemap.cpp index 55d15c02..bc81ad9c 100644 --- a/src/atta/graphics/compute/equiToCubemap.cpp +++ b/src/atta/graphics/compute/equiToCubemap.cpp @@ -66,12 +66,12 @@ std::shared_ptr EquiToCubemap::createCubemap(StringId imageSid) { // Define projection and view matrices. mat4 proj = perspective(M_PI / 2.0f, 1.0f, 0.1f, 10.0f); std::array views = { - lookAt(vec3(0.0f), vec3( 1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // +X face - lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // -X face - lookAt(vec3(0.0f), vec3( 0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), // +Y face - lookAt(vec3(0.0f), vec3( 0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f)), // -Y face - lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, 1.0f), vec3(0.0f, -1.0f, 0.0f)), // +Z face - lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f)), // -Z face + lookAt(vec3(0.0f), vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // +X face + lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // -X face + lookAt(vec3(0.0f), vec3(0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), // +Y face + lookAt(vec3(0.0f), vec3(0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f)), // -Y face + lookAt(vec3(0.0f), vec3(0.0f, 0.0f, 1.0f), vec3(0.0f, -1.0f, 0.0f)), // +Z face + lookAt(vec3(0.0f), vec3(0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f)), // -Z face }; // Render diff --git a/src/atta/graphics/compute/irradiance.cpp b/src/atta/graphics/compute/irradiance.cpp index 3d273e79..f3fc5d7e 100644 --- a/src/atta/graphics/compute/irradiance.cpp +++ b/src/atta/graphics/compute/irradiance.cpp @@ -58,12 +58,12 @@ std::shared_ptr Irradiance::createIrradianceCubemap(StringId environ // Same face orientations as equiToCubemap (learnopengl convention, no Y-flip) mat4 proj = perspective(M_PI / 2.0f, 1.0f, 0.1f, 10.0f); std::array views = { - lookAt(vec3(0.0f), vec3( 1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // +X face - lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // -X face - lookAt(vec3(0.0f), vec3( 0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), // +Y face - lookAt(vec3(0.0f), vec3( 0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f)), // -Y face - lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, 1.0f), vec3(0.0f, -1.0f, 0.0f)), // +Z face - lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f)), // -Z face + lookAt(vec3(0.0f), vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // +X face + lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // -X face + lookAt(vec3(0.0f), vec3(0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), // +Y face + lookAt(vec3(0.0f), vec3(0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f)), // -Y face + lookAt(vec3(0.0f), vec3(0.0f, 0.0f, 1.0f), vec3(0.0f, -1.0f, 0.0f)), // +Z face + lookAt(vec3(0.0f), vec3(0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f)), // -Z face }; for (size_t i = 0; i < 6; i++) { diff --git a/src/atta/graphics/compute/prefilter.cpp b/src/atta/graphics/compute/prefilter.cpp index c4be544e..105232ff 100644 --- a/src/atta/graphics/compute/prefilter.cpp +++ b/src/atta/graphics/compute/prefilter.cpp @@ -32,12 +32,12 @@ std::shared_ptr Prefilter::createPrefilterCubemap(StringId environme // Same face orientations as equiToCubemap/irradiance (learnopengl convention, no Y-flip) mat4 proj = perspective(M_PI / 2.0f, 1.0f, 0.1f, 10.0f); std::array views = { - lookAt(vec3(0.0f), vec3( 1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // +X face - lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // -X face - lookAt(vec3(0.0f), vec3( 0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), // +Y face - lookAt(vec3(0.0f), vec3( 0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f)), // -Y face - lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, 1.0f), vec3(0.0f, -1.0f, 0.0f)), // +Z face - lookAt(vec3(0.0f), vec3( 0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f)), // -Z face + lookAt(vec3(0.0f), vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // +X face + lookAt(vec3(0.0f), vec3(-1.0f, 0.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f)), // -X face + lookAt(vec3(0.0f), vec3(0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f)), // +Y face + lookAt(vec3(0.0f), vec3(0.0f, -1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f)), // -Y face + lookAt(vec3(0.0f), vec3(0.0f, 0.0f, 1.0f), vec3(0.0f, -1.0f, 0.0f)), // +Z face + lookAt(vec3(0.0f), vec3(0.0f, 0.0f, -1.0f), vec3(0.0f, -1.0f, 0.0f)), // -Z face }; // One framebuffer + renderpass + pipeline per mip level (different dimensions) diff --git a/src/atta/graphics/framebuffer.cpp b/src/atta/graphics/framebuffer.cpp index 0e4929d0..8705f386 100644 --- a/src/atta/graphics/framebuffer.cpp +++ b/src/atta/graphics/framebuffer.cpp @@ -5,7 +5,8 @@ namespace atta::graphics { Framebuffer::Framebuffer(const CreateInfo& info) - : _width(info.width), _height(info.height), _clearColor(info.clearColor), _isSwapchain(info.isSwapchain), _attachments(info.attachments), _debugName(info.debugName) { + : _width(info.width), _height(info.height), _clearColor(info.clearColor), _isSwapchain(info.isSwapchain), _attachments(info.attachments), + _debugName(info.debugName) { DASSERT(_attachments.size() > 0, "It is not possible to create framebuffer with no attachments"); // Check consistency and populate color and depth attachments From 05f0ec831a4bee7145843213cd717175b6b7ded1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Mar 2026 08:32:04 +0000 Subject: [PATCH 39/39] Chore: Bump version to 0.3.11 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8110db13..5ed2aa0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(atta VERSION 0.3.10 LANGUAGES CXX C) +project(atta VERSION 0.3.11 LANGUAGES CXX C) option(ATTA_BUILD_TESTS "Set to ON to build also the test executables" ON) option(ATTA_BUILD_DOCS "Build the documentation" OFF)