let GAMMA = 2.200000048; fn linearTosRGB(linear : vec3) -> vec3 { let INV_GAMMA = (1.0 / GAMMA); return pow(linear, vec3(INV_GAMMA)); } fn sRGBToLinear(srgb : vec3) -> vec3 { return pow(srgb, vec3(GAMMA)); } struct Camera { projection : mat4x4, inverseProjection : mat4x4, view : mat4x4, position : vec3, time : f32, outputSize : vec2, zNear : f32, zFar : f32, } @binding(0) @group(0) var camera : Camera; struct ClusterLights { offset : u32, count : u32, } struct ClusterLightGroup { offset : u32, lights : array, indices : array, } @binding(1) @group(0) var clusterLights : ClusterLightGroup; struct Light { position : vec3, range : f32, color : vec3, intensity : f32, } struct GlobalLights { ambient : vec3, dirColor : vec3, dirIntensity : f32, dirDirection : vec3, lightCount : u32, lights : array, } @binding(2) @group(0) var globalLights : GlobalLights; let tileCount = vec3(32u, 18u, 48u); fn linearDepth(depthSample : f32) -> f32 { return ((camera.zFar * camera.zNear) / fma(depthSample, (camera.zNear - camera.zFar), camera.zFar)); } fn getTile(fragCoord : vec4) -> vec3 { let sliceScale = (f32(tileCount.z) / log2((camera.zFar / camera.zNear))); let sliceBias = -(((f32(tileCount.z) * log2(camera.zNear)) / log2((camera.zFar / camera.zNear)))); let zTile = u32(max(((log2(linearDepth(fragCoord.z)) * sliceScale) + sliceBias), 0.0)); return vec3(u32((fragCoord.x / (camera.outputSize.x / f32(tileCount.x)))), u32((fragCoord.y / (camera.outputSize.y / f32(tileCount.y)))), zTile); } fn getClusterIndex(fragCoord : vec4) -> u32 { let tile = getTile(fragCoord); return ((tile.x + (tile.y * tileCount.x)) + ((tile.z * tileCount.x) * tileCount.y)); } @binding(3) @group(0) var defaultSampler : sampler; @binding(4) @group(0) var shadowTexture : texture_depth_2d; @binding(5) @group(0) var shadowSampler : sampler_comparison; struct LightShadowTable { light : array, } @binding(6) @group(0) var lightShadowTable : LightShadowTable; var shadowSampleOffsets : array, 16> = array, 16>(vec2(-1.5, -1.5), vec2(-1.5, -0.5), vec2(-1.5, 0.5), vec2(-1.5, 1.5), vec2(-0.5, -1.5), vec2(-0.5, -0.5), vec2(-0.5, 0.5), vec2(-0.5, 1.5), vec2(0.5, -1.5), vec2(0.5, -0.5), vec2(0.5, 0.5), vec2(0.5, 1.5), vec2(1.5, -1.5), vec2(1.5, -0.5), vec2(1.5, 0.5), vec2(1.5, 1.5)); let shadowSampleCount = 16u; struct ShadowProperties { viewport : vec4, viewProj : mat4x4, } struct LightShadows { properties : array, } @binding(7) @group(0) var shadow : LightShadows; fn dirLightVisibility(worldPos : vec3) -> f32 { let shadowIndex = lightShadowTable.light[0u]; if ((shadowIndex == -1)) { return 1.0; } let viewport = shadow.properties[shadowIndex].viewport; let lightPos = (shadow.properties[shadowIndex].viewProj * vec4(worldPos, 1.0)); let shadowPos = vec3((((lightPos.xy / lightPos.w) * vec2(0.5, -0.5)) + vec2(0.5, 0.5)), (lightPos.z / lightPos.w)); let viewportPos = vec2((viewport.xy + (shadowPos.xy * viewport.zw))); let texelSize = (1.0 / vec2(textureDimensions(shadowTexture, 0))); let clampRect = vec4((viewport.xy - texelSize), ((viewport.xy + viewport.zw) + texelSize)); var visibility = 0.0; for(var i = 0u; (i < shadowSampleCount); i = (i + 1u)) { visibility = (visibility + textureSampleCompareLevel(shadowTexture, shadowSampler, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.003))); } return (visibility / f32(shadowSampleCount)); } fn getCubeFace(v : vec3) -> i32 { let vAbs = abs(v); if (((vAbs.z >= vAbs.x) && (vAbs.z >= vAbs.y))) { if ((v.z < 0.0)) { return 5; } return 4; } if ((vAbs.y >= vAbs.x)) { if ((v.y < 0.0)) { return 3; } return 2; } if ((v.x < 0.0)) { return 1; } return 0; } fn pointLightVisibility(lightIndex : u32, worldPos : vec3, pointToLight : vec3) -> f32 { var shadowIndex = lightShadowTable.light[(lightIndex + 1u)]; if ((shadowIndex == -1)) { return 1.0; } shadowIndex = (shadowIndex + getCubeFace((pointToLight * -1.0))); let viewport = shadow.properties[shadowIndex].viewport; let lightPos = (shadow.properties[shadowIndex].viewProj * vec4(worldPos, 1.0)); let shadowPos = vec3((((lightPos.xy / lightPos.w) * vec2(0.5, -0.5)) + vec2(0.5, 0.5)), (lightPos.z / lightPos.w)); let viewportPos = vec2((viewport.xy + (shadowPos.xy * viewport.zw))); let texelSize = (1.0 / vec2(textureDimensions(shadowTexture, 0))); let clampRect = vec4(viewport.xy, (viewport.xy + viewport.zw)); var visibility = 0.0; for(var i = 0u; (i < shadowSampleCount); i = (i + 1u)) { visibility = (visibility + textureSampleCompareLevel(shadowTexture, shadowSampler, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.01))); } return (visibility / f32(shadowSampleCount)); } struct VertexOutput { @builtin(position) position : vec4, @location(0) worldPos : vec3, @location(1) view : vec3, @location(2) texcoord : vec2, @location(3) texcoord2 : vec2, @location(4) color : vec4, @location(5) instanceColor : vec4, @location(6) normal : vec3, @location(7) tangent : vec3, @location(8) bitangent : vec3, } struct Material { baseColorFactor : vec4, emissiveFactor : vec3, occlusionStrength : f32, metallicRoughnessFactor : vec2, alphaCutoff : f32, } @binding(8) @group(0) var material : Material; @binding(9) @group(0) var baseColorTexture : texture_2d; @binding(10) @group(0) var baseColorSampler : sampler; @binding(11) @group(0) var normalTexture : texture_2d; @binding(12) @group(0) var normalSampler : sampler; @binding(13) @group(0) var metallicRoughnessTexture : texture_2d; @binding(14) @group(0) var metallicRoughnessSampler : sampler; @binding(15) @group(0) var occlusionTexture : texture_2d; @binding(16) @group(0) var occlusionSampler : sampler; @binding(17) @group(0) var emissiveTexture : texture_2d; @binding(18) @group(0) var emissiveSampler : sampler; struct SurfaceInfo { baseColor : vec4, albedo : vec3, metallic : f32, roughness : f32, normal : vec3, f0 : vec3, ao : f32, emissive : vec3, v : vec3, } fn GetSurfaceInfo(input : VertexOutput) -> SurfaceInfo { var surface : SurfaceInfo; surface.v = normalize(input.view); let tbn = mat3x3(input.tangent, input.bitangent, input.normal); let normalMap = textureSample(normalTexture, normalSampler, input.texcoord).rgb; surface.normal = normalize((tbn * ((2.0 * normalMap) - vec3(1.0)))); let baseColorMap = textureSample(baseColorTexture, baseColorSampler, input.texcoord); surface.baseColor = ((input.color * material.baseColorFactor) * baseColorMap); if ((surface.baseColor.a < material.alphaCutoff)) { discard; } surface.albedo = surface.baseColor.rgb; let metallicRoughnessMap = textureSample(metallicRoughnessTexture, metallicRoughnessSampler, input.texcoord); surface.metallic = (material.metallicRoughnessFactor.x * metallicRoughnessMap.b); surface.roughness = (material.metallicRoughnessFactor.y * metallicRoughnessMap.g); let dielectricSpec = vec3(0.039999999); surface.f0 = mix(dielectricSpec, surface.albedo, vec3(surface.metallic)); let occlusionMap = textureSample(occlusionTexture, occlusionSampler, input.texcoord); surface.ao = (material.occlusionStrength * occlusionMap.r); let emissiveMap = textureSample(emissiveTexture, emissiveSampler, input.texcoord); surface.emissive = (material.emissiveFactor * emissiveMap.rgb); if ((input.instanceColor.a == 0.0)) { surface.albedo = (surface.albedo + input.instanceColor.rgb); } else { surface.albedo = (surface.albedo * input.instanceColor.rgb); } return surface; } let PI = 3.141592741; let LightType_Point = 0u; let LightType_Spot = 1u; let LightType_Directional = 2u; struct PuctualLight { lightType : u32, pointToLight : vec3, range : f32, color : vec3, intensity : f32, } fn FresnelSchlick(cosTheta : f32, F0 : vec3) -> vec3 { return (F0 + ((vec3(1.0) - F0) * pow((1.0 - cosTheta), 5.0))); } fn DistributionGGX(N : vec3, H : vec3, roughness : f32) -> f32 { let a = (roughness * roughness); let a2 = (a * a); let NdotH = max(dot(N, H), 0.0); let NdotH2 = (NdotH * NdotH); let num = a2; let denom = ((NdotH2 * (a2 - 1.0)) + 1.0); return (num / ((PI * denom) * denom)); } fn GeometrySchlickGGX(NdotV : f32, roughness : f32) -> f32 { let r = (roughness + 1.0); let k = ((r * r) / 8.0); let num = NdotV; let denom = ((NdotV * (1.0 - k)) + k); return (num / denom); } fn GeometrySmith(N : vec3, V : vec3, L : vec3, roughness : f32) -> f32 { let NdotV = max(dot(N, V), 0.0); let NdotL = max(dot(N, L), 0.0); let ggx2 = GeometrySchlickGGX(NdotV, roughness); let ggx1 = GeometrySchlickGGX(NdotL, roughness); return (ggx1 * ggx2); } fn lightAttenuation(light : PuctualLight) -> f32 { if ((light.lightType == LightType_Directional)) { return 1.0; } let distance = length(light.pointToLight); if ((light.range <= 0.0)) { return (1.0 / pow(distance, 2.0)); } return (clamp((1.0 - pow((distance / light.range), 4.0)), 0.0, 1.0) / pow(distance, 2.0)); } fn lightRadiance(light : PuctualLight, surface : SurfaceInfo) -> vec3 { let L = normalize(light.pointToLight); let H = normalize((surface.v + L)); let NDF = DistributionGGX(surface.normal, H, surface.roughness); let G = GeometrySmith(surface.normal, surface.v, L, surface.roughness); let F = FresnelSchlick(max(dot(H, surface.v), 0.0), surface.f0); let kD = ((vec3(1.0) - F) * (1.0 - surface.metallic)); let NdotL = max(dot(surface.normal, L), 0.0); let numerator = ((NDF * G) * F); let denominator = max(((4.0 * max(dot(surface.normal, surface.v), 0.0)) * NdotL), 0.001); let specular = (numerator / vec3(denominator)); let radiance = ((light.color * light.intensity) * lightAttenuation(light)); return (((((kD * surface.albedo) / vec3(PI)) + specular) * radiance) * NdotL); } @binding(19) @group(0) var ssaoTexture : texture_2d; struct FragmentOutput { @location(0) color : vec4, @location(1) emissive : vec4, } @stage(fragment) fn fragmentMain(input : VertexOutput) -> FragmentOutput { let surface = GetSurfaceInfo(input); var Lo = vec3(0.0, 0.0, 0.0); if ((globalLights.dirIntensity > 0.0)) { var light : PuctualLight; light.lightType = LightType_Directional; light.pointToLight = globalLights.dirDirection; light.color = globalLights.dirColor; light.intensity = globalLights.dirIntensity; let lightVis = dirLightVisibility(input.worldPos); Lo = (Lo + (lightRadiance(light, surface) * lightVis)); } let clusterIndex = getClusterIndex(input.position); let lightOffset = clusterLights.lights[clusterIndex].offset; let lightCount = clusterLights.lights[clusterIndex].count; for(var lightIndex = 0u; (lightIndex < lightCount); lightIndex = (lightIndex + 1u)) { let i = clusterLights.indices[(lightOffset + lightIndex)]; var light : PuctualLight; light.lightType = LightType_Point; light.pointToLight = (globalLights.lights[i].position.xyz - input.worldPos); light.range = globalLights.lights[i].range; light.color = globalLights.lights[i].color; light.intensity = globalLights.lights[i].intensity; let lightVis = pointLightVisibility(i, input.worldPos, light.pointToLight); Lo = (Lo + (lightRadiance(light, surface) * lightVis)); } let ssaoCoord = (input.position.xy / vec2(textureDimensions(ssaoTexture).xy)); let ssaoFactor = textureSample(ssaoTexture, defaultSampler, ssaoCoord).r; let ambient = (((globalLights.ambient * surface.albedo) * surface.ao) * ssaoFactor); let color = linearTosRGB(((Lo + ambient) + surface.emissive)); var out : FragmentOutput; out.color = vec4(color, surface.baseColor.a); out.emissive = vec4(surface.emissive, surface.baseColor.a); return out; }