Compare commits

...

14 Commits

33 changed files with 1417 additions and 229 deletions

Binary file not shown.

Binary file not shown.

View File

@ -7,7 +7,7 @@ bl_info = {
"description": "Full Stack SDK", "description": "Full Stack SDK",
"author": "Leenkx.com", "author": "Leenkx.com",
"version": (2026, 5, 0), "version": (2026, 5, 0),
"blender": (4, 5, 0), "blender": (5, 2, 0),
"doc_url": "https://leenkx.com/", "doc_url": "https://leenkx.com/",
"tracker_url": "https://leenkx.com/support" "tracker_url": "https://leenkx.com/support"
} }

View File

@ -21,6 +21,8 @@ in vec3 wnormal;
+-------------------+-----------------++--------------+--------------+-----------------+--------------------+ +-------------------+-----------------++--------------+--------------+-----------------+--------------------+
| GBUF_IDX_EMISSION | _EmissionShaded || emission color (RGB) | unused | | 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". The indices as well as the GBUF_SIZE define are defined in "compiled.inc".
*/ */
@ -54,4 +56,8 @@ void main() {
#ifdef _SSRefraction #ifdef _SSRefraction
fragColor[GBUF_IDX_REFRACTION] = vec4(ior, opacity, 0.0, 0.0); fragColor[GBUF_IDX_REFRACTION] = vec4(ior, opacity, 0.0, 0.0);
#endif #endif
#ifdef _Anisotropy
fragColor[GBUF_IDX_3] = vec4(0.0, 0.0, 0.0, 0.0);
#endif
} }

View File

@ -14,6 +14,7 @@
#ifdef _SSRS #ifdef _SSRS
#include "std/ssrs.glsl" #include "std/ssrs.glsl"
#endif #endif
#include "std/brdf.glsl"
uniform sampler2D gbufferD; uniform sampler2D gbufferD;
uniform sampler2D gbuffer0; uniform sampler2D gbuffer0;
@ -22,6 +23,9 @@ uniform sampler2D gbuffer1;
#ifdef _gbuffer2 #ifdef _gbuffer2
uniform sampler2D gbuffer2; uniform sampler2D gbuffer2;
#endif #endif
#ifdef _Anisotropy
uniform sampler2D gbuffer3;
#endif
#ifdef _EmissionShaded #ifdef _EmissionShaded
uniform sampler2D gbufferEmission; uniform sampler2D gbufferEmission;
#endif #endif
@ -254,6 +258,13 @@ void main() {
float metallic; float metallic;
uint matid; uint matid;
unpackFloatInt16(g0.a, metallic, 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); vec2 occspec = unpackFloat2(g1.a);
// re-investigate clamp basecolor to prevent extreme values causing glitches // re-investigate clamp basecolor to prevent extreme values causing glitches
@ -283,6 +294,11 @@ void main() {
vec4 g2 = textureLod(gbuffer2, texCoord, 0.0); vec4 g2 = textureLod(gbuffer2, texCoord, 0.0);
#endif #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 #ifdef _MicroShadowing
occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel 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 sdotVH = max(0.0, dot(v, sh));
float sdotNL = max(0.0, dot(n, sunDir)); float sdotNL = max(0.0, dot(n, sunDir));
vec3 svisibility = vec3(1.0); 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) + vec3 sdirect = lambertDiffuseBRDF(albedo, sdotNL) +
specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y; 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 _ShadowMap
#ifdef _CSM #ifdef _CSM
@ -555,6 +613,21 @@ void main() {
#ifdef _SSRS #ifdef _SSRS
, gbufferD, invVP, eye , gbufferD, invVP, eye
#endif #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 #ifdef _Spot
@ -618,6 +691,21 @@ void main() {
#ifdef _SSRS #ifdef _SSRS
, gbufferD, invVP, eye , gbufferD, invVP, eye
#endif #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 #endif // _Clusters

View File

@ -277,6 +277,11 @@
"link": "_biasLightWorldViewProjectionMatrixSpot3", "link": "_biasLightWorldViewProjectionMatrixSpot3",
"ifndef": ["_ShadowMapAtlas"], "ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"] "ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "materialParams",
"link": "_materialParams",
"type": "floats"
} }
], ],
"vertex_shader": "../include/pass_viewray.vert.glsl", "vertex_shader": "../include/pass_viewray.vert.glsl",

View File

@ -3,6 +3,7 @@
#include "compiled.inc" #include "compiled.inc"
#include "std/gbuffer.glsl" #include "std/gbuffer.glsl"
#include "std/math.glsl" #include "std/math.glsl"
#include "std/brdf.glsl"
#ifdef _Clusters #ifdef _Clusters
#include "std/clusters.glsl" #include "std/clusters.glsl"
#endif #endif
@ -13,6 +14,9 @@
uniform sampler2D gbufferD; uniform sampler2D gbufferD;
uniform sampler2D gbuffer0; uniform sampler2D gbuffer0;
uniform sampler2D gbuffer1; uniform sampler2D gbuffer1;
#ifdef _Anisotropy
uniform sampler2D gbuffer3;
#endif
uniform float envmapStrength; uniform float envmapStrength;
#ifdef _Irr #ifdef _Irr
@ -132,6 +136,13 @@ void main() {
float metallic; float metallic;
uint matid; uint matid;
unpackFloatInt16(g0.a, metallic, 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 vec4 g1 = textureLod(gbuffer1, texCoord, 0.0); // Basecolor.rgb, spec/occ
vec2 occspec = unpackFloat2(g1.a); vec2 occspec = unpackFloat2(g1.a);
@ -143,6 +154,11 @@ void main() {
vec3 v = normalize(eye - p); vec3 v = normalize(eye - p);
float dotNV = max(dot(n, v), 0.0); 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 #ifdef _Brdf
vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(dotNV, 1.0 - roughness) * 256.0), 0).xy; vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(dotNV, 1.0 - roughness) * 256.0), 0).xy;
#endif #endif
@ -189,8 +205,50 @@ void main() {
float sdotVH = max(0.0, dot(v, sh)); float sdotVH = max(0.0, dot(v, sh));
float sdotNL = max(0.0, dot(n, sunDir)); float sdotNL = max(0.0, dot(n, sunDir));
float svisibility = 1.0; 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) + vec3 sdirect = lambertDiffuseBRDF(albedo, sdotNL) +
specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y; 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 _ShadowMap
#ifdef _CSM #ifdef _CSM
@ -235,6 +293,21 @@ void main() {
#ifdef _Spot #ifdef _Spot
, true, spotData.x, spotData.y, spotDir, spotData.zw, spotRight // TODO: Test! , true, spotData.x, spotData.y, spotDir, spotData.zw, spotRight // TODO: Test!
#endif #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 #endif
@ -277,6 +350,21 @@ void main() {
, vec2(lightsArray[li * 3].w, lightsArray[li * 3 + 1].w) // scale , vec2(lightsArray[li * 3].w, lightsArray[li * 3 + 1].w) // scale
, lightsArraySpot[li * 2 + 1].xyz // right , lightsArraySpot[li * 2 + 1].xyz // right
#endif #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 #endif // _Clusters

View File

@ -214,6 +214,11 @@
"link": "_biasLightWorldViewProjectionMatrixSpot3", "link": "_biasLightWorldViewProjectionMatrixSpot3",
"ifndef": ["_ShadowMapAtlas"], "ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"] "ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "materialParams",
"link": "_materialParams",
"type": "floats"
} }
], ],
"vertex_shader": "../include/pass_viewray.vert.glsl", "vertex_shader": "../include/pass_viewray.vert.glsl",

View File

@ -138,4 +138,137 @@ float D_Approx(const float Roughness, const float RoL) {
return rcp_a2 * exp2( c * RoL - c ); 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 #endif

View File

@ -170,4 +170,19 @@ void unpackFloatInt16(float val, out float f, out uint i) {
f = (bitsValue & ~(0xF << numBitFloat)) / maxValFloat; 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 #endif

View File

@ -131,6 +131,21 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _SSRS #ifdef _SSRS
, sampler2D gbufferD, mat4 invVP, vec3 eye , sampler2D gbufferD, mat4 invVP, vec3 eye
#endif #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 ld = lp - p;
vec3 l = normalize(ld); 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); float ltcdiff = ltcEvaluate(n, v, dotNV, p, mat3(1.0), lightArea0, lightArea1, lightArea2, lightArea3);
vec3 direct = albedo * ltcdiff + ltcspec * spec * 0.05; vec3 direct = albedo * ltcdiff + ltcspec * spec * 0.05;
#else #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) + vec3 direct = lambertDiffuseBRDF(albedo, dotNL) +
specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec; specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec;
#endif #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 *= attenuate(distance(p, lp));
direct *= min(lightCol, vec3(100.0)); 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 #ifdef _Spot
, const bool isSpot, const float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right , const bool isSpot, const float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right
#endif #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 ld = lp - p;
vec3 l = normalize(ld); 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; specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec;
#endif #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)); direct *= attenuate(distance(p, lp));
// CRITICAL: Clamp light color to prevent extreme HDR values causing white sphere artifacts // CRITICAL: Clamp light color to prevent extreme HDR values causing white sphere artifacts
direct *= min(lightCol, vec3(100.0)); direct *= min(lightCol, vec3(100.0));

View File

@ -53,6 +53,21 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _Spot #ifdef _Spot
, bool isSpot, float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right , bool isSpot, float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right
#endif #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 ld = lp - p;
vec3 l = normalize(ld); 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 dotVH = max(0.0, dot(v, h));
float dotNL = max(0.0, dot(n, l)); 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) + vec3 direct = lambertDiffuseBRDF(albedo, dotNL) +
specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec; 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 *= lightCol;
direct *= attenuate(distance(p, lp)); direct *= attenuate(distance(p, lp));

View File

