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 (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.4

View File

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

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]);
#endif
#else
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[0], p, n, cLightDir, lightPlane.y, shadowMapSpot[0], sssColorCL, sssRadiusCL
#ifdef _ShadowMapAtlas
, vec4(0.0, 0.0, 1.0, 1.0)
#endif
);
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
);
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[0], p, n, cLightDir, lightPlane.y, shadowMapSpot[0], sssColorCL, sssRadiusCL);
else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[1], p, n, cLightDir, lightPlane.y, shadowMapSpot[1], sssColorCL, sssRadiusCL);
else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[2], p, n, cLightDir, lightPlane.y, shadowMapSpot[2], sssColorCL, sssRadiusCL);
else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[3], p, n, cLightDir, lightPlane.y, shadowMapSpot[3], sssColorCL, sssRadiusCL);
#endif
} else {
#ifdef _ShadowMapAtlas

View File

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

View File

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

View File

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

View File

@ -151,6 +151,7 @@ void main() {
}
// 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)
+ sin(p.x * 20.0 / waterDisplace + speed * 1.3) * cos(p.y * 20.0 / waterDisplace + speed * 1.3) * 0.5)
/ 50.0 * waterDisplace;
@ -227,15 +228,19 @@ void main() {
// Blinn-Phong specular using half-vector, faded at horizon
vec3 h = normalize(v + ld);
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
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);
// 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
float fd = isSky ? 1.0 : abs(p.z - waterLevel);
float fd = isSky ? 1.0 : abs(geomZ - waterLevel);
if (fd < 0.1) {
// Based on foam by Owen Deery
// http://fire-face.com/personal/water

View File

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

View File

@ -90,8 +90,16 @@ class LightObject extends Object {
#if lnx_spot
if (data.raw.type == "spot"){
this.size = data.raw.spot_size;
this.blend = data.raw.spot_blend;
this.size = 0.0;
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

View File

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

View File

@ -116,6 +116,11 @@ class Postprocess {
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 = [
1, //0: Auto Exposure Strength
1 //1: Auto Exposure Speed
@ -406,6 +411,10 @@ class Postprocess {
v.x = auto_focus[0];
v.y = auto_focus[1];
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;

View File

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

View File

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

View File

@ -288,10 +288,16 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
wrd.world_defs += '_EmissionShaded'
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
if parse_surface or parse_opacity:
state.parents = []
state.parsed = set()
state.parsing = set()
state.node_uids = {}
state.node_uids_used = set()
state.normal_parsed = False
curshader = state.frag
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_ior = outs[6]
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:
curshader.write(f'basecol = {out_basecol};')
curshader.write(f'roughness = {out_roughness};')
@ -347,13 +376,14 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
# Volume
# parse_volume_input(node.inputs[1])
# Displacement
if parse_displacement and disp_enabled() and node.inputs[2].is_linked:
if disp_active and disp_geom != 'off':
state.parents = []
state.parsed = set()
state.parsing = set()
state.node_uids = {}
state.node_uids_used = set()
state.normal_parsed = False
rpdat = lnx.utils.get_rp()
if rpdat.lnx_rp_displacement == 'Tessellation' and state.tese is not None:
if disp_geom == 'tessellation' and state.tese is not None:
state.curshader = state.tese
else:
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))
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.parsed = set()
state.parsing = set()
state.node_uids = {}
state.node_uids_used = set()
state.normal_parsed = False
state.curshader = state.vert
@ -511,6 +544,9 @@ def parse_displacement_input(inp):
l = inp.links[0]
if l.from_node.type == 'REROUTE':
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)
else:
return None
@ -527,7 +563,7 @@ def parse_vector_input(inp: bpy.types.NodeSocket) -> vec3str:
st = link.from_socket.type
if st in ('RGB', 'RGBA', 'VECTOR'):
return res_var
elif st in ('VALUE', 'INT'):
elif st in ('VALUE', 'INT', 'BOOLEAN'):
return f'vec3({res_var})'
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')
@ -572,6 +608,7 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
'HUE_SAT',
'INVERT',
'MIX',
'MIX_RGB',
'BLACKBODY',
'VALTORGB',
'CURVE_VEC',
@ -590,6 +627,8 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
'TANGENT',
'TEX_COORD',
'UVMAP',
'VOLUME_INFO',
'BEVEL',
'BUMP',
'MAPPING',
'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;')
if strength != '1.0':
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')
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'):
# RGB to BW
return rgb_to_bw(res_var)
elif socket_type in ('VALUE', 'INT'):
elif socket_type in ('VALUE', 'INT', 'BOOLEAN'):
return res_var
elif socket_type == 'BOOLEAN':
return f'({res_var} ? 1.0 : 0.0)'
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')
return '0.0'
@ -745,6 +783,7 @@ def parse_value(node, socket):
'SEPXYZ',
'VECT_MATH',
'MAP_RANGE',
'VOLUME_INFO',
)
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:
"""Return the name of the variable that stores the parsed result
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
name = name.replace('_', '_x')
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."""
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:
res_var += state.get_parser_pass_suffix()
# Unparsed node
if not is_parsed(res_var):
state.parsed.add(res_var)
state.parsing.add(res_var)
st = link.from_socket.type
try:
if st in ('RGB', 'RGBA', 'VECTOR'):
res = parse_vector(link.from_node, link.from_socket)
if res is None:
@ -837,7 +878,7 @@ def write_result(link: bpy.types.NodeLink) -> Optional[str]:
return None
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)
if res is None:
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:
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:
state.curshader.write(f'{res_var} = {apply_screenspace_derivative_offset_if_required(res_var)};')
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
elif link.from_node.type == 'NORMAL_MAP':
return None
@ -877,7 +927,7 @@ def to_uniform(inp: bpy.types.NodeSocket):
def store_var_name(node: bpy.types.Node) -> str:
name = node_name(node.name)
name = node_uid(node)
if name[-1] == "_":
return name + '_x_store' # Prevent consecutive __
return name + '_store'
@ -919,7 +969,7 @@ def texture_store(node, tex, tex_name, to_linear=False, unpremultiply=False, tex
nor = 'TBN[2]'
else:
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))
elif spherical:
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
if state.curshader.shader_type == 'frag':
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:
coords = f'({coords}) + {dfdy_fine(coords)}'
coords = f'({coords}) + {dfdy_fine(coords)} * ({state.dxdy_scale})'
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):
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
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
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:
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 False
@ -1077,6 +1158,22 @@ def node_name(s: str) -> str:
s = s.replace('_', '_x')
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():
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):
lnx.assets.add(path)

