#version 460 #extension GL_GOOGLE_include_directive : require #include "bindless.glsl" #include "scene_data.glsl" #include "mesh_pcs.glsl" #include "lighting.glsl" #include "shadows.glsl" layout (location = 0) in vec3 inPos; layout (location = 1) in vec2 inUV; layout (location = 2) in vec3 inNormal; layout (location = 3) in vec4 inTangent; layout (location = 0) out vec4 outFragColor; vec3 calculateNormal(MaterialData material, uint samplerID) { vec3 n = normalize(inNormal); // Meshes without tangents can't use a normal map if (dot(inTangent.xyz, inTangent.xyz) < 1e-8) { return n; } // Re-orthogonalize, the interpolated tangent isn't perpendicular to the normal anymore vec3 t = normalize(inTangent.xyz - n * dot(n, inTangent.xyz)); vec3 b = cross(n, t) * inTangent.w; vec3 tangentNormal = sampleTexture2D(material.normalTex, samplerID, inUV).rgb * 2.0 - 1.0; tangentNormal.xy *= material.metallicRoughnessNormalOcclusion.z; return normalize(mat3(t, b, n) * tangentNormal); } void main() { MaterialData material = pcs.sceneData.materials.data[pcs.materialID]; uint samplerID = material.textureFilteringMode; vec4 diffuse = sampleTexture2D(material.diffuseTex, samplerID, inUV) * material.baseColor; vec3 baseColor = diffuse.rgb; // if (diffuse.a < 0.1) { // discard; // } vec3 n = calculateNormal(material, samplerID); float metallicF = material.metallicRoughnessNormalOcclusion.x; float roughnessF = material.metallicRoughnessNormalOcclusion.y; float occlusionStrength = material.metallicRoughnessNormalOcclusion.w; float metallic = metallicF * sampleTexture2D(material.metallicTex, samplerID, inUV).b; float roughness = roughnessF * sampleTexture2D(material.roughnessTex, samplerID, inUV).g; roughness = max(roughness, 1e-2); // 0.0 roughness leads to NaNs float occlusion = sampleTexture2D(material.occlusionTex, samplerID, inUV).r; float ambientOcclusion = mix(1.0, occlusion, occlusionStrength); vec3 dielectricSpecular = vec3(0.04); vec3 black = vec3(0.0); vec3 diffuseColor = mix(baseColor * (1.0 - dielectricSpecular.r), black, metallic); vec3 f0 = mix(dielectricSpecular, baseColor, metallic); vec3 cameraPos = pcs.sceneData.cameraPos.xyz; vec3 fragPos = inPos.xyz; vec3 v = normalize(cameraPos - fragPos); // forward+: only loop over the lights the culling pass put in this pixel's tile uvec2 tileID = uvec2(gl_FragCoord.xy) / LIGHT_TILE_SIZE; uint tileOffset = (tileID.y * pcs.sceneData.tileCountX + tileID.x) * LIGHT_TILE_STRIDE; uint tileLightCount = pcs.sceneData.tileLightIndices.data[tileOffset]; vec3 fragColor = vec3(0.0); for (uint i = 0; i < tileLightCount; i++) { uint lightIndex = pcs.sceneData.tileLightIndices.data[tileOffset + 1 + i]; Light light = pcs.sceneData.lights.data[lightIndex]; float shadow = 1.0; if (light.castShadows != 0) { shadow = calculateDirectionalShadow( fragPos, normalize(inNormal), pcs.sceneData.lightViewProj, pcs.sceneData.shadowMapId, pcs.sceneData.shadowNormalOffset); } fragColor += calculateLight(light, fragPos, n, v, diffuseColor, roughness, metallic, f0, shadow); } // emissive vec3 emissiveColor = material.emissiveColor.rgb * sampleTexture2D(material.emissiveTex, samplerID, inUV).rgb; fragColor += emissiveColor; // ambient, the specular part keeps metals from turning black without lights float NoV = clamp(dot(n, v), 0.0001, 1.0); vec3 ambientLight = pcs.sceneData.ambientColor * pcs.sceneData.ambientIntensity; vec3 ambientSpecular = envBRDFApprox(f0, roughness, NoV); fragColor += (diffuseColor + ambientSpecular) * ambientLight * ambientOcclusion; if (pcs.sceneData.debugLightHeatmap != 0) { fragColor = mix(fragColor, lightHeatmapColor(tileLightCount), 0.5); } #if 0 // TANGENT DEBUG if (inTangent == vec4(0.0)) { fragColor = vec3(1.0f, 0.0f, 0.0f); } #endif #if 0 // NORMAL DEBUG fragColor = n; #endif outFragColor = vec4(fragColor, diffuse.a); }