@ -71,6 +71,11 @@ class Scene {
public var embedded: Map<String, kha.Image>; public var embedded: Map<String, kha.Image>;
#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 ready: Bool; // Async in progress
public var traitInits: Array<Void->Void> = []; public var traitInits: Array<Void->Void> = [];
@ -107,6 +112,9 @@ class Scene {
armatures = []; armatures = [];
#end #end
embedded = new Map(); embedded = new Map();
#if (rp_renderer == "Deferred")
materialParamsBuffer = new kha.arrays.Float32Array(512);
#end
root = new Object(); root = new Object();
root.name = "Root"; root.name = "Root";
traitInits = []; traitInits = [];
@ -204,6 +212,77 @@ class Scene {
root.remove(); 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; static var framePassed = true;
public static function setActive(sceneName: String, done: Object->Void = null) { public static function setActive(sceneName: String, done: Object->Void = null) {
if (!framePassed) return; if (!framePassed) return;
@ -351,6 +430,9 @@ class Scene {
public function addMeshObject(data: MeshData, materials: Vector<MaterialData>, parent: Object = null): MeshObject { public function addMeshObject(data: MeshData, materials: Vector<MaterialData>, parent: Object = null): MeshObject {
var object = new MeshObject(data, materials); var object = new MeshObject(data, materials);
parent != null ? object.setParent(parent) : object.setParent(root); parent != null ? object.setParent(parent) : object.setParent(root);
#if (rp_renderer == "Deferred")
materialParamsDirty = true;
#end
return object; return object;
} }

View File

@ -89,6 +89,9 @@ class MeshObject extends Object {
#end #end
if (tilesheet != null) tilesheet.remove(); if (tilesheet != null) tilesheet.remove();
if (Scene.active != null) Scene.active.meshes.remove(this); if (Scene.active != null) Scene.active.meshes.remove(this);
#if (rp_renderer == "Deferred")
if (Scene.active != null) Scene.active.materialParamsDirty = true;
#end
data.refcount--; data.refcount--;
super.remove(); super.remove();
} }

View File

@ -887,6 +887,9 @@ class Uniforms {
case "_envmapIrradiance": { case "_envmapIrradiance": {
fa = Scene.active.world == null ? WorldData.getEmptyIrradiance() : Scene.active.world.probe.irradiance; fa = Scene.active.world == null ? WorldData.getEmptyIrradiance() : Scene.active.world.probe.irradiance;
} }
case "_materialParams": {
fa = Scene.active.materialParamsBuffer;
}
#if lnx_clusters #if lnx_clusters
case "_lightsArray": { case "_lightsArray": {
fa = LightObject.lightsArray; fa = LightObject.lightsArray;

View File

@ -26,7 +26,8 @@ class RenderPathDeferred {
"gbuffer1", "gbuffer1",
#if rp_gbuffer2 "gbuffer2", #end #if rp_gbuffer2 "gbuffer2", #end
#if rp_gbuffer_emission "gbuffer_emission", #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 #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 #if rp_material_solid
path.loadShader("shader_datas/deferred_light_solid/deferred_light"); path.loadShader("shader_datas/deferred_light_solid/deferred_light");
#elseif rp_material_mobile #elseif rp_material_mobile
@ -566,6 +580,13 @@ class RenderPathDeferred {
} }
#end #end
#if lnx_anisotropy
{
path.setTarget("gbuffer3");
path.clearTarget(0x00000000);
}
#end
RenderPathCreator.setTargetMeshes(); RenderPathCreator.setTargetMeshes();
#if rp_dynres #if rp_dynres
@ -761,6 +782,10 @@ class RenderPathDeferred {
} }
#end #end
#if lnx_anisotropy
path.bindTarget("gbuffer3", "gbuffer3");
#end
#if rp_ssao #if rp_ssao
{ {
if (leenkx.data.Config.raw.rp_ssao != false) { if (leenkx.data.Config.raw.rp_ssao != false) {
@ -826,8 +851,10 @@ class RenderPathDeferred {
#if rp_material_solid #if rp_material_solid
path.drawShader("shader_datas/deferred_light_solid/deferred_light"); path.drawShader("shader_datas/deferred_light_solid/deferred_light");
#elseif rp_material_mobile #elseif rp_material_mobile
Scene.active.updateMaterialParams();
path.drawShader("shader_datas/deferred_light_mobile/deferred_light"); path.drawShader("shader_datas/deferred_light_mobile/deferred_light");
#else #else
Scene.active.updateMaterialParams();
voxelao_pass ? voxelao_pass ?
path.drawShader("shader_datas/deferred_light/deferred_light_VoxelAOvar") : path.drawShader("shader_datas/deferred_light/deferred_light_VoxelAOvar") :
path.drawShader("shader_datas/deferred_light/deferred_light"); path.drawShader("shader_datas/deferred_light/deferred_light");

View File

@ -2526,6 +2526,9 @@ class LeenkxExporter:
decals_used = False decals_used = False
sss_used = False sss_used = False
from lnx.material import mat_state
mat_state.next_ext_mat_id = 3
for material in self.material_array: for material in self.material_array:
# If the material is unlinked, material becomes None # If the material is unlinked, material becomes None
if material is None: if material is None:

View File

@ -136,7 +136,10 @@ def always() -> float:
# Force ui redraw # Force ui redraw
if state.redraw_ui: if state.redraw_ui:
if bpy.context.screen is not None: if bpy.context.screen is not None:
krom_active = bool(lnx.render_engine._active_krom_engines)
for area in bpy.context.screen.areas: for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D' and krom_active:
continue
if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'): if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'):
area.tag_redraw() area.tag_redraw()
state.redraw_ui = False state.redraw_ui = False

View File

@ -1,3 +1,4 @@
import bpy
import importlib import importlib
import inspect import inspect
import pkgutil import pkgutil

View File

@ -804,41 +804,71 @@ def viewport_build_done():
_viewport_pending_launches.clear() _viewport_pending_launches.clear()
log.error('Viewport build failed, check console') log.error('Viewport build failed, check console')
def play_viewport(viewport_id, width=1920, height=1080): def play(viewport_id=None, width=1920, height=1080):
"""Launch Krom in a viewport""" """Launch player external or embedded when viewport_id is passed"""
global _viewport_build_in_progress, _viewport_pending_launches, _viewport_proc_build global _viewport_build_in_progress, _viewport_pending_launches, _viewport_proc_build, scripts_mtime
if not viewport_id: if viewport_id and 'Lnx' not in bpy.data.worlds:
log.error('No viewport_id: play_viewport requires an id')
return
if 'Lnx' not in bpy.data.worlds:
log.error('No Lnx world found - cannot start viewport server') log.error('No Lnx world found - cannot start viewport server')
return return
wrd = bpy.data.worlds['Lnx'] wrd = bpy.data.worlds['Lnx']
target = 'krom' if viewport_id else runtime_to_target()
krom_js_path = lnx.utils.get_fp_build() + '/debug/krom/krom.js' khajs_path = get_khajs_path(target)
if os.path.exists(krom_js_path) and not wrd.lnx_recompile: if not wrd.lnx_cache_build or \
run_viewport_runtime(viewport_id, width, height) not os.path.isfile(khajs_path) or \
return assets.khafile_defs_last != assets.khafile_defs or \
state.last_target != state.target:
wrd.lnx_recompile = True
pending_entry = (viewport_id, width, height) state.last_target = state.target
if pending_entry not in _viewport_pending_launches:
_viewport_pending_launches.append(pending_entry)
if _viewport_build_in_progress: state.mod_scripts = []
if _viewport_proc_build is not None and _viewport_proc_build.poll() is None: script_path = lnx.utils.get_fp() + '/Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
log.info(f'Build in progress, viewport {viewport_id} will launch when ready') if os.path.isdir(script_path):
new_mtime = scripts_mtime
for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
mtime = os.path.getmtime(fn)
if scripts_mtime < mtime:
lnx.utils.fetch_script_props(fn)
fn = fn.split('Sources/')[1]
fn = fn[:-3]
fn = fn.replace('/', '.')
state.mod_scripts.append(fn)
wrd.lnx_recompile = True
if new_mtime < mtime:
new_mtime = mtime
scripts_mtime = new_mtime
if len(state.mod_scripts) > 0:
lnx.utils.fetch_trait_props()
if viewport_id:
krom_js_path = lnx.utils.get_fp_build() + '/debug/krom/krom.js'
if os.path.exists(krom_js_path) and not wrd.lnx_recompile:
run_viewport_runtime(viewport_id, width, height)
return return
else:
_viewport_build_in_progress = False
_viewport_build_in_progress = True pending_entry = (viewport_id, width, height)
if pending_entry not in _viewport_pending_launches:
_viewport_pending_launches.append(pending_entry)
build(target='krom', is_viewport=True, viewport_width=width, viewport_height=height) if _viewport_build_in_progress:
if _viewport_proc_build is not None and _viewport_proc_build.poll() is None:
log.info(f'Build in progress, viewport {viewport_id} will launch when ready')
return
else:
_viewport_build_in_progress = False
compile_viewport(assets_only=(not wrd.lnx_recompile)) _viewport_build_in_progress = True
assets.invalidate_enabled = False
build(target='krom', is_viewport=True, viewport_width=width, viewport_height=height)
compile_viewport(assets_only=(not wrd.lnx_recompile))
assets.invalidate_enabled = True
else:
build(target=target, is_play=True)
compile(assets_only=(not wrd.lnx_recompile))
def stop_viewport(viewport_id=None): def stop_viewport(viewport_id=None):
@ -905,43 +935,6 @@ def get_khajs_path(target):
return lnx.utils.build_dir() + '/debug/krom/krom.js' return lnx.utils.build_dir() + '/debug/krom/krom.js'
return lnx.utils.build_dir() + '/debug/html5/kha.js' return lnx.utils.build_dir() + '/debug/html5/kha.js'
def play():
global scripts_mtime
wrd = bpy.data.worlds['Lnx']
build(target=runtime_to_target(), is_play=True)
khajs_path = get_khajs_path(state.target)
if not wrd.lnx_cache_build or \
not os.path.isfile(khajs_path) or \
assets.khafile_defs_last != assets.khafile_defs or \
state.last_target != state.target:
wrd.lnx_recompile = True
state.last_target = state.target
# Trait sources modified
state.mod_scripts = []
script_path = lnx.utils.get_fp() + '/Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
if os.path.isdir(script_path):
new_mtime = scripts_mtime
for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
mtime = os.path.getmtime(fn)
if scripts_mtime < mtime:
lnx.utils.fetch_script_props(fn) # Trait props
fn = fn.split('Sources/')[1]
fn = fn[:-3] #.hx
fn = fn.replace('/', '.')
state.mod_scripts.append(fn)
wrd.lnx_recompile = True
if new_mtime < mtime:
new_mtime = mtime
scripts_mtime = new_mtime
if len(state.mod_scripts) > 0: # Trait props
lnx.utils.fetch_trait_props()
compile(assets_only=(not wrd.lnx_recompile))
def build_success(): def build_success():
log.clear() log.clear()
wrd = bpy.data.worlds['Lnx'] wrd = bpy.data.worlds['Lnx']

View File

@ -173,6 +173,9 @@ def add_world_defs():
wrd.world_defs += '_Brdf' wrd.world_defs += '_Brdf'
assets.add_khafile_def("lnx_brdf") assets.add_khafile_def("lnx_brdf")
if '_Anisotropy' in wrd.world_defs:
assets.add_khafile_def('lnx_anisotropy')
def build(): def build():
rpdat = lnx.utils.get_rp() rpdat = lnx.utils.get_rp()
project_path = lnx.utils.get_fp() project_path = lnx.utils.get_fp()
@ -510,4 +513,8 @@ def get_num_gbuffer_rts_deferred()-> int:
found_refraction_flag = True found_refraction_flag = True
else: else:
num += 1 num += 1
if '_Anisotropy' in wrd.world_defs:
num += 1
return num return num

View File

@ -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 # 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 # 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 state = None
@ -219,7 +277,15 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
curshader = state.frag curshader = state.frag
state.curshader = curshader state.curshader = curshader
out_basecol, out_roughness, out_metallic, out_occlusion, out_specular, out_opacity, out_ior, out_emission_col = parse_shader_input(node.inputs[0]) outs = parse_shader_input(node.inputs[0])
out_basecol = outs[0]
out_roughness = outs[1]
out_metallic = outs[2]
out_occlusion = outs[3]
out_specular = outs[4]
out_opacity = outs[5]
out_ior = outs[6]
out_emission_col = outs[7]
if parse_surface: if parse_surface:
curshader.write(f'basecol = {out_basecol};') curshader.write(f'basecol = {out_basecol};')
curshader.write(f'roughness = {out_roughness};') curshader.write(f'roughness = {out_roughness};')
@ -227,6 +293,25 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
curshader.write(f'occlusion = {out_occlusion};') curshader.write(f'occlusion = {out_occlusion};')
curshader.write(f'specular = {out_specular};') curshader.write(f'specular = {out_specular};')
curshader.write(f'emissionCol = {out_emission_col};') curshader.write(f'emissionCol = {out_emission_col};')
curshader.write(f'subsurface = {outs[8]};')
curshader.write(f'subsurfaceRadius = {outs[9]};')
curshader.write(f'subsurfaceColor = {outs[10]};')
curshader.write(f'specularTint = {outs[11]};')
curshader.write(f'anisotropy = {outs[12]};')
curshader.write(f'anisoRot = {outs[13]};')
curshader.write(f'sheen = {outs[14]};')
curshader.write(f'sheenRough = {outs[15]};')
curshader.write(f'sheenTint = {outs[16]};')
curshader.write(f'clearcoat = {outs[17]};')
curshader.write(f'clearcoatRough = {outs[18]};')
curshader.write(f'transmission = {outs[19]};')
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 mat_state.emission_type == mat_state.EmissionType.SHADELESS:
if '_EmissionShadeless' not in wrd.world_defs: if '_EmissionShadeless' not in wrd.world_defs:
@ -330,6 +415,10 @@ def parse_shader(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> Tuple[st
'BSDF_GLASS', 'BSDF_GLASS',
'HOLDOUT', 'HOLDOUT',
'SUBSURFACE_SCATTERING', 'SUBSURFACE_SCATTERING',
'BSDF_REFRACTION',
'BSDF_TOON',
'BSDF_HAIR',
'BSDF_HAIR_PRINCIPLED',
'BSDF_TRANSLUCENT', 'BSDF_TRANSLUCENT',
'BSDF_TRANSPARENT', 'BSDF_TRANSPARENT',
'BSDF_VELVET', 'BSDF_VELVET',

View File

@ -16,19 +16,61 @@ if lnx.is_reload(__name__):
else: else:
lnx.enable_reload(__name__) lnx.enable_reload(__name__)
# Principled BSDF socket names that differ between Blender versions
if bpy.app.version < (4, 0, 0):
EMISSION_COLOR = 'Emission'
SUBSURFACE = 'Subsurface'
SUBSURFACE_RADIUS = 'Subsurface Radius'
SUBSURFACE_COLOR = 'Subsurface Color'
SPECULAR = 'Specular'
SPECULAR_TINT = 'Specular Tint'
ANISOTROPIC = 'Anisotropic'
ANISOTROPIC_ROT = 'Anisotropic Rotation'
SHEEN = 'Sheen'
SHEEN_TINT = 'Sheen Tint'
CLEARCOAT = 'Clearcoat'
CLEARCOAT_ROUGHNESS = 'Clearcoat Roughness'
CLEARCOAT_NORMAL = 'Clearcoat Normal'
TRANSMISSION = 'Transmission'
TRANSMISSION_ROUGHNESS = 'Transmission Roughness'
TANGENT = 'Tangent'
else:
EMISSION_COLOR = 'Emission Color'
SUBSURFACE = 'Subsurface Weight'
SUBSURFACE_RADIUS = 'Subsurface Radius'
SUBSURFACE_COLOR = 'Subsurface Color'
SPECULAR = 'Specular IOR Level'
SPECULAR_TINT = 'Specular Tint'
ANISOTROPIC = 'Anisotropic'
ANISOTROPIC_ROT = 'Anisotropic Rotation'
SHEEN = 'Sheen Weight'
SHEEN_TINT = 'Sheen Tint'
SHEEN_ROUGHNESS = 'Sheen Roughness'
CLEARCOAT = 'Coat Weight'
CLEARCOAT_ROUGHNESS = 'Coat Roughness'
CLEARCOAT_NORMAL = 'Coat Normal'
COAT_IOR = 'Coat IOR'
COAT_TINT = 'Coat Tint'
TRANSMISSION = 'Transmission Weight'
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: def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket, state: ParserState) -> None:
# Skip mixing if only one input is effectively used # Skip mixing if only one input is effectively used
if not node.inputs[0].is_linked: if not node.inputs['Fac'].is_linked:
if node.inputs[0].default_value <= 0.0: if node.inputs['Fac'].default_value <= 0.0:
c.parse_shader_input(node.inputs[1]) c.parse_shader_input(node.inputs[1])
return return
elif node.inputs[0].default_value >= 1.0: elif node.inputs['Fac'].default_value >= 1.0:
c.parse_shader_input(node.inputs[2]) c.parse_shader_input(node.inputs[2])
return return
prefix = '' if node.inputs[0].is_linked else 'const ' prefix = '' if node.inputs['Fac'].is_linked else 'const '
fac = c.parse_value_input(node.inputs[0]) fac = c.parse_value_input(node.inputs['Fac'])
fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix() fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix()
fac_inv_var = c.node_name(node.name) + '_fac_inv' fac_inv_var = c.node_name(node.name) + '_fac_inv'
state.curshader.write('{0}float {1} = clamp({2}, 0.0, 1.0);'.format(prefix, fac_var, fac)) state.curshader.write('{0}float {1} = clamp({2}, 0.0, 1.0);'.format(prefix, fac_var, fac))
@ -36,134 +78,209 @@ def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket,
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
sss_before_1 = mat_state.needs_sss sss_before_1 = mat_state.needs_sss
bc1, rough1, met1, occ1, spec1, opac1, ior1, emi1 = c.parse_shader_input(node.inputs[1]) o1 = c.parse_shader_input(node.inputs[1])
sss_1 = mat_state.needs_sss sss_1 = mat_state.needs_sss
ek1 = mat_state.emission_type ek1 = mat_state.emission_type
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
mat_state.needs_sss = sss_before_1 # Reset to state before parsing input 1 mat_state.needs_sss = sss_before_1 # Reset to state before parsing input 1
bc2, rough2, met2, occ2, spec2, opac2, ior2, emi2 = c.parse_shader_input(node.inputs[2]) o2 = c.parse_shader_input(node.inputs[2])
sss_2 = mat_state.needs_sss sss_2 = mat_state.needs_sss
ek2 = mat_state.emission_type ek2 = mat_state.emission_type
mat_state.needs_sss = sss_1 or sss_2 mat_state.needs_sss = sss_1 or sss_2
if state.parse_surface: if state.parse_surface:
state.out_basecol = '({0} * {3} + {1} * {2})'.format(bc1, bc2, fac_var, fac_inv_var) fm = '{0} * {3} + {1} * {2}' # fac mix template: a*fac_inv + b*fac
state.out_roughness = '({0} * {3} + {1} * {2})'.format(rough1, rough2, fac_var, fac_inv_var) fv = (fac_var, fac_inv_var)
state.out_metallic = '({0} * {3} + {1} * {2})'.format(met1, met2, fac_var, fac_inv_var) state.out_basecol = fm.format(o1[0], o2[0], fv[0], fv[1])
state.out_occlusion = '({0} * {3} + {1} * {2})'.format(occ1, occ2, fac_var, fac_inv_var) state.out_roughness = fm.format(o1[1], o2[1], fv[0], fv[1])
state.out_specular = '({0} * {3} + {1} * {2})'.format(spec1, spec2, fac_var, fac_inv_var) state.out_metallic = fm.format(o1[2], o2[2], fv[0], fv[1])
state.out_emission_col = '({0} * {3} + {1} * {2})'.format(emi1, emi2, fac_var, fac_inv_var) state.out_occlusion = fm.format(o1[3], o2[3], fv[0], fv[1])
state.out_specular = fm.format(o1[4], o2[4], fv[0], fv[1])
state.out_emission_col = fm.format(o1[7], o2[7], fv[0], fv[1])
state.out_subsurface = fm.format(o1[8], o2[8], fv[0], fv[1])
state.out_subsurface_radius = fm.format(o1[9], o2[9], fv[0], fv[1])
state.out_subsurface_color = fm.format(o1[10], o2[10], fv[0], fv[1])
state.out_specular_tint = fm.format(o1[11], o2[11], fv[0], fv[1])
state.out_anisotropy = fm.format(o1[12], o2[12], fv[0], fv[1])
state.out_aniso_rot = fm.format(o1[13], o2[13], fv[0], fv[1])
state.out_sheen = fm.format(o1[14], o2[14], fv[0], fv[1])
state.out_sheen_rough = fm.format(o1[15], o2[15], fv[0], fv[1])
state.out_sheen_tint = fm.format(o1[16], o2[16], fv[0], fv[1])
state.out_clearcoat = fm.format(o1[17], o2[17], fv[0], fv[1])
state.out_clearcoat_rough = fm.format(o1[18], o2[18], fv[0], fv[1])
state.out_transmission = fm.format(o1[19], o2[19], fv[0], fv[1])
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) mat_state.emission_type = mat_state.EmissionType.get_effective_combination(ek1, ek2)
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '({0} * {3} + {1} * {2})'.format(opac1, opac2, fac_var, fac_inv_var) fm = '{0} * {3} + {1} * {2}'
state.out_ior = '({0} * {3} + {1} * {2})'.format(ior1, ior2, fac_var, fac_inv_var) fv = (fac_var, fac_inv_var)
state.out_opacity = fm.format(o1[5], o2[5], fv[0], fv[1])
state.out_ior = fm.format(o1[6], o2[6], fv[0], fv[1])
def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket, state: ParserState) -> None: def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket, state: ParserState) -> None:
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
sss_before_1 = mat_state.needs_sss sss_before_1 = mat_state.needs_sss
bc1, rough1, met1, occ1, spec1, opac1, ior1, emi1 = c.parse_shader_input(node.inputs[0]) o1 = c.parse_shader_input(node.inputs[0])
sss_1 = mat_state.needs_sss sss_1 = mat_state.needs_sss
ek1 = mat_state.emission_type ek1 = mat_state.emission_type
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
mat_state.needs_sss = sss_before_1 # Reset to state before parsing input 0 mat_state.needs_sss = sss_before_1 # Reset to state before parsing input 0
bc2, rough2, met2, occ2, spec2, opac2, ior2, emi2 = c.parse_shader_input(node.inputs[1]) o2 = c.parse_shader_input(node.inputs[1])
sss_2 = mat_state.needs_sss sss_2 = mat_state.needs_sss
ek2 = mat_state.emission_type ek2 = mat_state.emission_type
mat_state.needs_sss = sss_1 or sss_2 mat_state.needs_sss = sss_1 or sss_2
if state.parse_surface: if state.parse_surface:
state.out_basecol = '({0} + {1})'.format(bc1, bc2) am = '{0} * 0.5 + {1} * 0.5' # add mix template (average)
state.out_roughness = '({0} * 0.5 + {1} * 0.5)'.format(rough1, rough2) state.out_basecol = '({0} + {1})'.format(o1[0], o2[0])
state.out_metallic = '({0} * 0.5 + {1} * 0.5)'.format(met1, met2) state.out_roughness = am.format(o1[1], o2[1])
state.out_occlusion = '({0} * 0.5 + {1} * 0.5)'.format(occ1, occ2) state.out_metallic = am.format(o1[2], o2[2])
state.out_specular = '({0} * 0.5 + {1} * 0.5)'.format(spec1, spec2) state.out_occlusion = am.format(o1[3], o2[3])
state.out_emission_col = '({0} + {1})'.format(emi1, emi2) state.out_specular = am.format(o1[4], o2[4])
state.out_emission_col = '({0} + {1})'.format(o1[7], o2[7])
state.out_subsurface = am.format(o1[8], o2[8])
state.out_subsurface_radius = am.format(o1[9], o2[9])
state.out_subsurface_color = am.format(o1[10], o2[10])
state.out_specular_tint = am.format(o1[11], o2[11])
state.out_anisotropy = am.format(o1[12], o2[12])
state.out_aniso_rot = am.format(o1[13], o2[13])
state.out_sheen = am.format(o1[14], o2[14])
state.out_sheen_rough = am.format(o1[15], o2[15])
state.out_sheen_tint = am.format(o1[16], o2[16])
state.out_clearcoat = am.format(o1[17], o2[17])
state.out_clearcoat_rough = am.format(o1[18], o2[18])
state.out_transmission = am.format(o1[19], o2[19])
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) mat_state.emission_type = mat_state.EmissionType.get_effective_combination(ek1, ek2)
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '({0} * 0.5 + {1} * 0.5)'.format(opac1, opac2) am = '{0} * 0.5 + {1} * 0.5'
state.out_ior = '({0} * 0.5 + {1} * 0.5)'.format(ior1, ior2) state.out_opacity = am.format(o1[5], o2[5])
state.out_ior = am.format(o1[6], o2[6])
# TODO: Refactor using c.get_*_input() # TODO: Refactor using c.get_*_input()
if bpy.app.version < (2, 92, 0): if bpy.app.version < (2, 91, 0):
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[20]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Base Color'])
if node.inputs[1].is_linked or node.inputs[1].default_value > 0.0: if node.inputs[SUBSURFACE].is_linked or node.inputs[SUBSURFACE].default_value > 0.0:
mat_state.needs_sss = True mat_state.needs_sss = True
state.out_metallic = c.parse_value_input(node.inputs[4]) state.out_subsurface = c.parse_value_input(node.inputs[SUBSURFACE])
state.out_specular = c.parse_value_input(node.inputs[5]) state.out_subsurface_radius = c.parse_vector_input(node.inputs[SUBSURFACE_RADIUS])
state.out_roughness = c.parse_value_input(node.inputs[7]) state.out_subsurface_color = c.parse_vector_input(node.inputs[SUBSURFACE_COLOR])
if node.inputs['Emission'].is_linked or not mat_utils.equals_color_socket(node.inputs['Emission'], (0.0, 0.0, 0.0), comp_alpha=False): state.out_metallic = c.parse_value_input(node.inputs['Metallic'])
emission_col = c.parse_vector_input(node.inputs[17]) state.out_specular = c.parse_value_input(node.inputs[SPECULAR])
state.out_specular_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SPECULAR_TINT]))
state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
state.out_anisotropy = c.parse_value_input(node.inputs[ANISOTROPIC])
state.out_aniso_rot = c.parse_value_input(node.inputs[ANISOTROPIC_ROT])
state.out_sheen = c.parse_value_input(node.inputs[SHEEN])
state.out_sheen_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SHEEN_TINT]))
state.out_clearcoat = c.parse_value_input(node.inputs[CLEARCOAT])
state.out_clearcoat_rough = c.parse_value_input(node.inputs[CLEARCOAT_ROUGHNESS])
state.out_transmission = c.parse_value_input(node.inputs[TRANSMISSION])
trans_rough_socket = node.inputs.get(TRANSMISSION_ROUGHNESS)
if trans_rough_socket is not None:
state.out_transmission_rough = c.parse_value_input(trans_rough_socket)
state.out_tangent = c.parse_vector_input(node.inputs[TANGENT])
if node.inputs[EMISSION_COLOR].is_linked or not mat_utils.equals_color_socket(node.inputs[EMISSION_COLOR], (0.0, 0.0, 0.0), comp_alpha=False):
emission_col = c.parse_vector_input(node.inputs[EMISSION_COLOR])
state.out_emission_col = emission_col state.out_emission_col = emission_col
mat_state.emission_type = mat_state.EmissionType.SHADED mat_state.emission_type = mat_state.EmissionType.SHADED
else: else:
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
if state.parse_opacity: if state.parse_opacity:
state.out_ior = c.parse_value_input(node.inputs[14]) state.out_ior = c.parse_value_input(node.inputs['IOR'])
# In Blender 2.83, Alpha socket is at index 18, not 19
if 'Alpha' in node.inputs: if 'Alpha' in node.inputs:
state.out_opacity = c.parse_value_input(node.inputs['Alpha']) state.out_opacity = c.parse_value_input(node.inputs['Alpha'])
else: else:
state.out_opacity = '1.0' state.out_opacity = '1.0'
if bpy.app.version >= (2, 92, 0) and bpy.app.version <= (4, 1, 0): if bpy.app.version >= (2, 91, 0) and bpy.app.version < (4, 0, 0):
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[22]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Base Color'])
if node.inputs[1].is_linked or node.inputs[1].default_value > 0.0: if node.inputs[SUBSURFACE].is_linked or node.inputs[SUBSURFACE].default_value > 0.0:
mat_state.needs_sss = True mat_state.needs_sss = True
# subsurface_radius = c.parse_vector_input(node.inputs[2]) state.out_subsurface = c.parse_value_input(node.inputs[SUBSURFACE])
# subsurface_color = c.parse_vector_input(node.inputs[3]) state.out_subsurface_radius = c.parse_vector_input(node.inputs[SUBSURFACE_RADIUS])
state.out_metallic = c.parse_value_input(node.inputs[6]) state.out_subsurface_color = c.parse_vector_input(node.inputs[SUBSURFACE_COLOR])
state.out_specular = c.parse_value_input(node.inputs[7]) state.out_metallic = c.parse_value_input(node.inputs['Metallic'])
# specular_tint = c.parse_vector_input(node.inputs[6]) state.out_specular = c.parse_value_input(node.inputs[SPECULAR])
state.out_roughness = c.parse_value_input(node.inputs[9]) state.out_specular_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SPECULAR_TINT]))
# aniso = c.parse_vector_input(node.inputs[8]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
# aniso_rot = c.parse_vector_input(node.inputs[9]) state.out_anisotropy = c.parse_value_input(node.inputs[ANISOTROPIC])
# sheen = c.parse_vector_input(node.inputs[10]) state.out_aniso_rot = c.parse_value_input(node.inputs[ANISOTROPIC_ROT])
# sheen_tint = c.parse_vector_input(node.inputs[11]) state.out_sheen = c.parse_value_input(node.inputs[SHEEN])
# clearcoat = c.parse_vector_input(node.inputs[12]) state.out_sheen_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SHEEN_TINT]))
# clearcoat_rough = c.parse_vector_input(node.inputs[13]) state.out_clearcoat = c.parse_value_input(node.inputs[CLEARCOAT])
# ior = c.parse_vector_input(node.inputs[14]) state.out_clearcoat_rough = c.parse_value_input(node.inputs[CLEARCOAT_ROUGHNESS])
# transmission = c.parse_vector_input(node.inputs[15]) state.out_transmission = c.parse_value_input(node.inputs[TRANSMISSION])
# transmission_roughness = c.parse_vector_input(node.inputs[16]) state.out_transmission_rough = c.parse_value_input(node.inputs[TRANSMISSION_ROUGHNESS])
state.out_tangent = c.parse_vector_input(node.inputs[TANGENT])
if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\ if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\
and (node.inputs['Emission'].is_linked or not mat_utils.equals_color_socket(node.inputs['Emission'], (0.0, 0.0, 0.0), comp_alpha=False)): and (node.inputs[EMISSION_COLOR].is_linked or not mat_utils.equals_color_socket(node.inputs[EMISSION_COLOR], (0.0, 0.0, 0.0), comp_alpha=False)):
emission_col = c.parse_vector_input(node.inputs[19]) emission_col = c.parse_vector_input(node.inputs[EMISSION_COLOR])
emission_strength = c.parse_value_input(node.inputs[20]) emission_strength = c.parse_value_input(node.inputs['Emission Strength'])
state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength) state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength)
mat_state.emission_type = mat_state.EmissionType.SHADED mat_state.emission_type = mat_state.EmissionType.SHADED
else: else:
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
# clearcoar_normal = c.parse_vector_input(node.inputs[21])
# tangent = c.parse_vector_input(node.inputs[22])
if state.parse_opacity: if state.parse_opacity:
state.out_ior = c.parse_value_input(node.inputs[16]) state.out_ior = c.parse_value_input(node.inputs['IOR'])
if len(node.inputs) >= 21: if 'Alpha' in node.inputs:
state.out_opacity = c.parse_value_input(node.inputs[21]) state.out_opacity = c.parse_value_input(node.inputs['Alpha'])
if bpy.app.version > (4, 1, 0): else:
state.out_opacity = '1.0'
if bpy.app.version >= (4, 0, 0):
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[5]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Base Color'])
sss_input = node.inputs.get('Subsurface Weight') or node.inputs.get('Subsurface') sss_input = node.inputs.get(SUBSURFACE)
if sss_input is not None: if sss_input is not None:
if sss_input.is_linked or sss_input.default_value > 0.0: if sss_input.is_linked or sss_input.default_value > 0.0:
mat_state.needs_sss = True mat_state.needs_sss = True
state.out_subsurface = c.parse_value_input(sss_input)
subsurface = c.parse_value_input(node.inputs[7]) sss_radius_input = node.inputs.get(SUBSURFACE_RADIUS)
subsurface_radius = c.parse_vector_input(node.inputs[9]) if sss_radius_input is not None:
subsurface_color = c.parse_vector_input(node.inputs[8]) state.out_subsurface_radius = c.parse_vector_input(sss_radius_input)
state.out_metallic = c.parse_value_input(node.inputs[1])
specular_socket = node.inputs.get('Specular IOR Level') 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)
else:
state.out_subsurface_color = c.parse_vector_input(node.inputs['Base Color'])
state.out_metallic = c.parse_value_input(node.inputs['Metallic'])
specular_socket = node.inputs.get(SPECULAR)
if specular_socket is not None: if specular_socket is not None:
state.out_specular = f'({c.parse_value_input(specular_socket)} * 2.0)' state.out_specular = f'({c.parse_value_input(specular_socket)} * 2.0)'
else: else:
@ -173,88 +290,124 @@ if bpy.app.version > (4, 1, 0):
else: else:
state.out_specular = '1.0' state.out_specular = '1.0'
state.out_roughness = c.parse_value_input(node.inputs[2]) spec_tint_socket = node.inputs.get(SPECULAR_TINT)
if spec_tint_socket is not None:
state.out_specular_tint = c.parse_vector_input(spec_tint_socket)
state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
# Prevent black material when metal = 1.0 and roughness = 0.0 # Prevent black material when metal = 1.0 and roughness = 0.0
try: try:
if float(state.out_roughness) < 0.00101: if float(state.out_roughness) < 0.00101:
state.out_roughness = '0.001' state.out_roughness = '0.001'
except ValueError: except ValueError:
pass pass
aniso_socket = node.inputs.get(ANISOTROPIC)
if aniso_socket is not None:
state.out_anisotropy = c.parse_value_input(aniso_socket)
aniso_rot_socket = node.inputs.get(ANISOTROPIC_ROT)
if aniso_rot_socket is not None:
state.out_aniso_rot = c.parse_value_input(aniso_rot_socket)
sheen_socket = node.inputs.get(SHEEN)
if sheen_socket is not None:
state.out_sheen = c.parse_value_input(sheen_socket)
sheen_rough_socket = node.inputs.get(SHEEN_ROUGHNESS)
if sheen_rough_socket is not None:
state.out_sheen_rough = c.parse_value_input(sheen_rough_socket)
sheen_tint_socket = node.inputs.get(SHEEN_TINT)
if sheen_tint_socket is not None:
state.out_sheen_tint = c.parse_vector_input(sheen_tint_socket)
coat_socket = node.inputs.get(CLEARCOAT)
if coat_socket is not None:
state.out_clearcoat = c.parse_value_input(coat_socket)
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:
state.out_transmission = c.parse_value_input(trans_socket)
trans_rough_socket = node.inputs.get(TRANSMISSION_ROUGHNESS)
if trans_rough_socket is not None:
state.out_transmission_rough = c.parse_value_input(trans_rough_socket)
thin_wall_socket = node.inputs.get(THIN_WALL)
if thin_wall_socket is not None:
state.out_thin_wall = c.parse_value_input(thin_wall_socket)
tangent_socket = node.inputs.get(TANGENT)
if tangent_socket is not None:
state.out_tangent = c.parse_vector_input(tangent_socket)
if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\ if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\
and (node.inputs['Emission Color'].is_linked or not mat_utils.equals_color_socket(node.inputs['Emission Color'], (0.0, 0.0, 0.0), comp_alpha=False)): and (node.inputs[EMISSION_COLOR].is_linked or not mat_utils.equals_color_socket(node.inputs[EMISSION_COLOR], (0.0, 0.0, 0.0), comp_alpha=False)):
if bpy.app.version >= (4, 4, 0): emission_col = c.parse_vector_input(node.inputs[EMISSION_COLOR])
emission_col = c.parse_vector_input(node.inputs[27]) emission_strength = c.parse_value_input(node.inputs['Emission Strength'])
emission_strength = c.parse_value_input(node.inputs[28])
else:
emission_col = c.parse_vector_input(node.inputs[26])
emission_strength = c.parse_value_input(node.inputs[27])
state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength) state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength)
mat_state.emission_type = mat_state.EmissionType.SHADED mat_state.emission_type = mat_state.EmissionType.SHADED
else: else:
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
#state.out_occlusion = state.out_roughness
#state.out_aniso = c.parse_vector_input(node.inputs[14])
#state.out_aniso_rot = c.parse_vector_input(node.inputs[15])
#state.out_sheen = c.parse_vector_input(node.inputs[23])
#state.out_sheen_tint = c.parse_vector_input(node.inputs[25])
#state.out_clearcoat = c.parse_vector_input(node.inputs[18])
#state.out_clearcoat_rough = c.parse_vector_input(node.inputs[19])
#state.out_ior = c.parse_value_input(node.inputs[3])
#state.out_transmission = c.parse_vector_input(node.inputs[17])
#state.out_transmission_roughness = state.out_roughness
if state.parse_opacity: if state.parse_opacity:
state.out_ior = c.parse_value_input(node.inputs[3]) state.out_ior = c.parse_value_input(node.inputs['IOR'])
state.out_opacity = c.parse_value_input(node.inputs[4]) state.out_opacity = c.parse_value_input(node.inputs['Alpha'])
def parse_bsdfdiffuse(node: bpy.types.ShaderNodeBsdfDiffuse, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfdiffuse(node: bpy.types.ShaderNodeBsdfDiffuse, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[2]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
state.out_specular = '0.0' state.out_specular = '0.0'
if bpy.app.version >= (4, 0, 0): if bpy.app.version >= (4, 0, 0):
def parse_bsdfsheen(node: bpy.types.ShaderNodeBsdfSheen, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfsheen(node: bpy.types.ShaderNodeBsdfSheen, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[2]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
if bpy.app.version < (4, 1, 0): if bpy.app.version < (4, 0, 0):
def parse_bsdfglossy(node: bpy.types.ShaderNodeBsdfGlossy, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfglossy(node: bpy.types.ShaderNodeBsdfGlossy, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[2]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
state.out_metallic = '1.0' state.out_metallic = '1.0'
else: else:
def parse_bsdfglossy(node: bpy.types.ShaderNodeBsdfAnisotropic, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfglossy(node: bpy.types.ShaderNodeBsdfAnisotropic, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[4]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
state.out_metallic = '1.0' state.out_metallic = '1.0'
def parse_ambientocclusion(node: bpy.types.ShaderNodeAmbientOcclusion, out_socket: NodeSocket, state: ParserState) -> None: def parse_ambientocclusion(node: bpy.types.ShaderNodeAmbientOcclusion, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
# Single channel # Single channel
state.out_occlusion = c.parse_vector_input(node.inputs[0]) + '.r' state.out_occlusion = c.parse_vector_input(node.inputs['Distance']) + '.r'
def parse_bsdfanisotropic(node: bpy.types.ShaderNodeBsdfAnisotropic, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfanisotropic(node: bpy.types.ShaderNodeBsdfAnisotropic, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[4]) c.write_normal(node.inputs['Normal'])
# Revert to glossy # Revert to glossy
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
state.out_metallic = '1.0' state.out_metallic = '1.0'
def parse_emission(node: bpy.types.ShaderNodeEmission, out_socket: NodeSocket, state: ParserState) -> None: def parse_emission(node: bpy.types.ShaderNodeEmission, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
emission_col = c.parse_vector_input(node.inputs[0]) emission_col = c.parse_vector_input(node.inputs['Color'])
emission_strength = c.parse_value_input(node.inputs[1]) emission_strength = c.parse_value_input(node.inputs['Strength'])
state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength) state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength)
state.out_basecol = 'vec3(0.0)' state.out_basecol = 'vec3(0.0)'
state.out_specular = '0.0' state.out_specular = '0.0'
@ -264,16 +417,77 @@ def parse_emission(node: bpy.types.ShaderNodeEmission, out_socket: NodeSocket, s
def parse_bsdfglass(node: bpy.types.ShaderNodeBsdfGlass, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfglass(node: bpy.types.ShaderNodeBsdfGlass, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
c.write_normal(node.inputs[3]) c.write_normal(node.inputs['Normal'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '1.0' state.out_opacity = '1.0'
state.out_ior = c.parse_value_input(node.inputs[2]) state.out_ior = c.parse_value_input(node.inputs['IOR'])
def parse_bsdfhair(node: bpy.types.ShaderNodeBsdfHair, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfhair(node: bpy.types.ShaderNodeBsdfHair, out_socket: NodeSocket, state: ParserState) -> None:
pass if state.parse_surface:
state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs['RoughnessU'])
state.out_specular = '0.0'
rough_v_socket = node.inputs.get('RoughnessV')
if rough_v_socket is not None:
state.out_aniso_rot = c.parse_value_input(rough_v_socket)
tangent_socket = node.inputs.get('Tangent')
if tangent_socket is not None:
state.out_tangent = c.parse_vector_input(tangent_socket)
if bpy.app.version >= (2, 83, 0):
def parse_bsdfhairprincipled(node: 'bpy.types.ShaderNodeBsdfHairPrincipled', out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface:
c.write_normal(node.inputs['Normal'])
parametrization = getattr(node, 'parametrization', 'COLOR')
if parametrization == 'COLOR':
color_socket = node.inputs.get('Color')
if color_socket is not None:
state.out_basecol = c.parse_vector_input(color_socket)
elif parametrization == 'MELANIN':
melanin_socket = node.inputs.get('Melanin')
if melanin_socket is not None:
state.out_subsurface = c.parse_value_input(melanin_socket)
melanin_red_socket = node.inputs.get('Melanin Redness')
if melanin_red_socket is not None:
state.out_specular_tint = 'vec3({})'.format(c.parse_value_input(melanin_red_socket))
elif parametrization == 'ABSORPTION':
absorption_socket = node.inputs.get('Absorption Coefficient')
if absorption_socket is not None:
state.out_specular_tint = c.parse_vector_input(absorption_socket)
tint_socket = node.inputs.get('Tint')
if tint_socket is not None:
state.out_subsurface_color = c.parse_vector_input(tint_socket)
rough_socket = node.inputs.get('Roughness')
if rough_socket is not None:
state.out_roughness = c.parse_value_input(rough_socket)
model = getattr(node, 'model', 'CHIANG')
if model == 'CHIANG':
radial_rough_socket = node.inputs.get('Radial Roughness')
if radial_rough_socket is not None:
state.out_aniso_rot = c.parse_value_input(radial_rough_socket)
coat_socket = node.inputs.get('Coat')
if coat_socket is not None:
state.out_clearcoat = c.parse_value_input(coat_socket)
random_color_socket = node.inputs.get('Random Color')
if random_color_socket is not None:
state.out_sheen = c.parse_value_input(random_color_socket)
random_rough_socket = node.inputs.get('Random Roughness')
if random_rough_socket is not None:
state.out_sheen_rough = c.parse_value_input(random_rough_socket)
offset_socket = node.inputs.get('Offset')
if offset_socket is not None:
state.out_anisotropy = c.parse_value_input(offset_socket)
state.out_specular = '0.0'
state.out_metallic = '0.0'
if state.parse_opacity:
ior_socket = node.inputs.get('IOR')
if ior_socket is not None:
state.out_ior = c.parse_value_input(ior_socket)
state.out_opacity = '1.0'
def parse_holdout(node: bpy.types.ShaderNodeHoldout, out_socket: NodeSocket, state: ParserState) -> None: def parse_holdout(node: bpy.types.ShaderNodeHoldout, out_socket: NodeSocket, state: ParserState) -> None:
@ -284,46 +498,50 @@ def parse_holdout(node: bpy.types.ShaderNodeHoldout, out_socket: NodeSocket, sta
def parse_bsdfrefraction(node: bpy.types.ShaderNodeBsdfRefraction, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfrefraction(node: bpy.types.ShaderNodeBsdfRefraction, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
c.write_normal(node.inputs[3]) c.write_normal(node.inputs['Normal'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '1.0' state.out_opacity = '1.0'
state.out_ior = c.parse_value_input(node.inputs[2]) state.out_ior = c.parse_value_input(node.inputs['IOR'])
def parse_subsurfacescattering(node: bpy.types.ShaderNodeSubsurfaceScattering, out_socket: NodeSocket, state: ParserState) -> None: def parse_subsurfacescattering(node: bpy.types.ShaderNodeSubsurfaceScattering, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
# Mark that this material needs SSS # Mark that this material needs SSS
mat_state.needs_sss = True mat_state.needs_sss = True
if bpy.app.version < (4, 1, 0): c.write_normal(node.inputs['Normal'])
c.write_normal(node.inputs[4]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
else: state.out_subsurface = c.parse_value_input(node.inputs['Scale'])
c.write_normal(node.inputs[6]) state.out_subsurface_radius = c.parse_vector_input(node.inputs['Radius'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_subsurface_color = c.parse_vector_input(node.inputs['Color'])
state.out_specular = '0.0'
def parse_bsdftoon(node: bpy.types.ShaderNodeBsdfToon, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdftoon(node: bpy.types.ShaderNodeBsdfToon, out_socket: NodeSocket, state: ParserState) -> None:
# c.write_normal(node.inputs[3]) if state.parse_surface:
pass c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs['Size'])
state.out_specular = '0.0'
def parse_bsdftranslucent(node: bpy.types.ShaderNodeBsdfTranslucent, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdftranslucent(node: bpy.types.ShaderNodeBsdfTranslucent, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[1]) c.write_normal(node.inputs['Normal'])
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '(1.0 - {0}.r)'.format(c.parse_vector_input(node.inputs[0])) state.out_opacity = '(1.0 - {0}.r)'.format(c.parse_vector_input(node.inputs['Color']))
state.out_ior = '1.0' state.out_ior = '1.0'
def parse_bsdftransparent(node: bpy.types.ShaderNodeBsdfTransparent, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdftransparent(node: bpy.types.ShaderNodeBsdfTransparent, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '(1.0 - {0}.r)'.format(c.parse_vector_input(node.inputs[0])) state.out_opacity = '(1.0 - {0}.r)'.format(c.parse_vector_input(node.inputs['Color']))
state.out_ior = '1.0' state.out_ior = '1.0'
if bpy.app.version < (4, 1, 0): if bpy.app.version < (4, 0, 0):
def parse_bsdfvelvet(node: bpy.types.ShaderNodeBsdfVelvet, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfvelvet(node: bpy.types.ShaderNodeBsdfVelvet, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[2]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = '1.0' state.out_roughness = '1.0'
state.out_metallic = '1.0' state.out_metallic = '1.0'

View File

@ -40,6 +40,8 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]],
wrd = bpy.data.worlds['Lnx'] wrd = bpy.data.worlds['Lnx']
rpdat = lnx.utils.get_rp() rpdat = lnx.utils.get_rp()
mat_state.features = {}
# Texture caching for material batching # Texture caching for material batching
batch_cached_textures = [] batch_cached_textures = []
@ -120,6 +122,76 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]],
const['intValue'] = 0 const['intValue'] = 0
c['bind_constants'].append(const) 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 # TODO: Mesh only material batching
if wrd.lnx_batch_materials: if wrd.lnx_batch_materials:
# Set textures uniforms # Set textures uniforms

View File

@ -111,6 +111,16 @@ def write(vert: shader.Shader, frag: shader.Shader):
frag.add_uniform('mat4 invVP', '_inverseViewProjectionMatrix') frag.add_uniform('mat4 invVP', '_inverseViewProjectionMatrix')
frag.add_uniform('vec3 eye', '_cameraPosition') frag.add_uniform('vec3 eye', '_cameraPosition')
frag.write(', gbufferD, invVP, eye') 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(');')
frag.write('}') # for numLights frag.write('}') # for numLights

View File

@ -74,6 +74,8 @@ def make(context_id, rpasses):
con['color_attachments'] = [attachment_format, attachment_format] con['color_attachments'] = [attachment_format, attachment_format]
if '_gbuffer2' in wrd.world_defs: if '_gbuffer2' in wrd.world_defs:
con['color_attachments'].append(attachment_format) 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) con_mesh = mat_state.data.add_context(con)
mat_state.con_mesh = con_mesh 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);') frag.write('n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);')
is_shadeless = mat_state.emission_type == mat_state.EmissionType.SHADELESS 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;') frag.write('uint matid = 0;')
if is_shadeless: if is_shadeless:
frag.write('matid = 1;') 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: if '_SSS' in wrd.world_defs or '_Hair' in wrd.world_defs:
frag.add_uniform('int materialID') frag.add_uniform('int materialID')
frag.write('if (materialID == 2) matid = 2;') 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: else:
frag.write('const uint matid = 0;') 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: 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);') 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 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('mat4 invVP', '_inverseViewProjectionMatrix')
frag.add_uniform('vec3 eye', '_cameraPosition') frag.add_uniform('vec3 eye', '_cameraPosition')
frag.write(', gbufferD, invVP, eye') 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(');')
if '_Clusters' in wrd.world_defs: if '_Clusters' in wrd.world_defs:
@ -856,6 +881,26 @@ def _write_material_attribs_default(frag: shader.Shader, parse_opacity: bool):
# We may not use emission, but the attribute will then be removed # We may not use emission, but the attribute will then be removed
# by the shader compiler # by the shader compiler
frag.write('vec3 emissionCol;') frag.write('vec3 emissionCol;')
# Extended BRDF parameters
frag.write('float subsurface = 0.0;')
frag.write('vec3 subsurfaceRadius = vec3(0.0);')
frag.write('vec3 subsurfaceColor = vec3(0.8);')
frag.write('vec3 specularTint = vec3(1.0);')
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);')
if parse_opacity: if parse_opacity:
frag.write('float opacity;') frag.write('float opacity;')
frag.write('float ior = 1.45;') frag.write('float ior = 1.45;')

View File

@ -81,6 +81,25 @@ def make_gi(context_id):
frag.write('float occlusion;') # frag.write('float occlusion;') #
frag.write('float specular;') # frag.write('float specular;') #
frag.write('vec3 emissionCol = vec3(0.0);') 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 blend = mat_state.material.lnx_blending
parse_opacity = blend or mat_utils.is_transluc(mat_state.material) parse_opacity = blend or mat_utils.is_transluc(mat_state.material)
if parse_opacity: if parse_opacity:
@ -377,6 +396,16 @@ def make_gi(context_id):
frag.write(', opacity != 1.0') frag.write(', opacity != 1.0')
if '_Spot' in wrd.world_defs: if '_Spot' in wrd.world_defs:
frag.write(', true, spotData.x, spotData.y, spotDir, spotData.zw, spotRight') 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(');') frag.write(');')
if '_Clusters' in wrd.world_defs: 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, lightsArraySpot[li * 2].xyz') # spotDir
frag.write('\t, vec2(lightsArray[li * 3].w, lightsArray[li * 3 + 1].w)') # scale frag.write('\t, vec2(lightsArray[li * 3].w, lightsArray[li * 3 + 1].w)') # scale
frag.write('\t, lightsArraySpot[li * 2 + 1].xyz') # right 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(' );')
frag.write('}') frag.write('}')

View File

@ -39,3 +39,5 @@ con_mesh = None # Mesh context
uses_instancing = False # Whether the current material has at least one user with instancing enabled uses_instancing = False # Whether the current material has at least one user with instancing enabled
emission_type = EmissionType.NO_EMISSION emission_type = EmissionType.NO_EMISSION
needs_sss = False needs_sss = False
features = {} # tracks extended BRDF features used by current material
next_ext_mat_id = 3 # auto assigned materialID for extended BRDF

View File

@ -165,6 +165,10 @@ ALL_NODES: Dict[str, MaterialNodeMeta] = {
'TRANSPARENT_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftransparent), 'TRANSPARENT_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftransparent),
'BSDF_REFRACTION': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfrefraction), 'BSDF_REFRACTION': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfrefraction),
'REFRACTION_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfrefraction), 'REFRACTION_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfrefraction),
'BSDF_TOON': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftoon),
'TOON_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftoon),
'BSDF_HAIR': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfhair),
'HAIR_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfhair),
'EMISSION': MaterialNodeMeta(parse_func=nodes_shader.parse_emission), 'EMISSION': MaterialNodeMeta(parse_func=nodes_shader.parse_emission),
'HOLDOUT': MaterialNodeMeta( 'HOLDOUT': MaterialNodeMeta(
parse_func=nodes_shader.parse_holdout, parse_func=nodes_shader.parse_holdout,
@ -205,11 +209,14 @@ ALL_NODES: Dict[str, MaterialNodeMeta] = {
if bpy.app.version > (3, 2, 0): if bpy.app.version > (3, 2, 0):
ALL_NODES['SEPARATE_COLOR'] = MaterialNodeMeta(parse_func=nodes_converter.parse_separate_color) ALL_NODES['SEPARATE_COLOR'] = MaterialNodeMeta(parse_func=nodes_converter.parse_separate_color)
ALL_NODES['COMBINE_COLOR'] = MaterialNodeMeta(parse_func=nodes_converter.parse_combine_color) ALL_NODES['COMBINE_COLOR'] = MaterialNodeMeta(parse_func=nodes_converter.parse_combine_color)
if bpy.app.version < (4, 1, 0): if bpy.app.version < (4, 0, 0):
ALL_NODES['BSDF_VELVET'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfvelvet) ALL_NODES['BSDF_VELVET'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfvelvet)
if bpy.app.version < (4, 1, 0):
ALL_NODES['TEX_MUSGRAVE'] = MaterialNodeMeta(parse_func=nodes_texture.parse_tex_musgrave) ALL_NODES['TEX_MUSGRAVE'] = MaterialNodeMeta(parse_func=nodes_texture.parse_tex_musgrave)
if bpy.app.version >= (4, 0, 0): if bpy.app.version >= (4, 0, 0):
ALL_NODES['BSDF_SHEEN'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfsheen) ALL_NODES['BSDF_SHEEN'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfsheen)
if bpy.app.version >= (2, 83, 0):
ALL_NODES['BSDF_HAIR_PRINCIPLED'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfhairprincipled)
if bpy.app.version < (5, 0, 0): if bpy.app.version < (5, 0, 0):
ALL_NODES['TEX_POINTDENSITY'] = MaterialNodeMeta(parse_func=nodes_texture.parse_tex_pointdensity, compute_dxdy_variants=ComputeDXDYVariant.NEVER) ALL_NODES['TEX_POINTDENSITY'] = MaterialNodeMeta(parse_func=nodes_texture.parse_tex_pointdensity, compute_dxdy_variants=ComputeDXDYVariant.NEVER)

View File

@ -99,6 +99,25 @@ class ParserState:
self.out_opacity: floatstr = '1.0' self.out_opacity: floatstr = '1.0'
self.out_ior: floatstr = '1.450' self.out_ior: floatstr = '1.450'
self.out_emission_col: vec3str = 'vec3(0.0)' self.out_emission_col: vec3str = 'vec3(0.0)'
self.out_subsurface: floatstr = '0.0'
self.out_subsurface_radius: vec3str = 'vec3(0.0)'
self.out_subsurface_color: vec3str = 'vec3(0.8)'
self.out_specular_tint: vec3str = 'vec3(1.0)'
self.out_anisotropy: floatstr = '0.0'
self.out_aniso_rot: floatstr = '0.0'
self.out_sheen: floatstr = '0.0'
self.out_sheen_rough: floatstr = '0.5'
self.out_sheen_tint: vec3str = 'vec3(1.0)'
self.out_clearcoat: floatstr = '0.0'
self.out_clearcoat_rough: floatstr = '0.03'
self.out_transmission: floatstr = '0.0'
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): def reset_outs(self):
"""Reset the shader output values to their default values.""" """Reset the shader output values to their default values."""
@ -110,11 +129,40 @@ class ParserState:
self.out_opacity = '1.0' self.out_opacity = '1.0'
self.out_ior = '1.450' self.out_ior = '1.450'
self.out_emission_col = 'vec3(0.0)' self.out_emission_col = 'vec3(0.0)'
self.out_subsurface = '0.0'
self.out_subsurface_radius = 'vec3(0.0)'
self.out_subsurface_color = 'vec3(0.8)'
self.out_specular_tint = 'vec3(1.0)'
self.out_anisotropy = '0.0'
self.out_aniso_rot = '0.0'
self.out_sheen = '0.0'
self.out_sheen_rough = '0.5'
self.out_sheen_tint = 'vec3(1.0)'
self.out_clearcoat = '0.0'
self.out_clearcoat_rough = '0.03'
self.out_transmission = '0.0'
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[vec3str, floatstr, floatstr, floatstr, floatstr, floatstr, floatstr, vec3str]: def get_outs(self) -> Tuple:
"""Return the shader output values as a tuple.""" """Return the shader output values as a tuple."""
return (self.out_basecol, self.out_roughness, self.out_metallic, self.out_occlusion, self.out_specular, return (self.out_basecol, self.out_roughness, self.out_metallic,
self.out_opacity, self.out_ior, self.out_emission_col) self.out_occlusion, self.out_specular, self.out_opacity,
self.out_ior, self.out_emission_col,
self.out_subsurface, self.out_subsurface_radius,
self.out_subsurface_color, self.out_specular_tint,
self.out_anisotropy, self.out_aniso_rot,
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_subsurface_scale, self.out_subsurface_anisotropy,
self.out_coat_ior, self.out_coat_tint)
def get_parser_pass_suffix(self) -> str: def get_parser_pass_suffix(self) -> str:

View File

@ -11,12 +11,16 @@ import sys
import os import os
import array import array
import atexit import atexit
import time
from mathutils import Quaternion, Matrix, Vector from mathutils import Quaternion, Matrix, Vector
import lnx.make_state as state import lnx.make_state as state
HAS_GPU_STATE = bpy.app.version >= (3, 0, 0) HAS_GPU_STATE = bpy.app.version >= (3, 0, 0)
if not HAS_GPU_STATE: try:
import bgl import bgl
HAS_BGL = callable(getattr(bgl, 'glGenTextures', None))
except ImportError:
HAS_BGL = False
SHADER_UNIFORM_COLOR = 'UNIFORM_COLOR' if bpy.app.version >= (3, 4, 0) else '2D_UNIFORM_COLOR' SHADER_UNIFORM_COLOR = 'UNIFORM_COLOR' if bpy.app.version >= (3, 4, 0) else '2D_UNIFORM_COLOR'
SHADER_IMAGE = 'IMAGE' if bpy.app.version >= (3, 4, 0) else '2D_IMAGE' SHADER_IMAGE = 'IMAGE' if bpy.app.version >= (3, 4, 0) else '2D_IMAGE'
HAS_GPU_TEXTURE = bpy.app.version >= (3, 0, 0) HAS_GPU_TEXTURE = bpy.app.version >= (3, 0, 0)
@ -124,6 +128,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._last_frame_id = 0 self._last_frame_id = 0
self._width = 0 self._width = 0
self._height = 0 self._height = 0
self._tex_width = 0
self._tex_height = 0
self._initialized = False self._initialized = False
self._shader = None self._shader = None
self._batch = None self._batch = None
@ -140,6 +146,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._last_region_dims = None self._last_region_dims = None
self._header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)() self._header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
self._engine_id = id(self) self._engine_id = id(self)
self._viewport_image = None
self._float_buffer = None
# print(f"New engine instance created: {self._engine_id}") # print(f"New engine instance created: {self._engine_id}")
def _restore_overlay(self, context=None): def _restore_overlay(self, context=None):
@ -247,7 +255,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
def deferred_launch(): def deferred_launch():
try: try:
import lnx.make as make import lnx.make as make
make.play_viewport(viewport_id, width, height) make.play(viewport_id, width, height)
except Exception as e: except Exception as e:
print(f"Failed to launch viewport: {e}") print(f"Failed to launch viewport: {e}")
import traceback import traceback
@ -498,7 +506,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
"""Handle invalid/stale shared memory by cleaning up and allowing re-init.""" """Handle invalid/stale shared memory by cleaning up and allowing re-init."""
self._cleanup_shared_memory() self._cleanup_shared_memory()
self._initialized = False self._initialized = False
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0: if HAS_BGL and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
try: try:
bgl.glDeleteTextures(1, self._gl_texture_buf) bgl.glDeleteTextures(1, self._gl_texture_buf)
except: except:
@ -783,21 +791,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
if len(pixels) < num_pixels: if len(pixels) < num_pixels:
return return
if HAS_GPU_TEXTURE: if HAS_BGL:
if HAS_NUMPY: # Fast path: bgl with GL_UNSIGNED_BYTE, no float conversion, in-place update
pixels[3::4] = 255
float_array = pixels.astype(np.float32) / 255.0
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_array)
else:
pixel_array = array.array('B', pixels[:num_pixels])
for i in range(3, len(pixel_array), 4):
pixel_array[i] = 255
float_pixels = [p / 255.0 for p in pixel_array]
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_pixels)
self._texture = gpu.types.GPUTexture((width, height), format='SRGB8_A8', data=buffer)
else:
# bgl path for Blender < 3.0
if not hasattr(self, '_gl_texture_buf') or self._gl_texture_buf[0] == 0: if not hasattr(self, '_gl_texture_buf') or self._gl_texture_buf[0] == 0:
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1) self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
bgl.glGenTextures(1, self._gl_texture_buf) bgl.glGenTextures(1, self._gl_texture_buf)
@ -817,11 +812,42 @@ class KromViewportEngine(bpy.types.RenderEngine):
pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, bytes(pixel_array)) pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, bytes(pixel_array))
internal_format = getattr(bgl, 'GL_SRGB8_ALPHA8', bgl.GL_RGBA8) internal_format = getattr(bgl, 'GL_SRGB8_ALPHA8', bgl.GL_RGBA8)
bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, internal_format, width, height, 0, if self._tex_width == width and self._tex_height == height:
bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, pixel_buffer) bgl.glTexSubImage2D(bgl.GL_TEXTURE_2D, 0, 0, 0, width, height,
bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, pixel_buffer)
else:
bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, internal_format, width, height, 0,
bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, pixel_buffer)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0) bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
self._texture = tex_id self._texture = tex_id
elif HAS_GPU_TEXTURE:
# Blender 4.x+ path: use bpy.Image + gpu.texture.from_image
# from_image returns a cached GPUTexture (no 20ms constructor)
# pixels updated via foreach_set (fast C-level copy) + update()
if self._viewport_image is None or self._viewport_image.size[0] != width or self._viewport_image.size[1] != height:
if self._viewport_image is not None:
bpy.data.images.remove(self._viewport_image)
self._viewport_image = bpy.data.images.new(
"_lnx_viewport", width, height, float_buffer=False)
self._float_buffer = None
if HAS_NUMPY:
pixels[3::4] = 255
if self._float_buffer is None or len(self._float_buffer) != num_pixels:
self._float_buffer = np.empty(num_pixels, dtype=np.float32)
self._float_buffer[:num_pixels] = pixels[:num_pixels]
self._float_buffer *= (1.0 / 255.0)
self._viewport_image.pixels.foreach_set(self._float_buffer)
else:
pixel_array = array.array('B', pixels[:num_pixels])
for i in range(3, len(pixel_array), 4):
pixel_array[i] = 255
float_pixels = [p / 255.0 for p in pixel_array]
self._viewport_image.pixels.foreach_set(float_pixels)
self._viewport_image.update()
self._texture = gpu.texture.from_image(self._viewport_image)
self._tex_width = width self._tex_width = width
self._tex_height = height self._tex_height = height
@ -838,11 +864,17 @@ class KromViewportEngine(bpy.types.RenderEngine):
"""Called by Blender when the engine instance is destroyed.""" """Called by Blender when the engine instance is destroyed."""
self._stop_krom_process() self._stop_krom_process()
self._restore_overlay() self._restore_overlay()
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0: if HAS_BGL and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
try: try:
bgl.glDeleteTextures(1, self._gl_texture_buf) bgl.glDeleteTextures(1, self._gl_texture_buf)
except: except:
pass pass
if self._viewport_image is not None:
try:
bpy.data.images.remove(self._viewport_image)
except:
pass
self._viewport_image = None
if self._viewport_id and self._viewport_id in _active_krom_engines: if self._viewport_id and self._viewport_id in _active_krom_engines:
del _active_krom_engines[self._viewport_id] del _active_krom_engines[self._viewport_id]
@ -928,6 +960,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._last_region_dims = dims self._last_region_dims = dims
self._request_resize(region.width, region.height) self._request_resize(region.width, region.height)
self._write_camera_matrices(context)
self._read_krom_camera(context) self._read_krom_camera(context)
pixels = self._read_framebuffer() pixels = self._read_framebuffer()
@ -966,7 +999,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._batch_dims = dims self._batch_dims = dims
self._shader.bind() self._shader.bind()
if HAS_GPU_TEXTURE: _is_gl_texture = isinstance(self._texture, int)
if not _is_gl_texture:
self._shader.uniform_sampler("image", self._texture) self._shader.uniform_sampler("image", self._texture)
else: else:
bgl.glActiveTexture(bgl.GL_TEXTURE0) bgl.glActiveTexture(bgl.GL_TEXTURE0)
@ -975,7 +1009,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._batch.draw(self._shader) self._batch.draw(self._shader)
if not HAS_GPU_TEXTURE: if _is_gl_texture:
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0) bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
if HAS_GPU_STATE: if HAS_GPU_STATE:

View File

@ -875,7 +875,7 @@ def check_blender_version(op: bpy.types.Operator):
"""Check whether the Blender version is supported by Leenkx, """Check whether the Blender version is supported by Leenkx,
if not, report in UI. if not, report in UI.
""" """
if bpy.app.version[:2] not in [(4, 5), (4, 4), (4, 2), (3, 6), (3, 3)]: if bpy.app.version[:2] not in [(5, 2), (4, 5), (4, 4), (4, 2), (3, 6), (3, 3)]:
op.report({'INFO'}, 'INFO: For Leenkx to work correctly, use a Blender LTS version') op.report({'INFO'}, 'INFO: For Leenkx to work correctly, use a Blender LTS version')

View File

@ -835,6 +835,18 @@ def write_compiledglsl(defs, make_variants):
if '_SSRefraction' in wrd.world_defs or '_VoxelRefract' in wrd.world_defs: if '_SSRefraction' in wrd.world_defs or '_VoxelRefract' in wrd.world_defs:
f.write(f'#define GBUF_IDX_REFRACTION {idx_refraction}\n') 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) f.write("""#if defined(HLSL) || defined(METAL)
#define _InvY #define _InvY
#endif #endif