| let GAMMA = 2.200000048; |
| |
| fn linearTosRGB(linear : vec3<f32>) -> vec3<f32> { |
| let INV_GAMMA = (1.0 / GAMMA); |
| return pow(linear, vec3(INV_GAMMA)); |
| } |
| |
| fn sRGBToLinear(srgb : vec3<f32>) -> vec3<f32> { |
| return pow(srgb, vec3(GAMMA)); |
| } |
| |
| struct Camera { |
| projection : mat4x4<f32>; |
| inverseProjection : mat4x4<f32>; |
| view : mat4x4<f32>; |
| position : vec3<f32>; |
| time : f32; |
| outputSize : vec2<f32>; |
| zNear : f32; |
| zFar : f32; |
| } |
| |
| @binding(0) @group(0) var<uniform> camera : Camera; |
| |
| struct ClusterLights { |
| offset : u32; |
| count : u32; |
| } |
| |
| struct ClusterLightGroup { |
| offset : u32; |
| lights : array<ClusterLights, 27648>; |
| indices : array<u32, 1769472>; |
| } |
| |
| @binding(1) @group(0) var<storage, read> clusterLights : ClusterLightGroup; |
| |
| struct Light { |
| position : vec3<f32>; |
| range : f32; |
| color : vec3<f32>; |
| intensity : f32; |
| } |
| |
| struct GlobalLights { |
| ambient : vec3<f32>; |
| dirColor : vec3<f32>; |
| dirIntensity : f32; |
| dirDirection : vec3<f32>; |
| lightCount : u32; |
| lights : @stride(32) array<Light>; |
| } |
| |
| @binding(2) @group(0) var<storage, read> 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<f32>) -> vec3<u32> { |
| 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<f32>) -> 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<i32>; |
| } |
| |
| @binding(6) @group(0) var<storage, read> lightShadowTable : LightShadowTable; |
| |
| var<private> shadowSampleOffsets : array<vec2<f32>, 16> = array<vec2<f32>, 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<f32>; |
| viewProj : mat4x4<f32>; |
| } |
| |
| struct LightShadows { |
| properties : array<ShadowProperties>; |
| } |
| |
| @binding(7) @group(0) var<storage, read> shadow : LightShadows; |
| |
| fn dirLightVisibility(worldPos : vec3<f32>) -> 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<f32>(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<f32>) -> 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<f32>, pointToLight : vec3<f32>) -> 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<f32>(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<f32>; |
| @location(0) |
| worldPos : vec3<f32>; |
| @location(1) |
| view : vec3<f32>; |
| @location(2) |
| texcoord : vec2<f32>; |
| @location(3) |
| texcoord2 : vec2<f32>; |
| @location(4) |
| color : vec4<f32>; |
| @location(5) |
| instanceColor : vec4<f32>; |
| @location(6) |
| normal : vec3<f32>; |
| @location(7) |
| tangent : vec3<f32>; |
| @location(8) |
| bitangent : vec3<f32>; |
| } |
| |
| struct Material { |
| baseColorFactor : vec4<f32>; |
| emissiveFactor : vec3<f32>; |
| occlusionStrength : f32; |
| metallicRoughnessFactor : vec2<f32>; |
| alphaCutoff : f32; |
| } |
| |
| @binding(8) @group(0) var<uniform> material : Material; |
| |
| @binding(9) @group(0) var baseColorTexture : texture_2d<f32>; |
| |
| @binding(10) @group(0) var baseColorSampler : sampler; |
| |
| @binding(11) @group(0) var normalTexture : texture_2d<f32>; |
| |
| @binding(12) @group(0) var normalSampler : sampler; |
| |
| @binding(13) @group(0) var metallicRoughnessTexture : texture_2d<f32>; |
| |
| @binding(14) @group(0) var metallicRoughnessSampler : sampler; |
| |
| @binding(15) @group(0) var occlusionTexture : texture_2d<f32>; |
| |
| @binding(16) @group(0) var occlusionSampler : sampler; |
| |
| @binding(17) @group(0) var emissiveTexture : texture_2d<f32>; |
| |
| @binding(18) @group(0) var emissiveSampler : sampler; |
| |
| struct SurfaceInfo { |
| baseColor : vec4<f32>; |
| albedo : vec3<f32>; |
| metallic : f32; |
| roughness : f32; |
| normal : vec3<f32>; |
| f0 : vec3<f32>; |
| ao : f32; |
| emissive : vec3<f32>; |
| v : vec3<f32>; |
| } |
| |
| 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<f32>; |
| range : f32; |
| color : vec3<f32>; |
| intensity : f32; |
| } |
| |
| fn FresnelSchlick(cosTheta : f32, F0 : vec3<f32>) -> vec3<f32> { |
| return (F0 + ((vec3(1.0) - F0) * pow((1.0 - cosTheta), 5.0))); |
| } |
| |
| fn DistributionGGX(N : vec3<f32>, H : vec3<f32>, 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<f32>, V : vec3<f32>, L : vec3<f32>, 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<f32> { |
| 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<f32>; |
| |
| struct FragmentOutput { |
| @location(0) |
| color : vec4<f32>; |
| @location(1) |
| emissive : vec4<f32>; |
| } |
| |
| @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<f32>(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; |
| } |