Compare commits

20 Commits

Author SHA1 Message Date
058cbed20f Fix background color intensity for 'Filmic2' tonemap 2026-09-21 19:44:07 -04:00
7d609b126d Merge pull request 'main' (#144) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#144
2026-09-20 18:30:58 +00:00
36ecaea919 Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-09-20 11:15:57 -07:00
fac01a2849 Group Nodes, Shaders, Textures, Displacement 2026-09-20 11:15:14 -07:00
c71c1a98e4 Merge pull request 'MRT Index' (#143) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#143
2026-09-17 23:43:07 +00:00
f6d5f142e9 MRT Index 2026-09-17 16:38:36 -07:00
70961b0336 Merge pull request 'Fix thinWall and copy_pass in Forward rendering' (#141) from Moises/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#141
2026-09-17 21:16:33 +00:00
9c50274f29 Merge remote-tracking branch 'upstream/main' 2026-09-17 17:15:55 -04:00
a94b95afc8 Merge pull request 'main' (#142) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#142
2026-09-17 21:09:32 +00:00
fca51c7e61 Water Level/Reflectopm 2026-09-16 22:44:16 -07:00
0bb4bceb57 Water Foam Fix 2026-09-16 18:12:12 -07:00
af9a259fbb Water Sun Direction 2026-09-16 17:36:31 -07:00
f1d8522f51 Bloom Isnan Isinf 2026-09-16 16:04:32 -07:00
56d1b0e4a7 Forward / Mobile / Solid 2026-09-16 12:40:39 -07:00
73e4a0ad30 Scene Gamma / Exposure 2026-09-13 23:34:36 -07:00
67d5e6b2c7 SSRS Fix 2026-09-13 21:52:13 -07:00
635f8f6645 SSRS WebGL Fix 2026-09-13 20:18:00 -07:00
b2444be7e2 Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-09-13 20:16:57 -07:00
b4de377433 Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-09-13 15:23:25 -07:00
d787f9a7fc Update Theme 2026-09-13 12:10:51 -07:00
30 changed files with 879 additions and 361 deletions

View File

@ -48,6 +48,10 @@ void main() {
} }
if (currentMipLevel == 0) { if (currentMipLevel == 0) {
if (any(isnan(fragColor.rgb)) || any(isinf(fragColor.rgb))) {
fragColor.rgb = vec3(0.0);
}
// https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.2 // https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.2
// https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4 // https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4

View File

@ -69,6 +69,7 @@ uniform vec4 PPComp15;
uniform vec4 PPComp16; uniform vec4 PPComp16;
uniform vec4 PPComp18; uniform vec4 PPComp18;
uniform vec4 PPComp19; uniform vec4 PPComp19;
uniform vec4 PPComp20;
#endif #endif
// #ifdef _CPos // #ifdef _CPos
@ -499,14 +500,15 @@ void main() {
#ifdef _CExposure #ifdef _CExposure
#ifdef _CPostprocess #ifdef _CPostprocess
fragColor.rgb+=fragColor.rgb*PPComp8.x; fragColor.rgb *= pow(2.0, PPComp8.x);
#else #else
fragColor.rgb+= fragColor.rgb*compoExposureStrength; fragColor.rgb *= pow(2.0, compoExposureStrength);
#endif #endif
#endif #endif
#ifdef _CPostprocess #ifdef _CPostprocess
fragColor.rgb *= ComputeEV(0.0); fragColor.rgb *= ComputeEV(0.0);
fragColor.rgb *= pow(2.0, PPComp20.x); // exposure and gamma
#endif #endif
#ifdef _AutoExposure #ifdef _AutoExposure
@ -582,14 +584,16 @@ fragColor.rgb = min(fragColor.rgb, 65504 * 0.5);
fragColor.rgb = tonemapAgXFull(fragColor.rgb); fragColor.rgb = tonemapAgXFull(fragColor.rgb);
} //else { fragColor.rgb = vec3(0,1,0); //ERROR} } //else { fragColor.rgb = vec3(0,1,0); //ERROR}
#endif #endif
#ifdef _CGamma
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / PPComp20.y));
#endif
#else #else
#ifdef _CToneFilmic #ifdef _CToneFilmic
fragColor.rgb = tonemapFilmic(fragColor.rgb); // With gamma fragColor.rgb = tonemapFilmic(fragColor.rgb); // With gamma
#endif #endif
#ifdef _CToneFilmic2 #ifdef _CToneFilmic2
fragColor.rgb = acesFilm(fragColor.rgb); fragColor.rgb = acesFilm(fragColor.rgb);
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / 1.0)); fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / 2.2));
#endif #endif
#ifdef _CToneReinhard #ifdef _CToneReinhard
fragColor.rgb = tonemapReinhard(fragColor.rgb); fragColor.rgb = tonemapReinhard(fragColor.rgb);
@ -615,6 +619,10 @@ fragColor.rgb = min(fragColor.rgb, 65504 * 0.5);
#endif #endif
#endif #endif
#ifdef _CGamma
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / compoGammaStrength));
#endif
#ifdef _CBW #ifdef _CBW
// fragColor.rgb = vec3(clamp(dot(fragColor.rgb, fragColor.rgb), 0.0, 1.0)); // fragColor.rgb = vec3(clamp(dot(fragColor.rgb, fragColor.rgb), 0.0, 1.0));
fragColor.rgb = vec3((fragColor.r * 0.3 + fragColor.g * 0.59 + fragColor.b * 0.11) / 3.0) * 2.5; fragColor.rgb = vec3((fragColor.r * 0.3 + fragColor.g * 0.59 + fragColor.b * 0.11) / 3.0) * 2.5;

View File

