From 7aeebf20081a5b96ab068a0cf23d6cc9cdd26ab2 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Mon, 27 Jul 2026 12:10:11 -0700 Subject: [PATCH] Extend BRDF --- .../custom_mat_deferred.frag.glsl | 6 + .../deferred_light/deferred_light.frag.glsl | 88 ++++++++++++ .../deferred_light/deferred_light.json | 5 + .../deferred_light.frag.glsl | 88 ++++++++++++ .../deferred_light_mobile.json | 5 + leenkx/Shaders/std/brdf.glsl | 133 ++++++++++++++++++ leenkx/Shaders/std/gbuffer.glsl | 15 ++ leenkx/Shaders/std/light.glsl | 96 +++++++++++++ leenkx/Shaders/std/light_mobile.glsl | 54 +++++++ leenkx/Sources/iron/Scene.hx | 82 +++++++++++ leenkx/Sources/iron/object/MeshObject.hx | 3 + leenkx/Sources/iron/object/Uniforms.hx | 3 + .../leenkx/renderpath/RenderPathDeferred.hx | 29 +++- leenkx/blender/lnx/exporter.py | 3 + leenkx/blender/lnx/make_renderpath.py | 7 + leenkx/blender/lnx/material/cycles.py | 62 ++++++++ .../lnx/material/cycles_nodes/nodes_shader.py | 24 ++++ leenkx/blender/lnx/material/make.py | 72 ++++++++++ leenkx/blender/lnx/material/make_cluster.py | 10 ++ leenkx/blender/lnx/material/make_mesh.py | 31 +++- leenkx/blender/lnx/material/make_voxel.py | 39 +++++ leenkx/blender/lnx/material/mat_state.py | 2 + leenkx/blender/lnx/material/parser_state.py | 12 +- leenkx/blender/lnx/write_data.py | 12 ++ 24 files changed, 878 insertions(+), 3 deletions(-) diff --git a/leenkx/Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl b/leenkx/Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl index a2c034f1..49f94393 100644 --- a/leenkx/Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl +++ b/leenkx/Shaders/custom_mat_presets/custom_mat_deferred.frag.glsl @@ -21,6 +21,8 @@ in vec3 wnormal; +-------------------+-----------------++--------------+--------------+-----------------+--------------------+ | GBUF_IDX_EMISSION | _EmissionShaded || emission color (RGB) | unused | +-------------------+-----------------++--------------+--------------+-----------------+--------------------+ + | GBUF_IDX_3 | _Anisotropy || world tangent (XYZ) | unused | + +-------------------+-----------------++--------------+--------------+-----------------+--------------------+ The indices as well as the GBUF_SIZE define are defined in "compiled.inc". */ @@ -54,4 +56,8 @@ void main() { #ifdef _SSRefraction fragColor[GBUF_IDX_REFRACTION] = vec4(ior, opacity, 0.0, 0.0); #endif + + #ifdef _Anisotropy + fragColor[GBUF_IDX_3] = vec4(0.0, 0.0, 0.0, 0.0); + #endif } diff --git a/leenkx/Shaders/deferred_light/deferred_light.frag.glsl b/leenkx/Shaders/deferred_light/deferred_light.frag.glsl index 3ee1a85b..78b98aed 100644 --- a/leenkx/Shaders/deferred_light/deferred_light.frag.glsl +++ b/leenkx/Shaders/deferred_light/deferred_light.frag.glsl @@ -14,6 +14,7 @@ #ifdef _SSRS #include "std/ssrs.glsl" #endif +#include "std/brdf.glsl" uniform sampler2D gbufferD; uniform sampler2D gbuffer0; @@ -22,6 +23,9 @@ uniform sampler2D gbuffer1; #ifdef _gbuffer2 uniform sampler2D gbuffer2; #endif +#ifdef _Anisotropy + uniform sampler2D gbuffer3; +#endif #ifdef _EmissionShaded uniform sampler2D gbufferEmission; #endif @@ -254,6 +258,13 @@ void main() { float metallic; uint matid; unpackFloatInt16(g0.a, metallic, matid); + #ifdef _ExtBRDF + matid = min(matid, uint(MAX_MATERIALS - 1)); + + //!uniform vec4 materialParams[MAX_MATERIALS * 8]; + vec4 matp0, matp1, matp2, matp3, matp4, matp5, matp6; + getMaterialParams(matid, matp0, matp1, matp2, matp3, matp4, matp5, matp6); + #endif vec2 occspec = unpackFloat2(g1.a); // re-investigate clamp basecolor to prevent extreme values causing glitches @@ -283,6 +294,11 @@ void main() { vec4 g2 = textureLod(gbuffer2, texCoord, 0.0); #endif +#ifdef _Anisotropy + vec4 g3 = textureLod(gbuffer3, texCoord, 0.0); + vec3 wTangent = length(g3.xyz) > 0.0 ? normalize(g3.xyz) : vec3(1.0, 0.0, 0.0); +#endif + #ifdef _MicroShadowing occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel @@ -406,8 +422,50 @@ void main() { float sdotVH = max(0.0, dot(v, sh)); float sdotNL = max(0.0, dot(n, sunDir)); vec3 svisibility = vec3(1.0); + #ifdef _Anisotropy + vec3 sdirect; + if (abs(matp0.x) > 0.001) { + vec3 sbitangent = normalize(cross(n, wTangent)); + sdirect = lambertDiffuseBRDF(albedo, sdotNL) + + anisotropicBRDF(f0, roughness, matp0.x, matp0.y, + wTangent, sbitangent, n, sunDir, v, sdotNL, dotNV) * occspec.y; + } else { + sdirect = lambertDiffuseBRDF(albedo, sdotNL) + + specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y; + } + #else vec3 sdirect = lambertDiffuseBRDF(albedo, sdotNL) + specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y; + #endif + + float sunSheenWeight = 1.0; + float sunCoatWeight = 1.0; + + #ifdef _Sheen + vec3 sunSheen = sheenBRDF(matp0.z, matp0.w, vec3(matp5.z, matp5.w, matp6.x), sdotNL, sdotNH, dotNV); + sunSheenWeight = sheenAttenuation(matp0.z, matp0.w, vec3(matp5.z, matp5.w, matp6.x), dotNV); + #endif + + #ifdef _ClearCoat + vec3 sunCoat = clearcoatBRDF(matp1.x, matp1.y, matp1.z, sdotNL, sdotNH, dotNV, sdotVH); + sunCoatWeight = coatAttenuation(matp1.x, matp1.z, dotNV); + #endif + + float sunLayerWeight = sunSheenWeight * sunCoatWeight; + sdirect *= sunLayerWeight; + #ifdef _Subsurface + sdirect += subsurfaceBRDF(albedo, vec3(matp4.w, matp5.x, matp5.y), vec3(matp4.x, matp4.y, matp4.z), matp3.z, matp3.w, sdotNL) * sunLayerWeight; + #endif + #ifdef _Transmission + sdirect += transmissionBRDF(albedo, matp2.z, matp2.w, matp3.x, matp3.y, sdotNL, dotNV, sdotVH) * sunLayerWeight; + #endif + #ifdef _ClearCoat + sdirect *= coatTintAttenuation(matp1.x, vec3(matp1.w, matp2.x, matp2.y), dotNV); + sdirect += sunCoat * sunSheenWeight; + #endif + #ifdef _Sheen + sdirect += sunSheen; + #endif #ifdef _ShadowMap #ifdef _CSM @@ -555,6 +613,21 @@ void main() { #ifdef _SSRS , gbufferD, invVP, eye #endif + #ifdef _ClearCoat + , matp1.x, matp1.y, matp1.z, vec3(matp1.w, matp2.x, matp2.y) + #endif + #ifdef _Sheen + , matp0.z, matp0.w, vec3(matp5.z, matp5.w, matp6.x) + #endif + #ifdef _Anisotropy + , matp0.x, matp0.y, wTangent + #endif + #ifdef _Subsurface + , matp3.z, vec3(matp4.w, matp5.x, matp5.y), vec3(matp4.x, matp4.y, matp4.z), matp3.w + #endif + #ifdef _Transmission + , matp2.z, matp2.w, matp3.x, matp3.y + #endif ); #ifdef _Spot @@ -618,6 +691,21 @@ void main() { #ifdef _SSRS , gbufferD, invVP, eye #endif + #ifdef _ClearCoat + , matp1.x, matp1.y, matp1.z, vec3(matp1.w, matp2.x, matp2.y) + #endif + #ifdef _Sheen + , matp0.z, matp0.w, vec3(matp5.z, matp5.w, matp6.x) + #endif + #ifdef _Anisotropy + , matp0.x, matp0.y, wTangent + #endif + #ifdef _Subsurface + , matp3.z, vec3(matp4.w, matp5.x, matp5.y), vec3(matp4.x, matp4.y, matp4.z), matp3.w + #endif + #ifdef _Transmission + , matp2.z, matp2.w, matp3.x, matp3.y + #endif ); } #endif // _Clusters diff --git a/leenkx/Shaders/deferred_light/deferred_light.json b/leenkx/Shaders/deferred_light/deferred_light.json index 389a57be..db3c2d94 100644 --- a/leenkx/Shaders/deferred_light/deferred_light.json +++ b/leenkx/Shaders/deferred_light/deferred_light.json @@ -277,6 +277,11 @@ "link": "_biasLightWorldViewProjectionMatrixSpot3", "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_LTC", "_ShadowMap"] + }, + { + "name": "materialParams", + "link": "_materialParams", + "type": "floats" } ], "vertex_shader": "../include/pass_viewray.vert.glsl", diff --git a/leenkx/Shaders/deferred_light_mobile/deferred_light.frag.glsl b/leenkx/Shaders/deferred_light_mobile/deferred_light.frag.glsl index bb2292de..659de0da 100644 --- a/leenkx/Shaders/deferred_light_mobile/deferred_light.frag.glsl +++ b/leenkx/Shaders/deferred_light_mobile/deferred_light.frag.glsl @@ -3,6 +3,7 @@ #include "compiled.inc" #include "std/gbuffer.glsl" #include "std/math.glsl" +#include "std/brdf.glsl" #ifdef _Clusters #include "std/clusters.glsl" #endif @@ -13,6 +14,9 @@ uniform sampler2D gbufferD; uniform sampler2D gbuffer0; uniform sampler2D gbuffer1; +#ifdef _Anisotropy +uniform sampler2D gbuffer3; +#endif uniform float envmapStrength; #ifdef _Irr @@ -132,6 +136,13 @@ void main() { float metallic; uint matid; unpackFloatInt16(g0.a, metallic, matid); + #ifdef _ExtBRDF + matid = min(matid, uint(MAX_MATERIALS - 1)); + + //!uniform vec4 materialParams[MAX_MATERIALS * 8]; + vec4 matp0, matp1, matp2, matp3, matp4, matp5, matp6; + getMaterialParams(matid, matp0, matp1, matp2, matp3, matp4, matp5, matp6); + #endif vec4 g1 = textureLod(gbuffer1, texCoord, 0.0); // Basecolor.rgb, spec/occ vec2 occspec = unpackFloat2(g1.a); @@ -143,6 +154,11 @@ void main() { vec3 v = normalize(eye - p); float dotNV = max(dot(n, v), 0.0); +#ifdef _Anisotropy + vec4 g3 = textureLod(gbuffer3, texCoord, 0.0); + vec3 wTangent = length(g3.xyz) > 0.0 ? normalize(g3.xyz) : vec3(1.0, 0.0, 0.0); +#endif + #ifdef _Brdf vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(dotNV, 1.0 - roughness) * 256.0), 0).xy; #endif @@ -189,8 +205,50 @@ void main() { float sdotVH = max(0.0, dot(v, sh)); float sdotNL = max(0.0, dot(n, sunDir)); float svisibility = 1.0; + #ifdef _Anisotropy + vec3 sdirect; + if (abs(matp0.x) > 0.001) { + vec3 sbitangent = normalize(cross(n, wTangent)); + sdirect = lambertDiffuseBRDF(albedo, sdotNL) + + anisotropicBRDF(f0, roughness, matp0.x, matp0.y, + wTangent, sbitangent, n, sunDir, v, sdotNL, dotNV) * occspec.y; + } else { + sdirect = lambertDiffuseBRDF(albedo, sdotNL) + + specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y; + } + #else vec3 sdirect = lambertDiffuseBRDF(albedo, sdotNL) + specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y; + #endif + + float sunSheenWeight = 1.0; + float sunCoatWeight = 1.0; + + #ifdef _Sheen + vec3 sunSheen = sheenBRDF(matp0.z, matp0.w, vec3(matp5.z, matp5.w, matp6.x), sdotNL, sdotNH, dotNV); + sunSheenWeight = sheenAttenuation(matp0.z, matp0.w, vec3(matp5.z, matp5.w, matp6.x), dotNV); + #endif + + #ifdef _ClearCoat + vec3 sunCoat = clearcoatBRDF(matp1.x, matp1.y, matp1.z, sdotNL, sdotNH, dotNV, sdotVH); + sunCoatWeight = coatAttenuation(matp1.x, matp1.z, dotNV); + #endif + + float sunLayerWeight = sunSheenWeight * sunCoatWeight; + sdirect *= sunLayerWeight; + #ifdef _Subsurface + sdirect += subsurfaceBRDF(albedo, vec3(matp4.w, matp5.x, matp5.y), vec3(matp4.x, matp4.y, matp4.z), matp3.z, matp3.w, sdotNL) * sunLayerWeight; + #endif + #ifdef _Transmission + sdirect += transmissionBRDF(albedo, matp2.z, matp2.w, matp3.x, matp3.y, sdotNL, dotNV, sdotVH) * sunLayerWeight; + #endif + #ifdef _ClearCoat + sdirect *= coatTintAttenuation(matp1.x, vec3(matp1.w, matp2.x, matp2.y), dotNV); + sdirect += sunCoat * sunSheenWeight; + #endif + #ifdef _Sheen + sdirect += sunSheen; + #endif #ifdef _ShadowMap #ifdef _CSM @@ -235,6 +293,21 @@ void main() { #ifdef _Spot , true, spotData.x, spotData.y, spotDir, spotData.zw, spotRight // TODO: Test! #endif + #ifdef _ClearCoat + , matp1.x, matp1.y, matp1.z, vec3(matp1.w, matp2.x, matp2.y) + #endif + #ifdef _Sheen + , matp0.z, matp0.w, vec3(matp5.z, matp5.w, matp6.x) + #endif + #ifdef _Anisotropy + , matp0.x, matp0.y, wTangent + #endif + #ifdef _Subsurface + , matp3.z, vec3(matp4.w, matp5.x, matp5.y), vec3(matp4.x, matp4.y, matp4.z), matp3.w + #endif + #ifdef _Transmission + , matp2.z, matp2.w, matp3.x, matp3.y + #endif ); #endif @@ -277,6 +350,21 @@ void main() { , vec2(lightsArray[li * 3].w, lightsArray[li * 3 + 1].w) // scale , lightsArraySpot[li * 2 + 1].xyz // right #endif + #ifdef _ClearCoat + , matp1.x, matp1.y, matp1.z, vec3(matp1.w, matp2.x, matp2.y) + #endif + #ifdef _Sheen + , matp0.z, matp0.w, vec3(matp5.z, matp5.w, matp6.x) + #endif + #ifdef _Anisotropy + , matp0.x, matp0.y, wTangent + #endif + #ifdef _Subsurface + , matp3.z, vec3(matp4.w, matp5.x, matp5.y), vec3(matp4.x, matp4.y, matp4.z), matp3.w + #endif + #ifdef _Transmission + , matp2.z, matp2.w, matp3.x, matp3.y + #endif ); } #endif // _Clusters diff --git a/leenkx/Shaders/deferred_light_mobile/deferred_light_mobile.json b/leenkx/Shaders/deferred_light_mobile/deferred_light_mobile.json index dcc0d18d..c3989154 100644 --- a/leenkx/Shaders/deferred_light_mobile/deferred_light_mobile.json +++ b/leenkx/Shaders/deferred_light_mobile/deferred_light_mobile.json @@ -214,6 +214,11 @@ "link": "_biasLightWorldViewProjectionMatrixSpot3", "ifndef": ["_ShadowMapAtlas"], "ifdef": ["_LTC", "_ShadowMap"] + }, + { + "name": "materialParams", + "link": "_materialParams", + "type": "floats" } ], "vertex_shader": "../include/pass_viewray.vert.glsl", diff --git a/leenkx/Shaders/std/brdf.glsl b/leenkx/Shaders/std/brdf.glsl index 9c6e57b2..a0d76b4a 100644 --- a/leenkx/Shaders/std/brdf.glsl +++ b/leenkx/Shaders/std/brdf.glsl @@ -138,4 +138,137 @@ float D_Approx(const float Roughness, const float RoL) { return rcp_a2 * exp2( c * RoL - c ); } +#ifdef _ClearCoat +vec3 clearcoatBRDF(const float clearcoat, const float clearcoat_rough, + const float coat_ior, + const float dotNL, const float dotNH, const float dotNV, const float dotVH) { + if (clearcoat <= 0.0) return vec3(0.0); + float a = clearcoat_rough * clearcoat_rough; + // F0 from Fresnel equation for dielectric + float ccF0 = (coat_ior - 1.0) / (coat_ior + 1.0); + ccF0 = ccF0 * ccF0; + float F = ccF0 + (1.0 - ccF0) * pow(1.0 - dotVH, 5.0); + float D = d_ggx(dotNH, a); + float G = g2_approx(dotNL, dotNV, a); + return vec3(clearcoat * D * G * F / max(4.0 * dotNV, 1e-5)); +} + +float coatAttenuation(const float clearcoat, + const float coat_ior, const float dotNV) { + if (clearcoat <= 0.0) return 1.0; + float ccF0 = (coat_ior - 1.0) / (coat_ior + 1.0); + ccF0 = ccF0 * ccF0; + // Schlick with pow(.,5) - cheaper than exp2 + float F = ccF0 + (1.0 - ccF0) * pow(1.0 - dotNV, 5.0); + return max(1.0 - F * clearcoat, 0.0); +} + +vec3 coatTintAttenuation(const float clearcoat, const vec3 coat_tint, const float dotNV) { + if (clearcoat <= 0.0) return vec3(1.0); + float absorption = 1.0 / max(dotNV, 0.3); + return mix(vec3(1.0), coat_tint, clamp(absorption * 0.2, 0.0, 1.0)); +} +#endif + +#ifdef _Sheen +// based on Blender sheen model/Frostbite PBR +vec3 sheenBRDF(const float sheen, const float sheen_rough, + const vec3 sheen_tint, const float dotNL, const float dotNH, const float dotNV) { + if (sheen <= 0.0) return vec3(0.0); + float rough = clamp(sheen_rough, 1e-3, 1.0); + float a = rough * rough; + float sinNH2 = 1.0 - dotNH * dotNH; + float a2 = a * a; + float D = (2.0 + a2) * sinNH2 / (2.0 * 3.1415926535 * pow(1.0 + a2 * sinNH2, 2.0)); + float V = 1.0 / (4.0 * dotNL * dotNV + 1e-5); + float sheenAlbedo = (1.0 - 0.5 * rough) * mix(1.0, dotNV, 0.5); + return sheen_tint * sheen * D * V * dotNL * sheenAlbedo; +} + +float sheenAttenuation(const float sheen, const float sheen_rough, + const vec3 sheen_tint, const float dotNV) { + if (sheen <= 0.0) return 1.0; + float rough = clamp(sheen_rough, 1e-3, 1.0); + float sheenAlbedo = (1.0 - 0.5 * rough) * mix(1.0, dotNV, 0.5); + float maxComp = sheen * max(max(sheen_tint.r, sheen_tint.g), sheen_tint.b) * sheenAlbedo; + return max(1.0 - maxComp, 0.0); +} +#endif + +#ifdef _Anisotropy +// anisotropic GGX Burley 2012 +vec3 anisotropicBRDF(const vec3 f0, const float roughness, const float anisotropy, + const float aniso_rot, const vec3 tangent, const vec3 bitangent, + const vec3 n, const vec3 l, const vec3 v, + const float dotNL, const float dotNV) { + if (abs(anisotropy) <= 0.001) return vec3(0.0); + float rot = aniso_rot * 3.1415926535 * 2.0; + float cr = cos(rot); + float sr = sin(rot); + vec3 t = normalize(tangent * cr + bitangent * sr); + vec3 b = normalize(bitangent * cr - tangent * sr); + float aniso_abs = abs(anisotropy); + float at = max(roughness * (1.0 + aniso_abs), 1e-5); + float ab = max(roughness * (1.0 - aniso_abs), 1e-5); + if (anisotropy < 0.0) { vec3 tmp = t; t = b; b = tmp; } + float at2 = at * at; + float ab2 = ab * ab; + vec3 h = normalize(l + v); + float dotTH = dot(t, h); + float dotBH = dot(b, h); + float dotTV = dot(t, v); + float dotBV = dot(b, v); + float dotTL = dot(t, l); + float dotBL = dot(b, l); + float denom = max(dotTH * dotTH / at2 + dotBH * dotBH / ab2, 1e-7); + float D = 1.0 / (3.1415926535 * at * ab * denom * denom); + float V = 1.0 / max(dotNL * (dotTL / at + dotBL / ab) * (dotTV / at + dotBV / ab), 1e-5); + float dotVH = max(dot(v, h), 0.0); + vec3 F = f_schlick(f0, dotVH); + return D * V * F / max(4.0 * dotNV, 1e-5); +} +#endif + +#ifdef _Subsurface +// Blenders bssrdf_burley implementation +vec3 subsurfaceBRDF(const vec3 albedo, const vec3 sss_color, + const vec3 sss_radius, const float subsurface, const float sss_anisotropy, + const float dotNL) { + if (subsurface <= 0.0) return vec3(0.0); + vec3 mfp = sss_radius * (0.25 / 3.1415926535); + vec3 A = clamp(albedo, 0.0, 1.0); + vec3 d = 1.9 - A + 3.5 * (A - 0.8) * (A - 0.8); + d = mfp / max(d, 1e-5); + float aniso = clamp(sss_anisotropy, 0.0, 0.9); + float scatter = subsurface * (1.0 / 3.1415926535); + vec3 sssDiffuse = sss_color * scatter * dotNL; + float backScatter = max(0.0, 1.0 - dotNL) * (1.0 - aniso) * 0.5; + vec3 sssBack = sss_color * subsurface * backScatter; + float dist = max(0.0, 1.0 - dotNL); + vec3 rcp_d = 1.0 / max(d, vec3(1e-5)); + vec3 x = vec3(dist) * rcp_d; + vec3 extinction = 1.0 / (1.0 + x + 0.5 * x * x); + return sssDiffuse * extinction + sssBack * (vec3(1.0) - extinction); +} +#endif + +#ifdef _Transmission +// Blenders microfacet glass/refraction model +vec3 transmissionBRDF(const vec3 albedo, const float transmission, + const float trans_rough, const float ior, const float thin_wall, + const float dotNL, const float dotNV, const float dotVH) { + if (transmission <= 0.0) return vec3(0.0); + float F0 = (ior - 1.0) / (ior + 1.0); + F0 = F0 * F0; + float F = F0 + (1.0 - F0) * pow(1.0 - dotVH, 5.0); + float transmittance = 1.0 - F; + if (thin_wall > 0.5) { + return albedo * transmission * transmittance * dotNL; + } + float a = trans_rough * trans_rough; + float rough_atten = mix(1.0, 1.0 / max(dotNV, 0.1), a); + return albedo * transmission * transmittance * rough_atten * dotNL; +} +#endif + #endif diff --git a/leenkx/Shaders/std/gbuffer.glsl b/leenkx/Shaders/std/gbuffer.glsl index 2d26e073..58e140fb 100644 --- a/leenkx/Shaders/std/gbuffer.glsl +++ b/leenkx/Shaders/std/gbuffer.glsl @@ -170,4 +170,19 @@ void unpackFloatInt16(float val, out float f, out uint i) { f = (bitsValue & ~(0xF << numBitFloat)) / maxValFloat; } +#ifdef _ExtBRDF +// extended material parameters by material slot ID returns vec4s (28 floats) of extended BRDF parameters +void getMaterialParams(uint matid, out vec4 p0, out vec4 p1, out vec4 p2, out vec4 p3, + out vec4 p4, out vec4 p5, out vec4 p6) { + uint base = matid * 8u; + p0 = materialParams[base]; + p1 = materialParams[base + 1u]; + p2 = materialParams[base + 2u]; + p3 = materialParams[base + 3u]; + p4 = materialParams[base + 4u]; + p5 = materialParams[base + 5u]; + p6 = materialParams[base + 6u]; +} +#endif + #endif diff --git a/leenkx/Shaders/std/light.glsl b/leenkx/Shaders/std/light.glsl index c9089826..35f1276b 100644 --- a/leenkx/Shaders/std/light.glsl +++ b/leenkx/Shaders/std/light.glsl @@ -131,6 +131,21 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _SSRS , sampler2D gbufferD, mat4 invVP, vec3 eye #endif + #ifdef _ClearCoat + , float clearcoat, float clearcoatRough, float coatIOR, vec3 coatTint + #endif + #ifdef _Sheen + , float sheen, float sheenRough, vec3 sheenTint + #endif + #ifdef _Anisotropy + , float anisotropy, float anisoRot, vec3 tangent + #endif + #ifdef _Subsurface + , float subsurface, vec3 sssColor, vec3 sssRadius, float sssAnisotropy + #endif + #ifdef _Transmission + , float transmission, float transRough, float ior, float thinWall + #endif ) { vec3 ld = lp - p; vec3 l = normalize(ld); @@ -153,9 +168,49 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co float ltcdiff = ltcEvaluate(n, v, dotNV, p, mat3(1.0), lightArea0, lightArea1, lightArea2, lightArea3); vec3 direct = albedo * ltcdiff + ltcspec * spec * 0.05; #else + #ifdef _Anisotropy + vec3 direct; + if (abs(anisotropy) > 0.001) { + vec3 bitangent = normalize(cross(n, tangent)); + direct = lambertDiffuseBRDF(albedo, dotNL) + + anisotropicBRDF(f0, rough, anisotropy, anisoRot, + tangent, bitangent, n, l, v, dotNL, dotNV) * spec; + } else { + direct = lambertDiffuseBRDF(albedo, dotNL) + + specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec; + } + #else vec3 direct = lambertDiffuseBRDF(albedo, dotNL) + specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec; #endif + #endif + + // before attenuate/shadow so everything is properly shadowed in one pass + float sheenWeight = 1.0; + float coatWeight = 1.0; + #ifdef _Sheen + vec3 sheenContrib = sheenBRDF(sheen, sheenRough, sheenTint, dotNL, dotNH, dotNV); + sheenWeight = sheenAttenuation(sheen, sheenRough, sheenTint, dotNV); + #endif + #ifdef _ClearCoat + vec3 coatContrib = clearcoatBRDF(clearcoat, clearcoatRough, coatIOR, dotNL, dotNH, dotNV, dotVH); + coatWeight = coatAttenuation(clearcoat, coatIOR, dotNV); + #endif + float layerWeight = sheenWeight * coatWeight; + direct *= layerWeight; + #ifdef _Subsurface + direct += subsurfaceBRDF(albedo, sssColor, sssRadius, subsurface, sssAnisotropy, dotNL) * layerWeight; + #endif + #ifdef _Transmission + direct += transmissionBRDF(albedo, transmission, transRough, ior, thinWall, dotNL, dotNV, dotVH) * layerWeight; + #endif + #ifdef _ClearCoat + direct *= coatTintAttenuation(clearcoat, coatTint, dotNV); + direct += coatContrib * sheenWeight; + #endif + #ifdef _Sheen + direct += sheenContrib; + #endif direct *= attenuate(distance(p, lp)); direct *= min(lightCol, vec3(100.0)); @@ -418,6 +473,21 @@ vec3 sampleLightVoxels(const vec3 p, const vec3 n, const vec3 v, const float dot #ifdef _Spot , const bool isSpot, const float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right #endif + #ifdef _ClearCoat + , float clearcoat, float clearcoatRough, float coatIOR, vec3 coatTint + #endif + #ifdef _Sheen + , float sheen, float sheenRough, vec3 sheenTint + #endif + #ifdef _Anisotropy + , float anisotropy, float anisoRot, vec3 tangent + #endif + #ifdef _Subsurface + , float subsurface, vec3 sssColor, vec3 sssRadius, float sssAnisotropy + #endif + #ifdef _Transmission + , float transmission, float transRough, float ior, float thinWall + #endif ) { vec3 ld = lp - p; vec3 l = normalize(ld); @@ -444,6 +514,32 @@ vec3 sampleLightVoxels(const vec3 p, const vec3 n, const vec3 v, const float dot specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec; #endif + float sheenWeight = 1.0; + float coatWeight = 1.0; + #ifdef _Sheen + vec3 sheenContrib = sheenBRDF(sheen, sheenRough, sheenTint, dotNL, dotNH, dotNV); + sheenWeight = sheenAttenuation(sheen, sheenRough, sheenTint, dotNV); + #endif + #ifdef _ClearCoat + vec3 coatContrib = clearcoatBRDF(clearcoat, clearcoatRough, coatIOR, dotNL, dotNH, dotNV, dotVH); + coatWeight = coatAttenuation(clearcoat, coatIOR, dotNV); + #endif + float layerWeight = sheenWeight * coatWeight; + direct *= layerWeight; + #ifdef _Subsurface + direct += subsurfaceBRDF(albedo, sssColor, sssRadius, subsurface, sssAnisotropy, dotNL) * layerWeight; + #endif + #ifdef _Transmission + direct += transmissionBRDF(albedo, transmission, transRough, ior, thinWall, dotNL, dotNV, dotVH) * layerWeight; + #endif + #ifdef _ClearCoat + direct *= coatTintAttenuation(clearcoat, coatTint, dotNV); + direct += coatContrib * sheenWeight; + #endif + #ifdef _Sheen + direct += sheenContrib; + #endif + direct *= attenuate(distance(p, lp)); // CRITICAL: Clamp light color to prevent extreme HDR values causing white sphere artifacts direct *= min(lightCol, vec3(100.0)); diff --git a/leenkx/Shaders/std/light_mobile.glsl b/leenkx/Shaders/std/light_mobile.glsl index a0ce42e7..2b8f828c 100644 --- a/leenkx/Shaders/std/light_mobile.glsl +++ b/leenkx/Shaders/std/light_mobile.glsl @@ -53,6 +53,21 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Spot , bool isSpot, float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right #endif + #ifdef _ClearCoat + , float clearcoat, float clearcoatRough, float coatIOR, vec3 coatTint + #endif + #ifdef _Sheen + , float sheen, float sheenRough, vec3 sheenTint + #endif + #ifdef _Anisotropy + , float anisotropy, float anisoRot, vec3 tangent + #endif + #ifdef _Subsurface + , float subsurface, vec3 sssColor, vec3 sssRadius, float sssAnisotropy + #endif + #ifdef _Transmission + , float transmission, float transRough, float ior, float thinWall + #endif ) { vec3 ld = lp - p; vec3 l = normalize(ld); @@ -61,8 +76,47 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co float dotVH = max(0.0, dot(v, h)); float dotNL = max(0.0, dot(n, l)); + #ifdef _Anisotropy + vec3 direct; + if (abs(anisotropy) > 0.001) { + vec3 bitangent = normalize(cross(n, tangent)); + direct = lambertDiffuseBRDF(albedo, dotNL) + + anisotropicBRDF(f0, rough, anisotropy, anisoRot, + tangent, bitangent, n, l, v, dotNL, dotNV) * spec; + } else { + direct = lambertDiffuseBRDF(albedo, dotNL) + + specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec; + } + #else vec3 direct = lambertDiffuseBRDF(albedo, dotNL) + specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec; + #endif + + float sheenWeight = 1.0; + float coatWeight = 1.0; + #ifdef _Sheen + vec3 sheenContrib = sheenBRDF(sheen, sheenRough, sheenTint, dotNL, dotNH, dotNV); + sheenWeight = sheenAttenuation(sheen, sheenRough, sheenTint, dotNV); + #endif + #ifdef _ClearCoat + vec3 coatContrib = clearcoatBRDF(clearcoat, clearcoatRough, coatIOR, dotNL, dotNH, dotNV, dotVH); + coatWeight = coatAttenuation(clearcoat, coatIOR, dotNV); + #endif + float layerWeight = sheenWeight * coatWeight; + direct *= layerWeight; + #ifdef _Subsurface + direct += subsurfaceBRDF(albedo, sssColor, sssRadius, subsurface, sssAnisotropy, dotNL) * layerWeight; + #endif + #ifdef _Transmission + direct += transmissionBRDF(albedo, transmission, transRough, ior, thinWall, dotNL, dotNV, dotVH) * layerWeight; + #endif + #ifdef _ClearCoat + direct *= coatTintAttenuation(clearcoat, coatTint, dotNV); + direct += coatContrib * sheenWeight; + #endif + #ifdef _Sheen + direct += sheenContrib; + #endif direct *= lightCol; direct *= attenuate(distance(p, lp)); diff --git a/leenkx/Sources/iron/Scene.hx b/leenkx/Sources/iron/Scene.hx index 734165df..49b88f98 100644 --- a/leenkx/Sources/iron/Scene.hx +++ b/leenkx/Sources/iron/Scene.hx @@ -71,6 +71,11 @@ class Scene { public var embedded: Map; + #if (rp_renderer == "Deferred") + public var materialParamsBuffer: kha.arrays.Float32Array; + public var materialParamsDirty: Bool = true; + #end + public var ready: Bool; // Async in progress public var traitInits: ArrayVoid> = []; @@ -107,6 +112,9 @@ class Scene { armatures = []; #end embedded = new Map(); + #if (rp_renderer == "Deferred") + materialParamsBuffer = new kha.arrays.Float32Array(512); + #end root = new Object(); root.name = "Root"; traitInits = []; @@ -204,6 +212,77 @@ class Scene { root.remove(); } + #if (rp_renderer == "Deferred") + public function updateMaterialParams() { + if (!materialParamsDirty) return; + materialParamsDirty = false; + var buf = materialParamsBuffer; + for (i in 0...buf.length) buf[i] = 0.0; + for (m in meshes) { + if (m.materials == null) continue; + for (mat in m.materials) { + if (mat == null) continue; + if (mat.contexts == null) continue; + var slot = -1; + var bc = null; + for (ctx in mat.contexts) { + if (ctx == null || ctx.raw == null) continue; + if (ctx.raw.name == "mesh" && ctx.raw.bind_constants != null) { + bc = ctx.raw.bind_constants; + for (c in bc) { + if (c != null && c.name == "materialID" && c.intValue != null) { + slot = c.intValue; + } + } + break; + } + } + if (slot <= 0 || slot >= 16) continue; + var base = slot * 32; + if (base + 31 >= buf.length) continue; + if (bc == null) continue; + for (c in bc) { + if (c == null || c.name == null) continue; + switch (c.name) { + // matp0: vec4(anisotropy, anisoRot, sheen, sheenRough) + case "anisotropy": buf[base + 0] = c.floatValue; + case "anisoRot": buf[base + 1] = c.floatValue; + case "sheen": buf[base + 2] = c.floatValue; + case "sheenRough": buf[base + 3] = c.floatValue; + // matp1: vec4(clearcoat, clearcoatRough, coatIOR, coatTintR) + case "clearcoat": buf[base + 4] = c.floatValue; + case "clearcoatRough": buf[base + 5] = c.floatValue; + case "coatIOR": buf[base + 6] = c.floatValue; + case "coatTintR": buf[base + 7] = c.floatValue; + // matp2: vec4(coatTintG, coatTintB, transmission, transRough) + case "coatTintG": buf[base + 8] = c.floatValue; + case "coatTintB": buf[base + 9] = c.floatValue; + case "transmission": buf[base + 10] = c.floatValue; + case "transmissionRough": buf[base + 11] = c.floatValue; + // matp3: vec4(ior, thinWall, subsurface, subsurfaceAnisotropy) + case "ior": buf[base + 12] = c.floatValue; + case "thinWall": buf[base + 13] = c.floatValue; + case "subsurface": buf[base + 14] = c.floatValue; + case "subsurfaceAnisotropy": buf[base + 15] = c.floatValue; + // matp4: vec4(subsurfaceRadiusR, subsurfaceRadiusG, subsurfaceRadiusB, subsurfaceColorR) + case "subsurfaceRadiusR": buf[base + 16] = c.floatValue; + case "subsurfaceRadiusG": buf[base + 17] = c.floatValue; + case "subsurfaceRadiusB": buf[base + 18] = c.floatValue; + case "subsurfaceColorR": buf[base + 19] = c.floatValue; + // matp5: vec4(subsurfaceColorG, subsurfaceColorB, sheenTintR, sheenTintG) + case "subsurfaceColorG": buf[base + 20] = c.floatValue; + case "subsurfaceColorB": buf[base + 21] = c.floatValue; + case "sheenTintR": buf[base + 22] = c.floatValue; + case "sheenTintG": buf[base + 23] = c.floatValue; + // matp6: vec4(sheenTintB, 0, 0, 0) + case "sheenTintB": buf[base + 24] = c.floatValue; + } + } + } + } + } + #end + static var framePassed = true; public static function setActive(sceneName: String, done: Object->Void = null) { if (!framePassed) return; @@ -351,6 +430,9 @@ class Scene { public function addMeshObject(data: MeshData, materials: Vector, parent: Object = null): MeshObject { var object = new MeshObject(data, materials); parent != null ? object.setParent(parent) : object.setParent(root); + #if (rp_renderer == "Deferred") + materialParamsDirty = true; + #end return object; } diff --git a/leenkx/Sources/iron/object/MeshObject.hx b/leenkx/Sources/iron/object/MeshObject.hx index f69691b2..5b290cfe 100644 --- a/leenkx/Sources/iron/object/MeshObject.hx +++ b/leenkx/Sources/iron/object/MeshObject.hx @@ -89,6 +89,9 @@ class MeshObject extends Object { #end if (tilesheet != null) tilesheet.remove(); if (Scene.active != null) Scene.active.meshes.remove(this); + #if (rp_renderer == "Deferred") + if (Scene.active != null) Scene.active.materialParamsDirty = true; + #end data.refcount--; super.remove(); } diff --git a/leenkx/Sources/iron/object/Uniforms.hx b/leenkx/Sources/iron/object/Uniforms.hx index 0ec11bf9..686be5ba 100644 --- a/leenkx/Sources/iron/object/Uniforms.hx +++ b/leenkx/Sources/iron/object/Uniforms.hx @@ -887,6 +887,9 @@ class Uniforms { case "_envmapIrradiance": { fa = Scene.active.world == null ? WorldData.getEmptyIrradiance() : Scene.active.world.probe.irradiance; } + case "_materialParams": { + fa = Scene.active.materialParamsBuffer; + } #if lnx_clusters case "_lightsArray": { fa = LightObject.lightsArray; diff --git a/leenkx/Sources/leenkx/renderpath/RenderPathDeferred.hx b/leenkx/Sources/leenkx/renderpath/RenderPathDeferred.hx index b1363543..c2b3fd89 100644 --- a/leenkx/Sources/leenkx/renderpath/RenderPathDeferred.hx +++ b/leenkx/Sources/leenkx/renderpath/RenderPathDeferred.hx @@ -26,7 +26,8 @@ class RenderPathDeferred { "gbuffer1", #if rp_gbuffer2 "gbuffer2", #end #if rp_gbuffer_emission "gbuffer_emission", #end - #if (rp_ssrefr || lnx_voxelgi_refract) "gbuffer_refraction" #end + #if (rp_ssrefr || lnx_voxelgi_refract) "gbuffer_refraction", #end + #if lnx_anisotropy "gbuffer3" #end ]); } @@ -173,6 +174,19 @@ class RenderPathDeferred { } #end + #if lnx_anisotropy + { + var t = new RenderTargetRaw(); + t.name = "gbuffer3"; + t.width = 0; + t.height = 0; + t.displayp = Inc.getDisplayp(); + t.format = "RGBA64"; + t.scale = Inc.getSuperSampling(); + path.createRenderTarget(t); + } + #end + #if rp_material_solid path.loadShader("shader_datas/deferred_light_solid/deferred_light"); #elseif rp_material_mobile @@ -566,6 +580,13 @@ class RenderPathDeferred { } #end + #if lnx_anisotropy + { + path.setTarget("gbuffer3"); + path.clearTarget(0x00000000); + } + #end + RenderPathCreator.setTargetMeshes(); #if rp_dynres @@ -761,6 +782,10 @@ class RenderPathDeferred { } #end + #if lnx_anisotropy + path.bindTarget("gbuffer3", "gbuffer3"); + #end + #if rp_ssao { if (leenkx.data.Config.raw.rp_ssao != false) { @@ -826,8 +851,10 @@ class RenderPathDeferred { #if rp_material_solid path.drawShader("shader_datas/deferred_light_solid/deferred_light"); #elseif rp_material_mobile + Scene.active.updateMaterialParams(); path.drawShader("shader_datas/deferred_light_mobile/deferred_light"); #else + Scene.active.updateMaterialParams(); voxelao_pass ? path.drawShader("shader_datas/deferred_light/deferred_light_VoxelAOvar") : path.drawShader("shader_datas/deferred_light/deferred_light"); diff --git a/leenkx/blender/lnx/exporter.py b/leenkx/blender/lnx/exporter.py index bb2a3e8a..aa125713 100644 --- a/leenkx/blender/lnx/exporter.py +++ b/leenkx/blender/lnx/exporter.py @@ -2526,6 +2526,9 @@ class LeenkxExporter: decals_used = False sss_used = False + from lnx.material import mat_state + mat_state.next_ext_mat_id = 3 + for material in self.material_array: # If the material is unlinked, material becomes None if material is None: diff --git a/leenkx/blender/lnx/make_renderpath.py b/leenkx/blender/lnx/make_renderpath.py index c2a838a4..84a4f589 100644 --- a/leenkx/blender/lnx/make_renderpath.py +++ b/leenkx/blender/lnx/make_renderpath.py @@ -173,6 +173,9 @@ def add_world_defs(): wrd.world_defs += '_Brdf' assets.add_khafile_def("lnx_brdf") + if '_Anisotropy' in wrd.world_defs: + assets.add_khafile_def('lnx_anisotropy') + def build(): rpdat = lnx.utils.get_rp() project_path = lnx.utils.get_fp() @@ -510,4 +513,8 @@ def get_num_gbuffer_rts_deferred()-> int: found_refraction_flag = True else: num += 1 + + if '_Anisotropy' in wrd.world_defs: + num += 1 + return num diff --git a/leenkx/blender/lnx/material/cycles.py b/leenkx/blender/lnx/material/cycles.py index 215ff2e0..a6e5cd51 100644 --- a/leenkx/blender/lnx/material/cycles.py +++ b/leenkx/blender/lnx/material/cycles.py @@ -188,6 +188,64 @@ def parse(nodes, con: ShaderContext, # Make sure that individual functions in this module aren't called with an incorrect/old parser state, set it to # None so that it will raise exceptions when not set + # store extended BRDF feature flags before destroying parser state + import re as _re + + def _extract_vec3(s): + m = _re.match(r'vec3\s*\(\s*([^,\s\)]+)\s*\)', s) + if m and ',' not in m.group(1): + v = m.group(1) + return v, v, v + m = _re.match(r'vec3\s*\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s\)]+)', s) + if m: + return m.group(1), m.group(2), m.group(3) + return '1.0', '1.0', '1.0' + + def _try_float(val_str, default=0.0): + try: + return float(val_str) + except (ValueError, TypeError): + return default + + _sss_r, _sss_g, _sss_b = _extract_vec3(state.out_subsurface_radius) + _sss_scale = state.out_subsurface_scale + _sss_scale_f = _try_float(_sss_scale, 0.05) + if _sss_scale_f != 1.0: + _sss_r = str(_try_float(_sss_r, 1.0) * _sss_scale_f) + _sss_g = str(_try_float(_sss_g, 0.2) * _sss_scale_f) + _sss_b = str(_try_float(_sss_b, 0.1) * _sss_scale_f) + + _sheen_tint_r, _sheen_tint_g, _sheen_tint_b = _extract_vec3(state.out_sheen_tint) + _coat_tint_r, _coat_tint_g, _coat_tint_b = _extract_vec3(state.out_coat_tint) + _sss_color_r, _sss_color_g, _sss_color_b = _extract_vec3(state.out_subsurface_color) + + mat_state.features = { + 'clearcoat': state.out_clearcoat, + 'clearcoatRough': state.out_clearcoat_rough, + 'coatIOR': state.out_coat_ior, + 'coatTintR': _coat_tint_r, + 'coatTintG': _coat_tint_g, + 'coatTintB': _coat_tint_b, + 'sheen': state.out_sheen, + 'sheenRough': state.out_sheen_rough, + 'sheenTintR': _sheen_tint_r, + 'sheenTintG': _sheen_tint_g, + 'sheenTintB': _sheen_tint_b, + 'subsurface': state.out_subsurface, + 'subsurfaceAnisotropy': state.out_subsurface_anisotropy, + 'subsurfaceRadiusR': _sss_r, + 'subsurfaceRadiusG': _sss_g, + 'subsurfaceRadiusB': _sss_b, + 'subsurfaceColorR': _sss_color_r, + 'subsurfaceColorG': _sss_color_g, + 'subsurfaceColorB': _sss_color_b, + 'anisotropy': state.out_anisotropy, + 'anisoRot': state.out_aniso_rot, + 'transmission': state.out_transmission, + 'transmissionRough': state.out_transmission_rough, + 'ior': state.out_ior, + 'thinWall': state.out_thin_wall, + } state = None @@ -250,6 +308,10 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types. curshader.write(f'transmissionRough = {outs[20]};') curshader.write(f'thinWall = {outs[21]};') curshader.write(f'tangent = {outs[22]};') + curshader.write(f'subsurfaceScale = {outs[23]};') + curshader.write(f'subsurfaceAnisotropy = {outs[24]};') + curshader.write(f'coatIOR = {outs[25]};') + curshader.write(f'coatTint = {outs[26]};') if mat_state.emission_type == mat_state.EmissionType.SHADELESS: if '_EmissionShadeless' not in wrd.world_defs: diff --git a/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py b/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py index ee00e30e..36470dd3 100644 --- a/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py +++ b/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py @@ -55,6 +55,8 @@ else: TRANSMISSION_ROUGHNESS = 'Transmission Roughness' TANGENT = 'Tangent' THIN_WALL = 'Thin Wall' + SUBSURFACE_SCALE = 'Subsurface Scale' + SUBSURFACE_ANISOTROPY = 'Subsurface Anisotropy' def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket, state: ParserState) -> None: @@ -111,6 +113,10 @@ def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket, state.out_transmission_rough = fm.format(o1[20], o2[20], fv[0], fv[1]) state.out_thin_wall = fm.format(o1[21], o2[21], fv[0], fv[1]) state.out_tangent = fm.format(o1[22], o2[22], fv[0], fv[1]) + state.out_subsurface_scale = fm.format(o1[23], o2[23], fv[0], fv[1]) + state.out_subsurface_anisotropy = fm.format(o1[24], o2[24], fv[0], fv[1]) + state.out_coat_ior = fm.format(o1[25], o2[25], fv[0], fv[1]) + state.out_coat_tint = fm.format(o1[26], o2[26], fv[0], fv[1]) mat_state.emission_type = mat_state.EmissionType.get_effective_combination(ek1, ek2) if state.parse_opacity: fm = '{0} * {3} + {1} * {2}' @@ -155,6 +161,10 @@ def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket, state.out_transmission_rough = am.format(o1[20], o2[20]) state.out_thin_wall = am.format(o1[21], o2[21]) state.out_tangent = am.format(o1[22], o2[22]) + state.out_subsurface_scale = am.format(o1[23], o2[23]) + state.out_subsurface_anisotropy = am.format(o1[24], o2[24]) + state.out_coat_ior = am.format(o1[25], o2[25]) + state.out_coat_tint = am.format(o1[26], o2[26]) mat_state.emission_type = mat_state.EmissionType.get_effective_combination(ek1, ek2) if state.parse_opacity: am = '{0} * 0.5 + {1} * 0.5' @@ -254,6 +264,14 @@ if bpy.app.version >= (4, 0, 0): if sss_radius_input is not None: state.out_subsurface_radius = c.parse_vector_input(sss_radius_input) + sss_scale_input = node.inputs.get(SUBSURFACE_SCALE) + if sss_scale_input is not None: + state.out_subsurface_scale = c.parse_value_input(sss_scale_input) + + sss_aniso_input = node.inputs.get(SUBSURFACE_ANISOTROPY) + if sss_aniso_input is not None: + state.out_subsurface_anisotropy = c.parse_value_input(sss_aniso_input) + sss_color_input = node.inputs.get(SUBSURFACE_COLOR) if sss_color_input is not None: state.out_subsurface_color = c.parse_vector_input(sss_color_input) @@ -307,6 +325,12 @@ if bpy.app.version >= (4, 0, 0): coat_rough_socket = node.inputs.get(CLEARCOAT_ROUGHNESS) if coat_rough_socket is not None: state.out_clearcoat_rough = c.parse_value_input(coat_rough_socket) + coat_ior_socket = node.inputs.get(COAT_IOR) + if coat_ior_socket is not None: + state.out_coat_ior = c.parse_value_input(coat_ior_socket) + coat_tint_socket = node.inputs.get(COAT_TINT) + if coat_tint_socket is not None: + state.out_coat_tint = c.parse_vector_input(coat_tint_socket) trans_socket = node.inputs.get(TRANSMISSION) if trans_socket is not None: diff --git a/leenkx/blender/lnx/material/make.py b/leenkx/blender/lnx/material/make.py index f786ff3e..04d6f3ea 100644 --- a/leenkx/blender/lnx/material/make.py +++ b/leenkx/blender/lnx/material/make.py @@ -40,6 +40,8 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]], wrd = bpy.data.worlds['Lnx'] rpdat = lnx.utils.get_rp() + mat_state.features = {} + # Texture caching for material batching batch_cached_textures = [] @@ -120,6 +122,76 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]], const['intValue'] = 0 c['bind_constants'].append(const) + # extended BRDF parameters as bind_constants + if rpdat.rp_renderer == 'Deferred': + feats = mat_state.features + + def try_float(val_str, default=1.0): + try: + return float(val_str) + except (ValueError, TypeError): + return default + + has_ext_brdf = False + if feats.get('clearcoat', '0.0') not in ('0.0', '0', ''): + c['bind_constants'].append({'name': 'clearcoat', 'floatValue': try_float(feats.get('clearcoat', '1.0'))}) + c['bind_constants'].append({'name': 'clearcoatRough', 'floatValue': try_float(feats.get('clearcoatRough', '0.03'), 0.03)}) + c['bind_constants'].append({'name': 'coatIOR', 'floatValue': try_float(feats.get('coatIOR', '1.5'), 1.5)}) + c['bind_constants'].append({'name': 'coatTintR', 'floatValue': try_float(feats.get('coatTintR', '1.0'), 1.0)}) + c['bind_constants'].append({'name': 'coatTintG', 'floatValue': try_float(feats.get('coatTintG', '1.0'), 1.0)}) + c['bind_constants'].append({'name': 'coatTintB', 'floatValue': try_float(feats.get('coatTintB', '1.0'), 1.0)}) + if '_ClearCoat' not in wrd.world_defs: + wrd.world_defs += '_ClearCoat' + has_ext_brdf = True + if feats.get('sheen', '0.0') not in ('0.0', '0', ''): + c['bind_constants'].append({'name': 'sheen', 'floatValue': try_float(feats.get('sheen', '1.0'))}) + c['bind_constants'].append({'name': 'sheenRough', 'floatValue': try_float(feats.get('sheenRough', '0.5'), 0.5)}) + c['bind_constants'].append({'name': 'sheenTintR', 'floatValue': try_float(feats.get('sheenTintR', '1.0'), 1.0)}) + c['bind_constants'].append({'name': 'sheenTintG', 'floatValue': try_float(feats.get('sheenTintG', '1.0'), 1.0)}) + c['bind_constants'].append({'name': 'sheenTintB', 'floatValue': try_float(feats.get('sheenTintB', '1.0'), 1.0)}) + if '_Sheen' not in wrd.world_defs: + wrd.world_defs += '_Sheen' + has_ext_brdf = True + if feats.get('subsurface', '0.0') not in ('0.0', '0', '') and rpdat.rp_sss_state != 'Off' and not mat_uses_sss: + c['bind_constants'].append({'name': 'subsurface', 'floatValue': try_float(feats.get('subsurface', '1.0'))}) + c['bind_constants'].append({'name': 'subsurfaceAnisotropy', 'floatValue': try_float(feats.get('subsurfaceAnisotropy', '0.0'), 0.0)}) + c['bind_constants'].append({'name': 'subsurfaceRadiusR', 'floatValue': try_float(feats.get('subsurfaceRadiusR', '1.0'), 1.0)}) + c['bind_constants'].append({'name': 'subsurfaceRadiusG', 'floatValue': try_float(feats.get('subsurfaceRadiusG', '0.2'), 0.2)}) + c['bind_constants'].append({'name': 'subsurfaceRadiusB', 'floatValue': try_float(feats.get('subsurfaceRadiusB', '0.1'), 0.1)}) + c['bind_constants'].append({'name': 'subsurfaceColorR', 'floatValue': try_float(feats.get('subsurfaceColorR', '0.8'), 0.8)}) + c['bind_constants'].append({'name': 'subsurfaceColorG', 'floatValue': try_float(feats.get('subsurfaceColorG', '0.8'), 0.8)}) + c['bind_constants'].append({'name': 'subsurfaceColorB', 'floatValue': try_float(feats.get('subsurfaceColorB', '0.8'), 0.8)}) + if '_Subsurface' not in wrd.world_defs: + wrd.world_defs += '_Subsurface' + has_ext_brdf = True + if feats.get('anisotropy', '0.0') not in ('0.0', '0', ''): + c['bind_constants'].append({'name': 'anisotropy', 'floatValue': try_float(feats.get('anisotropy', '1.0'))}) + c['bind_constants'].append({'name': 'anisoRot', 'floatValue': try_float(feats.get('anisoRot', '0.0'), 0.0)}) + if '_Anisotropy' not in wrd.world_defs: + wrd.world_defs += '_Anisotropy' + has_ext_brdf = True + if feats.get('transmission', '0.0') not in ('0.0', '0', ''): + c['bind_constants'].append({'name': 'transmission', 'floatValue': try_float(feats.get('transmission', '1.0'))}) + c['bind_constants'].append({'name': 'transmissionRough', 'floatValue': try_float(feats.get('transmissionRough', '0.0'), 0.0)}) + c['bind_constants'].append({'name': 'ior', 'floatValue': try_float(feats.get('ior', '1.45'), 1.45)}) + c['bind_constants'].append({'name': 'thinWall', 'floatValue': try_float(feats.get('thinWall', '0.0'), 0.0)}) + if '_Transmission' not in wrd.world_defs: + wrd.world_defs += '_Transmission' + has_ext_brdf = True + + if has_ext_brdf and '_ExtBRDF' not in wrd.world_defs: + wrd.world_defs += '_ExtBRDF' + + # assign materialID for extended BRDF if not already set + # coexist with Scene.hx for last materialID in bind_constants + if has_ext_brdf and material.lnx_material_id == 0: + if mat_state.next_ext_mat_id <= 15: + ext_id = mat_state.next_ext_mat_id + mat_state.next_ext_mat_id += 1 + c['bind_constants'].append({'name': 'materialID', 'intValue': ext_id}) + else: + log.warn(f'material "{material.name}": extended BRDF material limit (15) exceeded, extended BRDF will be disabled for this material') + # TODO: Mesh only material batching if wrd.lnx_batch_materials: # Set textures uniforms diff --git a/leenkx/blender/lnx/material/make_cluster.py b/leenkx/blender/lnx/material/make_cluster.py index 1ee44e7e..ae896c52 100644 --- a/leenkx/blender/lnx/material/make_cluster.py +++ b/leenkx/blender/lnx/material/make_cluster.py @@ -111,6 +111,16 @@ def write(vert: shader.Shader, frag: shader.Shader): frag.add_uniform('mat4 invVP', '_inverseViewProjectionMatrix') frag.add_uniform('vec3 eye', '_cameraPosition') frag.write(', gbufferD, invVP, eye') + if '_ClearCoat' in wrd.world_defs: + frag.write(', clearcoat, clearcoatRough, coatIOR, coatTint') + if '_Sheen' in wrd.world_defs: + frag.write(', sheen, sheenRough, sheenTint') + if '_Anisotropy' in wrd.world_defs: + frag.write(', anisotropy, anisoRot, tangent') + if '_Subsurface' in wrd.world_defs: + frag.write(', subsurface, subsurfaceColor, subsurfaceRadius, subsurfaceAnisotropy') + if '_Transmission' in wrd.world_defs: + frag.write(', transmission, transmissionRough, ior, thinWall') frag.write(');') frag.write('}') # for numLights diff --git a/leenkx/blender/lnx/material/make_mesh.py b/leenkx/blender/lnx/material/make_mesh.py index eade9ed2..65af6032 100644 --- a/leenkx/blender/lnx/material/make_mesh.py +++ b/leenkx/blender/lnx/material/make_mesh.py @@ -74,6 +74,8 @@ def make(context_id, rpasses): con['color_attachments'] = [attachment_format, attachment_format] if '_gbuffer2' in wrd.world_defs: con['color_attachments'].append(attachment_format) + if '_Anisotropy' in wrd.world_defs: + con['color_attachments'].append('RGBA64') con_mesh = mat_state.data.add_context(con) mat_state.con_mesh = con_mesh @@ -252,7 +254,9 @@ def make_deferred(con_mesh, rpasses): frag.write('n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);') is_shadeless = mat_state.emission_type == mat_state.EmissionType.SHADELESS - if is_shadeless or '_SSS' in wrd.world_defs or '_Hair' in wrd.world_defs: + ext_brdf_defs = ('_ClearCoat', '_Sheen', '_Anisotropy', '_Subsurface', '_Transmission') + has_ext_brdf = any(d in wrd.world_defs for d in ext_brdf_defs) + if is_shadeless or '_SSS' in wrd.world_defs or '_Hair' in wrd.world_defs or has_ext_brdf: frag.write('uint matid = 0;') if is_shadeless: frag.write('matid = 1;') @@ -260,6 +264,9 @@ def make_deferred(con_mesh, rpasses): if '_SSS' in wrd.world_defs or '_Hair' in wrd.world_defs: frag.add_uniform('int materialID') frag.write('if (materialID == 2) matid = 2;') + if has_ext_brdf: + frag.add_uniform('int materialID') + frag.write('if (materialID >= 3) matid = uint(materialID);') else: frag.write('const uint matid = 0;') @@ -285,6 +292,14 @@ def make_deferred(con_mesh, rpasses): if '_SSRefraction' in wrd.world_defs or '_VoxelRefract' in wrd.world_defs: frag.write('fragColor[GBUF_IDX_REFRACTION] = vec4(1.0, 0.0, 0.0, 1.0);') + if '_Anisotropy' in wrd.world_defs: + frag.write('#ifdef _Anisotropy') + if con_mesh.is_elem('tang'): + frag.write('fragColor[GBUF_IDX_3] = vec4(normalize(TBN[0]), 0.0);') + else: + frag.write('fragColor[GBUF_IDX_3] = vec4(0.0, 0.0, 0.0, 0.0);') + frag.write('#endif') + return con_mesh @@ -830,6 +845,16 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): frag.add_uniform('mat4 invVP', '_inverseViewProjectionMatrix') frag.add_uniform('vec3 eye', '_cameraPosition') frag.write(', gbufferD, invVP, eye') + if '_ClearCoat' in wrd.world_defs: + frag.write(', clearcoat, clearcoatRough, coatIOR, coatTint') + if '_Sheen' in wrd.world_defs: + frag.write(', sheen, sheenRough, sheenTint') + if '_Anisotropy' in wrd.world_defs: + frag.write(', anisotropy, anisoRot, tangent') + if '_Subsurface' in wrd.world_defs: + frag.write(', subsurface, subsurfaceColor, subsurfaceRadius, subsurfaceAnisotropy') + if '_Transmission' in wrd.world_defs: + frag.write(', transmission, transmissionRough, ior, thinWall') frag.write(');') if '_Clusters' in wrd.world_defs: @@ -872,6 +897,10 @@ def _write_material_attribs_default(frag: shader.Shader, parse_opacity: bool): frag.write('float transmissionRough = 0.0;') frag.write('float thinWall = 0.0;') frag.write('vec3 tangent = vec3(0.0);') + frag.write('float subsurfaceScale = 0.05;') + frag.write('float subsurfaceAnisotropy = 0.0;') + frag.write('float coatIOR = 1.5;') + frag.write('vec3 coatTint = vec3(1.0);') if parse_opacity: frag.write('float opacity;') frag.write('float ior = 1.45;') diff --git a/leenkx/blender/lnx/material/make_voxel.py b/leenkx/blender/lnx/material/make_voxel.py index dd38f1aa..0b532933 100644 --- a/leenkx/blender/lnx/material/make_voxel.py +++ b/leenkx/blender/lnx/material/make_voxel.py @@ -81,6 +81,25 @@ def make_gi(context_id): frag.write('float occlusion;') # frag.write('float specular;') # frag.write('vec3 emissionCol = vec3(0.0);') + frag.write('vec3 specularTint = vec3(1.0);') + frag.write('float subsurface = 0.0;') + frag.write('vec3 subsurfaceRadius = vec3(0.0);') + frag.write('vec3 subsurfaceColor = vec3(0.8);') + frag.write('float anisotropy = 0.0;') + frag.write('float anisoRot = 0.0;') + frag.write('float sheen = 0.0;') + frag.write('float sheenRough = 0.5;') + frag.write('vec3 sheenTint = vec3(1.0);') + frag.write('float clearcoat = 0.0;') + frag.write('float clearcoatRough = 0.03;') + frag.write('float transmission = 0.0;') + frag.write('float transmissionRough = 0.0;') + frag.write('float thinWall = 0.0;') + frag.write('vec3 tangent = vec3(0.0);') + frag.write('float subsurfaceScale = 0.05;') + frag.write('float subsurfaceAnisotropy = 0.0;') + frag.write('float coatIOR = 1.5;') + frag.write('vec3 coatTint = vec3(1.0);') blend = mat_state.material.lnx_blending parse_opacity = blend or mat_utils.is_transluc(mat_state.material) if parse_opacity: @@ -377,6 +396,16 @@ def make_gi(context_id): frag.write(', opacity != 1.0') if '_Spot' in wrd.world_defs: frag.write(', true, spotData.x, spotData.y, spotDir, spotData.zw, spotRight') + if '_ClearCoat' in wrd.world_defs: + frag.write(', clearcoat, clearcoatRough, coatIOR, coatTint') + if '_Sheen' in wrd.world_defs: + frag.write(', sheen, sheenRough, sheenTint') + if '_Anisotropy' in wrd.world_defs: + frag.write(', anisotropy, anisoRot, tangent') + if '_Subsurface' in wrd.world_defs: + frag.write(', subsurface, subsurfaceColor, subsurfaceRadius, subsurfaceAnisotropy') + if '_Transmission' in wrd.world_defs: + frag.write(', transmission, transmissionRough, ior, thinWall') frag.write(');') if '_Clusters' in wrd.world_defs: @@ -458,6 +487,16 @@ def make_gi(context_id): frag.write('\t, lightsArraySpot[li * 2].xyz') # spotDir frag.write('\t, vec2(lightsArray[li * 3].w, lightsArray[li * 3 + 1].w)') # scale frag.write('\t, lightsArraySpot[li * 2 + 1].xyz') # right + if '_ClearCoat' in wrd.world_defs: + frag.write('\t, clearcoat, clearcoatRough, coatIOR, coatTint') + if '_Sheen' in wrd.world_defs: + frag.write('\t, sheen, sheenRough, sheenTint') + if '_Anisotropy' in wrd.world_defs: + frag.write('\t, anisotropy, anisoRot, tangent') + if '_Subsurface' in wrd.world_defs: + frag.write('\t, subsurface, subsurfaceColor, subsurfaceRadius, subsurfaceAnisotropy') + if '_Transmission' in wrd.world_defs: + frag.write('\t, transmission, transmissionRough, ior, thinWall') frag.write(' );') frag.write('}') diff --git a/leenkx/blender/lnx/material/mat_state.py b/leenkx/blender/lnx/material/mat_state.py index be44b69b..63c2b344 100644 --- a/leenkx/blender/lnx/material/mat_state.py +++ b/leenkx/blender/lnx/material/mat_state.py @@ -39,3 +39,5 @@ con_mesh = None # Mesh context uses_instancing = False # Whether the current material has at least one user with instancing enabled emission_type = EmissionType.NO_EMISSION needs_sss = False +features = {} # tracks extended BRDF features used by current material +next_ext_mat_id = 3 # auto assigned materialID for extended BRDF diff --git a/leenkx/blender/lnx/material/parser_state.py b/leenkx/blender/lnx/material/parser_state.py index 3d7e15be..c4574c72 100644 --- a/leenkx/blender/lnx/material/parser_state.py +++ b/leenkx/blender/lnx/material/parser_state.py @@ -114,6 +114,10 @@ class ParserState: self.out_transmission_rough: floatstr = '0.0' self.out_thin_wall: floatstr = '0.0' self.out_tangent: vec3str = 'vec3(0.0)' + self.out_subsurface_scale: floatstr = '0.05' + self.out_subsurface_anisotropy: floatstr = '0.0' + self.out_coat_ior: floatstr = '1.5' + self.out_coat_tint: vec3str = 'vec3(1.0)' def reset_outs(self): """Reset the shader output values to their default values.""" @@ -140,6 +144,10 @@ class ParserState: self.out_transmission_rough = '0.0' self.out_thin_wall = '0.0' self.out_tangent = 'vec3(0.0)' + self.out_subsurface_scale = '0.05' + self.out_subsurface_anisotropy = '0.0' + self.out_coat_ior = '1.5' + self.out_coat_tint = 'vec3(1.0)' def get_outs(self) -> Tuple: """Return the shader output values as a tuple.""" @@ -152,7 +160,9 @@ class ParserState: self.out_sheen, self.out_sheen_rough, self.out_sheen_tint, self.out_clearcoat, self.out_clearcoat_rough, self.out_transmission, self.out_transmission_rough, - self.out_thin_wall, self.out_tangent) + self.out_thin_wall, self.out_tangent, + self.out_subsurface_scale, self.out_subsurface_anisotropy, + self.out_coat_ior, self.out_coat_tint) def get_parser_pass_suffix(self) -> str: diff --git a/leenkx/blender/lnx/write_data.py b/leenkx/blender/lnx/write_data.py index deb1f8c1..9db6c343 100644 --- a/leenkx/blender/lnx/write_data.py +++ b/leenkx/blender/lnx/write_data.py @@ -835,6 +835,18 @@ def write_compiledglsl(defs, make_variants): if '_SSRefraction' in wrd.world_defs or '_VoxelRefract' in wrd.world_defs: f.write(f'#define GBUF_IDX_REFRACTION {idx_refraction}\n') + idx_anisotropy = idx_refraction + if '_Anisotropy' in wrd.world_defs: + f.write(f'#define GBUF_IDX_3 {idx_anisotropy}\n') + + ext_brdf_defs = ('_ClearCoat', '_Sheen', '_Anisotropy', '_Subsurface', '_Transmission') + if any(d in wrd.world_defs for d in ext_brdf_defs): + f.write('#ifndef _ExtBRDF\n') + f.write('#define _ExtBRDF\n') + f.write('#endif\n') + f.write('#define MAX_MATERIALS 16\n') + f.write('uniform vec4 materialParams[MAX_MATERIALS * 8];\n') + f.write("""#if defined(HLSL) || defined(METAL) #define _InvY #endif