#shader vertex #version 460 core layout(location = 0) in vec3 VertexPosition; layout(location = 1) in vec2 UVPosition; out flat vec2 UV; out vec3 FragPos; out mat4 InvProjection; out mat4 InvView; uniform mat4 u_Projection; uniform mat4 u_View; void main() { UV = UVPosition; FragPos = VertexPosition; InvProjection = inverse(u_Projection); InvView = inverse(u_View); gl_Position = vec4(VertexPosition, 1.0f); } #shader fragment #version 460 core out vec4 FragColor; in vec3 FragPos; in vec2 UV; in mat4 InvProjection; in mat4 InvView; // Camera uniform float u_Exposure; uniform vec3 u_EyePosition; // IBL uniform samplerCube u_IrradianceMap; uniform samplerCube u_PrefilterMap; uniform sampler2D u_BrdfLUT; // Material uniform sampler2D m_Depth; uniform sampler2D m_Albedo; uniform sampler2D m_Material; uniform sampler2D m_Normal; // Lights struct Light { int Type; // 0 = directional, 1 = point vec3 Direction; vec3 Color; float Strength; vec3 Position; float ConstantAttenuation; float LinearAttenuation; float QuadraticAttenuation; mat4 LightTransform; sampler2D ShadowMap; }; const int MaxLight = 20; uniform int LightCount = 0; uniform Light Lights[MaxLight]; vec3 WorldPosFromDepth(float depth) { float z = depth * 2.0 - 1.0; vec4 clipSpacePosition = vec4(UV * 2.0 - 1.0, z, 1.0); vec4 viewSpacePosition = InvProjection * clipSpacePosition; // Perspective division viewSpacePosition /= viewSpacePosition.w; vec4 worldSpacePosition = InvView * viewSpacePosition; return worldSpacePosition.xyz; } const float PI = 3.141592653589793f; // mark this as static const wait idk if you can do that in glsl float height_scale = 0.02f; float DistributionGGX(vec3 N, vec3 H, float a) { 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 k) { float nom = NdotV; float denom = NdotV * (1.0 - k) + k; return nom / denom; } float GeometrySmith(vec3 N, vec3 V, vec3 L, float k) { float NdotV = max(dot(N, V), 0.0); float NdotL = max(dot(N, L), 0.0); float ggx1 = GeometrySchlickGGX(NdotV, k); float ggx2 = GeometrySchlickGGX(NdotL, k); return ggx1 * ggx2; } vec3 fresnelSchlick(float cosTheta, vec3 F0) { return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0); } vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) { return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0); } float ShadowCalculation(vec4 fragPosLightSpace, sampler2D shadowMap, 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(shadowMap, 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.05 * (1.0 - dot(normal, lightDir)), 0.005); float shadow = 0.0; vec2 texelSize = 1.0 / textureSize(shadowMap, 0); for (int x = -1; x <= 1; ++x) { for (int y = -1; y <= 1; ++y) { float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; } } shadow /= 9.0; return shadow; } void main() { vec3 worldPos = WorldPosFromDepth(texture(m_Depth, UV).r); // Convert from [0, 1] to [-1, 1]. vec3 albedo = texture(m_Albedo, UV).rgb; vec3 normal = (texture(m_Normal, UV).rgb - 0.5) * 2.0; float metallic = texture(m_Material, UV).r; float roughness = texture(m_Material, UV).g; float ao = texture(m_Material, UV).b; vec3 N = normalize(normal); vec3 V = normalize(u_EyePosition - worldPos); vec3 R = reflect(-V, N); vec3 F0 = vec3(0.04); F0 = mix(F0, albedo, vec3(metallic)); // reflectance equation vec3 Lo = vec3(0.0); vec3 eyeDirection = normalize(u_EyePosition - worldPos); for (int i = 0; i < LightCount; i++) { vec3 L = normalize(Lights[i].Position - worldPos); float distance = length(Lights[i].Position - worldPos); float attenuation = 1.0 / (distance * distance); if (Lights[i].Type == 0) { L = Lights[i].Direction; attenuation = 1.0f; } float shadow = ShadowCalculation(Lights[i].LightTransform * vec4(worldPos, 1.0f), Lights[i].ShadowMap, N, Lights[i].Direction); vec3 H = normalize(V + L); vec3 radiance = Lights[i].Color * attenuation * (1.0f - shadow); // Cook-Torrance BRDF float NDF = DistributionGGX(N, H, roughness); float G = GeometrySmith(N, V, L, roughness); vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); vec3 nominator = NDF * G * F; float denominator = 4 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001; // 0.001 to prevent divide by zero. vec3 specular = nominator / 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 * albedo / PI + specular) * radiance * NdotL;// note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again } /// ambient lighting (we now use IBL as the ambient term) 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(u_IrradianceMap, N).rgb; vec3 diffuse = irradiance * albedo; // sample both the pre-filter map and the BRDF lut and combine them together as per the Split-Sum approximation to get the IBL specular part. const float MAX_REFLECTION_LOD = 4.0; vec3 prefilteredColor = textureLod(u_PrefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb; vec2 brdf = texture(u_BrdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg; vec3 specular = prefilteredColor * (F * brdf.x + brdf.y); vec3 ambient = (kD * diffuse + specular) * ao; vec3 color = ambient + Lo; // HDR tonemapping color = color / (color + vec3(1.0)); // gamma correct color = pow(color, vec3(1.0 / u_Exposure)); FragColor = vec4(color, 1.0); // so If i wanted to implement other stuff like SSR and bloom. I would need another render texture? using this same shader? }