@ -907,26 +907,10 @@ tileBounds = vec4(0.0, 0.0, 1.0, 1.0);
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[li], p, n, cLightDir, lightPlane.y, shadowMapAtlas, sssColorCL, sssRadiusCL, tileBoundsSpotArray[li]); fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[li], p, n, cLightDir, lightPlane.y, shadowMapAtlas, sssColorCL, sssRadiusCL, tileBoundsSpotArray[li]);
#endif #endif
#else #else
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[0], p, n, cLightDir, lightPlane.y, shadowMapSpot[0], sssColorCL, sssRadiusCL if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[0], p, n, cLightDir, lightPlane.y, shadowMapSpot[0], sssColorCL, sssRadiusCL);
#ifdef _ShadowMapAtlas else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[1], p, n, cLightDir, lightPlane.y, shadowMapSpot[1], sssColorCL, sssRadiusCL);
, vec4(0.0, 0.0, 1.0, 1.0) else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[2], p, n, cLightDir, lightPlane.y, shadowMapSpot[2], sssColorCL, sssRadiusCL);
#endif else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[3], p, n, cLightDir, lightPlane.y, shadowMapSpot[3], sssColorCL, sssRadiusCL);
);
else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[1], p, n, cLightDir, lightPlane.y, shadowMapSpot[1], sssColorCL, sssRadiusCL
#ifdef _ShadowMapAtlas
, vec4(0.0, 0.0, 1.0, 1.0)
#endif
);
else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[2], p, n, cLightDir, lightPlane.y, shadowMapSpot[2], sssColorCL, sssRadiusCL
#ifdef _ShadowMapAtlas
, vec4(0.0, 0.0, 1.0, 1.0)
#endif
);
else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[3], p, n, cLightDir, lightPlane.y, shadowMapSpot[3], sssColorCL, sssRadiusCL
#ifdef _ShadowMapAtlas
, vec4(0.0, 0.0, 1.0, 1.0)
#endif
);
#endif #endif
} else { } else {
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas

View File

@ -56,9 +56,15 @@ uniform vec2 cameraPlane;
#ifdef _SinglePoint #ifdef _SinglePoint
#ifdef _Spot #ifdef _Spot
//!uniform sampler2DShadow shadowMapSpot[1]; //!uniform sampler2DShadow shadowMapSpot[1];
#ifdef _ShadowMapTransparent
//!uniform sampler2D shadowMapSpotTransparent[1];
#endif
//!uniform mat4 LWVPSpotArray[1]; //!uniform mat4 LWVPSpotArray[1];
#else #else
//!uniform samplerCubeShadow shadowMapPoint[1]; //!uniform samplerCubeShadow shadowMapPoint[1];
#ifdef _ShadowMapTransparent
//!uniform samplerCube shadowMapPointTransparent[1];
#endif
//!uniform vec2 lightProj; //!uniform vec2 lightProj;
#endif #endif
#endif #endif
@ -66,26 +72,41 @@ uniform vec2 cameraPlane;
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
#ifdef _SingleAtlas #ifdef _SingleAtlas
uniform sampler2DShadow shadowMapAtlas; uniform sampler2DShadow shadowMapAtlas;
#ifdef _ShadowMapTransparent
uniform sampler2D shadowMapAtlasTransparent;
#endif
#endif #endif
#endif #endif
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
#ifndef _SingleAtlas #ifndef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlasPoint; //!uniform sampler2DShadow shadowMapAtlasPoint;
#ifdef _ShadowMapTransparent
//!uniform sampler2D shadowMapAtlasPointTransparent;
#endif #endif
//!uniform vec4 pointLightDataArray[4]; #endif
//!uniform vec4 pointLightDataArray[maxLightsCluster * 6];
#else #else
//!uniform samplerCubeShadow shadowMapPoint[4]; //!uniform samplerCubeShadow shadowMapPoint[4];
#ifdef _ShadowMapTransparent
//!uniform samplerCube shadowMapPointTransparent[4];
#endif
#endif #endif
//!uniform vec2 lightProj; //!uniform vec2 lightProj;
#ifdef _Spot #ifdef _Spot
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
#ifndef _SingleAtlas #ifndef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlasSpot; //!uniform sampler2DShadow shadowMapAtlasSpot;
#ifdef _ShadowMapTransparent
//!uniform sampler2D shadowMapAtlasSpotTransparent;
#endif
#endif #endif
#else #else
//!uniform sampler2DShadow shadowMapSpot[4]; //!uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
#ifdef _ShadowMapTransparent
//!uniform sampler2D shadowMapSpotTransparent[maxLightsCluster];
#endif #endif
//!uniform mat4 LWVPSpotArray[4]; #endif
//!uniform mat4 LWVPSpotArray[maxLightsCluster];
#endif #endif
#endif #endif
#endif #endif
@ -97,10 +118,16 @@ uniform vec3 sunCol;
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
#ifndef _SingleAtlas #ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSun; uniform sampler2DShadow shadowMapAtlasSun;
#ifdef _ShadowMapTransparent
uniform sampler2D shadowMapAtlasSunTransparent;
#endif
#endif #endif
//!uniform vec4 tileBoundsSunArray[maxLights * shadowmapCascades]; //!uniform vec4 tileBoundsSunArray[maxLights * shadowmapCascades];
#else #else
uniform sampler2DShadow shadowMap; uniform sampler2DShadow shadowMap;
#ifdef _ShadowMapTransparent
uniform sampler2D shadowMapTransparent;
#endif
#endif #endif
uniform float shadowsBias; uniform float shadowsBias;
#ifdef _CSM #ifdef _CSM
@ -167,10 +194,11 @@ void main() {
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);
vec3 albedo = surfaceAlbedo(g1.rgb, metallic); // g1.rgb - basecolor vec3 basecolor = min(g1.rgb, vec3(2.0));
vec3 f0 = surfaceF0(g1.rgb, metallic); vec3 albedo = surfaceAlbedo(basecolor, metallic);
vec3 f0 = surfaceF0(basecolor, metallic);
#ifdef _ExtBRDF #ifdef _ExtBRDF
f0 = mix(f0, min(g1.rgb, vec3(2.0)), vec3(matp6.y, matp6.z, matp6.w)); f0 = mix(f0, basecolor, vec3(matp6.y, matp6.z, matp6.w));
#endif #endif
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0; float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
@ -206,7 +234,7 @@ void main() {
envl /= PI; envl /= PI;
#endif #endif
#else #else
vec3 envl = vec3(1.0); vec3 envl = vec3(0.0);
#endif #endif
#ifdef _Rad #ifdef _Rad
@ -227,13 +255,23 @@ void main() {
#endif #endif
#endif #endif
#ifdef _Brdf
vec3 F = f0 * envBRDF.x + envBRDF.y;
#else
vec3 F = f0;
#endif
envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV); envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV);
#ifdef _Brdf
envl.rgb *= 1.0 - F;
#endif
#ifdef _Rad // Indirect specular #ifdef _Rad // Indirect specular
envl.rgb += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5 * occspec.y; envl.rgb += prefilteredColor * F;
#else #else
#ifdef _EnvCol #ifdef _EnvCol
envl.rgb += backgroundCol * surfaceF0(g1.rgb, metallic); // f0 envl.rgb += backgroundCol * F;
#endif #endif
#endif #endif
@ -281,7 +319,12 @@ void main() {
float transmittance = 1.0 - transF; float transmittance = 1.0 - transF;
#ifdef _Rad #ifdef _Rad
if (matp2.z > 0.0 && transmittance > 0.0) { if (matp2.z > 0.0 && transmittance > 0.0) {
vec3 refrDir = transmissionIBLDirection(n, v, matp3.x); vec3 refrDir;
if (matp3.y > 0.5) {
refrDir = reflect(-v, n);
} else {
refrDir = transmissionIBLDirection(n, v, matp3.x);
}
float transLod = getMipFromRoughness(matp2.w, envmapNumMipmaps); float transLod = getMipFromRoughness(matp2.w, envmapNumMipmaps);
vec3 transColor = textureLod(senvmapRadiance, vec3 transColor = textureLod(senvmapRadiance,
envMapEquirect(refrDir), transLod).rgb; envMapEquirect(refrDir), transLod).rgb;
@ -337,7 +380,7 @@ void main() {
float sdotNH = max(0.0, dot(n, sh)); float sdotNH = max(0.0, dot(n, sh));
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; vec3 svisibility = vec3(1.0);
#ifdef _Anisotropy #ifdef _Anisotropy
vec3 sdirect; vec3 sdirect;
if (abs(matp0.x) > 0.001 && dot(wTangent, wTangent) > 0.001) { if (abs(matp0.x) > 0.001 && dot(wTangent, wTangent) > 0.001) {
@ -385,29 +428,59 @@ void main() {
#ifdef _CSM #ifdef _CSM
svisibility = shadowTestCascade( svisibility = shadowTestCascade(
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
#ifdef _ShadowMapTransparent
#ifndef _SingleAtlas
shadowMapAtlasSun, shadowMapAtlasSunTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
#else
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSun shadowMapAtlasSun
#else #else
shadowMapAtlas shadowMapAtlas
#endif #endif
#endif
#else
#ifdef _ShadowMapTransparent
shadowMap, shadowMapTransparent
#else #else
shadowMap shadowMap
#endif #endif
, eye, p + n * shadowsBias * 10, shadowsBias #endif
, eye, p + n * shadowsBias * 2, shadowsBias
#ifdef _ShadowMapTransparent
, false
#endif
); );
#else #else
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0); vec4 lPos = LWVP * vec4(p + n * shadowsBias * 2, 1.0);
if (lPos.w > 0.0) svisibility = shadowTest( if (lPos.w > 0.0) svisibility = shadowTest(
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
#ifdef _ShadowMapTransparent
#ifndef _SingleAtlas
shadowMapAtlasSun, shadowMapAtlasSunTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
#else
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSun shadowMapAtlasSun
#else #else
shadowMapAtlas shadowMapAtlas
#endif #endif
#endif
#else
#ifdef _ShadowMapTransparent
shadowMap, shadowMapTransparent
#else #else
shadowMap shadowMap
#endif #endif
#endif
, lPos.xyz / lPos.w, shadowsBias , lPos.xyz / lPos.w, shadowsBias
#ifdef _ShadowMapTransparent
, false
#endif
); );
#endif #endif
#endif #endif
@ -415,11 +488,18 @@ void main() {
fragColor.rgb += sdirect * svisibility * sunCol; fragColor.rgb += sdirect * svisibility * sunCol;
#endif #endif
#ifdef _ShadowMapAtlas
tileBounds = vec4(0.0, 0.0, 1.0, 1.0);
#endif
#ifdef _SinglePoint #ifdef _SinglePoint
fragColor.rgb += sampleLight( fragColor.rgb += sampleLight(
p, n, v, dotNV, pointPos, pointCol, albedo, roughness, occspec.y, f0 p, n, v, dotNV, pointPos, pointCol, albedo, roughness, occspec.y, f0
#ifdef _ShadowMap #ifdef _ShadowMap
, 0, pointBias, true , 0, pointBias, true
#ifdef _ShadowMapTransparent
, false
#endif
#endif #endif
#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!
@ -472,12 +552,15 @@ void main() {
#ifdef _ShadowMap #ifdef _ShadowMap
// light index, shadow bias, cast_shadows // light index, shadow bias, cast_shadows
, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0 , li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0
#ifdef _ShadowMapTransparent
, false
#endif
#endif #endif
#ifdef _Spot #ifdef _Spot
, lightsArray[li * 3 + 2].y != 0.0 , lightsArray[li * 3 + 2].y != 0.0
, lightsArray[li * 3 + 2].y // spot size (cutoff) , lightsArray[li * 3 + 2].y // spot size (cutoff)
, lightsArraySpot[li].w // spot blend (exponent) , lightsArraySpot[li * 2].w // spot blend (exponent)
, lightsArraySpot[li].xyz // spotDir , lightsArraySpot[li * 2].xyz // spotDir
, 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

View File

@ -122,11 +122,6 @@
"link": "_inverseViewProjectionMatrixRight", "link": "_inverseViewProjectionMatrixRight",
"ifdef": ["_VRStereo"] "ifdef": ["_VRStereo"]
}, },
{
"name": "invVP",
"link": "_viewProjectionMatrix",
"ifdef": ["_SSRS"]
},
{ {
"name": "smSizeUniform", "name": "smSizeUniform",
"link": "_shadowMapSize", "link": "_shadowMapSize",
@ -137,14 +132,6 @@
"link": "_lightPlane", "link": "_lightPlane",
"ifdef": ["_SSS"] "ifdef": ["_SSS"]
}, },
{
"name": "VP",
"link": "_viewProjectionMatrix",
"ifdef": ["_SSRS"]
},
{
"ifdef": ["_SMSizeUniform"]
},
{ {
"name": "lightProj", "name": "lightProj",
"link": "_lightPlaneProj", "link": "_lightPlaneProj",

View File

@ -14,15 +14,24 @@
#ifdef _SinglePoint #ifdef _SinglePoint
#ifdef _Spot #ifdef _Spot
uniform sampler2DShadow shadowMapSpot[1]; uniform sampler2DShadow shadowMapSpot[1];
#ifdef _ShadowMapTransparent
uniform sampler2D shadowMapSpotTransparent[1];
#endif
uniform mat4 LWVPSpotArray[1]; uniform mat4 LWVPSpotArray[1];
#else #else
uniform samplerCubeShadow shadowMapPoint[1]; uniform samplerCubeShadow shadowMapPoint[1];
#ifdef _ShadowMapTransparent
uniform samplerCube shadowMapPointTransparent[1];
#endif
uniform vec2 lightProj; uniform vec2 lightProj;
#endif #endif
#endif #endif
#ifdef _Clusters #ifdef _Clusters
#ifdef _SingleAtlas #ifdef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlas; //!uniform sampler2DShadow shadowMapAtlas;
#ifdef _ShadowMapTransparent
//!uniform sampler2D shadowMapAtlasTransparent;
#endif
#endif #endif
#ifndef _SinglePoint #ifndef _SinglePoint
uniform vec2 lightProj; uniform vec2 lightProj;
@ -30,17 +39,29 @@
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
#ifndef _SingleAtlas #ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasPoint; uniform sampler2DShadow shadowMapAtlasPoint;
#ifdef _ShadowMapTransparent
uniform sampler2D shadowMapAtlasPointTransparent;
#endif
#endif #endif
#else #else
uniform samplerCubeShadow shadowMapPoint[4]; uniform samplerCubeShadow shadowMapPoint[4];
#ifdef _ShadowMapTransparent
uniform samplerCube shadowMapPointTransparent[4];
#endif
#endif #endif
#ifdef _Spot #ifdef _Spot
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
#ifndef _SingleAtlas #ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSpot; uniform sampler2DShadow shadowMapAtlasSpot;
#ifdef _ShadowMapTransparent
uniform sampler2D shadowMapAtlasSpotTransparent;
#endif
#endif #endif
#else #else
uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
#ifdef _ShadowMapTransparent
uniform sampler2D shadowMapSpotTransparent[maxLightsCluster];
#endif
#endif #endif
uniform mat4 LWVPSpotArray[maxLightsCluster]; uniform mat4 LWVPSpotArray[maxLightsCluster];
#endif #endif
@ -51,6 +72,9 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
const vec3 albedo, const float rough, const float spec, const vec3 f0 const vec3 albedo, const float rough, const float spec, const vec3 f0
#ifdef _ShadowMap #ifdef _ShadowMap
, int index, float bias, bool receiveShadow , int index, float bias, bool receiveShadow
#ifdef _ShadowMapTransparent
, bool transparent
#endif
#endif #endif
#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
@ -122,25 +146,76 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
if (receiveShadow) { if (receiveShadow) {
#ifdef _SinglePoint #ifdef _SinglePoint
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0); vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif #endif
#ifdef _Clusters #ifdef _Clusters
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
tileBounds = tileBoundsSpotArray[index]; tileBounds = tileBoundsSpotArray[index];
direct *= shadowTest( direct *= shadowTest(
#ifdef _ShadowMapTransparent
#ifndef _SingleAtlas
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
#else
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSpot shadowMapAtlasSpot
#else #else
shadowMapAtlas shadowMapAtlas
#endif #endif
#endif
, lPos.xyz / lPos.w, bias , lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
); );
#else #else
if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); if (index == 0) direct *= shadowTest(shadowMapSpot[0],
else if (index == 1) direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias); #ifdef _ShadowMapTransparent
else if (index == 2) direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias); shadowMapSpotTransparent[0],
else if (index == 3) direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias); #endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[1],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[2],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[3],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif #endif
#endif #endif
} }
@ -154,24 +229,75 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
if (receiveShadow) { if (receiveShadow) {
#ifdef _SinglePoint #ifdef _SinglePoint
#ifndef _Spot #ifndef _Spot
direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); direct *= PCFCube(shadowMapPoint[0],
#ifdef _ShadowMapTransparent
shadowMapPointTransparent[0],
#endif
ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif #endif
#endif #endif
#ifdef _Clusters #ifdef _Clusters
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
direct *= PCFFakeCube( direct *= PCFFakeCube(
#ifdef _ShadowMapTransparent
#ifndef _SingleAtlas
shadowMapAtlasPoint, shadowMapAtlasPointTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
#else
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasPoint shadowMapAtlasPoint
#else #else
shadowMapAtlas shadowMapAtlas
#endif #endif
#endif
, ld, -l, bias, lightProj, n, index , ld, -l, bias, lightProj, n, index
#ifdef _ShadowMapTransparent
, transparent
#endif
); );
#else #else
if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); if (index == 0) direct *= PCFCube(shadowMapPoint[0],
else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n); #ifdef _ShadowMapTransparent
else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n); shadowMapPointTransparent[0],
else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n); #endif
ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 1) direct *= PCFCube(shadowMapPoint[1],
#ifdef _ShadowMapTransparent
shadowMapPointTransparent[1],
#endif
ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 2) direct *= PCFCube(shadowMapPoint[2],
#ifdef _ShadowMapTransparent
shadowMapPointTransparent[2],
#endif
ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 3) direct *= PCFCube(shadowMapPoint[3],
#ifdef _ShadowMapTransparent
shadowMapPointTransparent[3],
#endif
ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif #endif
#endif #endif
} }
@ -186,6 +312,9 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
const vec3 albedo, const float rough, const float spec, const vec3 f0 const vec3 albedo, const float rough, const float spec, const vec3 f0
#ifdef _ShadowMap #ifdef _ShadowMap
, int index, float bias, bool receiveShadow , int index, float bias, bool receiveShadow
#ifdef _ShadowMapTransparent
, bool transparent
#endif
#endif #endif
#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
@ -194,6 +323,9 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
return sampleLight(p, n, v, dotNV, lp, lightCol, albedo, rough, spec, f0 return sampleLight(p, n, v, dotNV, lp, lightCol, albedo, rough, spec, f0
#ifdef _ShadowMap #ifdef _ShadowMap
, index, bias, receiveShadow , index, bias, receiveShadow
#ifdef _ShadowMapTransparent
, transparent
#endif
#endif #endif
#ifdef _Spot #ifdef _Spot
, isSpot, spotSize, spotBlend, spotDir, scale, right , isSpot, spotSize, spotBlend, spotDir, scale, right

View File

@ -151,6 +151,7 @@ void main() {
} }
// Displace surface // Displace surface
float geomZ = p.z; // undisplaced depth for foam/fog tests
p.z += (sin(p.x * 10.0 / waterDisplace + speed) * cos(p.y * 10.0 / waterDisplace + speed) p.z += (sin(p.x * 10.0 / waterDisplace + speed) * cos(p.y * 10.0 / waterDisplace + speed)
+ sin(p.x * 20.0 / waterDisplace + speed * 1.3) * cos(p.y * 20.0 / waterDisplace + speed * 1.3) * 0.5) + sin(p.x * 20.0 / waterDisplace + speed * 1.3) * cos(p.y * 20.0 / waterDisplace + speed * 1.3) * 0.5)
/ 50.0 * waterDisplace; / 50.0 * waterDisplace;
@ -227,15 +228,19 @@ void main() {
// Blinn-Phong specular using half-vector, faded at horizon // Blinn-Phong specular using half-vector, faded at horizon
vec3 h = normalize(v + ld); vec3 h = normalize(v + ld);
float specAmount = pow(max(dot(n2, h), 0.0), 200.0) * (200.0 + 8.0) / (PI * 8.0); float specAmount = pow(max(dot(n2, h), 0.0), 200.0) * (200.0 + 8.0) / (PI * 8.0);
fragColor.rgb += specAmount * (isSky ? 0.3 : 1.0) * horizonFactor; fragColor.rgb += specAmount * (isSky ? 0.3 : 1.0);
// Depth fog - blend toward waterColor with depth, faded at horizon // Depth fog - blend toward waterColor with depth, faded at horizon
float depthFog = clamp(-(p.z - waterLevel) * waterDensity, 0.0, 0.9); float depthFog = clamp(-(geomZ - waterLevel) * waterDensity, 0.0, 0.9);
fragColor.rgb = mix(fragColor.rgb, waterColor, depthFog * horizonFactor); fragColor.rgb = mix(fragColor.rgb, waterColor, depthFog * horizonFactor);
// Alpha fades smoothly at horizon instead of hard cut // Alpha fades smoothly at horizon instead of hard cut
fragColor.a = isSky ? horizonFactor : clamp(abs(p.z - waterLevel) * 5.0, 0.0, 1.0); // Skydome sits 3.5 below the camera (_skydomeMatrix), so its horizon
// appears where the ray z reaches -3.5 / domeRadius
float farPlane = cameraProj.y / (1.0 - cameraProj.x);
float horizonFade = clamp((-3.5 / (farPlane * 0.95) - vray.z) * 20.0, 0.0, 1.0);
fragColor.a = isSky ? horizonFade : clamp(abs(geomZ - waterLevel) * 5.0, 0.0, 1.0);
// Foam - based on actual geometry depth below water surface // Foam - based on actual geometry depth below water surface
float fd = isSky ? 1.0 : abs(p.z - waterLevel); float fd = isSky ? 1.0 : abs(geomZ - waterLevel);
if (fd < 0.1) { if (fd < 0.1) {
// Based on foam by Owen Deery // Based on foam by Owen Deery
// http://fire-face.com/personal/water // http://fire-face.com/personal/water

View File

@ -34,7 +34,7 @@
}, },
{ {
"name": "ld", "name": "ld",
"link": "_lightDirection" "link": "_sunDirection"
}, },
{ {
"name": "invVP", "name": "invVP",

View File

@ -90,8 +90,16 @@ class LightObject extends Object {
#if lnx_spot #if lnx_spot
if (data.raw.type == "spot"){ if (data.raw.type == "spot"){
this.size = data.raw.spot_size; this.size = 0.0;
this.blend = data.raw.spot_blend; this.blend = 0.0;
if (data.raw.spot_size != null) {
var s: kha.FastFloat = data.raw.spot_size;
this.size = s;
}
if (data.raw.spot_blend != null) {
var b: kha.FastFloat = data.raw.spot_blend;
this.blend = b;
}
} }
#end #end

View File

@ -669,8 +669,10 @@ class Inc {
#end #end
#if rp_ssrs #if rp_ssrs
#if kha_opengl
path.bindTarget("_main", "gbufferD"); path.bindTarget("_main", "gbufferD");
#end #end
#end
path.drawMeshes("translucent"); path.drawMeshes("translucent");
@ -1314,7 +1316,11 @@ class ShadowMapAtlas {
function new(light: LightObject, transparent: Bool) { function new(light: LightObject, transparent: Bool) {
#if lnx_shadowmap_atlas_single_map
var maxTileSize = Std.int(Math.max(Inc.getCubeSize(), Inc.getCascadeSize()));
#else
var maxTileSize = shadowMapAtlasSize(light); var maxTileSize = shadowMapAtlasSize(light);
#end
this.target = shadowMapAtlasName(light.data.raw.type, transparent); this.target = shadowMapAtlasName(light.data.raw.type, transparent);
this.sizew = this.sizeh = this.baseTileSizeConst = maxTileSize; this.sizew = this.sizeh = this.baseTileSizeConst = maxTileSize;
this.depth = getSubdivisions(); this.depth = getSubdivisions();
@ -1418,7 +1424,7 @@ class ShadowMapAtlas {
public static inline function shadowMapAtlasName(type: String, transparent: Bool): String { public static inline function shadowMapAtlasName(type: String, transparent: Bool): String {
#if lnx_shadowmap_atlas_single_map #if lnx_shadowmap_atlas_single_map
return "shadowMapAtlas"; return transparent ? "shadowMapAtlasTransparent" : "shadowMapAtlas";
#else #else
switch (type) { switch (type) {
case "point": case "point":

View File

@ -116,6 +116,11 @@ class Postprocess {
1 //0: Exposure 1 //0: Exposure
]; ];
public static var scene_color_uniforms = [
0.0, //0: Scene Exposure
1.0 //1: Scene Gamma
];
public static var auto_exposure_uniforms = [ public static var auto_exposure_uniforms = [
1, //0: Auto Exposure Strength 1, //0: Auto Exposure Strength
1 //1: Auto Exposure Speed 1 //1: Auto Exposure Speed
@ -406,6 +411,10 @@ class Postprocess {
v.x = auto_focus[0]; v.x = auto_focus[0];
v.y = auto_focus[1]; v.y = auto_focus[1];
v.z = auto_focus[2]; v.z = auto_focus[2];
case "_PPComp20":
v = iron.object.Uniforms.helpVec;
v.x = scene_color_uniforms[0]; // Scene Exposure
v.y = scene_color_uniforms[1]; // Scene Gamma
} }
return v; return v;

View File

@ -560,6 +560,7 @@ class RenderPathDeferred {
@:access(iron.RenderPath) @:access(iron.RenderPath)
public static function commands() { public static function commands() {
path.setTarget("gbuffer0"); // Only clear gbuffer0 path.setTarget("gbuffer0"); // Only clear gbuffer0
//path.setTarget("gbuffer0", ["gbuffer1"]);
#if (rp_background == "Clear") #if (rp_background == "Clear")
{ {
path.clearTarget(-1, 1.0); path.clearTarget(-1, 1.0);
@ -649,7 +650,11 @@ class RenderPathDeferred {
{ {
if (leenkx.data.Config.raw.rp_ssao != false) { if (leenkx.data.Config.raw.rp_ssao != false) {
path.setTarget("singlea"); path.setTarget("singlea");
#if rp_ssao_half
path.bindTarget("half", "gbufferD");
#else
path.bindTarget("_main", "gbufferD"); path.bindTarget("_main", "gbufferD");
#end
path.bindTarget("gbuffer0", "gbuffer0"); path.bindTarget("gbuffer0", "gbuffer0");
path.drawShader("shader_datas/ssao_pass/ssao_pass"); path.drawShader("shader_datas/ssao_pass/ssao_pass");
@ -670,7 +675,11 @@ class RenderPathDeferred {
{ {
if (leenkx.data.Config.raw.rp_ssgi != false) { if (leenkx.data.Config.raw.rp_ssgi != false) {
path.setTarget("ssgi_a"); path.setTarget("ssgi_a");
#if rp_ssgi_half
path.bindTarget("half", "gbufferD");
#else
path.bindTarget("_main", "gbufferD"); path.bindTarget("_main", "gbufferD");
#end
path.bindTarget("gbuffer0", "gbuffer0"); path.bindTarget("gbuffer0", "gbuffer0");
path.bindTarget("gbuffer1", "gbuffer1"); path.bindTarget("gbuffer1", "gbuffer1");
#if rp_gbuffer_emission #if rp_gbuffer_emission
@ -684,13 +693,21 @@ class RenderPathDeferred {
path.setTarget("ssgi_b"); path.setTarget("ssgi_b");
path.bindTarget("ssgi_a", "tex"); path.bindTarget("ssgi_a", "tex");
path.bindTarget("gbuffer0", "gbuffer0"); path.bindTarget("gbuffer0", "gbuffer0");
#if rp_ssgi_half
path.bindTarget("half", "gbufferD");
#else
path.bindTarget("_main", "gbufferD"); path.bindTarget("_main", "gbufferD");
#end
path.drawShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_x"); path.drawShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_x");
path.setTarget("ssgi_a"); path.setTarget("ssgi_a");
path.bindTarget("ssgi_b", "tex"); path.bindTarget("ssgi_b", "tex");
path.bindTarget("gbuffer0", "gbuffer0"); path.bindTarget("gbuffer0", "gbuffer0");
#if rp_ssgi_half
path.bindTarget("half", "gbufferD");
#else
path.bindTarget("_main", "gbufferD"); path.bindTarget("_main", "gbufferD");
#end
path.drawShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_y"); path.drawShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_y");
} }
} }
@ -960,7 +977,7 @@ class RenderPathDeferred {
path.bindTarget("tex", "tex"); path.bindTarget("tex", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass"); path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("gbuffer0", ["gbuffer1", "gbuffer_refraction"]); setTargetMeshes();
#if (rp_voxels != "Off") #if (rp_voxels != "Off")
path.bindTarget("voxelsOut", "voxels"); path.bindTarget("voxelsOut", "voxels");
@ -980,7 +997,7 @@ class RenderPathDeferred {
#end #end
#if rp_ssrs #if rp_ssrs
path.bindTarget("_main", "gbufferD"); path.bindTarget("gbufferD1", "gbufferD");
#end #end
path.drawMeshes("refraction"); path.drawMeshes("refraction");

View File

@ -266,6 +266,8 @@ def build():
wrd.compo_defs += '_CSharpen' wrd.compo_defs += '_CSharpen'
if lnx.utils.get_active_scene().view_settings.exposure != 0.0: if lnx.utils.get_active_scene().view_settings.exposure != 0.0:
wrd.compo_defs += '_CExposure' wrd.compo_defs += '_CExposure'
if lnx.utils.get_active_scene().view_settings.gamma != 1.0:
wrd.compo_defs += '_CGamma'
if rpdat.lnx_fog: if rpdat.lnx_fog:
wrd.compo_defs += '_CFog' wrd.compo_defs += '_CFog'
compo_depth = True compo_depth = True
@ -568,7 +570,8 @@ def get_num_gbuffer_rts_deferred()-> int:
for flag in ('_gbuffer2', '_EmissionShaded', '_SSRefraction', '_VoxelRefract', '_ClearCoat'): for flag in ('_gbuffer2', '_EmissionShaded', '_SSRefraction', '_VoxelRefract', '_ClearCoat'):
if flag in wrd.world_defs: if flag in wrd.world_defs:
if flag in refraction_flags and not found_refraction_flag: if flag in refraction_flags:
if not found_refraction_flag:
num += 1 num += 1
found_refraction_flag = True found_refraction_flag = True
else: else:

View File

@ -288,10 +288,16 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
wrd.world_defs += '_EmissionShaded' wrd.world_defs += '_EmissionShaded'
lnx.assets.add_khafile_def('rp_gbuffer_emission') lnx.assets.add_khafile_def('rp_gbuffer_emission')
disp_geom, disp_bump = disp_mode(node)
disp_active = parse_displacement and node.inputs[2].is_linked and (disp_geom != 'off' or disp_bump)
# Surface # Surface
if parse_surface or parse_opacity: if parse_surface or parse_opacity:
state.parents = [] state.parents = []
state.parsed = set() state.parsed = set()
state.parsing = set()
state.node_uids = {}
state.node_uids_used = set()
state.normal_parsed = False state.normal_parsed = False
curshader = state.frag curshader = state.frag
state.curshader = curshader state.curshader = curshader
@ -305,6 +311,29 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
out_opacity = outs[5] out_opacity = outs[5]
out_ior = outs[6] out_ior = outs[6]
out_emission_col = outs[7] out_emission_col = outs[7]
if parse_surface and not state.basecol_only and disp_active and disp_bump:
state.dxdy_scale = '0.1'
out_disp = parse_displacement_input(node.inputs[2])
state.current_pass = ParserPass.DX_SCREEN_SPACE
out_disp_dx = parse_displacement_input(node.inputs[2])
state.current_pass = ParserPass.DY_SCREEN_SPACE
out_disp_dy = parse_displacement_input(node.inputs[2])
state.current_pass = ParserPass.REGULAR
state.dxdy_scale = '1.0'
curshader.write('vec3 disp = {0};'.format(out_disp))
curshader.write('vec3 disp_dx = {0};'.format(out_disp_dx))
curshader.write('vec3 disp_dy = {0};'.format(out_disp_dy))
curshader.write('float bump_h = dot(disp, n);')
curshader.write('vec2 bump_dHd = vec2(dot(disp_dx, n + {0}), dot(disp_dy, n + {1})) - bump_h;'.format(dfdx_fine('n'), dfdy_fine('n')))
curshader.write('vec3 bump_dPdx = {0};'.format(dfdx_fine('wposition')))
curshader.write('vec3 bump_dPdy = {0};'.format(dfdy_fine('wposition')))
curshader.write('vec3 bump_Rx = cross(bump_dPdy, n);')
curshader.write('vec3 bump_Ry = cross(n, bump_dPdx);')
curshader.write('float bump_det = dot(bump_dPdx, bump_Rx);')
curshader.write('vec3 bump_surfgrad = bump_dHd.x * bump_Rx + bump_dHd.y * bump_Ry;')
curshader.write('float bump_facing = gl_FrontFacing ? 1.0 : -1.0;')
curshader.write('n = normalize(0.1 * abs(bump_det) * n - bump_facing * sign(bump_det) * bump_surfgrad);')
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};')
@ -347,13 +376,14 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
# Volume # Volume
# parse_volume_input(node.inputs[1]) # parse_volume_input(node.inputs[1])
# Displacement if disp_active and disp_geom != 'off':
if parse_displacement and disp_enabled() and node.inputs[2].is_linked:
state.parents = [] state.parents = []
state.parsed = set() state.parsed = set()
state.parsing = set()
state.node_uids = {}
state.node_uids_used = set()
state.normal_parsed = False state.normal_parsed = False
rpdat = lnx.utils.get_rp() if disp_geom == 'tessellation' and state.tese is not None:
if rpdat.lnx_rp_displacement == 'Tessellation' and state.tese is not None:
state.curshader = state.tese state.curshader = state.tese
else: else:
state.curshader = state.vert state.curshader = state.vert
@ -361,9 +391,12 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
state.curshader.write('vec3 disp = {0};'.format(out_disp)) state.curshader.write('vec3 disp = {0};'.format(out_disp))
if custom_particle_node is not None: if custom_particle_node is not None:
if not (parse_displacement and disp_enabled() and node.inputs[2].is_linked): if not (disp_active and disp_geom != 'off'):
state.parents = [] state.parents = []
state.parsed = set() state.parsed = set()
state.parsing = set()
state.node_uids = {}
state.node_uids_used = set()
state.normal_parsed = False state.normal_parsed = False
state.curshader = state.vert state.curshader = state.vert
@ -511,6 +544,9 @@ def parse_displacement_input(inp):
l = inp.links[0] l = inp.links[0]
if l.from_node.type == 'REROUTE': if l.from_node.type == 'REROUTE':
return parse_displacement_input(l.from_node.inputs[0]) return parse_displacement_input(l.from_node.inputs[0])
if l.from_socket.type in ('VALUE', 'INT', 'BOOLEAN'):
nor = 'n' if state.curshader.shader_type == 'frag' else 'wnormal'
return '{0} * vec3({1})'.format(nor, parse_value_input(inp))
return parse_vector_input(inp) return parse_vector_input(inp)
else: else:
return None return None
@ -527,7 +563,7 @@ def parse_vector_input(inp: bpy.types.NodeSocket) -> vec3str:
st = link.from_socket.type st = link.from_socket.type
if st in ('RGB', 'RGBA', 'VECTOR'): if st in ('RGB', 'RGBA', 'VECTOR'):
return res_var return res_var
elif st in ('VALUE', 'INT'): elif st in ('VALUE', 'INT', 'BOOLEAN'):
return f'vec3({res_var})' return f'vec3({res_var})'
else: else:
log.warn(f'Node tree "{tree_name()}": socket "{link.from_socket.name}" of node "{link.from_node.name}" cannot be connected to a vector-like socket') log.warn(f'Node tree "{tree_name()}": socket "{link.from_socket.name}" of node "{link.from_node.name}" cannot be connected to a vector-like socket')
@ -572,6 +608,7 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
'HUE_SAT', 'HUE_SAT',
'INVERT', 'INVERT',
'MIX', 'MIX',
'MIX_RGB',
'BLACKBODY', 'BLACKBODY',
'VALTORGB', 'VALTORGB',
'CURVE_VEC', 'CURVE_VEC',
@ -590,6 +627,8 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
'TANGENT', 'TANGENT',
'TEX_COORD', 'TEX_COORD',
'UVMAP', 'UVMAP',
'VOLUME_INFO',
'BEVEL',
'BUMP', 'BUMP',
'MAPPING', 'MAPPING',
'NORMAL', 'NORMAL',
@ -658,7 +697,8 @@ def parse_normal_map_color_input(inp, strength_input=None, space='TANGENT'):
frag.write(f'vec3 texn = ({color_val}) * 2.0 - 1.0;') frag.write(f'vec3 texn = ({color_val}) * 2.0 - 1.0;')
if strength != '1.0': if strength != '1.0':
frag.write(f'texn.xy *= {strength};') frag.write(f'texn.xy *= {strength};')
frag.write('n = normalize(TBN * texn);') frag.write('vec3 bitan = cross(n, TBN[0]) * sign(dot(cross(n, TBN[0]), TBN[1]));')
frag.write('n = normalize(mat3(TBN[0], bitan, n) * texn);')
state.con.add_elem('tang', 'short4norm') state.con.add_elem('tang', 'short4norm')
elif space in ['OBJECT', 'BLENDER_OBJECT']: elif space in ['OBJECT', 'BLENDER_OBJECT']:
@ -691,10 +731,8 @@ def parse_value_input(inp: bpy.types.NodeSocket) -> floatstr:
if socket_type in ('RGB', 'RGBA', 'VECTOR'): if socket_type in ('RGB', 'RGBA', 'VECTOR'):
# RGB to BW # RGB to BW
return rgb_to_bw(res_var) return rgb_to_bw(res_var)
elif socket_type in ('VALUE', 'INT'): elif socket_type in ('VALUE', 'INT', 'BOOLEAN'):
return res_var return res_var
elif socket_type == 'BOOLEAN':
return f'({res_var} ? 1.0 : 0.0)'
else: else:
log.warn(f'Node tree "{tree_name()}": socket "{link.from_socket.name}" of node "{link.from_node.name}" cannot be connected to a scalar value socket') log.warn(f'Node tree "{tree_name()}": socket "{link.from_socket.name}" of node "{link.from_node.name}" cannot be connected to a scalar value socket')
return '0.0' return '0.0'
@ -745,6 +783,7 @@ def parse_value(node, socket):
'SEPXYZ', 'SEPXYZ',
'VECT_MATH', 'VECT_MATH',
'MAP_RANGE', 'MAP_RANGE',
'VOLUME_INFO',
) )
if node.type in supported_node_types: if node.type in supported_node_types:
@ -811,7 +850,7 @@ def is_parsed(node_store_name: str):
def res_var_name(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str: def res_var_name(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
"""Return the name of the variable that stores the parsed result """Return the name of the variable that stores the parsed result
from the given node and socket.""" from the given node and socket."""
name = node_name(node.name) + '_' + safesrc(socket.name) + '_res' name = node_uid(node) + '_' + safesrc(socket.name) + '_res'
if '__' in name: # Consecutive _ are reserved if '__' in name: # Consecutive _ are reserved
name = name.replace('_', '_x') name = name.replace('_', '_x')
return name return name
@ -821,15 +860,17 @@ def write_result(link: bpy.types.NodeLink) -> Optional[str]:
"""Write the parsed result of the given node link to the shader.""" """Write the parsed result of the given node link to the shader."""
res_var = res_var_name(link.from_node, link.from_socket) res_var = res_var_name(link.from_node, link.from_socket)
need_dxdy_offset = node_need_reevaluation_for_screenspace_derivative(link.from_node) need_dxdy_offset = node_need_reevaluation_for_screenspace_derivative(link.from_node, link.from_socket)
if need_dxdy_offset: if need_dxdy_offset:
res_var += state.get_parser_pass_suffix() res_var += state.get_parser_pass_suffix()
# Unparsed node # Unparsed node
if not is_parsed(res_var): if not is_parsed(res_var):
state.parsed.add(res_var) state.parsed.add(res_var)
state.parsing.add(res_var)
st = link.from_socket.type st = link.from_socket.type
try:
if st in ('RGB', 'RGBA', 'VECTOR'): if st in ('RGB', 'RGBA', 'VECTOR'):
res = parse_vector(link.from_node, link.from_socket) res = parse_vector(link.from_node, link.from_socket)
if res is None: if res is None:
@ -837,7 +878,7 @@ def write_result(link: bpy.types.NodeLink) -> Optional[str]:
return None return None
state.curshader.write(f'vec3 {res_var} = {res};') state.curshader.write(f'vec3 {res_var} = {res};')
elif st == 'VALUE': elif st in ('VALUE', 'INT', 'BOOLEAN'):
res = parse_value(link.from_node, link.from_socket) res = parse_value(link.from_node, link.from_socket)
if res is None: if res is None:
log.error(f'{link.from_node.name} returned `None` while parsing!') log.error(f'{link.from_node.name} returned `None` while parsing!')
@ -847,10 +888,19 @@ def write_result(link: bpy.types.NodeLink) -> Optional[str]:
else: else:
state.curshader.write(f'float {res_var} = {res};') state.curshader.write(f'float {res_var} = {res};')
else:
state.curshader.write(f'float {res_var} = 0.0;')
finally:
state.parsing.discard(res_var)
if state.dxdy_varying_input_value: if state.dxdy_varying_input_value:
state.curshader.write(f'{res_var} = {apply_screenspace_derivative_offset_if_required(res_var)};') state.curshader.write(f'{res_var} = {apply_screenspace_derivative_offset_if_required(res_var)};')
state.dxdy_varying_input_value = False state.dxdy_varying_input_value = False
elif res_var in state.parsing:
log.warn(f'Node tree "{tree_name()}": cyclic dependency on node "{link.from_node.name}" ({res_var}); a node cannot depend on itself. Check the node group for a feedback loop.')
return 'vec3(0.0)' if link.from_socket.type in ('RGB', 'RGBA', 'VECTOR') else '0.0'
# Normal map already parsed, return # Normal map already parsed, return
elif link.from_node.type == 'NORMAL_MAP': elif link.from_node.type == 'NORMAL_MAP':
return None return None
@ -877,7 +927,7 @@ def to_uniform(inp: bpy.types.NodeSocket):
def store_var_name(node: bpy.types.Node) -> str: def store_var_name(node: bpy.types.Node) -> str:
name = node_name(node.name) name = node_uid(node)
if name[-1] == "_": if name[-1] == "_":
return name + '_x_store' # Prevent consecutive __ return name + '_x_store' # Prevent consecutive __
return name + '_store' return name + '_store'
@ -919,7 +969,7 @@ def texture_store(node, tex, tex_name, to_linear=False, unpremultiply=False, tex
nor = 'TBN[2]' nor = 'TBN[2]'
else: else:
nor = 'n' nor = 'n'
blend = getattr(node, 'projection_blend', 0.0) blend = node.projection_blend
curshader.write('vec4 {0} = boxProjection({1}, {2}, {3}, clamp({4}, 0.0, 1.0));'.format(tex_store, tex_name, nor, uv_name, blend)) curshader.write('vec4 {0} = boxProjection({1}, {2}, {3}, clamp({4}, 0.0, 1.0));'.format(tex_store, tex_name, nor, uv_name, blend))
elif spherical: elif spherical:
if not curshader.has_include('std/mapping.glsl'): if not curshader.has_include('std/mapping.glsl'):
@ -951,17 +1001,48 @@ def apply_screenspace_derivative_offset_if_required(coords: str) -> str:
# Derivative functions are only available in fragment shaders # Derivative functions are only available in fragment shaders
if state.curshader.shader_type == 'frag': if state.curshader.shader_type == 'frag':
if state.current_pass == ParserPass.DX_SCREEN_SPACE: if state.current_pass == ParserPass.DX_SCREEN_SPACE:
coords = f'({coords}) + {dfdx_fine(coords)}' coords = f'({coords}) + {dfdx_fine(coords)} * ({state.dxdy_scale})'
elif state.current_pass == ParserPass.DY_SCREEN_SPACE: elif state.current_pass == ParserPass.DY_SCREEN_SPACE:
coords = f'({coords}) + {dfdy_fine(coords)}' coords = f'({coords}) + {dfdy_fine(coords)} * ({state.dxdy_scale})'
return '(' + coords + ')' return '(' + coords + ')'
def node_need_reevaluation_for_screenspace_derivative(node: bpy.types.Node) -> bool: def node_need_reevaluation_for_screenspace_derivative(
node: bpy.types.Node,
socket: Optional[bpy.types.NodeSocket] = None,
parents: Tuple[bpy.types.Node, ...] = (),
) -> bool:
if state.current_pass not in (ParserPass.DX_SCREEN_SPACE, ParserPass.DY_SCREEN_SPACE): if state.current_pass not in (ParserPass.DX_SCREEN_SPACE, ParserPass.DY_SCREEN_SPACE):
return False return False
if node.type == 'GROUP':
output_node = node_by_type(node.node_tree.nodes, 'GROUP_OUTPUT')
if output_node is None:
return False
if socket is not None:
index = socket_index(node, socket)
inputs = [output_node.inputs[index]] if index is not None and index < len(output_node.inputs) else []
else:
inputs = output_node.inputs
for inp in inputs:
c_node, c_socket = lnx.node_utils.input_get_connected_node(inp)
if c_node is not None and node_need_reevaluation_for_screenspace_derivative(c_node, c_socket, parents + (node,)):
return True
return False
if node.type == 'GROUP_INPUT':
if socket is None or len(parents) == 0:
return False
index = socket_index(node, socket)
parent = parents[-1]
if index is None or index >= len(parent.inputs):
return False
c_node, c_socket = lnx.node_utils.input_get_connected_node(parent.inputs[index])
if c_node is None:
return False
return node_need_reevaluation_for_screenspace_derivative(c_node, c_socket, parents[:-1])
should_compute_offset = node_meta.get_node_meta(node).compute_dxdy_variants should_compute_offset = node_meta.get_node_meta(node).compute_dxdy_variants
if should_compute_offset == node_meta.ComputeDXDYVariant.ALWAYS: if should_compute_offset == node_meta.ComputeDXDYVariant.ALWAYS:
@ -971,11 +1052,11 @@ def node_need_reevaluation_for_screenspace_derivative(node: bpy.types.Node) -> b
# ComputeDXDYVariant.DYNAMIC # ComputeDXDYVariant.DYNAMIC
for inp in node.inputs: for inp in node.inputs:
c_node, _ = lnx.node_utils.input_get_connected_node(inp) c_node, c_socket = lnx.node_utils.input_get_connected_node(inp)
if c_node is None: if c_node is None:
continue continue
if node_need_reevaluation_for_screenspace_derivative(c_node): if node_need_reevaluation_for_screenspace_derivative(c_node, c_socket, parents):
return True return True
return False return False
@ -1077,6 +1158,22 @@ def node_name(s: str) -> str:
s = s.replace('_', '_x') s = s.replace('_', '_x')
return s return s
def node_uid(node: bpy.types.Node) -> str:
ctx = (tuple(p.as_pointer() for p in state.parents), state.curshader.write_textures > 0)
key = (node.as_pointer(), ctx)
name = state.node_uids.get(key)
if name is None:
base = node_name(node.name)
name = base
i = 1
while name in state.node_uids_used:
i += 1
name = f'{base}{i}' if base.endswith('_') else f'{base}_{i}'
state.node_uids[key] = name
state.node_uids_used.add(name)
return name
## ##
@ -1256,6 +1353,32 @@ def safesrc(name):
def disp_enabled(): def disp_enabled():
return lnx.utils.disp_enabled(lnx.make_state.target) return lnx.utils.disp_enabled(lnx.make_state.target)
def disp_mode(output_node):
inp = output_node.inputs[2]
if not inp.is_linked:
return ('off', False)
l = inp.links[0]
if l.from_node.type == 'GROUP' and l.from_node.node_tree.name.startswith('Leenkx PBR') and \
not l.from_node.inputs[7].is_linked:
return ('off', False)
mech = lnx.utils.get_rp().lnx_rp_displacement
if bpy.app.version >= (4, 1, 0):
method = mat_state.material.displacement_method
else:
method = getattr(mat_state.material.cycles, 'displacement_method', 'BUMP')
if mech == 'Off':
return ('off', False)
if mech == 'Bump':
return ('off', True)
if mech == 'Tessellation' and not disp_enabled():
log.warn('Tessellation not available on ' + lnx.make_state.target)
return ('off', True)
geom = 'tessellation' if mech == 'Tessellation' else 'vertex'
if method == 'BUMP':
return (geom, False)
return (geom, method in ('DISPLACEMENT', 'BOTH'))
def assets_add(path): def assets_add(path):
lnx.assets.add(path) lnx.assets.add(path)

View File

@ -1966,107 +1966,110 @@ float snoise(vec4 p) {
p = compatible_mod(p, 100000.0) + precision_correction; p = compatible_mod(p, 100000.0) + precision_correction;
return 0.8344 * noise_perlin(p); return 0.8344 * noise_perlin(p);
} }
"""
#define DEFINE_NOISE_FRACTAL(T) \\\ # Fractal noise variants, emitted on demand per coordinate type.
float noise_fbm(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\ # @T@ is replaced by the coordinate type (float/vec2/vec3/vec4).
T p = co; \\\ str_tex_noise_fbm = """float noise_fbm(@T@ co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) {
float fscale = 1.0; \\\ @T@ p = co;
float amp = 1.0; \\\ float fscale = 1.0;
float maxamp = 0.0; \\\ float amp = 1.0;
float sum = 0.0; \\\ float maxamp = 0.0;
for (int i = 0; i <= int(detail); i++) { \\\ float sum = 0.0;
float t = snoise(fscale * p); \\\ for (int i = 0; i <= int(detail); i++) {
sum += t * amp; \\\ float t = snoise(fscale * p);
maxamp += amp; \\\ sum += t * amp;
amp *= roughness; \\\ maxamp += amp;
fscale *= lacunarity; \\\ amp *= roughness;
} \\\ fscale *= lacunarity;
float rmd = detail - floor(detail); \\\ }
if (rmd != 0.0) { \\\ float rmd = detail - floor(detail);
float t = snoise(fscale * p); \\\ if (rmd != 0.0) {
float sum2 = sum + t * amp; \\\ float t = snoise(fscale * p);
return normalize ? mix(0.5 * sum / maxamp + 0.5, 0.5 * sum2 / (maxamp + amp) + 0.5, rmd) : mix(sum, sum2, rmd); \\\ float sum2 = sum + t * amp;
} else { \\\ return normalize ? mix(0.5 * sum / maxamp + 0.5, 0.5 * sum2 / (maxamp + amp) + 0.5, rmd) : mix(sum, sum2, rmd);
return normalize ? 0.5 * sum / maxamp + 0.5 : sum; \\\ } else {
} \\\ return normalize ? 0.5 * sum / maxamp + 0.5 : sum;
} \\\ }
float noise_multi_fractal(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\ }"""
T p = co; \\\
float value = 1.0; \\\
float pwr = 1.0; \\\
for (int i = 0; i <= int(detail); i++) { \\\
value *= (pwr * snoise(p) + 1.0); \\\
pwr *= roughness; \\\
p *= lacunarity; \\\
} \\\
float rmd = detail - floor(detail); \\\
if (rmd != 0.0) { \\\
value *= (rmd * pwr * snoise(p) + 1.0); \\\
} \\\
return value; \\\
} \\\
float noise_hetero_terrain(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\
T p = co; \\\
float pwr = roughness; \\\
float value = offset + snoise(p); \\\
p *= lacunarity; \\\
for (int i = 1; i <= int(detail); i++) { \\\
float increment = (snoise(p) + offset) * pwr * value; \\\
value += increment; \\\
pwr *= roughness; \\\
p *= lacunarity; \\\
} \\\
float rmd = detail - floor(detail); \\\
if (rmd != 0.0) { \\\
float increment = (snoise(p) + offset) * pwr * value; \\\
value += rmd * increment; \\\
} \\\
return value; \\\
} \\\
float noise_hybrid_multi_fractal(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\
T p = co; \\\
float pwr = 1.0; \\\
float value = 0.0; \\\
float weight = 1.0; \\\
for (int i = 0; (weight > 0.001) && (i <= int(detail)); i++) { \\\
if (weight > 1.0) weight = 1.0; \\\
float signal = (snoise(p) + offset) * pwr; \\\
pwr *= roughness; \\\
value += weight * signal; \\\
weight *= gain * signal; \\\
p *= lacunarity; \\\
} \\\
float rmd = detail - floor(detail); \\\
if ((rmd != 0.0) && (weight > 0.001)) { \\\
if (weight > 1.0) weight = 1.0; \\\
float signal = (snoise(p) + offset) * pwr; \\\
value += rmd * weight * signal; \\\
} \\\
return value; \\\
} \\\
float noise_ridged_multi_fractal(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\
T p = co; \\\
float pwr = roughness; \\\
float signal = offset - abs(snoise(p)); \\\
signal *= signal; \\\
float value = signal; \\\
float weight = 1.0; \\\
for (int i = 1; i <= int(detail); i++) { \\\
p *= lacunarity; \\\
weight = clamp(signal * gain, 0.0, 1.0); \\\
signal = offset - abs(snoise(p)); \\\
signal *= signal; \\\
signal *= weight; \\\
value += signal * pwr; \\\
pwr *= roughness; \\\
} \\\
return value; \\\
}
DEFINE_NOISE_FRACTAL(float) str_tex_noise_multi_fractal = """float noise_multi_fractal(@T@ co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) {
DEFINE_NOISE_FRACTAL(vec2) @T@ p = co;
DEFINE_NOISE_FRACTAL(vec3) float value = 1.0;
DEFINE_NOISE_FRACTAL(vec4) float pwr = 1.0;
for (int i = 0; i <= int(detail); i++) {
value *= (pwr * snoise(p) + 1.0);
pwr *= roughness;
p *= lacunarity;
}
float rmd = detail - floor(detail);
if (rmd != 0.0) {
value *= (rmd * pwr * snoise(p) + 1.0);
}
return value;
}"""
str_tex_noise_hetero_terrain = """float noise_hetero_terrain(@T@ co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) {
@T@ p = co;
float pwr = roughness;
float value = offset + snoise(p);
p *= lacunarity;
for (int i = 1; i <= int(detail); i++) {
float increment = (snoise(p) + offset) * pwr * value;
value += increment;
pwr *= roughness;
p *= lacunarity;
}
float rmd = detail - floor(detail);
if (rmd != 0.0) {
float increment = (snoise(p) + offset) * pwr * value;
value += rmd * increment;
}
return value;
}"""
str_tex_noise_hybrid_multi_fractal = """float noise_hybrid_multi_fractal(@T@ co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) {
@T@ p = co;
float pwr = 1.0;
float value = 0.0;
float weight = 1.0;
for (int i = 0; (weight > 0.001) && (i <= int(detail)); i++) {
if (weight > 1.0) weight = 1.0;
float signal = (snoise(p) + offset) * pwr;
pwr *= roughness;
value += weight * signal;
weight *= gain * signal;
p *= lacunarity;
}
float rmd = detail - floor(detail);
if ((rmd != 0.0) && (weight > 0.001)) {
if (weight > 1.0) weight = 1.0;
float signal = (snoise(p) + offset) * pwr;
value += rmd * weight * signal;
}
return value;
}"""
str_tex_noise_ridged_multi_fractal = """float noise_ridged_multi_fractal(@T@ co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) {
@T@ p = co;
float pwr = roughness;
float signal = offset - abs(snoise(p));
signal *= signal;
float value = signal;
float weight = 1.0;
for (int i = 1; i <= int(detail); i++) {
p *= lacunarity;
weight = clamp(signal * gain, 0.0, 1.0);
signal = offset - abs(snoise(p));
signal *= signal;
signal *= weight;
value += signal * pwr;
pwr *= roughness;
}
return value;
}"""
str_tex_noise += """
float random_float_offset(float seed) { return 100.0 + hash_float_to_float(seed) * 100.0; } float random_float_offset(float seed) { return 100.0 + hash_float_to_float(seed) * 100.0; }
vec2 random_vec2_offset(float seed) { return vec2(100.0 + hash_vec2_to_float(vec2(seed, 0.0)) * 100.0, 100.0 + hash_vec2_to_float(vec2(seed, 1.0)) * 100.0); } vec2 random_vec2_offset(float seed) { return vec2(100.0 + hash_vec2_to_float(vec2(seed, 0.0)) * 100.0, 100.0 + hash_vec2_to_float(vec2(seed, 1.0)) * 100.0); }
@ -2193,6 +2196,43 @@ vec3 hue_sat(const vec3 col, const vec4 shift) {
} }
""" """
str_mix_hsv = """
vec3 mix_hue(const float fac, const vec3 col1, const vec3 col2) {
vec3 hsv2 = rgb_to_hsv(col2);
if (hsv2.y != 0.0) {
vec3 hsv = rgb_to_hsv(col1);
hsv.x = hsv2.x;
return mix(col1, hsv_to_rgb(hsv), fac);
}
return col1;
}
vec3 mix_sat(const float fac, const vec3 col1, const vec3 col2) {
vec3 hsv = rgb_to_hsv(col1);
if (hsv.y != 0.0) {
vec3 hsv2 = rgb_to_hsv(col2);
hsv.y = mix(hsv.y, hsv2.y, fac);
return hsv_to_rgb(hsv);
}
return col1;
}
vec3 mix_val(const float fac, const vec3 col1, const vec3 col2) {
vec3 hsv = rgb_to_hsv(col1);
vec3 hsv2 = rgb_to_hsv(col2);
hsv.z = mix(hsv.z, hsv2.z, fac);
return hsv_to_rgb(hsv);
}
vec3 mix_color(const float fac, const vec3 col1, const vec3 col2) {
vec3 hsv2 = rgb_to_hsv(col2);
if (hsv2.y != 0.0) {
vec3 hsv = rgb_to_hsv(col1);
hsv.x = hsv2.x;
hsv.y = hsv2.y;
return mix(col1, hsv_to_rgb(hsv), fac);
}
return col1;
}
"""
# https://twitter.com/Donzanoid/status/903424376707657730 # https://twitter.com/Donzanoid/status/903424376707657730
str_wavelength_to_rgb = """ str_wavelength_to_rgb = """
vec3 wavelength_to_rgb(const float t) { vec3 wavelength_to_rgb(const float t) {

View File

@ -97,59 +97,76 @@ def _parse_mixrgb(node: bpy.types.ShaderNodeMixRGB, out_socket: bpy.types.NodeSo
col1 = c.parse_vector_input(node.inputs[6]) col1 = c.parse_vector_input(node.inputs[6])
col2 = c.parse_vector_input(node.inputs[7]) col2 = c.parse_vector_input(node.inputs[7])
# Store factor in variable for linked factor input fac = _mixrgb_fac(node, state)
if node.inputs[0].is_linked:
fac = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix()
state.curshader.write('float {0} = {1};'.format(fac, c.parse_value_input(node.inputs[0])))
else:
fac = c.parse_value_input(node.inputs[0])
if node.clamp_factor: if node.clamp_factor:
fac = f'clamp({fac}, 0.0, 1.0)' fac = f'clamp({fac}, 0.0, 1.0)'
# TODO: Do not mix if factor is constant 0.0 or 1.0? # TODO: Do not mix if factor is constant 0.0 or 1.0?
blend = node.blend_type out_col = _mixrgb_blend(col1, col2, fac, node.blend_type, state)
if node.clamp_result:
return 'clamp({0}, vec3(0.0), vec3(1.0))'.format(out_col)
return out_col
def _mixrgb_blend(col1: vec3str, col2: vec3str, fac: str, blend: str, state: ParserState) -> vec3str:
if blend == 'MIX': if blend == 'MIX':
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) return 'mix({0}, {1}, {2})'.format(col1, col2, fac)
elif blend == 'ADD': elif blend == 'ADD':
out_col = 'mix({0}, {0} + {1}, {2})'.format(col1, col2, fac) return 'mix({0}, {0} + {1}, {2})'.format(col1, col2, fac)
elif blend == 'MULTIPLY': elif blend == 'MULTIPLY':
out_col = 'mix({0}, {0} * {1}, {2})'.format(col1, col2, fac) return 'mix({0}, {0} * {1}, {2})'.format(col1, col2, fac)
elif blend == 'SUBTRACT': elif blend == 'SUBTRACT':
out_col = 'mix({0}, {0} - {1}, {2})'.format(col1, col2, fac) return 'mix({0}, {0} - {1}, {2})'.format(col1, col2, fac)
elif blend == 'SCREEN': elif blend == 'SCREEN':
out_col = 'mix({0}, vec3(1.0) - (vec3(1.0) - {0}) * (vec3(1.0) - {1}), {2})'.format(col1, col2, fac) return 'mix({0}, vec3(1.0) - (vec3(1.0) - {0}) * (vec3(1.0) - {1}), {2})'.format(col1, col2, fac)
elif blend == 'DIVIDE': elif blend == 'DIVIDE':
out_col = 'mix({0}, {0} / max({1}, vec3(0.000001)), {2})'.format(col1, col2, fac) return 'mix({0}, {0} / max({1}, vec3(0.000001)), {2} * vec3(notEqual({1}, vec3(0.0))))'.format(col1, col2, fac)
elif blend == 'DIFFERENCE': elif blend == 'DIFFERENCE':
out_col = 'mix({0}, abs({0} - {1}), {2})'.format(col1, col2, fac) return 'mix({0}, abs({0} - {1}), {2})'.format(col1, col2, fac)
elif blend == 'DARKEN': elif blend == 'DARKEN':
out_col = 'mix({0}, min({0}, {1}), {2})'.format(col1, col2, fac) return 'mix({0}, min({0}, {1}), {2})'.format(col1, col2, fac)
elif blend == 'LIGHTEN': elif blend == 'LIGHTEN':
out_col = 'mix({0}, max({0}, {1}), {2})'.format(col1, col2, fac) return 'mix({0}, max({0}, {1}), {2})'.format(col1, col2, fac)
elif blend == 'OVERLAY': elif blend == 'OVERLAY':
overlay = 'vec3({0}.r < 0.5 ? 2.0 * {0}.r * {1}.r : 1.0 - 2.0 * (1.0 - {0}.r) * (1.0 - {1}.r), {0}.g < 0.5 ? 2.0 * {0}.g * {1}.g : 1.0 - 2.0 * (1.0 - {0}.g) * (1.0 - {1}.g), {0}.b < 0.5 ? 2.0 * {0}.b * {1}.b : 1.0 - 2.0 * (1.0 - {0}.b) * (1.0 - {1}.b))'.format(col1, col2) overlay = 'vec3({0}.r < 0.5 ? 2.0 * {0}.r * {1}.r : 1.0 - 2.0 * (1.0 - {0}.r) * (1.0 - {1}.r), {0}.g < 0.5 ? 2.0 * {0}.g * {1}.g : 1.0 - 2.0 * (1.0 - {0}.g) * (1.0 - {1}.g), {0}.b < 0.5 ? 2.0 * {0}.b * {1}.b : 1.0 - 2.0 * (1.0 - {0}.b) * (1.0 - {1}.b))'.format(col1, col2)
out_col = 'mix({0}, {1}, {2})'.format(col1, overlay, fac) return 'mix({0}, {1}, {2})'.format(col1, overlay, fac)
elif blend == 'DODGE': elif blend == 'DODGE':
dodge = '{0} / max(vec3(1.0) - {1}, vec3(0.000001))'.format(col1, col2) return 'clamp({0} / max(vec3(1.0) - {2} * {1}, vec3(0.000001)), vec3(0.0), vec3(1.0))'.format(col1, col2, fac)
out_col = 'mix({0}, {1}, {2})'.format(col1, dodge, fac)
elif blend == 'BURN': elif blend == 'BURN':
burn = 'vec3(1.0) - (vec3(1.0) - {0}) / max({1}, vec3(0.000001))'.format(col1, col2) return 'clamp(vec3(1.0) - (vec3(1.0) - {0}) / max(vec3(1.0 - {2}) + {2} * {1}, vec3(0.000001)), vec3(0.0), vec3(1.0))'.format(col1, col2, fac)
out_col = 'mix({0}, {1}, {2})'.format(col1, burn, fac)
elif blend == 'SOFT_LIGHT': elif blend == 'SOFT_LIGHT':
soft = '(vec3(1.0) - {1}) * {0} * {0} + {1} * (2.0 * {0} * (vec3(1.0) - {0}) + sqrt({0}) * (2.0 * {0} - vec3(1.0)))'.format(col1, col2) soft = '(vec3(1.0) - {1}) * {0} * {0} + {1} * (2.0 * {0} * (vec3(1.0) - {0}) + sqrt({0}) * (2.0 * {0} - vec3(1.0)))'.format(col1, col2)
out_col = 'mix({0}, {1}, {2})'.format(col1, soft, fac) return 'mix({0}, {1}, {2})'.format(col1, soft, fac)
elif blend == 'LINEAR_LIGHT': elif blend == 'LINEAR_LIGHT':
linear = '{0} + 2.0 * {1} - vec3(1.0)'.format(col1, col2) linear = '{0} + 2.0 * {1} - vec3(1.0)'.format(col1, col2)
out_col = 'mix({0}, {1}, {2})'.format(col1, linear, fac) return 'mix({0}, {1}, {2})'.format(col1, linear, fac)
elif blend == 'EXCLUSION':
return 'max(mix({0}, {0} + {1} - 2.0 * {0} * {1}, {2}), vec3(0.0))'.format(col1, col2, fac)
elif blend in ['HUE', 'SATURATION', 'COLOR', 'VALUE']: elif blend in ['HUE', 'SATURATION', 'COLOR', 'VALUE']:
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) state.curshader.add_function(c_functions.str_hue_sat)
state.curshader.add_function(c_functions.str_mix_hsv)
func = {'HUE': 'mix_hue', 'SATURATION': 'mix_sat', 'COLOR': 'mix_color', 'VALUE': 'mix_val'}[blend]
return '{3}({2}, {0}, {1})'.format(col1, col2, fac, func)
else: else:
log.warn(f'MixRGB node: unsupported blend type {node.blend_type}.') log.warn(f'MixRGB node: unsupported blend type {blend}.')
out_col = col1 return col1
if node.clamp_result:
def _mixrgb_fac(node: bpy.types.Node, state: ParserState) -> str:
if node.inputs[0].is_linked:
fac = c.node_uid(node) + '_fac' + state.get_parser_pass_suffix()
state.curshader.write('float {0} = {1};'.format(fac, c.parse_value_input(node.inputs[0])))
return fac
return c.parse_value_input(node.inputs[0])
def parse_mixrgb(node: bpy.types.ShaderNodeMixRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
col1 = c.parse_vector_input(node.inputs[1])
col2 = c.parse_vector_input(node.inputs[2])
fac = f'clamp({_mixrgb_fac(node, state)}, 0.0, 1.0)'
out_col = _mixrgb_blend(col1, col2, fac, node.blend_type, state)
if node.use_clamp:
return 'clamp({0}, vec3(0.0), vec3(1.0))'.format(out_col) return 'clamp({0}, vec3(0.0), vec3(1.0))'.format(out_col)
return out_col return out_col
@ -158,7 +175,7 @@ def parse_curvergb(node: bpy.types.ShaderNodeRGBCurve, out_socket: bpy.types.Nod
fac = c.parse_value_input(node.inputs[0]) fac = c.parse_value_input(node.inputs[0])
vec = c.parse_vector_input(node.inputs[1]) vec = c.parse_vector_input(node.inputs[1])
curves = node.mapping.curves curves = node.mapping.curves
name = c.node_name(node.name) name = c.node_uid(node)
# mapping.curves[0].points[0].handle_type # mapping.curves[0].points[0].handle_type
return '(sqrt(vec3({0}, {1}, {2}) * vec3({4}, {5}, {6})) * {3})'.format( return '(sqrt(vec3({0}, {1}, {2}) * vec3({4}, {5}, {6})) * {3})'.format(
c.vector_curve(name + '0', vec + '.x', curves[0].points), c.vector_curve(name + '1', vec + '.y', curves[1].points), c.vector_curve(name + '2', vec + '.z', curves[2].points), fac, c.vector_curve(name + '0', vec + '.x', curves[0].points), c.vector_curve(name + '1', vec + '.y', curves[1].points), c.vector_curve(name + '2', vec + '.z', curves[2].points), fac,

View File

@ -89,7 +89,7 @@ def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB, out_socket: bpy.types.Nod
elems = node.color_ramp.elements elems = node.color_ramp.elements
use_color_out = out_socket == node.outputs[0] use_color_out = out_socket == node.outputs[0]
ramp_store = c.node_name(node.name) + '_res' + state.get_parser_pass_suffix() ramp_store = c.node_uid(node) + '_res' + state.get_parser_pass_suffix()
if c.is_parsed(ramp_store): if c.is_parsed(ramp_store):
return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a' return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a'
@ -100,26 +100,26 @@ def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB, out_socket: bpy.types.Nod
state.parsed.add(ramp_store) state.parsed.add(ramp_store)
return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a' return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a'
cols_var = c.node_name(node.name).upper() + '_COLS' cols_var = c.node_uid(node).upper() + '_COLS'
if state.current_pass == ParserPass.REGULAR: if state.current_pass == ParserPass.REGULAR:
cols_entries = ', '.join(f'vec4({e.color[0]}, {e.color[1]}, {e.color[2]}, {e.color[3]})' for e in elems) cols_entries = ', '.join(f'vec4({e.color[0]}, {e.color[1]}, {e.color[2]}, {e.color[3]})' for e in elems)
cols_entries += f', vec4({elems[-1].color[0]}, {elems[-1].color[1]}, {elems[-1].color[2]}, {elems[-1].color[3]})' cols_entries += f', vec4({elems[-1].color[0]}, {elems[-1].color[1]}, {elems[-1].color[2]}, {elems[-1].color[3]})'
state.curshader.add_const("vec4", cols_var, cols_entries, array_size=len(elems) + 1) state.curshader.add_const("vec4", cols_var, cols_entries, array_size=len(elems) + 1)
fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix() fac_var = c.node_uid(node) + '_fac' + state.get_parser_pass_suffix()
state.curshader.write(f'float {fac_var} = {fac};') state.curshader.write(f'float {fac_var} = {fac};')
index = '0' index = '0'
for i in range(1, len(elems)): for i in range(1, len(elems)):
index += f' + ({fac_var} > {elems[i].position} ? 1 : 0)' index += f' + ({fac_var} > {elems[i].position} ? 1 : 0)'
index_var = c.node_name(node.name) + '_i' + state.get_parser_pass_suffix() index_var = c.node_uid(node) + '_i' + state.get_parser_pass_suffix()
state.curshader.write(f'int {index_var} = {index};') state.curshader.write(f'int {index_var} = {index};')
if interp == 'CONSTANT': if interp == 'CONSTANT':
state.curshader.write(f'vec4 {ramp_store} = {cols_var}[{index_var}];') state.curshader.write(f'vec4 {ramp_store} = {cols_var}[{index_var}];')
else: else:
facs_var = c.node_name(node.name).upper() + '_FACS' facs_var = c.node_uid(node).upper() + '_FACS'
if state.current_pass == ParserPass.REGULAR: if state.current_pass == ParserPass.REGULAR:
facs_entries = ', '.join(str(e.position) for e in elems) facs_entries = ', '.join(str(e.position) for e in elems)
facs_entries += ', 1.0' facs_entries += ', 1.0'
@ -369,7 +369,7 @@ if bpy.app.version > (3, 2, 0):
def parse_sephsv(node: bpy.types.ShaderNodeSeparateHSV, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: def parse_sephsv(node: bpy.types.ShaderNodeSeparateHSV, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr:
state.curshader.add_function(c_functions.str_hue_sat) state.curshader.add_function(c_functions.str_hue_sat)
hsv_var = c.node_name(node.name) + '_hsv' + state.get_parser_pass_suffix() hsv_var = c.node_uid(node) + '_hsv' + state.get_parser_pass_suffix()
if not state.curshader.contains(hsv_var): # Already written if a second output is parsed if not state.curshader.contains(hsv_var): # Already written if a second output is parsed
state.curshader.write(f'const vec3 {hsv_var} = rgb_to_hsv({c.parse_vector_input(node.inputs["Color"])}.rgb);') state.curshader.write(f'const vec3 {hsv_var} = rgb_to_hsv({c.parse_vector_input(node.inputs["Color"])}.rgb);')

View File

@ -109,7 +109,7 @@ def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.N
def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
if node.lnx_material_param: if node.lnx_material_param:
nn = 'param_' + c.node_name(node.name) nn = 'param_' + c.node_uid(node)
v = out_socket.default_value v = out_socket.default_value
value = [float(v[0]), float(v[1]), float(v[2])] value = [float(v[0]), float(v[1]), float(v[2])]
state.curshader.add_uniform(f'vec3 {nn}', link=f'{node.name}', default_value=value, is_lnx_mat_param=True) state.curshader.add_uniform(f'vec3 {nn}', link=f'{node.name}', default_value=value, is_lnx_mat_param=True)
@ -163,7 +163,17 @@ def parse_geometry(node: bpy.types.ShaderNodeNewGeometry, out_socket: bpy.types.
# Incoming # Incoming
elif out_socket == node.outputs[4]: elif out_socket == node.outputs[4]:
state.dxdy_varying_input_value = True state.dxdy_varying_input_value = True
if state.curshader.shader_type == 'frag':
return 'vVec' return 'vVec'
state.curshader.add_uniform('vec3 eye', link='_cameraPosition')
if state.curshader.shader_type == 'tese':
return 'normalize(eye - wposition)'
if state.curshader.shader_type == 'vert':
state.curshader.add_uniform('mat4 W', link='_worldMatrix')
return 'normalize(eye - (W * spos).xyz)'
if state.curshader.shader_type == 'tesc':
return 'normalize(eye - wposition[gl_InvocationID])'
return 'normalize(eye - wposition[0])'
# Parametric # Parametric
elif out_socket == node.outputs[5]: elif out_socket == node.outputs[5]:
state.dxdy_varying_input_value = True state.dxdy_varying_input_value = True
@ -450,7 +460,7 @@ def parse_lightpath(node: bpy.types.ShaderNodeLightPath, out_socket: bpy.types.N
def parse_value(node: bpy.types.ShaderNodeValue, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: def parse_value(node: bpy.types.ShaderNodeValue, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr:
if node.lnx_material_param: if node.lnx_material_param:
nn = 'param_' + c.node_name(node.name) nn = 'param_' + c.node_uid(node)
value = node.outputs[0].default_value value = node.outputs[0].default_value
is_lnx_mat_param = True is_lnx_mat_param = True
state.curshader.add_uniform('float {0}'.format(nn), link='{0}'.format(node.name), default_value=value, is_lnx_mat_param=is_lnx_mat_param) state.curshader.add_uniform('float {0}'.format(nn), link='{0}'.format(node.name), default_value=value, is_lnx_mat_param=is_lnx_mat_param)
@ -463,3 +473,9 @@ def parse_wireframe(node: bpy.types.ShaderNodeWireframe, out_socket: bpy.types.N
# node.use_pixel_size # node.use_pixel_size
# size = c.parse_value_input(node.inputs[0]) # size = c.parse_value_input(node.inputs[0])
return '0.0' return '0.0'
def parse_volumeinfo(node: bpy.types.ShaderNodeVolumeInfo, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
if out_socket == node.outputs['Color']:
return 'vec3(0.0)'
return '0.0'

View File

@ -69,10 +69,12 @@ def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket,
c.parse_shader_input(node.inputs[2]) c.parse_shader_input(node.inputs[2])
return return
fac_var = c.node_uid(node) + '_fac' + state.get_parser_pass_suffix()
fac_inv_var = c.node_uid(node) + '_fac_inv' + state.get_parser_pass_suffix()
if not c.is_parsed(fac_var):
state.parsed.add(fac_var)
prefix = '' if node.inputs['Fac'].is_linked else 'const ' prefix = '' if node.inputs['Fac'].is_linked else 'const '
fac = c.parse_value_input(node.inputs['Fac']) fac = c.parse_value_input(node.inputs['Fac'])
fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix()
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))
state.curshader.write('{0}float {1} = 1.0 - {2};'.format(prefix, fac_inv_var, fac_var)) state.curshader.write('{0}float {1} = 1.0 - {2};'.format(prefix, fac_inv_var, fac_var))
@ -435,7 +437,7 @@ if bpy.app.version >= (2, 83, 0):
def parse_bsdfhairprincipled(node: 'bpy.types.ShaderNodeBsdfHairPrincipled', out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfhairprincipled(node: 'bpy.types.ShaderNodeBsdfHairPrincipled', out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs['Normal']) c.write_normal(node.inputs['Normal'])
parametrization = getattr(node, 'parametrization', 'COLOR') parametrization = node.parametrization
if parametrization == 'COLOR': if parametrization == 'COLOR':
color_socket = node.inputs.get('Color') color_socket = node.inputs.get('Color')
if color_socket is not None: if color_socket is not None:
@ -457,7 +459,7 @@ if bpy.app.version >= (2, 83, 0):
rough_socket = node.inputs.get('Roughness') rough_socket = node.inputs.get('Roughness')
if rough_socket is not None: if rough_socket is not None:
state.out_roughness = c.parse_value_input(rough_socket) state.out_roughness = c.parse_value_input(rough_socket)
model = getattr(node, 'model', 'CHIANG') model = node.model if bpy.app.version >= (4, 0, 0) else 'CHIANG'
if model == 'CHIANG': if model == 'CHIANG':
radial_rough_socket = node.inputs.get('Radial Roughness') radial_rough_socket = node.inputs.get('Radial Roughness')
if radial_rough_socket is not None: if radial_rough_socket is not None:

View File

@ -136,7 +136,7 @@ def parse_tex_image(node: bpy.types.ShaderNodeTexImage, out_socket: bpy.types.No
else: else:
return f'{tex_store}.a' return f'{tex_store}.a'
tex_name = c.node_name(node.name) tex_name = c.node_uid(node)
tex = c.make_texture_from_image_node(node, tex_name) tex = c.make_texture_from_image_node(node, tex_name)
tex_link = None tex_link = None
tex_default_file = None tex_default_file = None
@ -259,7 +259,7 @@ def parse_tex_magic(node: bpy.types.ShaderNodeTexMagic, out_socket: bpy.types.No
return res return res
if bpy.app.version < (4, 1, 0): if bpy.app.version < (4, 2, 0):
def parse_tex_musgrave(node: bpy.types.ShaderNodeTexMusgrave, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: def parse_tex_musgrave(node: bpy.types.ShaderNodeTexMusgrave, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
state.curshader.add_function(c_functions.str_tex_musgrave) state.curshader.add_function(c_functions.str_tex_musgrave)
@ -297,52 +297,65 @@ def parse_tex_noise(node: bpy.types.ShaderNodeTexNoise, out_socket: bpy.types.No
gain = c.parse_value_input(node.inputs['Gain']) if 'Gain' in node.inputs else '1.0' gain = c.parse_value_input(node.inputs['Gain']) if 'Gain' in node.inputs else '1.0'
distortion = c.parse_value_input(node.inputs['Distortion']) if 'Distortion' in node.inputs else '0.0' distortion = c.parse_value_input(node.inputs['Distortion']) if 'Distortion' in node.inputs else '0.0'
dimensions = getattr(node, 'noise_dimensions', '3D') dimensions = node.noise_dimensions
noise_type = getattr(node, 'noise_type', 'FBM') noise_type = node.noise_type if bpy.app.version >= (4, 1, 0) else 'FBM'
normalize = 'true' if getattr(node, 'normalize', True) else 'false' normalize = 'true' if bpy.app.version < (4, 0, 0) or node.normalize else 'false'
type_map = { if noise_type == 'MULTIFRACTAL':
'FBM': 'noise_fbm', func_name = 'noise_multi_fractal'
'MULTIFRACTAL': 'noise_multi_fractal', fractal_src = c_functions.str_tex_noise_multi_fractal
'RIDGED_MULTIFRACTAL': 'noise_ridged_multi_fractal', elif noise_type == 'RIDGED_MULTIFRACTAL':
'HYBRID_MULTIFRACTAL': 'noise_hybrid_multi_fractal', func_name = 'noise_ridged_multi_fractal'
'HETERO_TERRAIN': 'noise_hetero_terrain' fractal_src = c_functions.str_tex_noise_ridged_multi_fractal
} elif noise_type == 'HYBRID_MULTIFRACTAL':
func_name = type_map.get(noise_type, 'noise_fbm') func_name = 'noise_hybrid_multi_fractal'
fractal_src = c_functions.str_tex_noise_hybrid_multi_fractal
elif noise_type == 'HETERO_TERRAIN':
func_name = 'noise_hetero_terrain'
fractal_src = c_functions.str_tex_noise_hetero_terrain
else:
func_name = 'noise_fbm'
fractal_src = c_functions.str_tex_noise_fbm
is_color = (out_socket == node.outputs[1]) or (getattr(out_socket, 'name', '') == 'Color') is_color = (out_socket == node.outputs[1]) or (out_socket.name == 'Color')
if dimensions == '1D': if dimensions == '1D':
coord_type = 'float'
p_expr = f"({w}) * ({scale})" p_expr = f"({w}) * ({scale})"
dist_expr = f"({p_expr}) + snoise(({p_expr}) + random_float_offset(0.0)) * ({distortion})" if distortion != '0.0' else p_expr dist_expr = f"({p_expr}) + snoise(({p_expr}) + random_float_offset(0.0)) * ({distortion})" if distortion != '0.0' else p_expr
if is_color: off_a = 'random_float_offset(1.0)'
res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_float_offset(1.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_float_offset(2.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))" off_b = 'random_float_offset(2.0)'
else:
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
elif dimensions == '2D': elif dimensions == '2D':
coord_type = 'vec2'
p_expr = f"({co}).xy * ({scale})" p_expr = f"({co}).xy * ({scale})"
dist_expr = f"({p_expr}) + vec2(snoise(({p_expr}) + random_vec2_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec2_offset(1.0)) * ({distortion}))" if distortion != '0.0' else p_expr dist_expr = f"({p_expr}) + vec2(snoise(({p_expr}) + random_vec2_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec2_offset(1.0)) * ({distortion}))" if distortion != '0.0' else p_expr
if is_color: off_a = 'random_vec2_offset(2.0)'
res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec2_offset(2.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec2_offset(3.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))" off_b = 'random_vec2_offset(3.0)'
else:
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
elif dimensions == '4D': elif dimensions == '4D':
coord_type = 'vec4'
p_expr = f"vec4({co}, {w}) * ({scale})" p_expr = f"vec4({co}, {w}) * ({scale})"
dist_expr = f"({p_expr}) + vec4(snoise(({p_expr}) + random_vec4_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(1.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(2.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(3.0)) * ({distortion}))" if distortion != '0.0' else p_expr dist_expr = f"({p_expr}) + vec4(snoise(({p_expr}) + random_vec4_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(1.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(2.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(3.0)) * ({distortion}))" if distortion != '0.0' else p_expr
if is_color: off_a = 'random_vec4_offset(4.0)'
res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec4_offset(4.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec4_offset(5.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))" off_b = 'random_vec4_offset(5.0)'
else:
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
else: else:
coord_type = 'vec3'
p_expr = f"({co}) * ({scale})" p_expr = f"({co}) * ({scale})"
dist_expr = f"({p_expr}) + vec3(snoise(({p_expr}) + random_vec3_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec3_offset(1.0)) * ({distortion}), snoise(({p_expr}) + random_vec3_offset(2.0)) * ({distortion}))" if distortion != '0.0' else p_expr dist_expr = f"({p_expr}) + vec3(snoise(({p_expr}) + random_vec3_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec3_offset(1.0)) * ({distortion}), snoise(({p_expr}) + random_vec3_offset(2.0)) * ({distortion}))" if distortion != '0.0' else p_expr
off_a = 'random_vec3_offset(3.0)'
off_b = 'random_vec3_offset(4.0)'
state.curshader.add_function('// ' + func_name + ' ' + coord_type + '\n' + fractal_src.replace('@T@', coord_type))
co_var = c.node_uid(node) + '_co' + state.get_parser_pass_suffix()
if not c.is_parsed(co_var):
state.parsed.add(co_var)
state.curshader.write(f'{coord_type} {co_var} = {dist_expr};')
fbm_args = f"clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}"
if is_color: if is_color:
res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec3_offset(3.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec3_offset(4.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))" res = f"vec3({func_name}({co_var}, {fbm_args}), {func_name}(({co_var}) + {off_a}, {fbm_args}), {func_name}(({co_var}) + {off_b}, {fbm_args}))"
else: else:
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})" res = f"{func_name}({co_var}, {fbm_args})"
return res return res
@ -513,7 +526,7 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket:
state.parsed.add(tex_store) state.parsed.add(tex_store)
tex_name = c.node_name(node.name) tex_name = c.node_uid(node)
tex_link = None tex_link = None
tex_default_file = None tex_default_file = None
is_lnx_mat_param = None is_lnx_mat_param = None
@ -525,8 +538,8 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket:
image, image,
tex_name, tex_name,
c.mat_get_material(), c.mat_get_material(),
getattr(node, 'interpolation', 'Smart'), node.interpolation,
getattr(node, 'extension', 'REPEAT') node.extension if bpy.app.version >= (3, 1, 0) else 'REPEAT'
) )
if tex is None: if tex is None:
log.warn(f'Object "{state.tree_name}": missing environment texture image "{node.name}"') log.warn(f'Object "{state.tree_name}": missing environment texture image "{node.name}"')
@ -670,6 +683,7 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type
outp = 3 outp = 3
m = 0 m = 0
exp = '0.0'
if node.distance == 'MANHATTAN': if node.distance == 'MANHATTAN':
m = 1 m = 1
elif node.distance == 'CHEBYCHEV': elif node.distance == 'CHEBYCHEV':
@ -689,10 +703,13 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type
f = 4 f = 4
dim = node.voronoi_dimensions dim = node.voronoi_dimensions
normalize = 1 if node.normalize else 0 normalize = 1 if bpy.app.version >= (4, 0, 0) and node.normalize else 0
c.write_procedurals() c.write_procedurals()
state.curshader.add_function(getattr(c_functions, f'str_tex_voronoi_{bpy.app.version[0]}')) if bpy.app.version >= (5, 0, 0):
state.curshader.add_function(c_functions.str_tex_voronoi_5)
else:
state.curshader.add_function(c_functions.str_tex_voronoi_4)
if node.inputs['Vector'].is_linked: if node.inputs['Vector'].is_linked:
co = c.get_vector_input(node, ['Vector']) co = c.get_vector_input(node, ['Vector'])
@ -718,6 +735,7 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type
def parse_tex_wave(node: bpy.types.ShaderNodeTexWave, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: def parse_tex_wave(node: bpy.types.ShaderNodeTexWave, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
c.write_procedurals() c.write_procedurals()
state.curshader.add_function(c_functions.str_tex_noise) state.curshader.add_function(c_functions.str_tex_noise)
state.curshader.add_function('// noise_fbm vec3\n' + c_functions.str_tex_noise_fbm.replace('@T@', 'vec3'))
state.curshader.add_function(c_functions.str_tex_wave) state.curshader.add_function(c_functions.str_tex_wave)
if node.inputs['Vector'].is_linked: if node.inputs['Vector'].is_linked:
@ -735,9 +753,7 @@ def parse_tex_wave(node: bpy.types.ShaderNodeTexWave, out_socket: bpy.types.Node
dir_map = {'X': 0, 'Y': 1, 'Z': 2, 'DIAGONAL': 3} dir_map = {'X': 0, 'Y': 1, 'Z': 2, 'DIAGONAL': 3}
if hasattr(node, 'wave_direction'): if wave_type == 0:
wave_dir = dir_map.get(node.wave_direction, 0)
elif wave_type == 0:
wave_dir = dir_map.get(node.bands_direction, 0) wave_dir = dir_map.get(node.bands_direction, 0)
else: else:
wave_dir = dir_map.get(node.rings_direction, 0) wave_dir = dir_map.get(node.rings_direction, 0)
@ -804,8 +820,8 @@ def parse_tex_white_noise(node: bpy.types.ShaderNodeTexWhiteNoise, out_socket: b
w = c.parse_value_input(node.inputs['W']) if 'W' in node.inputs else '0.0' w = c.parse_value_input(node.inputs['W']) if 'W' in node.inputs else '0.0'
dimensions = getattr(node, 'noise_dimensions', '3D') dimensions = node.noise_dimensions
is_color = (out_socket == node.outputs[1]) or (getattr(out_socket, 'name', '') == 'Color') is_color = (out_socket == node.outputs[1]) or (out_socket.name == 'Color')
if dimensions == '1D': if dimensions == '1D':
if is_color: if is_color:

View File

@ -28,7 +28,7 @@ def parse_curvevec(node: bpy.types.ShaderNodeVectorCurve, out_socket: bpy.types.
fac = c.get_value_input(node, ['Fac']) fac = c.get_value_input(node, ['Fac'])
vec = c.get_vector_input(node, ['Vector']) vec = c.get_vector_input(node, ['Vector'])
curves = node.mapping.curves curves = node.mapping.curves
name = c.node_name(node.name) name = c.node_uid(node)
res_x = c.vector_curve(name + '0', vec + '.x', curves[0].points) res_x = c.vector_curve(name + '0', vec + '.x', curves[0].points)
res_y = c.vector_curve(name + '1', vec + '.y', curves[1].points) res_y = c.vector_curve(name + '1', vec + '.y', curves[1].points)
@ -47,33 +47,39 @@ def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket,
# Interpolation strength # Interpolation strength
strength = c.get_value_input(node, ['Strength']) strength = c.get_value_input(node, ['Strength'])
distance = c.get_value_input(node, ['Distance']) distance = c.get_value_input(node, ['Distance'])
filter_width = c.get_value_input(node, ['Filter Width']) if 'Filter Width' in node.inputs else '1.0'
height = c.get_value_input(node, ['Height']) height = c.get_value_input(node, ['Height'])
prev_pass = state.current_pass
prev_scale = state.dxdy_scale
state.dxdy_scale = filter_width
state.current_pass = ParserPass.DX_SCREEN_SPACE state.current_pass = ParserPass.DX_SCREEN_SPACE
height_dx = c.get_value_input(node, ['Height']) height_dx = c.get_value_input(node, ['Height'])
state.current_pass = ParserPass.DY_SCREEN_SPACE state.current_pass = ParserPass.DY_SCREEN_SPACE
height_dy = c.get_value_input(node, ['Height']) height_dy = c.get_value_input(node, ['Height'])
state.current_pass = ParserPass.REGULAR state.current_pass = prev_pass
state.dxdy_scale = prev_scale
nor = c.get_vector_input(node, ['Normal']) if node.inputs['Normal'].is_linked else 'n' nor = c.get_vector_input(node, ['Normal']) if node.inputs['Normal'].is_linked else 'n'
if height_dx != height or height_dy != height: if height_dx != height or height_dy != height:
tangent = f'{c.dfdx_fine("wposition")} + {nor} * (({height_dx} - {height}) * {distance})' name = c.node_uid(node)
bitangent = f'{c.dfdy_fine("wposition")} + {nor} * (({height_dy} - {height}) * {distance})' if '__' in name:
name = name.replace('_', '_x')
# Cross-product operand order, dFdy is flipped on d3d11 sfx = state.get_parser_pass_suffix()
bitangent_first = utils.get_gapi() == 'direct3d11' invert = '-1.0' if node.invert else '1.0'
w = state.curshader.write
if node.invert: w(f'vec3 {name}_N{sfx} = normalize({nor});')
bitangent_first = not bitangent_first w(f'float {name}_dist{sfx} = ({distance}) * (gl_FrontFacing ? {invert} : -({invert}));')
w(f'vec3 {name}_dPdx{sfx} = {c.dfdx_fine("wposition")};')
if bitangent_first: w(f'vec3 {name}_dPdy{sfx} = {c.dfdy_fine("wposition")};')
# We need to normalize twice, once for the correct "weight" of the strength, w(f'vec3 {name}_Rx{sfx} = cross({name}_dPdy{sfx}, {name}_N{sfx});')
# once for having a normalized output vector (lerping vectors does not preserve magnitude) w(f'vec3 {name}_Ry{sfx} = cross({name}_N{sfx}, {name}_dPdx{sfx});')
res = f'normalize(mix({nor}, normalize(cross({bitangent}, {tangent})), {strength}))' w(f'float {name}_det{sfx} = dot({name}_dPdx{sfx}, {name}_Rx{sfx});')
else: w(f'vec2 {name}_dHd{sfx} = vec2({height_dx}, {height_dy}) - vec2({height});')
res = f'normalize(mix({nor}, normalize(cross({tangent}, {bitangent})), {strength}))' w(f'vec3 {name}_surfgrad{sfx} = {name}_dHd{sfx}.x * {name}_Rx{sfx} + {name}_dHd{sfx}.y * {name}_Ry{sfx};')
w(f'vec3 {name}_res{sfx} = normalize(({filter_width}) * abs({name}_det{sfx}) * {name}_N{sfx} - {name}_dist{sfx} * sign({name}_det{sfx}) * {name}_surfgrad{sfx});')
res = f'normalize(mix({name}_N{sfx}, {name}_res{sfx}, max({strength}, 0.0)))'
else: else:
res = nor res = nor
@ -96,7 +102,7 @@ def parse_mapping(node: bpy.types.ShaderNodeMapping, out_socket: bpy.types.NodeS
out = f"({out} - {location})" out = f"({out} - {location})"
if input_rotation.is_linked or any(v != 0.0 for v in input_rotation.default_value): if input_rotation.is_linked or any(v != 0.0 for v in input_rotation.default_value):
var_name = c.node_name(node.name) + "_rotation" + state.get_parser_pass_suffix() var_name = c.node_uid(node) + "_rotation" + state.get_parser_pass_suffix()
state.curshader.write(f"mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos({rotation}.x), sin({rotation}.x), 0.0, -sin({rotation}.x), cos({rotation}.x));") state.curshader.write(f"mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos({rotation}.x), sin({rotation}.x), 0.0, -sin({rotation}.x), cos({rotation}.x));")
state.curshader.write(f"mat3 {var_name}Y = mat3(cos({rotation}.y), 0.0, -sin({rotation}.y), 0.0, 1.0, 0.0, sin({rotation}.y), 0.0, cos({rotation}.y));") state.curshader.write(f"mat3 {var_name}Y = mat3(cos({rotation}.y), 0.0, -sin({rotation}.y), 0.0, 1.0, 0.0, sin({rotation}.y), 0.0, cos({rotation}.y));")
state.curshader.write(f"mat3 {var_name}Z = mat3(cos({rotation}.z), sin({rotation}.z), 0.0, -sin({rotation}.z), cos({rotation}.z), 0.0, 0.0, 0.0, 1.0);") state.curshader.write(f"mat3 {var_name}Z = mat3(cos({rotation}.z), sin({rotation}.z), 0.0, -sin({rotation}.z), cos({rotation}.z), 0.0, 0.0, 0.0, 1.0);")
@ -110,7 +116,7 @@ def parse_mapping(node: bpy.types.ShaderNodeMapping, out_socket: bpy.types.NodeS
out = f"({out} * {scale})" out = f"({out} * {scale})"
if input_rotation.is_linked or any(v != 0.0 for v in input_rotation.default_value): if input_rotation.is_linked or any(v != 0.0 for v in input_rotation.default_value):
var_name = c.node_name(node.name) + "_rotation" + state.get_parser_pass_suffix() var_name = c.node_uid(node) + "_rotation" + state.get_parser_pass_suffix()
state.curshader.write(f"mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos({rotation}.x), -sin({rotation}.x), 0.0, sin({rotation}.x), cos({rotation}.x));") state.curshader.write(f"mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos({rotation}.x), -sin({rotation}.x), 0.0, sin({rotation}.x), cos({rotation}.x));")
state.curshader.write(f"mat3 {var_name}Y = mat3(cos({rotation}.y), 0.0, sin({rotation}.y), 0.0, 1.0, 0.0, -sin({rotation}.y), 0.0, cos({rotation}.y));") state.curshader.write(f"mat3 {var_name}Y = mat3(cos({rotation}.y), 0.0, sin({rotation}.y), 0.0, 1.0, 0.0, -sin({rotation}.y), 0.0, cos({rotation}.y));")
state.curshader.write(f"mat3 {var_name}Z = mat3(cos({rotation}.z), -sin({rotation}.z), 0.0, sin({rotation}.z), cos({rotation}.z), 0.0, 0.0, 0.0, 1.0);") state.curshader.write(f"mat3 {var_name}Z = mat3(cos({rotation}.z), -sin({rotation}.z), 0.0, sin({rotation}.z), cos({rotation}.z), 0.0, 0.0, 0.0, 1.0);")
@ -203,7 +209,8 @@ def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.t
nor = 'wnormal' nor = 'wnormal'
if node.space == 'OBJECT': if node.space == 'OBJECT':
nor = f'(inverse(mat3(W)) * {nor})' state.curshader.add_uniform('mat4 W', link='_worldMatrix')
nor = f'(transpose(mat3(W)) * {nor})'
disp = f'normalize({nor}) * (vec3({height}) - vec3({midlevel})) * {scale}' disp = f'normalize({nor}) * (vec3({height}) - vec3({midlevel})) * {scale}'
@ -221,12 +228,14 @@ def parse_vector_displacement(node: bpy.types.ShaderNodeVectorDisplacement, out_
offset = f'(({vector} - vec3({midlevel})) * {scale})' offset = f'(({vector} - vec3({midlevel})) * {scale})'
if node.space == 'TANGENT': if node.space == 'TANGENT':
state.curshader.add_uniform('mat4 W', link='_worldMatrix')
t_obj = 'normalize(inverse(mat3(W)) * wtangent)' t_obj = 'normalize(inverse(mat3(W)) * wtangent)'
n_obj = 'normalize(inverse(mat3(W)) * wnormal)' n_obj = 'normalize(transpose(mat3(W)) * wnormal)'
b_obj = f'normalize(cross({n_obj}, {t_obj}))' b_obj = f'normalize(cross({n_obj}, {t_obj}))'
disp = f'({t_obj} * {offset}.x + {n_obj} * {offset}.y + {b_obj} * {offset}.z)' disp = f'({t_obj} * {offset}.x + {b_obj} * {offset}.y + {n_obj} * {offset}.z)'
return f'(vec4(W * vec4({disp}, 0.0)).xyz)' return f'(vec4(W * vec4({disp}, 0.0)).xyz)'
elif node.space == 'OBJECT': elif node.space == 'OBJECT':
state.curshader.add_uniform('mat4 W', link='_worldMatrix')
return f'(vec4(W * vec4({offset}, 0.0)).xyz)' return f'(vec4(W * vec4({offset}, 0.0)).xyz)'
else: else:
return offset return offset
@ -262,3 +271,9 @@ def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.t
return f'vec3( euler_to_mat3({rot_val}) * ({input_vector} - {input_center}) + {input_center})' return f'vec3( euler_to_mat3({rot_val}) * ({input_vector} - {input_center}) + {input_center})'
return f'vec3(0.0, 0.0, 0.0)' return f'vec3(0.0, 0.0, 0.0)'
def parse_bevel(node: bpy.types.ShaderNodeBevel, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
if node.inputs['Normal'].is_linked:
return c.get_vector_input(node, ['Normal'])
return 'n' if state.curshader.shader_type == 'frag' else 'wnormal'

View File

@ -46,7 +46,9 @@ def write(vert: shader.Shader, frag: shader.Shader):
if is_transparent_shadows: if is_transparent_shadows:
frag.add_uniform('samplerCube shadowMapPointTransparent[4]', included=True) frag.add_uniform('samplerCube shadowMapPointTransparent[4]', included=True)
if not '_VoxelAOvar' in wrd.world_defs and not '_VoxelGI' in wrd.world_defs or ((parse_opacity or '_VoxelShadow' in wrd.world_defs) and ('_VoxelAOvar' in wrd.world_defs or '_VoxelGI' in wrd.world_defs)): #if not '_VoxelAOvar' in wrd.world_defs and not '_VoxelGI' in wrd.world_defs or ((parse_opacity or '_VoxelShadow' in wrd.world_defs) and ('_VoxelAOvar' in wrd.world_defs or '_VoxelGI' in wrd.world_defs)):
#vert.add_out('vec4 wvpposition')
#vert.write('wvpposition = gl_Position;')
vert.add_out('vec4 wvpposition') vert.add_out('vec4 wvpposition')
vert.write('wvpposition = gl_Position;') vert.write('wvpposition = gl_Position;')
# wvpposition.z / wvpposition.w # wvpposition.z / wvpposition.w

View File

@ -31,7 +31,8 @@ else:
def make(context_id, rpasses, shadowmap=False, shadowmap_transparent=False): def make(context_id, rpasses, shadowmap=False, shadowmap_transparent=False):
is_disp = mat_utils.disp_linked(mat_state.output_node) disp_geom = cycles.disp_mode(mat_state.output_node)[0]
is_disp = disp_geom != 'off'
vs = [{'name': 'pos', 'data': 'short4norm'}] vs = [{'name': 'pos', 'data': 'short4norm'}]
if is_disp or shadowmap_transparent: if is_disp or shadowmap_transparent:
@ -116,7 +117,7 @@ def make(context_id, rpasses, shadowmap=False, shadowmap_transparent=False):
make_particle.write(vert, shadowmap=shadowmap) make_particle.write(vert, shadowmap=shadowmap)
if is_disp: if is_disp:
if rpdat.lnx_rp_displacement == 'Vertex': if disp_geom == 'vertex':
frag.ins = vert.outs frag.ins = vert.outs
vert.add_uniform('mat3 N', '_normalMatrix') vert.add_uniform('mat3 N', '_normalMatrix')
vert.write('vec3 wnormal = normalize(N * vec3(nor.xy, pos.w));') vert.write('vec3 wnormal = normalize(N * vec3(nor.xy, pos.w));')
@ -137,7 +138,7 @@ def make(context_id, rpasses, shadowmap=False, shadowmap_transparent=False):
if con_depth.is_elem('col'): if con_depth.is_elem('col'):
vert.add_out('vec3 vcolor') vert.add_out('vec3 vcolor')
vert.write_attrib('vcolor = col.rgb;') vert.write_attrib('vcolor = col.rgb;')
vert.write('wposition += wnormal * disp;') vert.write('wposition += disp;')
if shadowmap: if shadowmap:
vert.add_uniform('mat4 LVP', '_lightViewProjectionMatrix') vert.add_uniform('mat4 LVP', '_lightViewProjectionMatrix')
vert.write('gl_Position = LVP * vec4(wposition, 1.0);') vert.write('gl_Position = LVP * vec4(wposition, 1.0);')

View File

@ -126,10 +126,11 @@ def make_base(con_mesh, parse_opacity):
vattr_written = False vattr_written = False
rpdat = lnx.utils.get_rp() rpdat = lnx.utils.get_rp()
is_displacement = mat_utils.disp_linked(mat_state.output_node) disp_geom = cycles.disp_mode(mat_state.output_node)[0]
is_displacement = disp_geom != 'off'
wrd = bpy.data.worlds['Lnx'] wrd = bpy.data.worlds['Lnx']
if is_displacement: if is_displacement:
if rpdat.lnx_rp_displacement == 'Vertex': if disp_geom == 'vertex':
frag.ins = vert.outs frag.ins = vert.outs
else: # Tessellation else: # Tessellation
tesc = con_mesh.make_tesc() tesc = con_mesh.make_tesc()
@ -193,7 +194,7 @@ def make_base(con_mesh, parse_opacity):
vert.write_attrib('TBN = mat3(tangent, bitangent, wnormal);') vert.write_attrib('TBN = mat3(tangent, bitangent, wnormal);')
if is_displacement: if is_displacement:
if rpdat.lnx_rp_displacement == 'Vertex': if disp_geom == 'vertex':
sh = vert sh = vert
else: else:
sh = tese sh = tese

View File

@ -33,10 +33,28 @@ def make(context_id):
frag = con_refract.frag frag = con_refract.frag
tese = con_refract.tese tese = con_refract.tese
frag.add_include('std/gbuffer.glsl') frag.add_include('std/gbuffer.glsl')
frag.add_out('vec4 fragColor[3]')
rpdat = lnx.utils.get_rp() rpdat = lnx.utils.get_rp()
is_deferred = rpdat.rp_renderer == 'Deferred'
wrd = bpy.data.worlds['Lnx']
idx_2 = idx_emission = idx_coat = -1
idx = 2
if '_gbuffer2' in wrd.world_defs:
idx_2 = idx
idx += 1
if '_EmissionShaded' in wrd.world_defs:
idx_emission = idx
idx += 1
idx_refraction = idx
idx += 1
if '_ClearCoat' in wrd.world_defs:
idx_coat = idx
idx += 1
frag.add_out(f'vec4 fragColor[{idx}]' if is_deferred else 'vec4 fragColor[3]')
# Remove fragColor = ...; # Remove fragColor = ...;
frag.main = frag.main[:frag.main.rfind('fragColor')] frag.main = frag.main[:frag.main.rfind('fragColor')]
frag.write('\n') frag.write('\n')
@ -53,15 +71,21 @@ def make(context_id):
else: else:
frag.write('const uint matid = 0;') frag.write('const uint matid = 0;')
if rpdat.rp_renderer == 'Deferred': if is_deferred:
frag.write('fragColor[0] = vec4(n.xy, roughness, 1.0);') frag.write('fragColor[0] = vec4(n.xy, roughness, 1.0);')
frag.write('vec3 finalColor = direct + indirect;') frag.write('vec3 finalColor = direct + indirect;')
frag.write('fragColor[1] = vec4(finalColor * opacity, 1.0);') frag.write('fragColor[1] = vec4(finalColor * opacity, 1.0);')
frag.write(f'fragColor[{idx_refraction}] = vec4(packIOR(ior), 1.0 - opacity, gl_FragCoord.z, 1.0);')
if idx_2 >= 0:
frag.write(f'fragColor[{idx_2}] = vec4(0.0);')
if idx_emission >= 0:
frag.write(f'fragColor[{idx_emission}] = vec4(0.0);')
if idx_coat >= 0:
frag.write(f'fragColor[{idx_coat}] = vec4(0.0);')
else: else:
frag.write('vec3 finalColor = direct + indirect;') frag.write('vec3 finalColor = direct + indirect;')
frag.write('fragColor[0] = vec4(finalColor * opacity, 1.0);') frag.write('fragColor[0] = vec4(finalColor * opacity, 1.0);')
frag.write('fragColor[1] = vec4(n.xy, roughness, 1.0);') frag.write('fragColor[1] = vec4(n.xy, roughness, 1.0);')
frag.write('fragColor[2] = vec4(packIOR(ior), 1.0 - opacity, gl_FragCoord.z, 1.0);') frag.write('fragColor[2] = vec4(packIOR(ior), 1.0 - opacity, gl_FragCoord.z, 1.0);')
# frag.write('fragColor[2] = vec4(ior, 1.0 - opacity, packFloat2(basecol.r, basecol.g), basecol.b);') # frag.write('fragColor[2] = vec4(ior, 1.0 - opacity, packFloat2(basecol.r, basecol.g), basecol.b);')

View File

@ -19,8 +19,7 @@ else:
add_mesh_contexts = [] add_mesh_contexts = []
def disp_linked(output_node): def disp_linked(output_node):
linked = output_node.inputs[2].is_linked if not output_node.inputs[2].is_linked:
if not linked:
return False return False
# Leenkx PBR with unlinked height socket # Leenkx PBR with unlinked height socket
l = output_node.inputs[2].links[0] l = output_node.inputs[2].links[0]
@ -51,7 +50,7 @@ def get_rpasses(material):
ar.append('translucent') ar.append('translucent')
elif is_transluc(material) and not material.lnx_discard and not material.lnx_blending and rpdat.rp_ss_refraction: elif is_transluc(material) and not material.lnx_discard and not material.lnx_blending and rpdat.rp_ss_refraction:
ar.append('refraction') ar.append('refraction')
if rpdat.rp_voxels != "Off" and has_voxels: if rpdat.rp_voxels != "Off" and has_voxels and rpdat.lnx_material_model == 'Full':
ar.append('voxel') ar.append('voxel')
if rpdat.rp_renderer == 'Forward' and rpdat.rp_depthprepass and not material.lnx_blending and not material.lnx_particle_flag: if rpdat.rp_renderer == 'Forward' and rpdat.rp_depthprepass and not material.lnx_blending and not material.lnx_particle_flag:
ar.append('depth') ar.append('depth')

View File

@ -143,6 +143,10 @@ ALL_NODES: Dict[str, MaterialNodeMeta] = {
compute_dxdy_variants=ComputeDXDYVariant.NEVER compute_dxdy_variants=ComputeDXDYVariant.NEVER
), ),
'VERTEX_COLOR': MaterialNodeMeta(parse_func=nodes_input.parse_vertex_color), 'VERTEX_COLOR': MaterialNodeMeta(parse_func=nodes_input.parse_vertex_color),
'VOLUME_INFO': MaterialNodeMeta(
parse_func=nodes_input.parse_volumeinfo,
compute_dxdy_variants=ComputeDXDYVariant.NEVER
),
'WIREFRAME': MaterialNodeMeta( 'WIREFRAME': MaterialNodeMeta(
parse_func=nodes_input.parse_wireframe, parse_func=nodes_input.parse_wireframe,
compute_dxdy_variants=ComputeDXDYVariant.NEVER compute_dxdy_variants=ComputeDXDYVariant.NEVER
@ -192,6 +196,7 @@ ALL_NODES: Dict[str, MaterialNodeMeta] = {
'TEX_WHITE_NOISE': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_white_noise), 'TEX_WHITE_NOISE': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_white_noise),
# --- nodes_vector # --- nodes_vector
'BEVEL': MaterialNodeMeta(parse_func=nodes_vector.parse_bevel),
'BUMP': MaterialNodeMeta(parse_func=nodes_vector.parse_bump), 'BUMP': MaterialNodeMeta(parse_func=nodes_vector.parse_bump),
'CURVE_VEC': MaterialNodeMeta(parse_func=nodes_vector.parse_curvevec), 'CURVE_VEC': MaterialNodeMeta(parse_func=nodes_vector.parse_curvevec),
'DISPLACEMENT': MaterialNodeMeta(parse_func=nodes_vector.parse_displacement), 'DISPLACEMENT': MaterialNodeMeta(parse_func=nodes_vector.parse_displacement),
@ -214,7 +219,8 @@ if bpy.app.version > (3, 2, 0):
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, 0, 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['MIX_RGB'] = MaterialNodeMeta(parse_func=nodes_color.parse_mixrgb)
if bpy.app.version < (4, 2, 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)

View File

@ -1,5 +1,5 @@
from enum import IntEnum, unique from enum import IntEnum, unique
from typing import List, Set, Tuple, Union, Optional from typing import Dict, List, Set, Tuple, Union, Optional
import bpy import bpy
@ -69,6 +69,9 @@ class ParserState:
# Cache for computing nodes only once # Cache for computing nodes only once
self.parsed: Set[str] = set() self.parsed: Set[str] = set()
self.parsing: Set[str] = set()
self.node_uids: Dict[tuple, str] = {}
self.node_uids_used: Set[str] = set()
# What to parse from the node tree # What to parse from the node tree
self.parse_surface = True self.parse_surface = True
@ -90,6 +93,11 @@ class ParserState:
dx/dy offsets (if required by the parser pass). dx/dy offsets (if required by the parser pass).
""" """
self.dxdy_scale = '1.0'
"""Scale factor applied to screen-space dx/dy offsets, matching
Blender's bump filter_width (offset = dFdx(v) * filter_width).
"""
# Shader output values # Shader output values
self.out_basecol: vec3str = 'vec3(0.8)' self.out_basecol: vec3str = 'vec3(0.8)'
self.out_roughness: floatstr = '0.0' self.out_roughness: floatstr = '0.0'

View File

@ -347,7 +347,7 @@ class LnxRPListItem(bpy.types.PropertyGroup):
name="Max Lights Shadows", description="Max number of rendered shadow maps that can be visible in the screen. Always equal or lower than Max Lights", default='16') name="Max Lights Shadows", description="Max number of rendered shadow maps that can be visible in the screen. Always equal or lower than Max Lights", default='16')
rp_shadowmap_atlas: BoolProperty(name="Shadow Map Atlasing", description="Group shadow maps of lights of the same type in the same texture", default=False, update=update_renderpath) rp_shadowmap_atlas: BoolProperty(name="Shadow Map Atlasing", description="Group shadow maps of lights of the same type in the same texture", default=False, update=update_renderpath)
rp_shadowmap_atlas_auto: BoolProperty(name="Automatic Atlasing", description="Automatically compute atlas sizes based on max lights and shadow map sizes", default=True, update=update_renderpath) rp_shadowmap_atlas_auto: BoolProperty(name="Automatic Atlasing", description="Automatically compute atlas sizes based on max lights and shadow map sizes", default=True, update=update_renderpath)
rp_shadowmap_atlas_single_map: BoolProperty(name="Shadow Map Atlas single map", description="Use a single texture for all different light types.", default=False, update=update_renderpath) rp_shadowmap_atlas_single_map: BoolProperty(name="Shadow Map Atlas single map", description="Use a single texture for all different light types.", default=True, update=update_renderpath)
rp_shadowmap_atlas_lod: BoolProperty(name="Shadow Map Atlas LOD (Experimental)", description="When enabled, the size of the shadow map will be determined on runtime based on the distance of the light to the camera", default=False, update=update_renderpath) rp_shadowmap_atlas_lod: BoolProperty(name="Shadow Map Atlas LOD (Experimental)", description="When enabled, the size of the shadow map will be determined on runtime based on the distance of the light to the camera", default=False, update=update_renderpath)
rp_shadowmap_transparent: BoolProperty(name="Transparency", description="Enable shadows for transparent objects", default=True, update=update_renderpath) rp_shadowmap_transparent: BoolProperty(name="Transparency", description="Enable shadows for transparent objects", default=True, update=update_renderpath)
rp_shadowmap_atlas_lod_subdivisions: EnumProperty( rp_shadowmap_atlas_lod_subdivisions: EnumProperty(
@ -496,9 +496,10 @@ class LnxRPListItem(bpy.types.PropertyGroup):
name="Diffuse BRDF", description="Diffuse BRDF model", default='Burley', update=update_material_model) name="Diffuse BRDF", description="Diffuse BRDF model", default='Burley', update=update_material_model)
lnx_rp_displacement: EnumProperty( lnx_rp_displacement: EnumProperty(
items=[('Off', 'Off', 'Off'), items=[('Off', 'Off', 'Off'),
('Bump', 'Bump', 'Bump'),
('Vertex', 'Vertex', 'Vertex'), ('Vertex', 'Vertex', 'Vertex'),
('Tessellation', 'Tessellation', 'Tessellation')], ('Tessellation', 'Tessellation', 'Tessellation')],
name="Displacement", description="Enable material displacement", default='Vertex', update=assets.invalidate_shader_cache) name="Displacement", description="Enable material displacement", default='Bump', update=assets.invalidate_shader_cache)
lnx_tess_mesh_inner: IntProperty(name="Inner", description="Inner tessellation level", default=7) lnx_tess_mesh_inner: IntProperty(name="Inner", description="Inner tessellation level", default=7)
lnx_tess_mesh_outer: IntProperty(name="Outer", description="Outer tessellation level", default=7) lnx_tess_mesh_outer: IntProperty(name="Outer", description="Outer tessellation level", default=7)
lnx_tess_shadows_inner: IntProperty(name="Inner", description="Inner tessellation level", default=7) lnx_tess_shadows_inner: IntProperty(name="Inner", description="Inner tessellation level", default=7)

View File

@ -1009,6 +1009,7 @@ const vec3 compoSharpenColor = vec3(""" + str(round(rpdat.lnx_sharpen_color[0] *
if lnx.utils.get_active_scene().view_settings.exposure != 0.0: if lnx.utils.get_active_scene().view_settings.exposure != 0.0:
f.write( f.write(
"""const float compoExposureStrength = """ + str(round(lnx.utils.get_active_scene().view_settings.exposure * 100) / 100) + """; """const float compoExposureStrength = """ + str(round(lnx.utils.get_active_scene().view_settings.exposure * 100) / 100) + """;
const float compoGammaStrength = """ + str(round(lnx.utils.get_active_scene().view_settings.gamma * 100) / 100) + """;
""") """)
if rpdat.lnx_fog: if rpdat.lnx_fog: