struct Vertex { @location(0) positionOS: vec3, @location(1) texCoord: vec2, @location(2) normalOS: vec3, @location(3) tangentOS: vec4, } struct Varyings { @builtin(position) positionCS: vec4, @location(0) positionVS: vec3, @location(1) texCoord: vec2, @location(2) normalVS: vec3, @location(3) tangentVS: vec3, @location(4) bitangentVS: vec3, } struct PointLight { positionWS: vec3, color: vec3, } struct DirectionalLight { directionWS: vec3, color: vec3, } struct GlobalUniforms { matrixWStoVS: mat4x4, matrixVStoCS: mat4x4, ambientLight: vec3, pointLightCount: u32, directionalLightCount: u32, } struct ObjectUniforms { matrixOStoWS: mat4x4, matrixOStoWSNormal: mat4x4, } // --- GROUP 0 --- GLOBAL ------------------------------------------------------ @group(0) @binding(0) var _Global: GlobalUniforms; @group(0) @binding(1) var _PointLights: array; @group(0) @binding(2) var _DirectionalLights: array; @group(0) @binding(3) var _Sampler: sampler; @group(0) @binding(4) var _BaseColorTexture: texture_2d_array; @group(0) @binding(5) var _OcclusionRoughnessMetallicTexture: texture_2d_array; @group(0) @binding(6) var _NormalTexture: texture_2d_array; // --- GROUP 1 --- PER MATERIAL ------------------------------------------------ @group(1) @binding(0) var _TextureIndex: u32; // --- GROUP 2 --- PER OBJECT -------------------------------------------------- @group(2) @binding(0) var _Object: ObjectUniforms; // ----------------------------------------------------------------------------- const INV_PI: f32 = 0.31830987; const IOR: f32 = 1.45; const DIELECTRIC_F0: vec3 = vec3(pow((IOR - 1.0) / (IOR + 1.0), 2.0)); const F90 = vec3(1.0); fn fresnelSchlick(dotVH: f32, f0: vec3) -> vec3 { return mix(f0, F90, pow(1.0 - dotVH, 5.0)); } fn visibilityGGX(dotNL: f32, dotNV: f32, alpha: f32) -> f32 { let alphaSquared = alpha * alpha; let vGGX = dotNL * sqrt(dotNV * dotNV * (1.0 - alphaSquared) + alphaSquared); let lGGX = dotNV * sqrt(dotNL * dotNL * (1.0 - alphaSquared) + alphaSquared); let GGX = vGGX + lGGX; return select(0.0, 0.5 / GGX, GGX > 0.0); } fn distributionGGX(dotNH: f32, alpha: f32) -> f32 { let alphaSquared = alpha * alpha; let tmp = dotNH * dotNH * (alphaSquared - 1.0) + 1.0; return alphaSquared * INV_PI / (tmp * tmp); } fn toneMapAcesNarkowicz(color: vec3) -> vec3 { const A: f32 = 2.51; const B: f32 = 0.03; const C: f32 = 2.43; const D: f32 = 0.59; const E: f32 = 0.14; return saturate((color * (A * color + B)) / (color * (C * color + D) + E)); } fn lightOutgoingRadiance( viewDirectionVS: vec3, normalVS: vec3, dotNV: f32, baseColor: vec3, alpha: f32, metallic: f32, f0: vec3, incomingRadiance: vec3, lightDirectionVS: vec3, ) -> vec3 { let halfVectorVS = normalize(lightDirectionVS + viewDirectionVS); let dotVH = saturate(dot(viewDirectionVS, halfVectorVS)); let dotNH = saturate(dot(normalVS, halfVectorVS)); let dotNL = saturate(dot(normalVS, lightDirectionVS)); let fresnel = fresnelSchlick(dotVH, f0); let visibility = visibilityGGX(dotNL, dotNV, alpha); let distribution = distributionGGX(dotNH, alpha); let scatteredFactor = (1.0 - fresnel) * (1.0 - metallic) * baseColor * INV_PI; let reflectedFactor = fresnel * visibility * distribution; return (scatteredFactor + reflectedFactor) * incomingRadiance * dotNL; } @vertex fn vert(vertex: Vertex) -> Varyings { let positionWS = (_Object.matrixOStoWS * vec4(vertex.positionOS, 1.0)).xyz; let positionVS = (_Global.matrixWStoVS * vec4(positionWS, 1.0)).xyz; let positionCS = _Global.matrixVStoCS * vec4(positionVS, 1.0); let normalWS = normalize((_Object.matrixOStoWSNormal * vec4(vertex.normalOS, 0.0)).xyz); let normalVS = normalize((_Global.matrixWStoVS * vec4(normalWS, 0.0)).xyz); let tangentWS = normalize((_Object.matrixOStoWS * vec4(vertex.tangentOS.xyz, 0.0)).xyz); let tangentVS = normalize((_Global.matrixWStoVS * vec4(tangentWS, 0.0)).xyz); let bitangentVS = vertex.tangentOS.w * normalize(cross(normalVS, tangentVS)); var output: Varyings; output.positionCS = positionCS; output.positionVS = positionVS; output.texCoord = vertex.texCoord; output.normalVS = normalVS; output.tangentVS = tangentVS; output.bitangentVS = bitangentVS; return output; } @fragment fn frag(fragment: Varyings) -> @location(0) vec4 { let baseColorTexel = textureSample(_BaseColorTexture, _Sampler, fragment.texCoord, _TextureIndex); let occlusionRoughnessMetallicTexel = textureSample(_OcclusionRoughnessMetallicTexture, _Sampler, fragment.texCoord, _TextureIndex); let normalTextureTexel = textureSample(_NormalTexture, _Sampler, fragment.texCoord, _TextureIndex); let baseColor = baseColorTexel.rgb; let occlusion = occlusionRoughnessMetallicTexel.r; let roughness = occlusionRoughnessMetallicTexel.g; let metallic = occlusionRoughnessMetallicTexel.b; let tangentVS = normalize(fragment.tangentVS); let bitangentVS = normalize(fragment.bitangentVS); let matrixTStoVS = mat3x3(tangentVS, bitangentVS, fragment.normalVS); let normalTS = normalTextureTexel.xyz * 2.0 - 1.0; let normalVS = normalize(matrixTStoVS * normalTS); let positionVS = fragment.positionVS; let viewDirectionVS = normalize(-positionVS); let dotNV = saturate(dot(normalVS, viewDirectionVS)); let alpha = roughness * roughness; let f0 = mix(DIELECTRIC_F0, baseColor, metallic); var outgoingRadiance = vec3(0.0); for (var i: u32 = 0; i < _Global.pointLightCount; i++) { let light = _PointLights[i]; let lightPositionVS = (_Global.matrixWStoVS * vec4(light.positionWS, 1.0)).xyz; let lightDirectionVS = normalize(lightPositionVS - positionVS); let lightDistance = distance(positionVS, lightPositionVS); let lightAttenuation = 1.0 / (lightDistance * lightDistance); let incomingRadiance = light.color * lightAttenuation; outgoingRadiance += lightOutgoingRadiance( viewDirectionVS, normalVS, dotNV, baseColor, alpha, metallic, f0, incomingRadiance, lightDirectionVS, ); } for (var i: u32 = 0; i < _Global.directionalLightCount; i++) { let light = _DirectionalLights[i]; let lightDirectionVS = normalize((_Global.matrixWStoVS * vec4(light.directionWS, 0.0)).xyz); let incomingRadiance = light.color; outgoingRadiance += lightOutgoingRadiance( viewDirectionVS, normalVS, dotNV, baseColor, alpha, metallic, f0, incomingRadiance, lightDirectionVS, ); } outgoingRadiance += _Global.ambientLight * baseColor * occlusion; let toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance); let toneMappedSrgbColor = pow(toneMappedLinearColor, vec3(1.0 / 2.2)); return vec4(toneMappedSrgbColor, 1.0); }