@felixfaire @paul.houx

It’s actually an Intel Iris Pro GPU (1536 MB VRAM) on a MacBook Pro. Obviously not ideal, but seems to be keeping up. Here’re the relevent shaders -

- The gBuffer vertex shader that provides vPosition, vNormal, vTexCoord0, and vColor to the fragment shader.

```
uniform mat4 ciModelViewProjection;
uniform mat4 ciModelView;
uniform mat3 ciNormalMatrix;
in vec4 ciPosition;
in vec3 ciNormal;
in vec2 ciTexCoord0;
in vec3 ciColor;
out vec4 vPosition;
out vec3 vNormal;
out vec2 vTexCoord0;
out vec3 vColor;
void main()
{
vPosition = ciModelView * ciPosition;
vNormal = ciNormalMatrix * ciNormal;
vTexCoord0 = ciTexCoord0;
vColor = ciColor;
gl_Position = ciModelViewProjection * ciPosition;
}
```

Then the gBuffer fragment shader, which renders a position buffer, a normal buffer, and a color buffer (using the pickingColor uniform which is passed in per-mesh).

```
uniform sampler2D tex0;
uniform vec3 pickingColor;
in vec4 vPosition;
in vec3 vNormal;
in vec2 vTexCoord0;
in vec3 vColor;
out vec4 [3] FragColor;
void main()
{
// OUTPUT POSITION
FragColor[0] = vPosition;
// OUTPUT NORMAL
FragColor[1] = vec4( vNormal, 1.0 );
// OUTPUT COLOR
FragColor[2] = vec4( pickingColor, 1.0 );
}
```

And then finally the SSAO pass fragment shader, which outputs a single float value for the occlusion, and is given the Position and Normal passes from the gBuffer, in addition to a 4x4 pixel noiseTexture.

```
out float FragColor;
in vec2 TexCoords;
uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D noiseTexture;
uniform vec3 samples[64];
uniform mat4 ciProjectionMatrix;
uniform vec2 screenSize;
int kernelSize = 64;
float radius = 0.5;
float bias = 0.025;
void main()
{
// tile noise texture over screen based on screen dimensions divided by noise size
vec2 noiseScale = screenSize / 4.0;
// get input for SSAO algorithm
vec3 fragPos = texture(gPosition, TexCoords).xyz;
vec3 normal = normalize(texture(gNormal, TexCoords).rgb);
vec3 randomVec = normalize(texture(noiseTexture, TexCoords * noiseScale).xyz);
// create TBN change-of-basis matrix: from tangent-space to view-space
vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
vec3 bitangent = cross(normal, tangent);
mat3 TBN = mat3(tangent, bitangent, normal);
// iterate over the sample kernel and calculate occlusion factor
float occlusion = 0.0;
for(int i = 0; i < kernelSize; ++i)
{
// get sample position
vec3 sample = TBN * samples[i]; // from tangent to view-space
sample = fragPos + sample * radius;
// project sample position (to sample texture) (to get position on screen/texture)
vec4 offset = vec4(sample, 1.0);
offset = ciProjectionMatrix * offset; // from view to clip-space
offset.xyz /= offset.w; // perspective divide
offset.xyz = offset.xyz * 0.5 + 0.5; // transform to range 0.0 - 1.0
// get sample depth
float sampleDepth = texture(gPosition, offset.xy).z; // get depth value of kernel sample
// range check & accumulate
float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth));
occlusion += (sampleDepth >= sample.z + bias ? 1.0 : 0.0) * rangeCheck;
}
occlusion = 1.0 - (occlusion / kernelSize);
FragColor = occlusion;
}
```