2026-07-17 15:30:29 +01:00
//*PIXEL*
#version 460 core
#extension GL_ARB_bindless_texture : require
uint INVALID_HYDRA_ID = 4294967295 ;
uint DEPTH_TEXTURE_ID = 0 ;
uint DIFFUSE_TEXTURE_ID = 1 ;
uint METAL_ROUGH_TEXTURE_ID = 2 ;
uint NORMAL_TEXTURE_ID = 3 ;
uint POSITION_TEXTIURE_ID = 4 ;
2026-07-17 21:06:44 +01:00
uint HYDRA_LIGHT_DIRECTIONAL = 1 ;
uint HYDRA_LIGHT_POINT = 2 ;
uint HYDRA_LIGHT_SPOT = 3 ;
2026-07-17 15:30:29 +01:00
struct light_structure
{
2026-07-18 18:39:17 +01:00
vec4 ambient ;
vec4 diffuse ;
vec4 specular ;
float intensity ;
float constant ;
float linear ;
float quadratic ;
float falloff ;
float falloff_smooth ;
uint entity_id ;
uint light_type ;
2026-07-17 15:30:29 +01:00
};
layout ( binding = 0 ) uniform uniform_per_frame
{
mat4 view ;
mat4 proj ;
vec3 viewer_pos ;
uint light_count ;
} ubo_per_frame ;
layout ( binding = 1 ) uniform uniform_output_textures
{
uint texture_ids [ 16 ];
};
layout ( binding = 1 ) readonly buffer PositionsBuffer
{
mat4 model_matrix [];
};
layout ( std430 , binding = 3 ) readonly buffer TextureBuffer {
uvec2 textures [ 1000 ];
};
layout ( std430 , binding = 4 ) readonly buffer LightBuffer
{
light_structure lights [ 500 ];
};
layout ( location = 0 ) in VS_OUT
{
vec2 vsUV ;
} vs_out ;
layout ( location = 0 ) out vec4 uFragColor ;
const float PI = 3.14159265358979323846 ;
const float M_INV_PI = 0.31830988618379067153776752674503 ;
float sqr ( float x ) { return x * x ; }
// ----------------------------------------------------------------------------
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 nom = a2 ;
float denom = ( NdotH2 * ( a2 - 1.0 ) + 1.0 );
denom = PI * denom * denom ;
return nom / denom ;
}
// ----------------------------------------------------------------------------
float GeometrySchlickGGX ( float NdotV , float roughness )
{
float r = ( roughness + 1.0 );
float k = ( r * r ) / 8.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 ;
}
// ----------------------------------------------------------------------------
vec3 fresnelSchlick ( float cosTheta , vec3 F0 )
{
return F0 + ( 1.0 - F0 ) * pow ( clamp ( 1.0 - cosTheta , 0.0 , 1.0 ), 5.0 );
}
2026-07-18 18:39:17 +01:00
vec3 CalculatePBR ( vec3 normal , vec3 view_pos , vec3 world_pos , vec3 light_dir , vec3 diffuse , vec3 radiance , float roughness , float metallic )
2026-07-17 15:30:29 +01:00
{
vec3 N = normalize ( normal );
2026-07-18 18:39:17 +01:00
vec3 V = normalize ( view_pos - world_pos );
2026-07-17 15:30:29 +01:00
// calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)
vec3 F0 = vec3 ( 0.04 );
F0 = mix ( F0 , diffuse , metallic );
2026-07-18 18:39:17 +01:00
2026-07-17 15:30:29 +01:00
// calculate per-light radiance
2026-07-18 18:39:17 +01:00
vec3 L = normalize ( light_dir );
2026-07-17 15:30:29 +01:00
vec3 H = normalize ( V + L );
// Cook-Torrance BRDF
float NDF = DistributionGGX ( N , H , roughness );
float G = GeometrySmith ( N , V , L , roughness );
vec3 F = fresnelSchlick ( clamp ( dot ( H , V ), 0.0 , 1.0 ), F0 );
vec3 numerator = NDF * G * F ;
float denominator = 4.0 * max ( dot ( N , V ), 0.0 ) * max ( dot ( N , L ), 0.0 ) + 0.0001 ; // + 0.0001 to prevent divide by zero
vec3 specular = numerator / denominator ;
// kS is equal to Fresnel
vec3 kS = F ;
// for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light); to preserve this
// relationship the diffuse component (kD) should equal 1.0 - kS.
vec3 kD = vec3 ( 1.0 ) - kS ;
// multiply kD by the inverse metalness such that only non-metals
// have diffuse lighting, or a linear blend if partly metal (pure metals
// have no diffuse light).
kD *= 1.0 - metallic ;
// scale light by NdotL
float NdotL = max ( dot ( N , L ), 0.0 );
// add to outgoing radiance Lo
2026-07-18 18:39:17 +01:00
vec3 Lo = ( kD * diffuse / PI + specular ) * radiance * NdotL ; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again
return Lo ;
}
vec3 DirectionalShading ( vec3 normal ,
vec3 diffuse ,
float roughness ,
float metallic ,
float light_intensity ,
vec3 light_dir ,
vec3 light_pos ,
vec3 world_pos ,
vec3 view_pos )
{
// vec3 N = normalize(normal);
// vec3 V = normalize(vieview_pos - world_pos);
// // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)
// vec3 F0 = vec3(0.04);
// F0 = mix(F0, diffuse, metallic);
// // reflectance equation
// vec3 Lo = vec3(0.0);
// // calculate per-light radiance
// vec3 L = normalize(lightDir);
// vec3 H = normalize(V + L);
float distance = length ( light_pos - world_pos );
float attenuation = 1.0 ;
vec3 radiance = diffuse * attenuation ;
vec3 Lo = CalculatePBR ( normal , view_pos , world_pos , light_dir , diffuse , radiance , roughness , metallic );
// // Cook-Torrance BRDF
// float NDF = DistributionGGX(N, H, roughness);
// float G = GeometrySmith(N, V, L, roughness);
// vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);
// vec3 numerator = NDF * G * F;
// float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; // + 0.0001 to prevent divide by zero
// vec3 specular = numerator / denominator;
// // kS is equal to Fresnel
// vec3 kS = F;
// // for energy conservation, the diffuse and specular light can't
// // be above 1.0 (unless the surface emits light); to preserve this
// // relationship the diffuse component (kD) should equal 1.0 - kS.
// vec3 kD = vec3(1.0) - kS;
// // multiply kD by the inverse metalness such that only non-metals
// // have diffuse lighting, or a linear blend if partly metal (pure metals
// // have no diffuse light).
// kD *= 1.0 - metallic;
// // scale light by NdotL
// float NdotL = max(dot(N, L), 0.0);
// // add to outgoing radiance Lo
// Lo += (kD * diffuse / PI + specular) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again
2026-07-17 15:30:29 +01:00
return Lo ;
}
2026-07-17 21:06:44 +01:00
2026-07-18 18:39:17 +01:00
vec3 PointShading ( vec3 normal ,
vec3 diffuse ,
float roughness ,
float metallic ,
float light_intensity ,
vec3 light_pos ,
float light_constant ,
float light_linear ,
float light_quadratic ,
vec3 world_pos ,
vec3 view_pos )
2026-07-17 21:06:44 +01:00
{
2026-07-18 18:39:17 +01:00
// vec3 N = normalize(normal);
// vec3 V = normalize(viewPos - worldPos);
2026-07-17 21:06:44 +01:00
2026-07-18 18:39:17 +01:00
// // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)
// vec3 F0 = vec3(0.04);
// F0 = mix(F0, diffuse, metallic);
2026-07-17 21:06:44 +01:00
2026-07-18 18:39:17 +01:00
// // reflectance equation
// vec3 Lo = vec3(0.0);
2026-07-17 21:06:44 +01:00
2026-07-18 18:39:17 +01:00
// // calculate per-light radiance
// vec3 L = normalize(lightPos - worldPos);
// vec3 H = normalize(V + L);
float distance = length ( light_pos - world_pos );
2026-07-17 21:06:44 +01:00
float attenuation = 1.0 ;
2026-07-18 18:39:17 +01:00
//attenuation = light_intensity / (distance * distance);
attenuation = light_intensity / ( light_constant + ( light_linear * distance ) + ( light_quadratic * distance * distance ));
vec3 radiance = diffuse * attenuation ;
vec3 light_dir = normalize ( light_pos - world_pos );
vec3 Lo = CalculatePBR ( normal , view_pos , world_pos , light_dir , diffuse , radiance , roughness , metallic );
2026-07-17 21:06:44 +01:00
2026-07-18 18:39:17 +01:00
// // Cook-Torrance BRDF
// float NDF = DistributionGGX(N, H, roughness);
// float G = GeometrySmith(N, V, L, roughness);
// vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);
2026-07-17 21:06:44 +01:00
2026-07-18 18:39:17 +01:00
// vec3 numerator = NDF * G * F;
// float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; // + 0.0001 to prevent divide by zero
// vec3 specular = (numerator / denominator) * attenuation;
2026-07-17 21:06:44 +01:00
2026-07-18 18:39:17 +01:00
// // kS is equal to Fresnel
// vec3 kS = F;
// // for energy conservation, the diffuse and specular light can't
// // be above 1.0 (unless the surface emits light); to preserve this
// // relationship the diffuse component (kD) should equal 1.0 - kS.
// vec3 kD = vec3(1.0) - kS;
// // multiply kD by the inverse metalness such that only non-metals
// // have diffuse lighting, or a linear blend if partly metal (pure metals
// // have no diffuse light).
// kD *= 1.0 - metallic;
2026-07-17 21:06:44 +01:00
2026-07-18 18:39:17 +01:00
// // scale light by NdotL
// float NdotL = max(dot(N, L), 0.0);
2026-07-17 21:06:44 +01:00
2026-07-18 18:39:17 +01:00
// // add to outgoing radiance Lo
// Lo += (kD * diffuse / PI + specular) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again
2026-07-17 21:06:44 +01:00
return Lo ;
}
2026-07-17 15:30:29 +01:00
mat3 GetRotationOnlyMatrix ( mat4 model_mat )
{
mat3 rot_mat ;
rot_mat [ 0 ] = model_mat [ 0 ]. xyz ;
rot_mat [ 1 ] = model_mat [ 1 ]. xyz ;
rot_mat [ 2 ] = model_mat [ 2 ]. xyz ;
return rot_mat ;
}
void main ()
{
uvec2 diffuse_handle = textures [ texture_ids [ DIFFUSE_TEXTURE_ID ]];
uvec2 normal_handle = textures [ texture_ids [ NORMAL_TEXTURE_ID ]];
uvec2 metal_rough_handle = textures [ texture_ids [ METAL_ROUGH_TEXTURE_ID ]];
uvec2 world_pos_handle = textures [ texture_ids [ POSITION_TEXTIURE_ID ]];
vec4 diffuse = texture ( sampler2D ( diffuse_handle ), vs_out . vsUV . xy );
if ( diffuse [ 3 ] <= 0.1 )
{
discard ;
}
vec3 normal = texture ( sampler2D ( normal_handle ), vs_out . vsUV . xy ). xyz ;
vec3 metal_rough = texture ( sampler2D ( metal_rough_handle ), vs_out . vsUV . xy ). xyz ;
vec3 world_pos = texture ( sampler2D ( world_pos_handle ), vs_out . vsUV . xy ). xyz ;
float rough = metal_rough [ 1 ];
float metal = metal_rough [ 2 ];
vec3 Lo = vec3 ( 0 , 0 , 0 );
for ( int i = 0 ; i < ubo_per_frame . light_count ; i ++ )
{
2026-07-18 18:39:17 +01:00
mat4 model = model_matrix [ lights [ i ]. entity_id ];
2026-07-17 15:30:29 +01:00
mat3 rot_mat = GetRotationOnlyMatrix ( model );
vec3 light_direction = rot_mat * vec3 ( 0 , 0 , 1 );
vec3 light_pos = model [ 3 ]. xyz ;
2026-07-17 21:06:44 +01:00
if ( lights [ i ]. light_type == HYDRA_LIGHT_DIRECTIONAL )
{
2026-07-18 18:39:17 +01:00
Lo += DirectionalShading ( normal , diffuse . xyz , rough , metal , lights [ i ]. intensity , light_direction , light_pos , world_pos , ubo_per_frame . viewer_pos );
2026-07-17 21:06:44 +01:00
}
else if ( lights [ i ]. light_type == HYDRA_LIGHT_POINT )
{
2026-07-18 18:39:17 +01:00
Lo += PointShading ( normal ,
diffuse . xyz ,
rough ,
metal ,
lights [ i ]. intensity ,
light_pos ,
lights [ i ]. constant ,
lights [ i ]. linear ,
lights [ i ]. quadratic ,
world_pos ,
ubo_per_frame . viewer_pos );
2026-07-17 21:06:44 +01:00
}
2026-07-17 15:30:29 +01:00
}
float lighting = 0 ;
// ambient lighting (note that the next IBL tutorial will replace
// this ambient lighting with environment lighting).
float ao = 1 ;
vec3 ambient = vec3 ( 0.05 ) * diffuse . xyz * ao ;
2026-07-18 18:39:17 +01:00
vec3 output_color = Lo ;
2026-07-17 15:30:29 +01:00
// HDR tonemapping
output_color = output_color / ( output_color + vec3 ( 1.0 ));
// gamma correct
output_color = pow ( output_color , vec3 ( 1.0 / 2.1 ));
uFragColor = vec4 ( output_color , 1.0 );
// float light = dot(lights[0].light_direction, normal);
// uFragColor = vec4(diffuse.xyz * light,1);
// uFragColor = vec4(normal, 1);
}