View File

@ -1966,107 +1966,110 @@ float snoise(vec4 p) {
p = compatible_mod(p, 100000.0) + precision_correction;
return 0.8344 * noise_perlin(p);
}
"""
#define DEFINE_NOISE_FRACTAL(T) \\\
float noise_fbm(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\
T p = co; \\\
float fscale = 1.0; \\\
float amp = 1.0; \\\
float maxamp = 0.0; \\\
float sum = 0.0; \\\
for (int i = 0; i <= int(detail); i++) { \\\
float t = snoise(fscale * p); \\\
sum += t * amp; \\\
maxamp += amp; \\\
amp *= roughness; \\\
fscale *= lacunarity; \\\
} \\\
float rmd = detail - floor(detail); \\\
if (rmd != 0.0) { \\\
float t = snoise(fscale * p); \\\
float sum2 = sum + t * amp; \\\
return normalize ? mix(0.5 * sum / maxamp + 0.5, 0.5 * sum2 / (maxamp + amp) + 0.5, rmd) : mix(sum, sum2, rmd); \\\
} 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; \\\
# Fractal noise variants, emitted on demand per coordinate type.
# @T@ is replaced by the coordinate type (float/vec2/vec3/vec4).
str_tex_noise_fbm = """float noise_fbm(@T@ co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) {
@T@ p = co;
float fscale = 1.0;
float amp = 1.0;
float maxamp = 0.0;
float sum = 0.0;
for (int i = 0; i <= int(detail); i++) {
float t = snoise(fscale * p);
sum += t * amp;
maxamp += amp;
amp *= roughness;
fscale *= lacunarity;
}
float rmd = detail - floor(detail);
if (rmd != 0.0) {
float t = snoise(fscale * p);
float sum2 = sum + t * amp;
return normalize ? mix(0.5 * sum / maxamp + 0.5, 0.5 * sum2 / (maxamp + amp) + 0.5, rmd) : mix(sum, sum2, rmd);
} else {
return normalize ? 0.5 * sum / maxamp + 0.5 : sum;
}
}"""
DEFINE_NOISE_FRACTAL(float)
DEFINE_NOISE_FRACTAL(vec2)
DEFINE_NOISE_FRACTAL(vec3)
DEFINE_NOISE_FRACTAL(vec4)
str_tex_noise_multi_fractal = """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;
}"""
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; }
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
str_wavelength_to_rgb = """
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])
col2 = c.parse_vector_input(node.inputs[7])
# Store factor in variable for linked factor input
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])
fac = _mixrgb_fac(node, state)
if node.clamp_factor:
fac = f'clamp({fac}, 0.0, 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':
out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac)
return 'mix({0}, {1}, {2})'.format(col1, col2, fac)
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':
out_col = 'mix({0}, {0} * {1}, {2})'.format(col1, col2, fac)
return 'mix({0}, {0} * {1}, {2})'.format(col1, col2, fac)
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':
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':
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':
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':
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':
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':
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':
dodge = '{0} / max(vec3(1.0) - {1}, vec3(0.000001))'.format(col1, col2)
out_col = 'mix({0}, {1}, {2})'.format(col1, dodge, fac)
return 'clamp({0} / max(vec3(1.0) - {2} * {1}, vec3(0.000001)), vec3(0.0), vec3(1.0))'.format(col1, col2, fac)
elif blend == 'BURN':
burn = 'vec3(1.0) - (vec3(1.0) - {0}) / max({1}, vec3(0.000001))'.format(col1, col2)
out_col = 'mix({0}, {1}, {2})'.format(col1, burn, fac)
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)
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)
out_col = 'mix({0}, {1}, {2})'.format(col1, soft, fac)
return 'mix({0}, {1}, {2})'.format(col1, soft, fac)
elif blend == 'LINEAR_LIGHT':
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']:
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:
log.warn(f'MixRGB node: unsupported blend type {node.blend_type}.')
out_col = col1
log.warn(f'MixRGB node: unsupported blend type {blend}.')
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 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])
vec = c.parse_vector_input(node.inputs[1])
curves = node.mapping.curves
name = c.node_name(node.name)
name = c.node_uid(node)
# mapping.curves[0].points[0].handle_type
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,

