Ben Clayton | 73ced33 | 2022-01-21 22:38:16 +0000 | [diff] [blame] | 1 | let GAMMA = 2.200000048; |
| 2 | |
| 3 | fn linearTosRGB(linear : vec3<f32>) -> vec3<f32> { |
| 4 | let INV_GAMMA = (1.0 / GAMMA); |
| 5 | return pow(linear, vec3(INV_GAMMA)); |
| 6 | } |
| 7 | |
| 8 | fn sRGBToLinear(srgb : vec3<f32>) -> vec3<f32> { |
| 9 | return pow(srgb, vec3(GAMMA)); |
| 10 | } |
| 11 | |
| 12 | struct Camera { |
| 13 | projection : mat4x4<f32>; |
| 14 | inverseProjection : mat4x4<f32>; |
| 15 | view : mat4x4<f32>; |
| 16 | position : vec3<f32>; |
| 17 | time : f32; |
| 18 | outputSize : vec2<f32>; |
| 19 | zNear : f32; |
| 20 | zFar : f32; |
| 21 | } |
| 22 | |
| 23 | @binding(0) @group(0) var<uniform> camera : Camera; |
| 24 | |
| 25 | struct ClusterLights { |
| 26 | offset : u32; |
| 27 | count : u32; |
| 28 | } |
| 29 | |
| 30 | struct ClusterLightGroup { |
| 31 | offset : u32; |
| 32 | lights : array<ClusterLights, 27648>; |
| 33 | indices : array<u32, 1769472>; |
| 34 | } |
| 35 | |
| 36 | @binding(1) @group(0) var<storage, read> clusterLights : ClusterLightGroup; |
| 37 | |
| 38 | struct Light { |
| 39 | position : vec3<f32>; |
| 40 | range : f32; |
| 41 | color : vec3<f32>; |
| 42 | intensity : f32; |
| 43 | } |
| 44 | |
| 45 | struct GlobalLights { |
| 46 | ambient : vec3<f32>; |
| 47 | dirColor : vec3<f32>; |
| 48 | dirIntensity : f32; |
| 49 | dirDirection : vec3<f32>; |
| 50 | lightCount : u32; |
| 51 | lights : @stride(32) array<Light>; |
| 52 | } |
| 53 | |
| 54 | @binding(2) @group(0) var<storage, read> globalLights : GlobalLights; |
| 55 | |
| 56 | let tileCount = vec3(32u, 18u, 48u); |
| 57 | |
| 58 | fn linearDepth(depthSample : f32) -> f32 { |
| 59 | return ((camera.zFar * camera.zNear) / fma(depthSample, (camera.zNear - camera.zFar), camera.zFar)); |
| 60 | } |
| 61 | |
| 62 | fn getTile(fragCoord : vec4<f32>) -> vec3<u32> { |
| 63 | let sliceScale = (f32(tileCount.z) / log2((camera.zFar / camera.zNear))); |
| 64 | let sliceBias = -(((f32(tileCount.z) * log2(camera.zNear)) / log2((camera.zFar / camera.zNear)))); |
| 65 | let zTile = u32(max(((log2(linearDepth(fragCoord.z)) * sliceScale) + sliceBias), 0.0)); |
| 66 | return vec3(u32((fragCoord.x / (camera.outputSize.x / f32(tileCount.x)))), u32((fragCoord.y / (camera.outputSize.y / f32(tileCount.y)))), zTile); |
| 67 | } |
| 68 | |
| 69 | fn getClusterIndex(fragCoord : vec4<f32>) -> u32 { |
| 70 | let tile = getTile(fragCoord); |
| 71 | return ((tile.x + (tile.y * tileCount.x)) + ((tile.z * tileCount.x) * tileCount.y)); |
| 72 | } |
| 73 | |
| 74 | @binding(3) @group(0) var defaultSampler : sampler; |
| 75 | |
| 76 | @binding(4) @group(0) var shadowTexture : texture_depth_2d; |
| 77 | |
| 78 | @binding(5) @group(0) var shadowSampler : sampler_comparison; |
| 79 | |
| 80 | struct LightShadowTable { |
| 81 | light : array<i32>; |
| 82 | } |
| 83 | |
| 84 | @binding(6) @group(0) var<storage, read> lightShadowTable : LightShadowTable; |
| 85 | |
| 86 | 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)); |
| 87 | |
| 88 | let shadowSampleCount = 16u; |
| 89 | |
| 90 | struct ShadowProperties { |
| 91 | viewport : vec4<f32>; |
| 92 | viewProj : mat4x4<f32>; |
| 93 | } |
| 94 | |
| 95 | struct LightShadows { |
| 96 | properties : array<ShadowProperties>; |
| 97 | } |
| 98 | |
| 99 | @binding(7) @group(0) var<storage, read> shadow : LightShadows; |
| 100 | |
| 101 | fn dirLightVisibility(worldPos : vec3<f32>) -> f32 { |
| 102 | let shadowIndex = lightShadowTable.light[0u]; |
| 103 | if ((shadowIndex == -1)) { |
| 104 | return 1.0; |
| 105 | } |
| 106 | let viewport = shadow.properties[shadowIndex].viewport; |
| 107 | let lightPos = (shadow.properties[shadowIndex].viewProj * vec4(worldPos, 1.0)); |
| 108 | let shadowPos = vec3((((lightPos.xy / lightPos.w) * vec2(0.5, -0.5)) + vec2(0.5, 0.5)), (lightPos.z / lightPos.w)); |
| 109 | let viewportPos = vec2((viewport.xy + (shadowPos.xy * viewport.zw))); |
| 110 | let texelSize = (1.0 / vec2<f32>(textureDimensions(shadowTexture, 0))); |
| 111 | let clampRect = vec4((viewport.xy - texelSize), ((viewport.xy + viewport.zw) + texelSize)); |
| 112 | var visibility = 0.0; |
| 113 | for(var i = 0u; (i < shadowSampleCount); i = (i + 1u)) { |
| 114 | visibility = (visibility + textureSampleCompareLevel(shadowTexture, shadowSampler, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.003))); |
| 115 | } |
| 116 | return (visibility / f32(shadowSampleCount)); |
| 117 | } |
| 118 | |
| 119 | fn getCubeFace(v : vec3<f32>) -> i32 { |
| 120 | let vAbs = abs(v); |
| 121 | if (((vAbs.z >= vAbs.x) && (vAbs.z >= vAbs.y))) { |
| 122 | if ((v.z < 0.0)) { |
| 123 | return 5; |
| 124 | } |
| 125 | return 4; |
| 126 | } |
| 127 | if ((vAbs.y >= vAbs.x)) { |
| 128 | if ((v.y < 0.0)) { |
| 129 | return 3; |
| 130 | } |
| 131 | return 2; |
| 132 | } |
| 133 | if ((v.x < 0.0)) { |
| 134 | return 1; |
| 135 | } |
| 136 | return 0; |
| 137 | } |
| 138 | |
| 139 | fn pointLightVisibility(lightIndex : u32, worldPos : vec3<f32>, pointToLight : vec3<f32>) -> f32 { |
| 140 | var shadowIndex = lightShadowTable.light[(lightIndex + 1u)]; |
| 141 | if ((shadowIndex == -1)) { |
| 142 | return 1.0; |
| 143 | } |
| 144 | shadowIndex = (shadowIndex + getCubeFace((pointToLight * -1.0))); |
| 145 | let viewport = shadow.properties[shadowIndex].viewport; |
| 146 | let lightPos = (shadow.properties[shadowIndex].viewProj * vec4(worldPos, 1.0)); |
| 147 | let shadowPos = vec3((((lightPos.xy / lightPos.w) * vec2(0.5, -0.5)) + vec2(0.5, 0.5)), (lightPos.z / lightPos.w)); |
| 148 | let viewportPos = vec2((viewport.xy + (shadowPos.xy * viewport.zw))); |
| 149 | let texelSize = (1.0 / vec2<f32>(textureDimensions(shadowTexture, 0))); |
| 150 | let clampRect = vec4(viewport.xy, (viewport.xy + viewport.zw)); |
| 151 | var visibility = 0.0; |
| 152 | for(var i = 0u; (i < shadowSampleCount); i = (i + 1u)) { |
| 153 | visibility = (visibility + textureSampleCompareLevel(shadowTexture, shadowSampler, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.01))); |
| 154 | } |
| 155 | return (visibility / f32(shadowSampleCount)); |
| 156 | } |
| 157 | |
| 158 | struct VertexOutput { |
| 159 | @builtin(position) |
| 160 | position : vec4<f32>; |
| 161 | @location(0) |
| 162 | worldPos : vec3<f32>; |
| 163 | @location(1) |
| 164 | view : vec3<f32>; |
| 165 | @location(2) |
| 166 | texcoord : vec2<f32>; |
| 167 | @location(3) |
| 168 | texcoord2 : vec2<f32>; |
| 169 | @location(4) |
| 170 | color : vec4<f32>; |
| 171 | @location(5) |
| 172 | instanceColor : vec4<f32>; |
| 173 | @location(6) |
| 174 | normal : vec3<f32>; |
| 175 | @location(7) |
| 176 | tangent : vec3<f32>; |
| 177 | @location(8) |
| 178 | bitangent : vec3<f32>; |
| 179 | } |
| 180 | |
| 181 | struct Material { |
| 182 | baseColorFactor : vec4<f32>; |
| 183 | emissiveFactor : vec3<f32>; |
| 184 | occlusionStrength : f32; |
| 185 | metallicRoughnessFactor : vec2<f32>; |
| 186 | alphaCutoff : f32; |
| 187 | } |
| 188 | |
| 189 | @binding(8) @group(0) var<uniform> material : Material; |
| 190 | |
| 191 | @binding(9) @group(0) var baseColorTexture : texture_2d<f32>; |
| 192 | |
| 193 | @binding(10) @group(0) var baseColorSampler : sampler; |
| 194 | |
| 195 | @binding(11) @group(0) var normalTexture : texture_2d<f32>; |
| 196 | |
| 197 | @binding(12) @group(0) var normalSampler : sampler; |
| 198 | |
| 199 | @binding(13) @group(0) var metallicRoughnessTexture : texture_2d<f32>; |
| 200 | |
| 201 | @binding(14) @group(0) var metallicRoughnessSampler : sampler; |
| 202 | |
| 203 | @binding(15) @group(0) var occlusionTexture : texture_2d<f32>; |
| 204 | |
| 205 | @binding(16) @group(0) var occlusionSampler : sampler; |
| 206 | |
| 207 | @binding(17) @group(0) var emissiveTexture : texture_2d<f32>; |
| 208 | |
| 209 | @binding(18) @group(0) var emissiveSampler : sampler; |
| 210 | |
| 211 | struct SurfaceInfo { |
| 212 | baseColor : vec4<f32>; |
| 213 | albedo : vec3<f32>; |
| 214 | metallic : f32; |
| 215 | roughness : f32; |
| 216 | normal : vec3<f32>; |
| 217 | f0 : vec3<f32>; |
| 218 | ao : f32; |
| 219 | emissive : vec3<f32>; |
| 220 | v : vec3<f32>; |
| 221 | } |
| 222 | |
| 223 | fn GetSurfaceInfo(input : VertexOutput) -> SurfaceInfo { |
| 224 | var surface : SurfaceInfo; |
| 225 | surface.v = normalize(input.view); |
| 226 | let tbn = mat3x3(input.tangent, input.bitangent, input.normal); |
| 227 | let normalMap = textureSample(normalTexture, normalSampler, input.texcoord).rgb; |
| 228 | surface.normal = normalize((tbn * ((2.0 * normalMap) - vec3(1.0)))); |
| 229 | let baseColorMap = textureSample(baseColorTexture, baseColorSampler, input.texcoord); |
| 230 | surface.baseColor = ((input.color * material.baseColorFactor) * baseColorMap); |
| 231 | if ((surface.baseColor.a < material.alphaCutoff)) { |
| 232 | discard; |
| 233 | } |
| 234 | surface.albedo = surface.baseColor.rgb; |
| 235 | let metallicRoughnessMap = textureSample(metallicRoughnessTexture, metallicRoughnessSampler, input.texcoord); |
| 236 | surface.metallic = (material.metallicRoughnessFactor.x * metallicRoughnessMap.b); |
| 237 | surface.roughness = (material.metallicRoughnessFactor.y * metallicRoughnessMap.g); |
| 238 | let dielectricSpec = vec3(0.039999999); |
| 239 | surface.f0 = mix(dielectricSpec, surface.albedo, vec3(surface.metallic)); |
| 240 | let occlusionMap = textureSample(occlusionTexture, occlusionSampler, input.texcoord); |
| 241 | surface.ao = (material.occlusionStrength * occlusionMap.r); |
| 242 | let emissiveMap = textureSample(emissiveTexture, emissiveSampler, input.texcoord); |
| 243 | surface.emissive = (material.emissiveFactor * emissiveMap.rgb); |
| 244 | if ((input.instanceColor.a == 0.0)) { |
| 245 | surface.albedo = (surface.albedo + input.instanceColor.rgb); |
| 246 | } else { |
| 247 | surface.albedo = (surface.albedo * input.instanceColor.rgb); |
| 248 | } |
| 249 | return surface; |
| 250 | } |
| 251 | |
| 252 | let PI = 3.141592741; |
| 253 | |
| 254 | let LightType_Point = 0u; |
| 255 | |
| 256 | let LightType_Spot = 1u; |
| 257 | |
| 258 | let LightType_Directional = 2u; |
| 259 | |
| 260 | struct PuctualLight { |
| 261 | lightType : u32; |
| 262 | pointToLight : vec3<f32>; |
| 263 | range : f32; |
| 264 | color : vec3<f32>; |
| 265 | intensity : f32; |
| 266 | } |
| 267 | |
| 268 | fn FresnelSchlick(cosTheta : f32, F0 : vec3<f32>) -> vec3<f32> { |
| 269 | return (F0 + ((vec3(1.0) - F0) * pow((1.0 - cosTheta), 5.0))); |
| 270 | } |
| 271 | |
| 272 | fn DistributionGGX(N : vec3<f32>, H : vec3<f32>, roughness : f32) -> f32 { |
| 273 | let a = (roughness * roughness); |
| 274 | let a2 = (a * a); |
| 275 | let NdotH = max(dot(N, H), 0.0); |
| 276 | let NdotH2 = (NdotH * NdotH); |
| 277 | let num = a2; |
| 278 | let denom = ((NdotH2 * (a2 - 1.0)) + 1.0); |
| 279 | return (num / ((PI * denom) * denom)); |
| 280 | } |
| 281 | |
| 282 | fn GeometrySchlickGGX(NdotV : f32, roughness : f32) -> f32 { |
| 283 | let r = (roughness + 1.0); |
| 284 | let k = ((r * r) / 8.0); |
| 285 | let num = NdotV; |
| 286 | let denom = ((NdotV * (1.0 - k)) + k); |
| 287 | return (num / denom); |
| 288 | } |
| 289 | |
| 290 | fn GeometrySmith(N : vec3<f32>, V : vec3<f32>, L : vec3<f32>, roughness : f32) -> f32 { |
| 291 | let NdotV = max(dot(N, V), 0.0); |
| 292 | let NdotL = max(dot(N, L), 0.0); |
| 293 | let ggx2 = GeometrySchlickGGX(NdotV, roughness); |
| 294 | let ggx1 = GeometrySchlickGGX(NdotL, roughness); |
| 295 | return (ggx1 * ggx2); |
| 296 | } |
| 297 | |
| 298 | fn lightAttenuation(light : PuctualLight) -> f32 { |
| 299 | if ((light.lightType == LightType_Directional)) { |
| 300 | return 1.0; |
| 301 | } |
| 302 | let distance = length(light.pointToLight); |
| 303 | if ((light.range <= 0.0)) { |
| 304 | return (1.0 / pow(distance, 2.0)); |
| 305 | } |
| 306 | return (clamp((1.0 - pow((distance / light.range), 4.0)), 0.0, 1.0) / pow(distance, 2.0)); |
| 307 | } |
| 308 | |
| 309 | fn lightRadiance(light : PuctualLight, surface : SurfaceInfo) -> vec3<f32> { |
| 310 | let L = normalize(light.pointToLight); |
| 311 | let H = normalize((surface.v + L)); |
| 312 | let NDF = DistributionGGX(surface.normal, H, surface.roughness); |
| 313 | let G = GeometrySmith(surface.normal, surface.v, L, surface.roughness); |
| 314 | let F = FresnelSchlick(max(dot(H, surface.v), 0.0), surface.f0); |
| 315 | let kD = ((vec3(1.0) - F) * (1.0 - surface.metallic)); |
| 316 | let NdotL = max(dot(surface.normal, L), 0.0); |
| 317 | let numerator = ((NDF * G) * F); |
| 318 | let denominator = max(((4.0 * max(dot(surface.normal, surface.v), 0.0)) * NdotL), 0.001); |
| 319 | let specular = (numerator / vec3(denominator)); |
| 320 | let radiance = ((light.color * light.intensity) * lightAttenuation(light)); |
| 321 | return (((((kD * surface.albedo) / vec3(PI)) + specular) * radiance) * NdotL); |
| 322 | } |
| 323 | |
| 324 | @binding(19) @group(0) var ssaoTexture : texture_2d<f32>; |
| 325 | |
| 326 | struct FragmentOutput { |
| 327 | @location(0) |
| 328 | color : vec4<f32>; |
| 329 | @location(1) |
| 330 | emissive : vec4<f32>; |
| 331 | } |
| 332 | |
| 333 | @stage(fragment) |
| 334 | fn fragmentMain(input : VertexOutput) -> FragmentOutput { |
| 335 | let surface = GetSurfaceInfo(input); |
| 336 | var Lo = vec3(0.0, 0.0, 0.0); |
| 337 | if ((globalLights.dirIntensity > 0.0)) { |
| 338 | var light : PuctualLight; |
| 339 | light.lightType = LightType_Directional; |
| 340 | light.pointToLight = globalLights.dirDirection; |
| 341 | light.color = globalLights.dirColor; |
| 342 | light.intensity = globalLights.dirIntensity; |
| 343 | let lightVis = dirLightVisibility(input.worldPos); |
| 344 | Lo = (Lo + (lightRadiance(light, surface) * lightVis)); |
| 345 | } |
| 346 | let clusterIndex = getClusterIndex(input.position); |
| 347 | let lightOffset = clusterLights.lights[clusterIndex].offset; |
| 348 | let lightCount = clusterLights.lights[clusterIndex].count; |
| 349 | for(var lightIndex = 0u; (lightIndex < lightCount); lightIndex = (lightIndex + 1u)) { |
| 350 | let i = clusterLights.indices[(lightOffset + lightIndex)]; |
| 351 | var light : PuctualLight; |
| 352 | light.lightType = LightType_Point; |
| 353 | light.pointToLight = (globalLights.lights[i].position.xyz - input.worldPos); |
| 354 | light.range = globalLights.lights[i].range; |
| 355 | light.color = globalLights.lights[i].color; |
| 356 | light.intensity = globalLights.lights[i].intensity; |
| 357 | let lightVis = pointLightVisibility(i, input.worldPos, light.pointToLight); |
| 358 | Lo = (Lo + (lightRadiance(light, surface) * lightVis)); |
| 359 | } |
| 360 | let ssaoCoord = (input.position.xy / vec2<f32>(textureDimensions(ssaoTexture).xy)); |
| 361 | let ssaoFactor = textureSample(ssaoTexture, defaultSampler, ssaoCoord).r; |
| 362 | let ambient = (((globalLights.ambient * surface.albedo) * surface.ao) * ssaoFactor); |
| 363 | let color = linearTosRGB(((Lo + ambient) + surface.emissive)); |
| 364 | var out : FragmentOutput; |
| 365 | out.color = vec4(color, surface.baseColor.a); |
| 366 | out.emissive = vec4(surface.emissive, surface.baseColor.a); |
| 367 | return out; |
| 368 | } |