UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

281 lines (278 loc) 11.4 kB
var ltcPS = ` fn LTC_Uv(N: vec3f, V: vec3f, roughness: f32) -> vec2f { const LUT_SIZE: f32 = 64.0; const LUT_SCALE: f32 = (LUT_SIZE - 1.0) / LUT_SIZE; const LUT_BIAS: f32 = 0.5 / LUT_SIZE; let dotNV: f32 = saturate(dot( N, V )); let uv: vec2f = vec2f( roughness, sqrt( 1.0 - dotNV ) ); return uv * LUT_SCALE + LUT_BIAS; } fn LTC_ClippedSphereFormFactor( f: vec3f ) -> f32 { let l: f32 = length( f ); return max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 ); } fn LTC_EdgeVectorFormFactor( v1: vec3f, v2: vec3f ) -> vec3f { let x: f32 = dot( v1, v2 ); let y: f32 = abs( x ); let a: f32 = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y; let b: f32 = 3.4175940 + ( 4.1616724 + y ) * y; let v: f32 = a / b; let inv_sqrt_term = inverseSqrt( max( 1.0 - x * x, 1e-7f ) ); let theta_sintheta: f32 = select( (0.5 * inv_sqrt_term - v), v, x > 0.0 ); return cross( v1, v2 ) * theta_sintheta; } struct Coords { coord0: vec3f, coord1: vec3f, coord2: vec3f, coord3: vec3f, } fn LTC_EvaluateRect( N: vec3f, V: vec3f, P: vec3f, mInv: mat3x3f, rectCoords: Coords) -> f32 { let v1: vec3f = rectCoords.coord1 - rectCoords.coord0; let v2: vec3f = rectCoords.coord3 - rectCoords.coord0; let lightNormal: vec3f = cross( v1, v2 ); let factor: f32 = sign(-dot( lightNormal, P - rectCoords.coord0 )); let T1: vec3f = normalize( V - N * dot( V, N ) ); let T2: vec3f = factor * cross( N, T1 ); let mat: mat3x3f = mInv * transpose( mat3x3f( T1, T2, N ) ); var coords: array<vec3f, 4>; coords[0] = mat * ( rectCoords.coord0 - P ); coords[1] = mat * ( rectCoords.coord1 - P ); coords[2] = mat * ( rectCoords.coord2 - P ); coords[3] = mat * ( rectCoords.coord3 - P ); coords[0] = normalize( coords[0] ); coords[1] = normalize( coords[1] ); coords[2] = normalize( coords[2] ); coords[3] = normalize( coords[3] ); var vectorFormFactor: vec3f = vec3f( 0.0 ); vectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[0], coords[1] ); vectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[1], coords[2] ); vectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[2], coords[3] ); vectorFormFactor = vectorFormFactor + LTC_EdgeVectorFormFactor( coords[3], coords[0] ); let result: f32 = LTC_ClippedSphereFormFactor( vectorFormFactor ); return result; } var<private> dLTCCoords: Coords; fn getLTCLightCoords(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) -> Coords { var coords: Coords; coords.coord0 = lightPos + halfWidth - halfHeight; coords.coord1 = lightPos - halfWidth - halfHeight; coords.coord2 = lightPos - halfWidth + halfHeight; coords.coord3 = lightPos + halfWidth + halfHeight; return coords; } var<private> dSphereRadius: f32; fn getSphereLightCoords(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) -> Coords { dSphereRadius = max(length(halfWidth), length(halfHeight)); let f: vec3f = reflect(normalize(lightPos - uniform.view_position), vNormalW); let w: vec3f = normalize(cross(f, halfHeight)); let h: vec3f = normalize(cross(f, w)); return getLTCLightCoords(lightPos, w * dSphereRadius, h * dSphereRadius); } var<private> dLTCUV: vec2f; #ifdef LIT_CLEARCOAT var<private> ccLTCUV: vec2f; #endif fn getLTCLightUV(gloss: f32, worldNormal: vec3f, viewDir: vec3f) -> vec2f { let roughness: f32 = max((1.0 - gloss) * (1.0 - gloss), 0.001); return LTC_Uv( worldNormal, viewDir, roughness ); } var<private> dLTCSpecFres: vec3f; #ifdef LIT_CLEARCOAT var<private> ccLTCSpecFres: vec3f; #endif fn getLTCLightSpecFres(uv: vec2f, specularity: vec3f) -> vec3f { let t2: vec4f = textureSampleLevel(areaLightsLutTex2, areaLightsLutTex2Sampler, uv, 0.0); return specularity * t2.x + ( vec3f( 1.0 ) - specularity) * t2.y; } fn calcLTCLightValues(gloss: f32, worldNormal: vec3f, viewDir: vec3f, specularity: vec3f, clearcoatGloss: f32, clearcoatWorldNormal: vec3f, clearcoatSpecularity: f32) { dLTCUV = getLTCLightUV(gloss, worldNormal, viewDir); dLTCSpecFres = getLTCLightSpecFres(dLTCUV, specularity); #ifdef LIT_CLEARCOAT ccLTCUV = getLTCLightUV(clearcoatGloss, clearcoatWorldNormal, viewDir); ccLTCSpecFres = getLTCLightSpecFres(ccLTCUV, vec3f(clearcoatSpecularity)); #endif } fn calcRectLightValues(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) { dLTCCoords = getLTCLightCoords(lightPos, halfWidth, halfHeight); } fn calcDiskLightValues(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) { calcRectLightValues(lightPos, halfWidth, halfHeight); } fn calcSphereLightValues(lightPos: vec3f, halfWidth: vec3f, halfHeight: vec3f) { dLTCCoords = getSphereLightCoords(lightPos, halfWidth, halfHeight); } fn SolveCubic(Coefficient_in: vec4f) -> vec3f { let pi: f32 = 3.14159; var Coefficient = Coefficient_in; Coefficient = vec4f(Coefficient.xyz / Coefficient.w, Coefficient.w); let new_yz: vec2f = Coefficient.yz / 3.0; Coefficient = vec4f(Coefficient.x, new_yz.x, new_yz.y, Coefficient.w); let A: f32 = Coefficient.w; let B: f32 = Coefficient.z; let C: f32 = Coefficient.y; let D: f32 = Coefficient.x; let Delta: vec3f = vec3f( -Coefficient.z * Coefficient.z + Coefficient.y, -Coefficient.y * Coefficient.z + Coefficient.x, dot(vec2f(Coefficient.z, -Coefficient.y), Coefficient.xy) ); let Discriminant: f32 = dot(vec2f(4.0 * Delta.x, -Delta.y), Delta.zy); var xlc: vec2f; var xsc: vec2f; { let A_a: f32 = 1.0; let C_a: f32 = Delta.x; let D_a: f32 = -2.0 * B * Delta.x + Delta.y; let Theta: f32 = atan2(sqrt(Discriminant), -D_a) / 3.0; let sqrt_neg_Ca = sqrt(-C_a); let x_1a: f32 = 2.0 * sqrt_neg_Ca * cos(Theta); let x_3a: f32 = 2.0 * sqrt_neg_Ca * cos(Theta + (2.0 / 3.0) * pi); let xl: f32 = select(x_3a, x_1a, (x_1a + x_3a) > 2.0 * B); xlc = vec2f(xl - B, A); } { let A_d: f32 = D; let C_d: f32 = Delta.z; let D_d: f32 = -D * Delta.y + 2.0 * C * Delta.z; let Theta: f32 = atan2(D * sqrt(Discriminant), -D_d) / 3.0; let sqrt_neg_Cd = sqrt(-C_d); let x_1d: f32 = 2.0 * sqrt_neg_Cd * cos(Theta); let x_3d: f32 = 2.0 * sqrt_neg_Cd * cos(Theta + (2.0 / 3.0) * pi); let xs: f32 = select(x_3d, x_1d, x_1d + x_3d < 2.0 * C); xsc = vec2f(-D, xs + C); } let E: f32 = xlc.y * xsc.y; let F: f32 = -xlc.x * xsc.y - xlc.y * xsc.x; let G: f32 = xlc.x * xsc.x; let xmc: vec2f = vec2f(C * F - B * G, -B * F + C * E); var Root: vec3f = vec3f(xsc.x / xsc.y, xmc.x / xmc.y, xlc.x / xlc.y); if (Root.x < Root.y && Root.x < Root.z) { Root = Root.yxz; } else if (Root.z < Root.x && Root.z < Root.y) { Root = Root.xzy; } return Root; } fn LTC_EvaluateDisk(N: vec3f, V: vec3f, P: vec3f, Minv: mat3x3f, points: Coords) -> f32 { let T1: vec3f = normalize(V - N * dot(V, N)); let T2: vec3f = cross(N, T1); let R: mat3x3f = transpose( mat3x3f( T1, T2, N ) ); var L_: array<vec3f, 3>; L_[0] = R * ( points.coord0 - P ); L_[1] = R * ( points.coord1 - P ); L_[2] = R * ( points.coord2 - P ); let C: vec3f = 0.5 * (L_[0] + L_[2]); var V1: vec3f = 0.5 * (L_[1] - L_[2]); var V2: vec3f = 0.5 * (L_[1] - L_[0]); let C_Minv: vec3f = Minv * C; let V1_Minv: vec3f = Minv * V1; let V2_Minv: vec3f = Minv * V2; var a: f32; var b: f32; let d11: f32 = dot(V1_Minv, V1_Minv); let d22: f32 = dot(V2_Minv, V2_Minv); let d12: f32 = dot(V1_Minv, V2_Minv); if (abs(d12) / sqrt(d11 * d22) > 0.0001) { let tr: f32 = d11 + d22; let det_inner: f32 = -d12 * d12 + d11 * d22; let det: f32 = sqrt(det_inner); let u: f32 = 0.5 * sqrt(tr - 2.0 * det); let v: f32 = 0.5 * sqrt(tr + 2.0 * det); let e_max: f32 = (u + v) * (u + v); let e_min: f32 = (u - v) * (u - v); var V1_: vec3f; var V2_: vec3f; if (d11 > d22) { V1_ = d12 * V1_Minv + (e_max - d11) * V2_Minv; V2_ = d12 * V1_Minv + (e_min - d11) * V2_Minv; } else { V1_ = d12*V2_Minv + (e_max - d22)*V1_Minv; V2_ = d12*V2_Minv + (e_min - d22)*V1_Minv; } a = 1.0 / e_max; b = 1.0 / e_min; V1 = normalize(V1_); V2 = normalize(V2_); } else { a = 1.0 / dot(V1_Minv, V1_Minv); b = 1.0 / dot(V2_Minv, V2_Minv); V1 = V1_Minv * sqrt(a); V2 = V2_Minv * sqrt(b); } var V3: vec3f = normalize(cross(V1, V2)); if (dot(C_Minv, V3) < 0.0) { V3 = V3 * -1.0; } let L: f32 = dot(V3, C_Minv); let x0: f32 = dot(V1, C_Minv) / L; let y0: f32 = dot(V2, C_Minv) / L; let E1: f32 = inverseSqrt(a); let E2: f32 = inverseSqrt(b); let a_scaled = a * L * L; let b_scaled = b * L * L; let c0: f32 = a_scaled * b_scaled; let c1: f32 = a_scaled * b_scaled * (1.0 + x0 * x0 + y0 * y0) - a_scaled - b_scaled; let c2: f32 = 1.0 - a_scaled * (1.0 + x0 * x0) - b_scaled * (1.0 + y0 * y0); let c3: f32 = 1.0; let roots: vec3f = SolveCubic(vec4f(c0, c1, c2, c3)); let e1: f32 = roots.x; let e2: f32 = roots.y; let e3: f32 = roots.z; var avgDir: vec3f = vec3f(a_scaled * x0 / (a_scaled - e2), b_scaled * y0 / (b_scaled - e2), 1.0); let rotate: mat3x3f = mat3x3f(V1, V2, V3); avgDir = rotate * avgDir; avgDir = normalize(avgDir); let L1: f32 = sqrt(-e2 / e3); let L2: f32 = sqrt(-e2 / e1); let formFactor: f32 = max(0.0, L1 * L2 * inverseSqrt((1.0 + L1 * L1) * (1.0 + L2 * L2))); const LUT_SIZE_disk: f32 = 64.0; const LUT_SCALE_disk: f32 = ( LUT_SIZE_disk - 1.0 ) / LUT_SIZE_disk; const LUT_BIAS_disk: f32 = 0.5 / LUT_SIZE_disk; var uv: vec2f = vec2f(avgDir.z * 0.5 + 0.5, formFactor); uv = uv * LUT_SCALE_disk + LUT_BIAS_disk; let scale: f32 = textureSampleLevel(areaLightsLutTex2, areaLightsLutTex2Sampler, uv, 0.0).w; return formFactor * scale; } fn FixNan(value: f32) -> f32 { return select(value, 0.0, value != value); } fn getRectLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDir: vec3f, lightDirNorm: vec3f) -> f32 { let identityMat = mat3x3f(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0), vec3f(0.0, 0.0, 1.0)); return LTC_EvaluateRect( worldNormal, viewDir, vPositionW, identityMat, dLTCCoords ); } fn getDiskLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDir: vec3f, lightDirNorm: vec3f) -> f32 { let identityMat = mat3x3f(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0), vec3f(0.0, 0.0, 1.0)); return FixNan(LTC_EvaluateDisk( worldNormal, viewDir, vPositionW, identityMat, dLTCCoords )); } fn getSphereLightDiffuse(worldNormal: vec3f, viewDir: vec3f, lightDir: vec3f, lightDirNorm: vec3f) -> f32 { let falloff: f32 = dSphereRadius / (dot(lightDir, lightDir) + dSphereRadius); return FixNan(getLightDiffuse(worldNormal, viewDir, lightDirNorm) * falloff); } fn getLTCLightInvMat(uv: vec2f) -> mat3x3f { let t1: vec4f = textureSampleLevel(areaLightsLutTex1, areaLightsLutTex1Sampler, uv, 0.0); return mat3x3f( vec3f( t1.x, 0.0, t1.y ), vec3f( 0.0, 1.0, 0.0 ), vec3f( t1.z, 0.0, t1.w ) ); } fn calcRectLightSpecular(worldNormal: vec3f, viewDir: vec3f, uv: vec2f) -> f32 { let mInv: mat3x3f = getLTCLightInvMat(uv); return LTC_EvaluateRect( worldNormal, viewDir, vPositionW, mInv, dLTCCoords ); } fn getRectLightSpecular(worldNormal: vec3f, viewDir: vec3f) -> f32 { return calcRectLightSpecular(worldNormal, viewDir, dLTCUV); } fn calcDiskLightSpecular(worldNormal: vec3f, viewDir: vec3f, uv: vec2f) -> f32 { let mInv: mat3x3f = getLTCLightInvMat(uv); return LTC_EvaluateDisk( worldNormal, viewDir, vPositionW, mInv, dLTCCoords ); } fn getDiskLightSpecular(worldNormal: vec3f, viewDir: vec3f) -> f32 { return calcDiskLightSpecular(worldNormal, viewDir, dLTCUV); } fn getSphereLightSpecular(worldNormal: vec3f, viewDir: vec3f) -> f32 { return calcDiskLightSpecular(worldNormal, viewDir, dLTCUV); } `; export { ltcPS as default };