View File

@ -89,7 +89,7 @@ def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB, out_socket: bpy.types.Nod
elems = node.color_ramp.elements
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):
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)
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:
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]})'
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};')
index = '0'
for i in range(1, len(elems)):
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};')
if interp == 'CONSTANT':
state.curshader.write(f'vec4 {ramp_store} = {cols_var}[{index_var}];')
else:
facs_var = c.node_name(node.name).upper() + '_FACS'
facs_var = c.node_uid(node).upper() + '_FACS'
if state.current_pass == ParserPass.REGULAR:
facs_entries = ', '.join(str(e.position) for e in elems)
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:
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
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:
if node.lnx_material_param:
nn = 'param_' + c.node_name(node.name)
nn = 'param_' + c.node_uid(node)
v = out_socket.default_value
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)
@ -163,7 +163,17 @@ def parse_geometry(node: bpy.types.ShaderNodeNewGeometry, out_socket: bpy.types.
# Incoming
elif out_socket == node.outputs[4]:
state.dxdy_varying_input_value = True
if state.curshader.shader_type == 'frag':
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
elif out_socket == node.outputs[5]:
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:
if node.lnx_material_param:
nn = 'param_' + c.node_name(node.name)
nn = 'param_' + c.node_uid(node)
value = node.outputs[0].default_value
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)
@ -463,3 +473,9 @@ def parse_wireframe(node: bpy.types.ShaderNodeWireframe, out_socket: bpy.types.N
# node.use_pixel_size
# size = c.parse_value_input(node.inputs[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])
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 '
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} = 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:
if state.parse_surface:
c.write_normal(node.inputs['Normal'])
parametrization = getattr(node, 'parametrization', 'COLOR')
parametrization = node.parametrization
if parametrization == 'COLOR':
color_socket = node.inputs.get('Color')
if color_socket is not None:
@ -457,7 +459,7 @@ if bpy.app.version >= (2, 83, 0):
rough_socket = node.inputs.get('Roughness')
if rough_socket is not None:
state.out_roughness = c.parse_value_input(rough_socket)
model = getattr(node, 'model', 'CHIANG')
model = node.model if bpy.app.version >= (4, 0, 0) else 'CHIANG'
if model == 'CHIANG':
radial_rough_socket = node.inputs.get('Radial Roughness')
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:
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_link = None
tex_default_file = None
@ -259,7 +259,7 @@ def parse_tex_magic(node: bpy.types.ShaderNodeTexMagic, out_socket: bpy.types.No
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]:
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'
distortion = c.parse_value_input(node.inputs['Distortion']) if 'Distortion' in node.inputs else '0.0'
dimensions = getattr(node, 'noise_dimensions', '3D')
noise_type = getattr(node, 'noise_type', 'FBM')
normalize = 'true' if getattr(node, 'normalize', True) else 'false'
dimensions = node.noise_dimensions
noise_type = node.noise_type if bpy.app.version >= (4, 1, 0) else 'FBM'
normalize = 'true' if bpy.app.version < (4, 0, 0) or node.normalize else 'false'
type_map = {
'FBM': 'noise_fbm',
'MULTIFRACTAL': 'noise_multi_fractal',
'RIDGED_MULTIFRACTAL': 'noise_ridged_multi_fractal',
'HYBRID_MULTIFRACTAL': 'noise_hybrid_multi_fractal',
'HETERO_TERRAIN': 'noise_hetero_terrain'
}
func_name = type_map.get(noise_type, 'noise_fbm')
if noise_type == 'MULTIFRACTAL':
func_name = 'noise_multi_fractal'
fractal_src = c_functions.str_tex_noise_multi_fractal
elif noise_type == 'RIDGED_MULTIFRACTAL':
func_name = 'noise_ridged_multi_fractal'
fractal_src = c_functions.str_tex_noise_ridged_multi_fractal
elif noise_type == 'HYBRID_MULTIFRACTAL':
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':
coord_type = 'float'
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
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_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}))"
else:
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
off_a = 'random_float_offset(1.0)'
off_b = 'random_float_offset(2.0)'
elif dimensions == '2D':
coord_type = 'vec2'
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
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_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}))"
else:
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
off_a = 'random_vec2_offset(2.0)'
off_b = 'random_vec2_offset(3.0)'
elif dimensions == '4D':
coord_type = 'vec4'
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
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_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}))"
else:
res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})"
off_a = 'random_vec4_offset(4.0)'
off_b = 'random_vec4_offset(5.0)'
else:
coord_type = 'vec3'
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
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:
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:
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
@ -513,7 +526,7 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket:
state.parsed.add(tex_store)
tex_name = c.node_name(node.name)
tex_name = c.node_uid(node)
tex_link = None
tex_default_file = None
is_lnx_mat_param = None
@ -525,8 +538,8 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket:
image,
tex_name,
c.mat_get_material(),
getattr(node, 'interpolation', 'Smart'),
getattr(node, 'extension', 'REPEAT')
node.interpolation,
node.extension if bpy.app.version >= (3, 1, 0) else 'REPEAT'
)
if tex is None:
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
m = 0
exp = '0.0'
if node.distance == 'MANHATTAN':
m = 1
elif node.distance == 'CHEBYCHEV':
@ -689,10 +703,13 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type
f = 4
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()
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:
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]:
c.write_procedurals()
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)
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}
if hasattr(node, 'wave_direction'):
wave_dir = dir_map.get(node.wave_direction, 0)
elif wave_type == 0:
if wave_type == 0:
wave_dir = dir_map.get(node.bands_direction, 0)
else:
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'
dimensions = getattr(node, 'noise_dimensions', '3D')
is_color = (out_socket == node.outputs[1]) or (getattr(out_socket, 'name', '') == 'Color')
dimensions = node.noise_dimensions
is_color = (out_socket == node.outputs[1]) or (out_socket.name == 'Color')
if dimensions == '1D':
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'])
vec = c.get_vector_input(node, ['Vector'])
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_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
strength = c.get_value_input(node, ['Strength'])
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'])
prev_pass = state.current_pass
prev_scale = state.dxdy_scale
state.dxdy_scale = filter_width
state.current_pass = ParserPass.DX_SCREEN_SPACE
height_dx = c.get_value_input(node, ['Height'])
state.current_pass = ParserPass.DY_SCREEN_SPACE
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'
if height_dx != height or height_dy != height:
tangent = f'{c.dfdx_fine("wposition")} + {nor} * (({height_dx} - {height}) * {distance})'
bitangent = f'{c.dfdy_fine("wposition")} + {nor} * (({height_dy} - {height}) * {distance})'
# Cross-product operand order, dFdy is flipped on d3d11
bitangent_first = utils.get_gapi() == 'direct3d11'
if node.invert:
bitangent_first = not bitangent_first
if bitangent_first:
# We need to normalize twice, once for the correct "weight" of the strength,
# once for having a normalized output vector (lerping vectors does not preserve magnitude)
res = f'normalize(mix({nor}, normalize(cross({bitangent}, {tangent})), {strength}))'
else:
res = f'normalize(mix({nor}, normalize(cross({tangent}, {bitangent})), {strength}))'
name = c.node_uid(node)
if '__' in name:
name = name.replace('_', '_x')
sfx = state.get_parser_pass_suffix()
invert = '-1.0' if node.invert else '1.0'
w = state.curshader.write
w(f'vec3 {name}_N{sfx} = normalize({nor});')
w(f'float {name}_dist{sfx} = ({distance}) * (gl_FrontFacing ? {invert} : -({invert}));')
w(f'vec3 {name}_dPdx{sfx} = {c.dfdx_fine("wposition")};')
w(f'vec3 {name}_dPdy{sfx} = {c.dfdy_fine("wposition")};')
w(f'vec3 {name}_Rx{sfx} = cross({name}_dPdy{sfx}, {name}_N{sfx});')
w(f'vec3 {name}_Ry{sfx} = cross({name}_N{sfx}, {name}_dPdx{sfx});')
w(f'float {name}_det{sfx} = dot({name}_dPdx{sfx}, {name}_Rx{sfx});')
w(f'vec2 {name}_dHd{sfx} = vec2({height_dx}, {height_dy}) - vec2({height});')
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:
res = nor
@ -96,7 +102,7 @@ def parse_mapping(node: bpy.types.ShaderNodeMapping, out_socket: bpy.types.NodeS
out = f"({out} - {location})"
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}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);")
@ -110,7 +116,7 @@ def parse_mapping(node: bpy.types.ShaderNodeMapping, out_socket: bpy.types.NodeS
out = f"({out} * {scale})"
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}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);")
@ -203,7 +209,8 @@ def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.t
nor = 'wnormal'
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}'
@ -221,12 +228,14 @@ def parse_vector_displacement(node: bpy.types.ShaderNodeVectorDisplacement, out_
offset = f'(({vector} - vec3({midlevel})) * {scale})'
if node.space == 'TANGENT':
state.curshader.add_uniform('mat4 W', link='_worldMatrix')
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}))'
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)'
elif node.space == 'OBJECT':
state.curshader.add_uniform('mat4 W', link='_worldMatrix')
return f'(vec4(W * vec4({offset}, 0.0)).xyz)'
else:
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(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:
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.write('wvpposition = gl_Position;')
# wvpposition.z / wvpposition.w

View File

@ -31,7 +31,8 @@ else:
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'}]
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)
if is_disp:
if rpdat.lnx_rp_displacement == 'Vertex':
if disp_geom == 'vertex':
frag.ins = vert.outs
vert.add_uniform('mat3 N', '_normalMatrix')
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'):
vert.add_out('vec3 vcolor')
vert.write_attrib('vcolor = col.rgb;')
vert.write('wposition += wnormal * disp;')
vert.write('wposition += disp;')
if shadowmap:
vert.add_uniform('mat4 LVP', '_lightViewProjectionMatrix')
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
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']
if is_displacement:
if rpdat.lnx_rp_displacement == 'Vertex':
if disp_geom == 'vertex':
frag.ins = vert.outs
else: # Tessellation
tesc = con_mesh.make_tesc()
@ -193,7 +194,7 @@ def make_base(con_mesh, parse_opacity):
vert.write_attrib('TBN = mat3(tangent, bitangent, wnormal);')
if is_displacement:
if rpdat.lnx_rp_displacement == 'Vertex':
if disp_geom == 'vertex':
sh = vert
else:
sh = tese

View File

@ -33,10 +33,28 @@ def make(context_id):
frag = con_refract.frag
tese = con_refract.tese
frag.add_include('std/gbuffer.glsl')
frag.add_out('vec4 fragColor[3]')
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 = ...;
frag.main = frag.main[:frag.main.rfind('fragColor')]
frag.write('\n')
@ -53,15 +71,21 @@ def make(context_id):
else:
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('vec3 finalColor = direct + indirect;')
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:
frag.write('vec3 finalColor = direct + indirect;')
frag.write('fragColor[0] = vec4(finalColor * opacity, 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(ior, 1.0 - opacity, packFloat2(basecol.r, basecol.g), basecol.b);')

View File

@ -19,8 +19,7 @@ else:
add_mesh_contexts = []
def disp_linked(output_node):
linked = output_node.inputs[2].is_linked
if not linked:
if not output_node.inputs[2].is_linked:
return False
# Leenkx PBR with unlinked height socket
l = output_node.inputs[2].links[0]
@ -51,7 +50,7 @@ def get_rpasses(material):
ar.append('translucent')
elif is_transluc(material) and not material.lnx_discard and not material.lnx_blending and rpdat.rp_ss_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')
if rpdat.rp_renderer == 'Forward' and rpdat.rp_depthprepass and not material.lnx_blending and not material.lnx_particle_flag:
ar.append('depth')

View File

@ -143,6 +143,10 @@ ALL_NODES: Dict[str, MaterialNodeMeta] = {
compute_dxdy_variants=ComputeDXDYVariant.NEVER
),
'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(
parse_func=nodes_input.parse_wireframe,
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),
# --- nodes_vector
'BEVEL': MaterialNodeMeta(parse_func=nodes_vector.parse_bevel),
'BUMP': MaterialNodeMeta(parse_func=nodes_vector.parse_bump),
'CURVE_VEC': MaterialNodeMeta(parse_func=nodes_vector.parse_curvevec),
'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)
if bpy.app.version < (4, 0, 0):
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)
if bpy.app.version >= (4, 0, 0):
ALL_NODES['BSDF_SHEEN'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfsheen)

View File

@ -1,5 +1,5 @@
from enum import IntEnum, unique
from typing import List, Set, Tuple, Union, Optional
from typing import Dict, List, Set, Tuple, Union, Optional
import bpy
@ -69,6 +69,9 @@ class ParserState:
# Cache for computing nodes only once
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
self.parse_surface = True
@ -90,6 +93,11 @@ class ParserState:
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
self.out_basecol: vec3str = 'vec3(0.8)'
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')
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_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_transparent: BoolProperty(name="Transparency", description="Enable shadows for transparent objects", default=True, update=update_renderpath)
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)
lnx_rp_displacement: EnumProperty(
items=[('Off', 'Off', 'Off'),
('Bump', 'Bump', 'Bump'),
('Vertex', 'Vertex', 'Vertex'),
('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_outer: IntProperty(name="Outer", description="Outer 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:
f.write(
"""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: