Merge branch 'master' into blender2.9

# Conflicts:
#	blender/arm/material/shader.py
#	blender/arm/props_collision_filter_mask.py
#	blender/arm/props_ui.py
This commit is contained in:
Moritz Brückner 2021-04-03 14:12:54 +02:00
commit f64419dd06
58 changed files with 2874 additions and 602 deletions

View file

@ -0,0 +1,31 @@
#version 450
// Include functions for gbuffer operations (packFloat2() etc.)
#include "../std/gbuffer.glsl"
// World-space normal from the vertex shader stage
in vec3 wnormal;
// Gbuffer output. Deferred rendering uses the following layout:
// [0]: normal x normal y roughness metallic/matID
// [1]: base color r base color g base color b occlusion/specular
out vec4 fragColor[2];
void main() {
// Pack normals into 2 components to fit into the gbuffer
vec3 n = normalize(wnormal);
n /= (abs(n.x) + abs(n.y) + abs(n.z));
n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);
// Define PBR material values
vec3 basecol = vec3(1.0);
float roughness = 0.0;
float metallic = 0.0;
float occlusion = 1.0;
float specular = 1.0;
uint materialId = 0;
// Store in gbuffer (see layout table above)
fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, materialId));
fragColor[1] = vec4(basecol.rgb, packFloat2(occlusion, specular));
}

View file

@ -0,0 +1,20 @@
#version 450
// World to view projection matrix to correctly position the vertex on screen
uniform mat4 WVP;
// Matrix to transform normals from local into world space
uniform mat3 N;
// Position and normal vectors of the current vertex in local space
// Armory packs the vertex data to preserve memory, so nor.z values are
// saved in pos.w
in vec4 pos; // pos.xyz, nor.w
in vec2 nor; // nor.xy
// Normal vector in world space
out vec3 wnormal;
void main() {
wnormal = normalize(N * vec3(nor.xy, pos.w));
gl_Position = WVP * vec4(pos.xyz, 1.0);
}

View file

@ -0,0 +1,9 @@
#version 450
// Color of each fragment on the screen
out vec4 fragColor;
void main() {
// Shadeless white color
fragColor = vec4(1.0);
}

View file

@ -0,0 +1,11 @@
#version 450
// World to view projection matrix to correctly position the vertex on screen
uniform mat4 WVP;
// Position vector of the current vertex in local space
in vec3 pos;
void main() {
gl_Position = WVP * vec4(pos, 1.0);
}

View file

@ -2,7 +2,6 @@
#include "compiled.inc"
#include "std/gbuffer.glsl"
#include "std/light.glsl"
#ifdef _Clusters
#include "std/clusters.glsl"
#endif
@ -22,6 +21,7 @@
uniform sampler2D gbufferD;
uniform sampler2D gbuffer0;
uniform sampler2D gbuffer1;
uniform sampler2D gbuffer2;
#ifdef _VoxelAOvar
uniform sampler3D voxels;
@ -80,14 +80,11 @@ uniform mat4 invVP;
#ifdef _ShadowMap
#ifdef _SinglePoint
//!uniform sampler2DShadow shadowMapSpot[1];
//!uniform mat4 LWVPSpot0;
//!uniform mat4 LWVPSpot[1];
#endif
#ifdef _Clusters
//!uniform sampler2DShadow shadowMapSpot[4];
//!uniform mat4 LWVPSpot0;
//!uniform mat4 LWVPSpot1;
//!uniform mat4 LWVPSpot2;
//!uniform mat4 LWVPSpot3;
//!uniform mat4 LWVPSpotArray[4];
#endif
#endif
#endif
@ -97,7 +94,7 @@ uniform vec3 eye;
uniform vec3 eyeLook;
#ifdef _Clusters
uniform vec4 lightsArray[maxLights * 2];
uniform vec4 lightsArray[maxLights * 3];
#ifdef _Spot
uniform vec4 lightsArraySpot[maxLights];
#endif
@ -109,21 +106,36 @@ uniform vec2 cameraPlane;
#ifdef _SinglePoint
#ifdef _Spot
//!uniform sampler2DShadow shadowMapSpot[1];
//!uniform mat4 LWVPSpot0;
//!uniform mat4 LWVPSpot[1];
#else
//!uniform samplerCubeShadow shadowMapPoint[1];
//!uniform vec2 lightProj;
#endif
#endif
#ifdef _Clusters
//!uniform samplerCubeShadow shadowMapPoint[4];
#ifdef _ShadowMapAtlas
#ifdef _SingleAtlas
uniform sampler2DShadow shadowMapAtlas;
#endif
#endif
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlasPoint;
#endif
//!uniform vec4 pointLightDataArray[4];
#else
//!uniform samplerCubeShadow shadowMapPoint[4];
#endif
//!uniform vec2 lightProj;
#ifdef _Spot
//!uniform sampler2DShadow shadowMapSpot[4];
//!uniform mat4 LWVPSpot0;
//!uniform mat4 LWVPSpot1;
//!uniform mat4 LWVPSpot2;
//!uniform mat4 LWVPSpot3;
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlasSpot;
#endif
#else
//!uniform sampler2DShadow shadowMapSpot[4];
#endif
//!uniform mat4 LWVPSpotArray[4];
#endif
#endif
#endif
@ -132,7 +144,13 @@ uniform vec2 cameraPlane;
uniform vec3 sunDir;
uniform vec3 sunCol;
#ifdef _ShadowMap
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSun;
#endif
#else
uniform sampler2DShadow shadowMap;
#endif
uniform float shadowsBias;
#ifdef _CSM
//!uniform vec4 casData[shadowmapCascades * 4 + 4];
@ -159,6 +177,8 @@ uniform sampler2D texClouds;
uniform float time;
#endif
#include "std/light.glsl"
in vec2 texCoord;
in vec3 viewRay;
out vec4 fragColor;
@ -186,6 +206,8 @@ void main() {
vec3 v = normalize(eye - p);
float dotNV = max(dot(n, v), 0.0);
vec4 g2 = textureLod(gbuffer2, texCoord, 0.0);
#ifdef _MicroShadowing
occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel
#endif
@ -196,7 +218,15 @@ void main() {
// Envmap
#ifdef _Irr
vec3 envl = shIrradiance(n, shirr);
if (g2.b < 0.5) {
envl = envl;
} else {
envl = vec3(1.0);
}
#ifdef _EnvTex
envl /= PI;
#endif
@ -289,10 +319,32 @@ void main() {
#ifdef _ShadowMap
#ifdef _CSM
svisibility = shadowTestCascade(shadowMap, eye, p + n * shadowsBias * 10, shadowsBias);
svisibility = shadowTestCascade(
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
shadowMapAtlasSun
#else
shadowMapAtlas
#endif
#else
shadowMap
#endif
, eye, p + n * shadowsBias * 10, shadowsBias
);
#else
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0);
if (lPos.w > 0.0) svisibility = shadowTest(shadowMap, lPos.xyz / lPos.w, shadowsBias);
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0);
if (lPos.w > 0.0) svisibility = shadowTest(
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
shadowMapAtlasSun
#else
shadowMapAtlas
#endif
#else
shadowMap
#endif
, lPos.xyz / lPos.w, shadowsBias
);
#endif
#endif
@ -335,7 +387,18 @@ void main() {
int casi, casindex;
mat4 LWVP = getCascadeMat(distance(eye, p), casi, casindex);
#endif
fragColor.rgb += fragColor.rgb * SSSSTransmittance(LWVP, p, n, sunDir, lightPlane.y, shadowMap);
fragColor.rgb += fragColor.rgb * SSSSTransmittance(
LWVP, p, n, sunDir, lightPlane.y,
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
shadowMapAtlasSun
#else
shadowMapAtlas
#endif
#else
shadowMap
#endif
);
}
#endif
@ -393,18 +456,19 @@ void main() {
n,
v,
dotNV,
lightsArray[li * 2].xyz, // lp
lightsArray[li * 2 + 1].xyz, // lightCol
lightsArray[li * 3].xyz, // lp
lightsArray[li * 3 + 1].xyz, // lightCol
albedo,
roughness,
occspec.y,
f0
#ifdef _ShadowMap
, li, lightsArray[li * 2].w, true // bias
// light index, shadow bias, cast_shadows
, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0
#endif
#ifdef _Spot
, lightsArray[li * 2 + 1].w != 0.0
, lightsArray[li * 2 + 1].w // cutoff
, lightsArray[li * 3 + 2].y != 0.0
, lightsArray[li * 3 + 2].y // cutoff
, lightsArraySpot[li].w // cutoff - exponent
, lightsArraySpot[li].xyz // spotDir
#endif

View file

@ -99,7 +99,7 @@
},
{
"name": "LWVP",
"link": "_biasLightWorldViewProjectionMatrix",
"link": "_biasLightWorldViewProjectionMatrixSun",
"ifndef": ["_CSM"],
"ifdef": ["_Sun", "_ShadowMap"]
},
@ -199,43 +199,37 @@
"ifdef": ["_SinglePoint", "_Spot"]
},
{
"name": "LWVPSpot0",
"name": "LWVPSpotArray",
"link": "_biasLightWorldViewProjectionMatrixSpotArray",
"ifdef": ["_Clusters", "_ShadowMap", "_Spot"]
},
{
"name": "pointLightDataArray",
"link": "_pointLightsAtlasArray",
"ifdef": ["_Clusters", "_ShadowMap", "_ShadowMapAtlas"]
},
{
"name": "LWVPSpot[0]",
"link": "_biasLightWorldViewProjectionMatrixSpot0",
"ifdef": ["_Spot", "_ShadowMap"]
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot1",
"name": "LWVPSpot[1]",
"link": "_biasLightWorldViewProjectionMatrixSpot1",
"ifdef": ["_Spot", "_ShadowMap"]
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot2",
"name": "LWVPSpot[2]",
"link": "_biasLightWorldViewProjectionMatrixSpot2",
"ifdef": ["_Spot", "_ShadowMap"]
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot3",
"link": "_biasLightWorldViewProjectionMatrixSpot3",
"ifdef": ["_Spot", "_ShadowMap"]
},
{
"name": "LWVPSpot0",
"link": "_biasLightWorldViewProjectionMatrixSpot0",
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot1",
"link": "_biasLightWorldViewProjectionMatrixSpot1",
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot2",
"link": "_biasLightWorldViewProjectionMatrixSpot2",
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot3",
"name": "LWVPSpot[3]",
"link": "_biasLightWorldViewProjectionMatrixSpot3",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
}
],

View file

@ -3,7 +3,6 @@
#include "compiled.inc"
#include "std/gbuffer.glsl"
#include "std/math.glsl"
#include "std/light_mobile.glsl"
#ifdef _Clusters
#include "std/clusters.glsl"
#endif
@ -30,12 +29,15 @@ uniform int envmapNumMipmaps;
uniform vec3 backgroundCol;
#endif
#ifdef _SMSizeUniform
//!uniform vec2 smSizeUniform;
#endif
uniform vec2 cameraProj;
uniform vec3 eye;
uniform vec3 eyeLook;
#ifdef _Clusters
uniform vec4 lightsArray[maxLights * 2];
uniform vec4 lightsArray[maxLights * 3];
#ifdef _Spot
uniform vec4 lightsArraySpot[maxLights];
#endif
@ -47,21 +49,36 @@ uniform vec2 cameraPlane;
#ifdef _SinglePoint
#ifdef _Spot
//!uniform sampler2DShadow shadowMapSpot[1];
//!uniform mat4 LWVPSpot0;
//!uniform mat4 LWVPSpot[1];
#else
//!uniform samplerCubeShadow shadowMapPoint[1];
//!uniform vec2 lightProj;
#endif
#endif
#ifdef _Clusters
//!uniform samplerCubeShadow shadowMapPoint[4];
#ifdef _ShadowMapAtlas
#ifdef _SingleAtlas
uniform sampler2DShadow shadowMapAtlas;
#endif
#endif
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlasPoint;
#endif
//!uniform vec4 pointLightDataArray[4];
#else
//!uniform samplerCubeShadow shadowMapPoint[4];
#endif
//!uniform vec2 lightProj;
#ifdef _Spot
//!uniform sampler2DShadow shadowMapSpot[4];
//!uniform mat4 LWVPSpot0;
//!uniform mat4 LWVPSpot1;
//!uniform mat4 LWVPSpot2;
//!uniform mat4 LWVPSpot3;
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlasSpot;
#endif
#else
//!uniform sampler2DShadow shadowMapSpot[4];
#endif
//!uniform mat4 LWVPSpotArray[4];
#endif
#endif
#endif
@ -70,7 +87,13 @@ uniform vec2 cameraPlane;
uniform vec3 sunDir;
uniform vec3 sunCol;
#ifdef _ShadowMap
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSun;
#endif
#else
uniform sampler2DShadow shadowMap;
#endif
uniform float shadowsBias;
#ifdef _CSM
//!uniform vec4 casData[shadowmapCascades * 4 + 4];
@ -90,6 +113,8 @@ uniform float pointBias;
#endif
#endif
#include "std/light_mobile.glsl"
in vec2 texCoord;
in vec3 viewRay;
out vec4 fragColor;
@ -168,10 +193,32 @@ void main() {
#ifdef _ShadowMap
#ifdef _CSM
svisibility = shadowTestCascade(shadowMap, eye, p + n * shadowsBias * 10, shadowsBias, shadowmapSize * vec2(shadowmapCascades, 1.0));
svisibility = shadowTestCascade(
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
shadowMapAtlasSun
#else
shadowMapAtlas
#endif
#else
shadowMap
#endif
, eye, p + n * shadowsBias * 10, shadowsBias
);
#else
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0);
if (lPos.w > 0.0) svisibility = shadowTest(shadowMap, lPos.xyz / lPos.w, shadowsBias, shadowmapSize);
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0);
if (lPos.w > 0.0) svisibility = shadowTest(
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
shadowMapAtlasSun
#else
shadowMapAtlas
#endif
#else
shadowMap
#endif
, lPos.xyz / lPos.w, shadowsBias
);
#endif
#endif
@ -211,18 +258,19 @@ void main() {
n,
v,
dotNV,
lightsArray[li * 2].xyz, // lp
lightsArray[li * 2 + 1].xyz, // lightCol
lightsArray[li * 3].xyz, // lp
lightsArray[li * 3 + 1].xyz, // lightCol
albedo,
roughness,
occspec.y,
f0
#ifdef _ShadowMap
, li, lightsArray[li * 2].w, true // bias
// light index, shadow bias, cast_shadows
, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0
#endif
#ifdef _Spot
, lightsArray[li * 2 + 1].w != 0.0
, lightsArray[li * 2 + 1].w // cutoff
, lightsArray[li * 3 + 2].y != 0.0
, lightsArray[li * 3 + 2].y // cutoff
, lightsArraySpot[li].w // cutoff - exponent
, lightsArraySpot[li].xyz // spotDir
#endif

View file

@ -88,7 +88,7 @@
},
{
"name": "LWVP",
"link": "_biasLightWorldViewProjectionMatrix",
"link": "_biasLightWorldViewProjectionMatrixSun",
"ifndef": ["_CSM"],
"ifdef": ["_Sun", "_ShadowMap"]
},
@ -107,6 +107,11 @@
"link": "_viewProjectionMatrix",
"ifdef": ["_SSRS"]
},
{
"name": "smSizeUniform",
"link": "_shadowMapSize",
"ifdef": ["_SMSizeUniform"]
},
{
"name": "lightProj",
"link": "_lightPlaneProj",
@ -138,24 +143,38 @@
"ifdef": ["_SinglePoint", "_Spot"]
},
{
"name": "LWVPSpot0",
"name": "LWVPSpotArray",
"link": "_biasLightWorldViewProjectionMatrixSpotArray",
"ifdef": ["_Clusters", "_ShadowMap", "_Spot"]
},
{
"name": "pointLightDataArray",
"link": "_pointLightsAtlasArray",
"ifdef": ["_Clusters", "_ShadowMap", "_ShadowMapAtlas"]
},
{
"name": "LWVPSpot[0]",
"link": "_biasLightWorldViewProjectionMatrixSpot0",
"ifdef": ["_Spot", "_ShadowMap"]
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot1",
"name": "LWVPSpot[1]",
"link": "_biasLightWorldViewProjectionMatrixSpot1",
"ifdef": ["_Spot", "_ShadowMap"]
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot2",
"name": "LWVPSpot[2]",
"link": "_biasLightWorldViewProjectionMatrixSpot2",
"ifdef": ["_Spot", "_ShadowMap"]
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot3",
"name": "LWVPSpot[3]",
"link": "_biasLightWorldViewProjectionMatrixSpot3",
"ifdef": ["_Spot", "_ShadowMap"]
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
}
],
"vertex_shader": "../include/pass_viewray.vert.glsl",

View file

@ -1,7 +1,4 @@
const int maxLights = 16;
const int maxLightsCluster = 4; // Ensure fast loop unroll before going higher
const float clusterNear = 3.0;
const vec3 clusterSlices = vec3(16, 16, 16);
int getClusterI(vec2 tc, float viewz, vec2 cameraPlane) {
@ -11,6 +8,10 @@ int getClusterI(vec2 tc, float viewz, vec2 cameraPlane) {
float z = log(viewz - cnear + 1.0) / log(cameraPlane.y - cnear + 1.0);
sliceZ = int(z * (clusterSlices.z - 1)) + 1;
}
// address gap between near plane and cluster near offset
else if (viewz >= cameraPlane.x) {
sliceZ = 1;
}
return int(tc.x * clusterSlices.x) +
int(int(tc.y * clusterSlices.y) * clusterSlices.x) +
int(sliceZ * clusterSlices.x * clusterSlices.y);

View file

@ -21,27 +21,41 @@
#endif
#ifdef _ShadowMap
#ifdef _SinglePoint
#ifdef _Spot
uniform sampler2DShadow shadowMapSpot[1];
uniform mat4 LWVPSpot0;
#else
uniform samplerCubeShadow shadowMapPoint[1];
uniform vec2 lightProj;
#ifdef _SinglePoint
#ifdef _Spot
#ifndef _LTC
uniform sampler2DShadow shadowMapSpot[1];
uniform mat4 LWVPSpot[1];
#endif
#else
uniform samplerCubeShadow shadowMapPoint[1];
uniform vec2 lightProj;
#endif
#endif
#endif
#ifdef _Clusters
uniform samplerCubeShadow shadowMapPoint[4];
uniform vec2 lightProj;
#ifdef _Spot
uniform sampler2DShadow shadowMapSpot[4];
uniform mat4 LWVPSpot0;
uniform mat4 LWVPSpot1;
uniform mat4 LWVPSpot2;
uniform mat4 LWVPSpot3;
#ifdef _Clusters
#ifdef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlas;
#endif
uniform vec2 lightProj;
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasPoint;
#endif
#else
uniform samplerCubeShadow shadowMapPoint[4];
#endif
#ifdef _Spot
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSpot;
#endif
#else
uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
#endif
uniform mat4 LWVPSpotArray[maxLightsCluster];
#endif
#endif
#endif
#endif
#ifdef _LTC
uniform vec3 lightArea0;
@ -51,17 +65,14 @@ uniform vec3 lightArea3;
uniform sampler2D sltcMat;
uniform sampler2D sltcMag;
#ifdef _ShadowMap
#ifndef _Spot
#ifndef _Spot
#ifdef _SinglePoint
uniform sampler2DShadow shadowMapSpot[1];
uniform mat4 LWVPSpot0;
uniform sampler2DShadow shadowMapSpot[1];
uniform mat4 LWVPSpot[1];
#endif
#ifdef _Clusters
uniform sampler2DShadow shadowMapSpot[4];
uniform mat4 LWVPSpot0;
uniform mat4 LWVPSpot1;
uniform mat4 LWVPSpot2;
uniform mat4 LWVPSpot3;
uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
uniform mat4 LWVPSpotArray[maxLightsCluster];
#endif
#endif
#endif
@ -132,24 +143,24 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _ShadowMap
if (receiveShadow) {
#ifdef _SinglePoint
vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0);
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
#endif
#ifdef _Clusters
if (index == 0) {
vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0);
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
}
else if (index == 1) {
vec4 lPos = LWVPSpot1 * vec4(p + n * bias * 10, 1.0);
vec4 lPos = LWVPSpot[1] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias);
}
else if (index == 2) {
vec4 lPos = LWVPSpot2 * vec4(p + n * bias * 10, 1.0);
vec4 lPos = LWVPSpot[2] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias);
}
else if (index == 3) {
vec4 lPos = LWVPSpot3 * vec4(p + n * bias * 10, 1.0);
vec4 lPos = LWVPSpot[3] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias);
}
#endif
@ -168,26 +179,26 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _ShadowMap
if (receiveShadow) {
#ifdef _SinglePoint
vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0);
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
#endif
#ifdef _Clusters
if (index == 0) {
vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
}
else if (index == 1) {
vec4 lPos = LWVPSpot1 * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias);
}
else if (index == 2) {
vec4 lPos = LWVPSpot2 * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias);
}
else if (index == 3) {
vec4 lPos = LWVPSpot3 * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias);
}
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
#ifdef _ShadowMapAtlas
direct *= shadowTest(
#ifndef _SingleAtlas
shadowMapAtlasSpot
#else
shadowMapAtlas
#endif
, lPos.xyz / lPos.w, bias
);
#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);
#endif
#endif
}
#endif
@ -207,10 +218,21 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#endif
#endif
#ifdef _Clusters
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);
#ifdef _ShadowMapAtlas
direct *= PCFFakeCube(
#ifndef _SingleAtlas
shadowMapAtlasPoint
#else
shadowMapAtlas
#endif
, ld, -l, bias, lightProj, n, index
);
#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);
#endif
#endif
}
#endif

View file

@ -8,27 +8,39 @@
#endif
#ifdef _ShadowMap
#ifdef _SinglePoint
#ifdef _Spot
uniform sampler2DShadow shadowMapSpot[1];
uniform mat4 LWVPSpot0;
#else
uniform samplerCubeShadow shadowMapPoint[1];
uniform vec2 lightProj;
#ifdef _SinglePoint
#ifdef _Spot
uniform sampler2DShadow shadowMapSpot[1];
uniform mat4 LWVPSpot[1];
#else
uniform samplerCubeShadow shadowMapPoint[1];
uniform vec2 lightProj;
#endif
#endif
#endif
#ifdef _Clusters
uniform samplerCubeShadow shadowMapPoint[4];
uniform vec2 lightProj;
#ifdef _Spot
uniform sampler2DShadow shadowMapSpot[4];
uniform mat4 LWVPSpot0;
uniform mat4 LWVPSpot1;
uniform mat4 LWVPSpot2;
uniform mat4 LWVPSpot3;
#ifdef _Clusters
#ifdef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlas;
#endif
uniform vec2 lightProj;
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasPoint;
#endif
#else
uniform samplerCubeShadow shadowMapPoint[4];
#endif
#ifdef _Spot
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSpot;
#endif
#else
uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
#endif
uniform mat4 LWVPSpotArray[maxLightsCluster];
#endif
#endif
#endif
#endif
vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol,
const vec3 albedo, const float rough, const float spec, const vec3 f0
@ -62,26 +74,26 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _ShadowMap
if (receiveShadow) {
#ifdef _SinglePoint
vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0);
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
#endif
#ifdef _Clusters
if (index == 0) {
vec4 lPos = LWVPSpot0 * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
}
else if (index == 1) {
vec4 lPos = LWVPSpot1 * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias);
}
else if (index == 2) {
vec4 lPos = LWVPSpot2 * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias);
}
else if (index == 3) {
vec4 lPos = LWVPSpot3 * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias);
}
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
#ifdef _ShadowMapAtlas
direct *= shadowTest(
#ifndef _SingleAtlas
shadowMapAtlasSpot
#else
shadowMapAtlas
#endif
, lPos.xyz / lPos.w, bias
);
#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);
#endif
#endif
}
#endif
@ -90,20 +102,32 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#endif
#ifdef _ShadowMap
#ifndef _Spot
if (receiveShadow) {
#ifdef _SinglePoint
#ifndef _Spot
direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n);
#endif
#endif
#ifdef _Clusters
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);
#ifdef _ShadowMapAtlas
direct *= PCFFakeCube(
#ifndef _SingleAtlas
shadowMapAtlasPoint
#else
shadowMapAtlas
#endif
, ld, -l, bias, lightProj, n, index
);
#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);
#endif
#endif
}
#endif
#endif
return direct;
}

View file

@ -11,6 +11,50 @@ uniform vec4 casData[shadowmapCascades * 4 + 4];
uniform vec2 smSizeUniform;
#endif
#ifdef _ShadowMap
#ifdef _Clusters
#ifdef _ShadowMapAtlas
uniform vec4 pointLightDataArray[maxLightsCluster * 6];
#endif
#endif
#endif
#ifdef _ShadowMapAtlas
// https://www.khronos.org/registry/OpenGL/specs/gl/glspec20.pdf // p:168
// https://www.gamedev.net/forums/topic/687535-implementing-a-cube-map-lookup-function/5337472/
vec2 sampleCube(vec3 dir, out int faceIndex) {
vec3 dirAbs = abs(dir);
float ma;
vec2 uv;
if(dirAbs.z >= dirAbs.x && dirAbs.z >= dirAbs.y) {
faceIndex = dir.z < 0.0 ? 5 : 4;
ma = 0.5 / dirAbs.z;
uv = vec2(dir.z < 0.0 ? -dir.x : dir.x, -dir.y);
}
else if(dirAbs.y >= dirAbs.x) {
faceIndex = dir.y < 0.0 ? 3 : 2;
ma = 0.5 / dirAbs.y;
uv = vec2(dir.x, dir.y < 0.0 ? -dir.z : dir.z);
}
else {
faceIndex = dir.x < 0.0 ? 1 : 0;
ma = 0.5 / dirAbs.x;
uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y);
}
// downscale uv a little to hide seams
// transform coordinates from clip space to texture space
#ifndef _FlipY
return uv * 0.9976 * ma + 0.5;
#else
#ifdef HLSL
return uv * 0.9976 * ma + 0.5;
#else
return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5;
#endif
#endif
}
#endif
float PCF(sampler2DShadow shadowMap, const vec2 uv, const float compare, const vec2 smSize) {
float result = texture(shadowMap, vec3(uv + (vec2(-1.0, -1.0) / smSize), compare));
result += texture(shadowMap, vec3(uv + (vec2(-1.0, 0.0) / smSize), compare));
@ -50,6 +94,185 @@ float PCFCube(samplerCubeShadow shadowMapCube, const vec3 lp, vec3 ml, const flo
return result / 9.0;
}
#ifdef _ShadowMapAtlas
// transform "out-of-bounds" coordinates to the correct face/coordinate system
vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) {
if (uv.x < 0.0) {
if (faceIndex == 0) { // X+
newFaceIndex = 4; // Z+
}
else if (faceIndex == 1) { // X-
newFaceIndex = 5; // Z-
}
else if (faceIndex == 2) { // Y+
newFaceIndex = 1; // X-
}
else if (faceIndex == 3) { // Y-
newFaceIndex = 1; // X-
}
else if (faceIndex == 4) { // Z+
newFaceIndex = 1; // X-
}
else { // Z-
newFaceIndex = 0; // X+
}
uv = vec2(1.0 + uv.x, uv.y);
}
else if (uv.x > 1.0) {
if (faceIndex == 0) { // X+
newFaceIndex = 5; // Z-
}
else if (faceIndex == 1) { // X-
newFaceIndex = 4; // Z+
}
else if (faceIndex == 2) { // Y+
newFaceIndex = 0; // X+
}
else if (faceIndex == 3) { // Y-
newFaceIndex = 0; // X+
}
else if (faceIndex == 4) { // Z+
newFaceIndex = 0; // X+
}
else { // Z-
newFaceIndex = 1; // X-
}
uv = vec2(1.0 - uv.x, uv.y);
}
else if (uv.y < 0.0) {
if (faceIndex == 0) { // X+
newFaceIndex = 2; // Y+
}
else if (faceIndex == 1) { // X-
newFaceIndex = 2; // Y+
}
else if (faceIndex == 2) { // Y+
newFaceIndex = 5; // Z-
}
else if (faceIndex == 3) { // Y-
newFaceIndex = 4; // Z+
}
else if (faceIndex == 4) { // Z+
newFaceIndex = 2; // Y+
}
else { // Z-
newFaceIndex = 2; // Y+
}
uv = vec2(uv.x, 1.0 + uv.y);
}
else if (uv.y > 1.0) {
if (faceIndex == 0) { // X+
newFaceIndex = 3; // Y-
}
else if (faceIndex == 1) { // X-
newFaceIndex = 3; // Y-
}
else if (faceIndex == 2) { // Y+
newFaceIndex = 4; // Z+
}
else if (faceIndex == 3) { // Y-
newFaceIndex = 5; // Z-
}
else if (faceIndex == 4) { // Z+
newFaceIndex = 3; // Y-
}
else { // Z-
newFaceIndex = 3; // Y-
}
uv = vec2(uv.x, 1.0 - uv.y);
} else {
newFaceIndex = faceIndex;
}
// cover corner cases too
return uv;
}
float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float bias, const vec2 lightProj, const vec3 n, const int index) {
const vec2 smSize = smSizeUniform; // TODO: incorrect...
const float compare = lpToDepth(lp, lightProj) - bias * 1.5;
ml = ml + n * bias * 20;
int faceIndex = 0;
const int lightIndex = index * 6;
const vec2 uv = sampleCube(ml, faceIndex);
vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas
vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
#endif
float result = texture(shadowMap, vec3(uvtiled, compare));
// soft shadowing
int newFaceIndex = 0;
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize)));
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
#endif
result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare));
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize)));
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
#endif
result += texture(shadowMap, vec3(uvtiled, compare));
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize)));
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
#endif
result += texture(shadowMap, vec3(uvtiled, compare));
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, -1.0) / smSize)));
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
#endif
result += texture(shadowMap, vec3(uvtiled, compare));
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, 1.0) / smSize)));
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
#endif
result += texture(shadowMap, vec3(uvtiled, compare));
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, -1.0) / smSize)));
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
#endif
result += texture(shadowMap, vec3(uvtiled, compare));
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 0.0) / smSize)));
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
#endif
result += texture(shadowMap, vec3(uvtiled, compare));
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 1.0) / smSize)));
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
#endif
result += texture(shadowMap, vec3(uvtiled, compare));
return result / 9.0;
}
#endif
float shadowTest(sampler2DShadow shadowMap, const vec3 lPos, const float shadowsBias) {
#ifdef _SMSizeUniform
vec2 smSize = smSizeUniform;
@ -95,7 +318,7 @@ mat4 getCascadeMat(const float d, out int casi, out int casIndex) {
float shadowTestCascade(sampler2DShadow shadowMap, const vec3 eye, const vec3 p, const float shadowsBias) {
#ifdef _SMSizeUniform
vec2 smSize = smSizeUniform * vec2(shadowmapCascades, 1.0);
vec2 smSize = smSizeUniform;
#else
const vec2 smSize = shadowmapSize * vec2(shadowmapCascades, 1.0);
#endif

View file

@ -12,7 +12,7 @@ uniform sampler2D gbufferD;
uniform sampler2D snoise;
#ifdef _Clusters
uniform vec4 lightsArray[maxLights * 2];
uniform vec4 lightsArray[maxLights * 3];
#ifdef _Spot
uniform vec4 lightsArraySpot[maxLights];
#endif
@ -24,7 +24,7 @@ uniform vec2 cameraPlane;
#ifdef _SinglePoint
#ifdef _Spot
uniform sampler2DShadow shadowMapSpot[1];
uniform mat4 LWVPSpot0;
uniform mat4 LWVPSpot[1];
#else
uniform samplerCubeShadow shadowMapPoint[1];
uniform vec2 lightProj;
@ -35,10 +35,7 @@ uniform vec2 cameraPlane;
uniform vec2 lightProj;
#ifdef _Spot
uniform sampler2DShadow shadowMapSpot[4];
uniform mat4 LWVPSpot0;
uniform mat4 LWVPSpot1;
uniform mat4 LWVPSpot2;
uniform mat4 LWVPSpot3;
uniform mat4 LWVPSpot[maxLightsCluster];
#endif
#endif
#endif
@ -103,7 +100,7 @@ void rayStep(inout vec3 curPos, inout float curOpticalDepth, inout float scatter
#ifdef _SinglePoint
#ifdef _Spot
vec4 lPos = LWVPSpot0 * vec4(curPos, 1.0);
vec4 lPos = LWVPSpot[0] * vec4(curPos, 1.0);
visibility = shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, pointBias);
float spotEffect = dot(spotDir, normalize(pointPos - curPos)); // lightDir
if (spotEffect < spotData.x) { // x - cutoff, y - cutoff - exponent

View file

@ -63,7 +63,7 @@
},
{
"name": "LWVP",
"link": "_biasLightWorldViewProjectionMatrix",
"link": "_biasLightWorldViewProjectionMatrixSun",
"ifndef": ["_CSM"],
"ifdef": ["_Sun", "_ShadowMap"]
},
@ -108,23 +108,32 @@
"ifdef": ["_SinglePoint", "_Spot"]
},
{
"name": "LWVPSpot0",
"name": "LWVPSpotArray",
"link": "_biasLightWorldViewProjectionMatrixSpotArray",
"ifdef": ["_Clusters", "_ShadowMap", "_Spot"]
},
{
"name": "LWVPSpot[0]",
"link": "_biasLightWorldViewProjectionMatrixSpot0",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_Spot", "_ShadowMap"]
},
{
"name": "LWVPSpot1",
"name": "LWVPSpot[1]",
"link": "_biasLightWorldViewProjectionMatrixSpot1",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_Spot", "_ShadowMap"]
},
{
"name": "LWVPSpot2",
"name": "LWVPSpot[2]",
"link": "_biasLightWorldViewProjectionMatrixSpot2",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_Spot", "_ShadowMap"]
},
{
"name": "LWVPSpot3",
"name": "LWVPSpot[3]",
"link": "_biasLightWorldViewProjectionMatrixSpot3",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_Spot", "_ShadowMap"]
}
],

View file

@ -1,18 +1,18 @@
package armory.logicnode;
import kha.FastFloat;
class ClampNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var value: kha.FastFloat = inputs[0].get();
var min: kha.FastFloat = inputs[1].get();
var max: kha.FastFloat = inputs[2].get();
override function get(from: Int): FastFloat {
var value = inputs[0].get();
var min = inputs[1].get();
var max = inputs[2].get();
if (value == null || min == null || max == null) return null;
value <= min ? return min : value >= max ? return max : return value;
return value < min ? min : value > max ? max : value;
}
}

View file

@ -15,7 +15,7 @@ class PickLocationNode extends LogicNode {
var object: Object = inputs[0].get();
var coords: Vec4 = inputs[1].get();
if (object == null || coords == null) null;
if (object == null || coords == null) return null;
#if arm_physics
var physics = armory.trait.physics.PhysicsWorld.active;

View file

@ -12,12 +12,13 @@ class PickObjectNode extends LogicNode {
override function get(from: Int): Dynamic {
var coords: Vec4 = inputs[0].get();
var mask: Int = inputs[1].get();
if (coords == null) return null;
#if arm_physics
var physics = armory.trait.physics.PhysicsWorld.active;
var rb = physics.pickClosest(coords.x, coords.y);
var rb = physics.pickClosest(coords.x, coords.y, mask);
if (rb == null) return null;
if (from == 0) { // Object

View file

@ -5,8 +5,6 @@ import armory.system.Event;
class SendEventNode extends LogicNode {
var entries: Array<TEvent> = null;
public function new(tree: LogicTree) {
super(tree);
}
@ -17,13 +15,9 @@ class SendEventNode extends LogicNode {
if (object == null) return;
var all = Event.get(name);
if (all != null) {
entries = [];
for (e in all) if (e.mask == object.uid) entries.push(e);
}
var entries = Event.get(name);
if (entries == null) return;
for (e in entries) e.onEvent();
for (e in entries) if (e.mask == object.uid) e.onEvent();
runOutput(0);
}

View file

@ -4,8 +4,6 @@ import armory.system.Event;
class SendGlobalEventNode extends LogicNode {
var entries: Array<TEvent> = null;
public function new(tree: LogicTree) {
super(tree);
}
@ -13,7 +11,7 @@ class SendGlobalEventNode extends LogicNode {
override function run(from: Int) {
var name: String = inputs[1].get();
entries = Event.get(name);
var entries = Event.get(name);
if (entries == null) return; // Event does not exist
for (e in entries) e.onEvent();

View file

@ -1,6 +1,7 @@
package armory.renderpath;
import iron.RenderPath;
import iron.object.LightObject;
class Inc {
@ -39,6 +40,175 @@ class Inc {
#end
}
#if arm_shadowmap_atlas
public static function updatePointLightAtlasData(): Void {
var atlas = ShadowMapAtlas.shadowMapAtlases.get(ShadowMapAtlas.shadowMapAtlasName("point"));
if (atlas != null) {
if(LightObject.pointLightsData == null) {
LightObject.pointLightsData = new kha.arrays.Float32Array(
LightObject.maxLightsCluster * ShadowMapTile.tilesLightType("point") * 4 ); // max possible visible lights * 6 or 2 (faces) * 4 (xyzw)
}
var n = iron.Scene.active.lights.length > LightObject.maxLightsCluster ? LightObject.maxLightsCluster : iron.Scene.active.lights.length;
var i = 0;
var j = 0;
for (light in iron.Scene.active.lights) {
if (i >= n)
break;
if (LightObject.discardLightCulled(light)) continue;
if (light.data.raw.type == "point") {
if (!light.data.raw.cast_shadow) {
j += 4 * 6;
continue;
}
for(k in 0...6) {
LightObject.pointLightsData[j ] = light.tileOffsetX[k]; // posx
LightObject.pointLightsData[j + 1] = light.tileOffsetY[k]; // posy
LightObject.pointLightsData[j + 2] = light.tileScale[k]; // tile scale factor relative to atlas
LightObject.pointLightsData[j + 3] = 0; // padding
j += 4;
}
}
i++;
}
}
}
public static function bindShadowMapAtlas() {
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
path.bindTarget(atlas.target, atlas.target);
}
}
static function getShadowMapAtlas(atlas:ShadowMapAtlas):String {
inline function createDepthTarget(name: String, size: Int) {
var t = new RenderTargetRaw();
t.name = name;
t.width = t.height = size;
t.format = "DEPTH16";
return path.createRenderTarget(t);
}
var rt = path.renderTargets.get(atlas.target);
// Create shadowmap atlas texture on the fly and replace existing on size change
if (rt == null) {
rt = createDepthTarget(atlas.target, atlas.sizew);
}
else if (atlas.updateRenderTarget) {
atlas.updateRenderTarget = false;
// Resize shadow map
rt.unload();
rt = createDepthTarget(atlas.target, atlas.sizew);
}
return atlas.target;
}
public static function drawShadowMapAtlas() {
#if rp_shadowmap
#if rp_probes
// Share shadow map with probe
if (lastFrame == RenderPath.active.frame)
return;
lastFrame = RenderPath.active.frame;
#end
// add new lights to the atlases
for (light in iron.Scene.active.lights) {
if (!light.lightInAtlas && !light.culledLight && light.visible && light.shadowMapScale > 0.0
&& light.data.raw.strength > 0.0 && light.data.raw.cast_shadow) {
light.lightInAtlas = ShadowMapAtlas.addLight(light);
}
}
// update point light data before rendering
updatePointLightAtlasData();
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
var tilesToRemove = [];
#if arm_shadowmap_atlas_lod
var tilesToChangeSize = [];
#end
var shadowmap = getShadowMapAtlas(atlas);
path.setTargetStream(shadowmap);
path.clearTarget(null, 1.0);
for (tile in atlas.activeTiles) {
if (tile.light == null || !tile.light.visible || tile.light.culledLight
|| !tile.light.data.raw.cast_shadow || tile.light.data.raw.strength == 0) {
tile.unlockLight = true;
tilesToRemove.push(tile);
continue;
}
#if arm_shadowmap_atlas_lod
var newTileSize = atlas.getTileSize(tile.light.shadowMapScale);
if (newTileSize != tile.size) {
if (newTileSize == 0) {
tile.unlockLight = true;
tilesToRemove.push(tile);
continue;
}
// queue for size change
tile.newTileSize = newTileSize;
tilesToChangeSize.push(tile);
}
#end
// set the tile offset for this tile and every linked tile to this one
var j = 0;
tile.forEachTileLinked(function (lTile) {
tile.light.tileOffsetX[j] = (lTile.coordsX == 0) ? 0.0 : lTile.coordsX / atlas.sizew;
tile.light.tileOffsetY[j] = (lTile.coordsY == 0) ? 0.0 : lTile.coordsY / atlas.sizew;
tile.light.tileScale[j] = lTile.size / atlas.sizew;
j++;
});
// set shadowmap size for uniform
tile.light.data.raw.shadowmap_size = atlas.sizew;
path.light = tile.light;
var face = 0;
var faces = ShadowMapTile.tilesLightType(tile.light.data.raw.type);
tile.forEachTileLinked(function (lTile) {
if (faces > 1) {
#if arm_csm
switch (tile.light.data.raw.type) {
case "sun": tile.light.setCascade(iron.Scene.active.camera, face);
case "point": path.currentFace = face;
}
#else
path.currentFace = face;
#end
face++;
}
path.setCurrentViewportWithOffset(lTile.size, lTile.size, lTile.coordsX, lTile.coordsY);
path.drawMeshesStream("shadowmap");
});
path.currentFace = -1;
}
path.endStream();
#if arm_shadowmap_atlas_lod
for (tile in tilesToChangeSize) {
tilesToRemove.push(tile);
var newTile = ShadowMapTile.assignTiles(tile.light, atlas, tile);
if (newTile != null)
atlas.activeTiles.push(newTile);
}
// update point light data after changing size of tiles to avoid render issues
updatePointLightAtlasData();
#end
for (tile in tilesToRemove) {
atlas.activeTiles.remove(tile);
tile.freeTile();
}
}
#end // rp_shadowmap
}
#else
public static function bindShadowMap() {
for (l in iron.Scene.active.lights) {
if (!l.visible || l.data.raw.type != "sun") continue;
@ -56,10 +226,15 @@ class Inc {
}
}
static function shadowMapName(l: iron.object.LightObject): String {
if (l.data.raw.type == "sun") return "shadowMap";
if (l.data.raw.type == "point") return "shadowMapPoint[" + pointIndex + "]";
else return "shadowMapSpot[" + spotIndex + "]";
static function shadowMapName(light: LightObject): String {
switch (light.data.raw.type) {
case "sun":
return "shadowMap";
case "point":
return "shadowMapPoint[" + pointIndex + "]";
default:
return "shadowMapSpot[" + spotIndex + "]";
}
}
static function getShadowMap(l: iron.object.LightObject): String {
@ -130,6 +305,7 @@ class Inc {
#end // rp_shadowmap
}
#end
public static function applyConfig() {
#if arm_config
@ -203,7 +379,11 @@ class Inc {
path.setTarget("accum", ["revealage"]);
#if rp_shadowmap
{
#if arm_shadowmap_atlas
bindShadowMapAtlas();
#else
bindShadowMap();
#end
}
#end
path.drawMeshes("translucent");
@ -342,3 +522,490 @@ class Inc {
#end
}
}
#if arm_shadowmap_atlas
class ShadowMapAtlas {
public var target: String;
public var baseTileSizeConst: Int;
public var maxAtlasSizeConst: Int;
public var sizew: Int;
public var sizeh: Int;
public var currTileOffset = 0;
public var tiles: Array<ShadowMapTile> = [];
public var activeTiles: Array<ShadowMapTile> = [];
public var depth = 1;
#if arm_shadowmap_atlas_lod
static var tileSizes: Array<Int> = [];
static var tileSizeFactor: Array<Float> = [];
#end
public var updateRenderTarget = false;
public static var shadowMapAtlases:Map<String, ShadowMapAtlas> = new Map(); // map a shadowmap atlas to their light type
function new(light: LightObject) {
var maxTileSize = shadowMapAtlasSize(light);
this.target = shadowMapAtlasName(light.data.raw.type);
this.sizew = this.sizeh = this.baseTileSizeConst = maxTileSize;
this.depth = getSubdivisions();
this.maxAtlasSizeConst = getMaxAtlasSize(light.data.raw.type);
#if arm_shadowmap_atlas_lod
if (tileSizes.length == 0)
computeTileSizes(maxTileSize, depth);
#end
}
/**
* Adds a light to an atlas. The atlas is decided based on the type of the light
* @param light of type LightObject to be added to an yatlas
* @return if the light was added succesfully
*/
public static function addLight(light: LightObject): Bool {
// check if light can be added based on culling
if (light.culledLight || light.shadowMapScale == 0.0)
return false;
var atlasName = shadowMapAtlasName(light.data.raw.type);
var atlas = shadowMapAtlases.get(atlasName);
if (atlas == null) {
// create a new atlas
atlas = new ShadowMapAtlas(light);
shadowMapAtlases.set(atlasName, atlas);
}
// find a free tile for this light
var mainTile = ShadowMapTile.assignTiles(light, atlas, null);
if (mainTile == null)
return false;
// push main tile to active tiles
atlas.activeTiles.push(mainTile);
return true;
}
static inline function shadowMapAtlasSize(light:LightObject):Int {
// TODO: this can break because we are changing shadowmap_size elsewhere.
return light.data.raw.shadowmap_size;
}
public function getTileSize(shadowMapScale: Float): Int {
#if arm_shadowmap_atlas_lod
// find the first scale factor that is smaller to the shadowmap scale, and then return the previous one.
var i = 0;
for (sizeFactor in tileSizeFactor) {
if (sizeFactor < shadowMapScale) break;
i++;
}
return tileSizes[i - 1];
#else
return this.baseTileSizeConst;
#end
}
#if arm_shadowmap_atlas_lod
static function computeTileSizes(maxTileSize: Int, depth: Int): Void {
// find the highest value based on the calculation done in the cluster code
var base = LightObject.zToShadowMapScale(0, 16);
var subdiv = base / depth;
for(i in 0...depth){
tileSizes.push(Std.int(maxTileSize / Math.pow(2, i)));
tileSizeFactor.push(base);
base -= subdiv;
}
tileSizes.push(0);
tileSizeFactor.push(0.0);
}
#end
public inline function atlasLimitReached() {
// asume square atlas
return (currTileOffset + 1) * baseTileSizeConst > maxAtlasSizeConst;
}
public static inline function shadowMapAtlasName(type: String): String {
#if arm_shadowmap_atlas_single_map
return "shadowMapAtlas";
#else
switch (type) {
case "point":
return "shadowMapAtlasPoint";
case "sun":
return "shadowMapAtlasSun";
default:
return "shadowMapAtlasSpot";
}
#end
}
public static inline function getSubdivisions(): Int {
#if (rp_shadowmap_atlas_lod_subdivisions == 2)
return 2;
#elseif (rp_shadowmap_atlas_lod_subdivisions == 3)
return 3;
#elseif (rp_shadowmap_atlas_lod_subdivisions == 4)
return 4;
#elseif (rp_shadowmap_atlas_lod_subdivisions == 5)
return 5;
#elseif (rp_shadowmap_atlas_lod_subdivisions == 6)
return 6;
#elseif (rp_shadowmap_atlas_lod_subdivisions == 7)
return 7;
#elseif (rp_shadowmap_atlas_lod_subdivisions == 8)
return 8;
#elseif (!arm_shadowmap_atlas_lod)
return 1;
#end
}
public static inline function getMaxAtlasSize(type: String): Int {
#if arm_shadowmap_atlas_single_map
#if (rp_shadowmap_atlas_max_size == 1024)
return 1024;
#elseif (rp_shadowmap_atlas_max_size == 2048)
return 2048;
#elseif (rp_shadowmap_atlas_max_size == 4096)
return 4096;
#elseif (rp_shadowmap_atlas_max_size == 8192)
return 8192;
#elseif (rp_shadowmap_atlas_max_size == 16384)
return 16384;
#end
#else
switch (type) {
case "point": {
#if (rp_shadowmap_atlas_max_size_point == 1024)
return 1024;
#elseif (rp_shadowmap_atlas_max_size_point == 2048)
return 2048;
#elseif (rp_shadowmap_atlas_max_size_point == 4096)
return 4096;
#elseif (rp_shadowmap_atlas_max_size_point == 8192)
return 8192;
#elseif (rp_shadowmap_atlas_max_size_point == 16384)
return 16384;
#end
}
case "spot": {
#if (rp_shadowmap_atlas_max_size_spot == 1024)
return 1024;
#elseif (rp_shadowmap_atlas_max_size_spot == 2048)
return 2048;
#elseif (rp_shadowmap_atlas_max_size_spot == 4096)
return 4096;
#elseif (rp_shadowmap_atlas_max_size_spot == 8192)
return 8192;
#elseif (rp_shadowmap_atlas_max_size_spot == 16384)
return 16384;
#end
}
case "sun": {
#if (rp_shadowmap_atlas_max_size_sun == 1024)
return 1024;
#elseif (rp_shadowmap_atlas_max_size_sun == 2048)
return 2048;
#elseif (rp_shadowmap_atlas_max_size_sun == 4096)
return 4096;
#elseif (rp_shadowmap_atlas_max_size_sun == 8192)
return 8192;
#elseif (rp_shadowmap_atlas_max_size_sun == 16384)
return 16384;
#end
}
default: {
#if (rp_shadowmap_atlas_max_size == 1024)
return 1024;
#elseif (rp_shadowmap_atlas_max_size == 2048)
return 2048;
#elseif (rp_shadowmap_atlas_max_size == 4096)
return 4096;
#elseif (rp_shadowmap_atlas_max_size == 8192)
return 8192;
#elseif (rp_shadowmap_atlas_max_size == 16384)
return 16384;
#end
}
}
#end
}
}
class ShadowMapTile {
public var light:Null<LightObject> = null;
public var coordsX:Int;
public var coordsY:Int;
public var size:Int;
public var tiles:Array<ShadowMapTile> = [];
public var linkedTile:ShadowMapTile = null;
#if arm_shadowmap_atlas_lod
public var parentTile: ShadowMapTile = null;
public var activeSubTiles: Int = 0;
public var newTileSize: Int = -1;
static var tilePattern = [[0, 0], [1, 0], [0, 1], [1, 1]];
#end
function new(coordsX: Int, coordsY: Int, size: Int) {
this.coordsX = coordsX;
this.coordsY = coordsY;
this.size = size;
}
public static function assignTiles(light: LightObject, atlas: ShadowMapAtlas, oldTile: ShadowMapTile): ShadowMapTile {
var tileSize = 0;
#if arm_shadowmap_atlas_lod
if (oldTile != null && oldTile.newTileSize != -1) {
// reuse tilesize instead of computing it again
tileSize = oldTile.newTileSize;
oldTile.newTileSize = -1;
}
else
#end
tileSize = atlas.getTileSize(light.shadowMapScale);
if (tileSize == 0)
return null;
var tiles = [];
tiles = findCreateTiles(light, oldTile, atlas, tilesLightType(light.data.raw.type), tileSize);
// lock new tiles with light
for (tile in tiles)
tile.lockTile(light);
return linkTiles(tiles);
}
static inline function linkTiles(tiles: Array<ShadowMapTile>): ShadowMapTile {
if (tiles.length > 1) {
var linkedTile = tiles[0];
for (i in 1...tiles.length) {
linkedTile.linkedTile = tiles[i];
linkedTile = tiles[i];
}
}
return tiles[0];
}
static inline function findCreateTiles(light: LightObject, oldTile: ShadowMapTile, atlas: ShadowMapAtlas, tilesPerLightType: Int, tileSize: Int): Array<ShadowMapTile> {
var tilesFound: Array<ShadowMapTile> = [];
var updateAtlas = false;
while (tilesFound.length < tilesPerLightType) {
findTiles(light, oldTile, atlas.tiles, tileSize, tilesPerLightType, tilesFound);
if (tilesFound.length < tilesPerLightType) {
tilesFound = []; // empty tilesFound
// skip creating more tiles if limit has been reached
if (atlas.atlasLimitReached())
break;
createTiles(atlas.tiles, atlas.baseTileSizeConst, atlas.depth, atlas.currTileOffset, atlas.currTileOffset);
atlas.currTileOffset++;
// update texture to accomodate new size
atlas.updateRenderTarget = true;
atlas.sizew = atlas.sizeh = atlas.currTileOffset * atlas.baseTileSizeConst;
}
}
return tilesFound;
}
inline static function findTiles(light:LightObject, oldTile: ShadowMapTile,
tiles: Array<ShadowMapTile>, size: Int, tilesCount: Int, tilesFound: Array<ShadowMapTile>): Void {
#if arm_shadowmap_atlas_lod
if (oldTile != null) {
// reuse children tiles
if (size < oldTile.size) {
oldTile.forEachTileLinked(function(lTile) {
var childTile = findFreeChildTile(lTile, size);
tilesFound.push(childTile);
});
}
// reuse parent tiles
else {
oldTile.forEachTileLinked(function(lTile) {
// find out if parents tiles are not occupied
var parentTile = findFreeParentTile(lTile, size);
// if parent is free, add it to found tiles
if (parentTile != null)
tilesFound.push(parentTile);
});
if (tilesFound.length < tilesCount) {
// find naively the rest of the tiles that couldn't be reused
findTilesNaive(light, tiles, size, tilesCount, tilesFound);
}
}
}
else
#end
findTilesNaive(light, tiles, size, tilesCount, tilesFound);
}
#if arm_shadowmap_atlas_lod
static inline function findFreeChildTile(tile: ShadowMapTile, size: Int): ShadowMapTile {
var childrenTile = tile;
while (size < childrenTile.size) {
childrenTile = childrenTile.tiles[0];
}
return childrenTile;
}
static inline function findFreeParentTile(tile: ShadowMapTile, size: Int): ShadowMapTile {
var parentTile = tile;
while (size > parentTile.size) {
parentTile = parentTile.parentTile;
// stop if parent tile is occupied
if (parentTile.activeSubTiles > 1) {
parentTile = null;
break;
}
}
return parentTile;
}
#end
static function findTilesNaive(light:LightObject, tiles: Array<ShadowMapTile>, size: Int, tilesCount: Int, tilesFound: Array<ShadowMapTile>): Void {
for (tile in tiles) {
if (tile.size == size) {
if (tile.light == null #if arm_shadowmap_atlas_lod && tile.activeSubTiles == 0 #end) {
tilesFound.push(tile);
// stop after finding enough tiles
if (tilesFound.length == tilesCount)
return;
}
}
else {
// skip over if end of the tree or tile is occupied
if (tile.tiles.length == 0 || tile.light != null)
continue;
findTilesNaive(light, tile.tiles, size, tilesCount, tilesFound);
// skip iterating over the rest of the tiles if found enough
if (tilesFound.length == tilesCount)
return;
}
}
}
// create a basic tile and subdivide it if needed
public static function createTiles(tiles:Array<ShadowMapTile>, size:Int, depth: Int, baseX:Int, baseY:Int) {
var i = baseX;
var j = 0;
var lastTile = tiles.length;
// assume occupied tiles start from 1 line before the base x
var occupiedTiles = baseX - 1;
while (i >= 0) {
if (i <= occupiedTiles) { // avoid overriding tiles
j = baseY;
}
while (j <= baseY) {
// create base tile of max-size
tiles.push(new ShadowMapTile(size * i, size * j, size));
#if arm_shadowmap_atlas_lod
tiles[lastTile].tiles = subDivTile(tiles[lastTile], size, size * i, size * j, depth - 1);
#end
lastTile++;
j++;
}
i--;
}
}
#if arm_shadowmap_atlas_lod
static function subDivTile(parent: ShadowMapTile, size: Int, baseCoordsX: Int, baseCoordsY: Int, depth: Int): Array<ShadowMapTile> {
var tileSize = Std.int(size / 2);
var tiles = [];
for (i in 0...4) {
var coordsX = baseCoordsX + tilePattern[i][0] * tileSize;
var coordsY = baseCoordsY + tilePattern[i][1] * tileSize;
var tile = new ShadowMapTile(coordsX, coordsY, tileSize);
tile.parentTile = parent;
if (depth > 1)
tile.tiles = subDivTile(tile, tileSize, coordsX, coordsY, depth - 1);
tiles.push(tile);
}
return tiles;
}
#end
public static inline function tilesLightType(type: String): Int {
switch (type) {
case "sun":
return LightObject.cascadeCount;
case "point":
return 6;
default:
return 1;
}
}
inline function lockTile(light: LightObject): Void {
if (this.light != null)
return;
this.light = light;
#if arm_shadowmap_atlas_lod
// update the count of used tiles for parents
this.forEachParentTile(function (pTile) {
pTile.activeSubTiles++;
});
#end
}
public var unlockLight: Bool = false;
public function freeTile(): Void {
// prevent duplicates
if (light != null && unlockLight) {
light.lightInAtlas = false;
unlockLight = false;
}
var linkedTile = this;
var tempTile = this;
while (linkedTile != null) {
linkedTile.light = null;
#if arm_shadowmap_atlas_lod
// update the count of used tiles for parents
linkedTile.forEachParentTile(function (pTile) {
if (pTile.activeSubTiles > 0)
pTile.activeSubTiles--;
});
#end
linkedTile = linkedTile.linkedTile;
// unlink linked tiles
tempTile.linkedTile = null;
tempTile = linkedTile;
}
}
public inline function forEachTileLinked(action: ShadowMapTile->Void): Void {
var linkedTile = this;
while (linkedTile != null) {
action(linkedTile);
linkedTile = linkedTile.linkedTile;
}
}
#if arm_shadowmap_atlas_lod
public inline function forEachParentTile(action: ShadowMapTile->Void): Void {
var parentTile = this.parentTile;
while (parentTile != null) {
action(parentTile);
parentTile = parentTile.parentTile;
}
}
#end
}
#end

View file

@ -7,6 +7,8 @@ class RenderPathCreator {
public static var path: RenderPath;
public static var commands: Void->Void = function() {};
#if (rp_renderer == "Forward")
public static var setTargetMeshes: Void->Void = RenderPathForward.setTargetMeshes;
public static var drawMeshes: Void->Void = RenderPathForward.drawMeshes;
@ -33,13 +35,22 @@ class RenderPathCreator {
#if (rp_renderer == "Forward")
RenderPathForward.init(path);
path.commands = RenderPathForward.commands;
path.commands = function() {
RenderPathForward.commands();
commands();
}
#elseif (rp_renderer == "Deferred")
RenderPathDeferred.init(path);
path.commands = RenderPathDeferred.commands;
path.commands = function() {
RenderPathDeferred.commands();
commands();
}
#elseif (rp_renderer == "Raytracer")
RenderPathRaytracer.init(path);
path.commands = RenderPathRaytracer.commands;
path.commands = function() {
RenderPathRaytracer.commands();
commands();
}
#end
return path;
}

View file

@ -502,8 +502,13 @@ class RenderPathDeferred {
#end
#if (rp_shadowmap)
// atlasing is exclusive for now
#if arm_shadowmap_atlas
Inc.drawShadowMapAtlas();
#else
Inc.drawShadowMap();
#end
#end
// Voxels
#if rp_voxelao
@ -544,6 +549,13 @@ class RenderPathDeferred {
path.bindTarget("_main", "gbufferD");
path.bindTarget("gbuffer0", "gbuffer0");
path.bindTarget("gbuffer1", "gbuffer1");
#if rp_gbuffer2
{
path.bindTarget("gbuffer2", "gbuffer2");
}
#end
#if (rp_ssgi != "Off")
{
if (armory.data.Config.raw.rp_ssgi != false) {
@ -572,7 +584,11 @@ class RenderPathDeferred {
#if rp_shadowmap
{
#if arm_shadowmap_atlas
Inc.bindShadowMapAtlas();
#else
Inc.bindShadowMap();
#end
}
#end
@ -624,7 +640,11 @@ class RenderPathDeferred {
{
path.setTarget("singlea");
path.bindTarget("_main", "gbufferD");
#if arm_shadowmap_atlas
Inc.bindShadowMapAtlas();
#else
Inc.bindShadowMap();
#end
path.drawShader("shader_datas/volumetric_light/volumetric_light");
path.setTarget("singleb");
@ -652,6 +672,7 @@ class RenderPathDeferred {
#if rp_blending
{
path.setTarget("tex");
path.drawMeshes("blend");
}
#end

View file

@ -300,7 +300,11 @@ class RenderPathForward {
#if rp_shadowmap
{
#if arm_shadowmap_atlas
Inc.drawShadowMapAtlas();
#else
Inc.drawShadowMap();
#end
}
#end
@ -352,7 +356,11 @@ class RenderPathForward {
#if rp_shadowmap
{
#if arm_shadowmap_atlas
Inc.bindShadowMapAtlas();
#else
Inc.bindShadowMap();
#end
}
#end
@ -466,7 +474,11 @@ class RenderPathForward {
{
path.setTarget("singlea");
path.bindTarget("_main", "gbufferD");
#if arm_shadowmap_atlas
Inc.bindShadowMapAtlas();
#else
Inc.bindShadowMap();
#end
path.drawShader("shader_datas/volumetric_light/volumetric_light");
path.setTarget("singleb");

View file

@ -0,0 +1,74 @@
package armory.system;
class FSM<T> {
final transitions = new Array<Transition<T>>();
final tempTransitions = new Array<Transition<T>>();
var state: Null<State<T>>;
var entered = false;
public function new() {}
public function bindTransition(canEnter: Void -> Bool, fromState: State<T>, toState: State<T>) {
final transition = new Transition<T>(canEnter, fromState, toState);
transitions.push(transition);
syncTransitions();
}
public function setInitState(state: State<T>) {
this.state = state;
syncTransitions();
}
public function update() {
if (!entered) {
state.onEnter();
entered = true;
}
state.onUpdate();
for (transition in tempTransitions) {
if (transition.canEnter()) {
state.onExit();
state = transition.toState;
entered = false;
syncTransitions();
break;
}
}
}
public function syncTransitions() {
tempTransitions.resize(0);
for (transition in transitions) {
if (transition.fromState == state) tempTransitions.push(transition);
}
}
}
class Transition<T> {
public final canEnter: Void -> Bool;
public final fromState: State<T>;
public final toState: State<T>;
public function new(canEnter: Void -> Bool, fromState: State<T>, toState: State<T>) {
this.canEnter = canEnter;
this.fromState = fromState;
this.toState = toState;
}
}
class State<T> {
final owner: T;
public function new(owner: T) {
this.owner = owner;
}
public function onEnter() {}
public function onUpdate() {}
public function onExit() {}
}

View file

@ -0,0 +1,350 @@
package armory.system;
import kha.FastFloat;
import iron.system.Input;
class InputMap {
var commands = new Map<String, Null<Array<InputCommand>>>();
public function new() {}
public function addKeyboard(config: String) {
var command = new KeyboardCommand();
return addCustomCommand(command, config);
}
public function addGamepad(config: String) {
var command = new GamepadCommand();
return addCustomCommand(command, config);
}
public function addCustomCommand(command: InputCommand, config: String) {
if (commands[config] == null) commands[config] = new Array<InputCommand>();
commands[config].push(command);
return command;
}
}
class ActionMap extends InputMap {
public inline function started(config: String) {
var started = false;
for (c in commands[config]) {
if (c.started()) {
started = true;
break;
}
}
return started;
}
public inline function released(config: String) {
var released = false;
for (c in commands[config]) {
if (c.released()) {
released = true;
break;
}
}
return released;
}
}
class AxisMap extends InputMap {
var scale: FastFloat = 1.0;
public inline function getAxis(config: String) {
var axis = 0.0;
for (c in commands[config]) {
var tempAxis = c.getAxis();
if (tempAxis != 0.0 && tempAxis != axis) {
axis += tempAxis;
scale = c.getScale();
}
}
return axis;
}
public inline function getScale() {
return scale;
}
}
class InputCommand {
var keys = new Array<String>();
var modifiers = new Array<String>();
var displacementKeys = new Array<String>();
var displacementModifiers = new Array<String>();
var deadzone: FastFloat = 0.0;
var scale: FastFloat = 1.0;
public function new() {}
public function setKeys(keys: Array<String>) {
return this.keys = keys;
}
public function setMods(modifiers: Array<String>) {
return this.modifiers = modifiers;
}
public function setDisplacementKeys(keys: Array<String>) {
return displacementKeys = keys;
}
public function setDisplacementMods(modifiers: Array<String>) {
return displacementModifiers = modifiers;
}
public function setDeadzone(deadzone: FastFloat) {
return this.deadzone = deadzone;
}
public function setScale(scale: FastFloat) {
return this.scale = scale;
}
public function getScale() {
return scale;
}
public function started() {
return false;
}
public function released() {
return false;
}
public function getAxis(): FastFloat {
return 0.0;
}
}
class KeyboardCommand extends InputCommand {
var keyboard = Input.getKeyboard();
var mouse = Input.getMouse();
public inline override function started() {
for (k in keys) {
if (keyboard.started(k)) {
for (m in modifiers) {
if (!keyboard.down(m)) return false;
}
for (m in displacementModifiers) {
if (!mouse.down(m)) return false;
}
return true;
}
}
for (k in displacementKeys) {
if (mouse.started(k)) {
for (m in modifiers) {
if (!keyboard.down(m)) return false;
}
for (m in displacementModifiers) {
if (!mouse.down(m)) return false;
}
return true;
}
}
return false;
}
public inline override function released() {
for (k in keys) {
if (keyboard.released(k)) {
for (m in modifiers) {
if (!keyboard.down(m)) return false;
}
for (m in displacementModifiers) {
if (!mouse.down(m)) return false;
}
return true;
}
}
for (k in displacementKeys) {
if (mouse.released(k)) {
for (m in modifiers) {
if (!keyboard.down(m)) return false;
}
for (m in displacementModifiers) {
if (!mouse.down(m)) return false;
}
return true;
}
}
return false;
}
public inline override function getAxis() {
var axis = 0.0;
var movementX = mouse.movementX;
var movementY = mouse.movementY;
var wheelDelta = mouse.wheelDelta;
for (k in keys) {
if (keyboard.down(k)) {
axis++;
break;
}
}
for (m in modifiers) {
if (keyboard.down(m)) {
axis --;
break;
}
}
for (k in displacementKeys) {
switch (k) {
case "moved x": if (movementX > deadzone) axis++;
case "moved y": if (movementY > deadzone) axis--;
case "wheel": if (wheelDelta < -deadzone) axis++;
case "movement x": if (movementX > deadzone) return movementX - deadzone;
case "movement y": if (movementY > deadzone) return movementY - deadzone;
default: {
if (mouse.down(k)) {
axis ++;
break;
}
}
}
}
for (m in displacementModifiers) {
switch (m) {
case "moved x": if (movementX < -deadzone) axis--;
case "moved y": if (movementY < -deadzone) axis++;
case "wheel": if (wheelDelta > deadzone) axis--;
case "movement x": if (movementX < -deadzone) return movementX + deadzone;
case "movement y": if (movementY < -deadzone) return movementY + deadzone;
default: {
if (mouse.down(m)) {
axis --;
break;
}
}
}
}
return axis > 1 ? 1 : axis < -1 ? -1 : axis;
}
}
class GamepadCommand extends InputCommand {
var gamepad = Input.getGamepad(0);
public inline override function started() {
for (k in keys) {
if (gamepad.started(k)) {
for (m in modifiers) {
if (gamepad.down(m) < deadzone) return false;
}
return true;
}
}
return false;
}
public inline override function released() {
for (k in keys) {
if (gamepad.released(k)) {
for (m in modifiers) {
if (gamepad.down(m) < deadzone) return false;
}
return true;
}
}
return false;
}
public inline override function getAxis() {
var axis = 0.0;
var rsMovementX = gamepad.rightStick.movementX;
var rsMovementY = gamepad.rightStick.movementY;
var lsMovementX = gamepad.leftStick.movementX;
var lsMovementY = gamepad.leftStick.movementY;
var rtPressure = gamepad.down("r2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0;
var ltPressure = gamepad.down("l2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0;
for (k in keys) {
switch(k) {
case "rtPressure": axis += rtPressure;
case "ltPressure": axis += ltPressure;
default: {
if (gamepad.down(k) > deadzone) {
axis++;
break;
}
}
}
}
for (m in modifiers) {
switch (m) {
case "rtPressure": axis -= rtPressure;
case "ltPressure": axis -= ltPressure;
default: {
if (gamepad.down(m) > deadzone) {
axis--;
break;
}
}
}
}
for (k in displacementKeys) {
switch(k) {
case "rs moved x": if (rsMovementX > deadzone) axis++;
case "rs moved y": if (rsMovementY > deadzone) axis++;
case "ls moved x": if (lsMovementX > deadzone) axis++;
case "ls moved y": if (lsMovementY > deadzone) axis++;
case "rs movement x": if (rsMovementX > deadzone) return rsMovementX - deadzone;
case "rs movement y": if (rsMovementY > deadzone) return rsMovementY - deadzone;
case "ls movement x": if (lsMovementX > deadzone) return lsMovementX - deadzone;
case "ls movement y": if (lsMovementY > deadzone) return lsMovementY - deadzone;
}
}
for (m in displacementModifiers) {
switch (m) {
case "rs moved x": if (rsMovementX < -deadzone) axis--;
case "rs moved y": if (rsMovementY < -deadzone) axis--;
case "ls moved x": if (lsMovementX < -deadzone) axis--;
case "ls moved y": if (lsMovementY < -deadzone) axis--;
case "rs movement x": if (rsMovementX < -deadzone) return rsMovementX + deadzone;
case "rs movement y": if (rsMovementY < -deadzone) return rsMovementY + deadzone;
case "ls movement x": if (lsMovementX < -deadzone) return lsMovementX + deadzone;
case "ls movement y": if (lsMovementY < -deadzone) return lsMovementY + deadzone;
}
}
return axis > 1 ? 1 : axis < -1 ? -1 : axis;
}
}

View file

@ -421,6 +421,9 @@ class DebugConsole extends Trait {
var lightHandle = Id.handle();
lightHandle.value = light.data.raw.strength / 10;
light.data.raw.strength = ui.slider(lightHandle, "Strength", 0.0, 5.0, true) * 10;
#if arm_shadowmap_atlas
ui.text("status: " + (light.culledLight ? "culled" : "rendered"));
#end
}
else if (Std.is(selectedObject, iron.object.CameraObject)) {
selectedType = "(Camera)";

View file

@ -6,7 +6,6 @@ import iron.Trait;
import iron.system.Time;
import iron.math.Vec4;
import iron.math.RayCaster;
import iron.data.SceneFormat;
class Hit {
@ -285,7 +284,7 @@ class PhysicsWorld extends Trait {
}
function updateContacts() {
contacts = [];
contacts.resize(0);
var disp: bullet.Bt.Dispatcher = dispatcher;
var numManifolds = disp.getNumManifolds();
@ -329,12 +328,12 @@ class PhysicsWorld extends Trait {
}
}
public function pickClosest(inputX: Float, inputY: Float): RigidBody {
public function pickClosest(inputX: Float, inputY: Float, group: Int = 0x00000001, mask = 0xFFFFFFFF): RigidBody {
var camera = iron.Scene.active.camera;
var start = new Vec4();
var end = new Vec4();
RayCaster.getDirection(start, end, inputX, inputY, camera);
var hit = rayCast(camera.transform.world.getLoc(), end);
var hit = rayCast(camera.transform.world.getLoc(), end, group, mask);
var rb = (hit != null) ? hit.rb : null;
return rb;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 725 B

View file

@ -32,6 +32,7 @@ import arm.material.mat_batch as mat_batch
import arm.utils
import arm.profiler
import arm.log as log
@unique
class NodeType(Enum):
@ -1157,7 +1158,16 @@ class ArmoryExporter:
else:
invscale_tex = 1 * 32767
if has_tang:
exportMesh.calc_tangents(uvmap=lay0.name)
try:
exportMesh.calc_tangents(uvmap=lay0.name)
except Exception as e:
if hasattr(e, 'message'):
log.error(e.message)
else:
# Assume it was caused because of encountering n-gons
log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping.
Make sure the mesh only has tris/quads.""")
tangdata = np.empty(num_verts * 3, dtype='<f4')
if has_col:
cdata = np.empty(num_verts * 3, dtype='<f4')
@ -1950,8 +1960,9 @@ class ArmoryExporter:
self.output['tilesheet_datas'].append(o)
def export_world(self):
"""Exports the world of the scene."""
"""Exports the world of the current scene."""
world = self.scene.world
if world is not None:
world_name = arm.utils.safestr(world.name)
@ -1962,6 +1973,9 @@ class ArmoryExporter:
self.post_export_world(world, out_world)
self.output['world_datas'].append(out_world)
elif arm.utils.get_rp().rp_background == 'World':
log.warn(f'Scene "{self.scene.name}" is missing a world, some render targets will not be cleared')
def export_objects(self, scene):
"""Exports all supported blender objects.
@ -2105,7 +2119,7 @@ class ArmoryExporter:
self.output['camera_ref'] = self.scene.camera.name
else:
if self.scene.name == arm.utils.get_project_scene_name():
log.warn('No camera found in active scene')
log.warn(f'Scene "{self.scene.name}" is missing a camera')
self.output['material_datas'] = []
@ -2148,7 +2162,7 @@ class ArmoryExporter:
# No camera found
if not self.camera_spawned:
log.warn('No camera found in active scene layers')
log.warn( f'Scene "{self.scene.name}" is missing a camera')
# No camera found, create a default one
if (len(self.output['camera_datas']) == 0 or len(bpy.data.cameras) == 0) or not self.camera_spawned:
@ -2182,7 +2196,7 @@ class ArmoryExporter:
if self.scene.frame_current != current_frame:
self.scene.frame_set(current_frame, subframe=current_subframe)
print('Scene exported in ' + str(time.time() - profile_time))
print('Scene exported in {:0.3f}s'.format(time.time() - profile_time))
def create_default_camera(self, is_viewport_camera=False):
"""Creates the default camera and adds a WalkNavigation trait to it."""
@ -2304,6 +2318,10 @@ class ArmoryExporter:
return instanced_type, instanced_data
@staticmethod
def rigid_body_static(rb):
return (not rb.enabled and not rb.kinematic) or (rb.type == 'PASSIVE' and not rb.kinematic)
def post_export_object(self, bobject: bpy.types.Object, o, type):
# Export traits
self.export_traits(bobject, o)
@ -2330,7 +2348,7 @@ class ArmoryExporter:
elif rb.collision_shape == 'CAPSULE':
shape = 6
body_mass = rb.mass
is_static = (not rb.enabled and not rb.kinematic) or (rb.type == 'PASSIVE' and not rb.kinematic)
is_static = self.rigid_body_static(rb)
if is_static:
body_mass = 0
x = {}

View file

@ -17,6 +17,8 @@ def register():
km = addon_keyconfig.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW")
km.keymap_items.new(props_ui.ArmoryPlayButton.bl_idname, type='F5', value='PRESS')
km.keymap_items.new("tlm.build_lightmaps", type='F6', value='PRESS')
km.keymap_items.new("tlm.clean_lightmaps", type='F7', value='PRESS')
arm_keymaps.append(km)
def unregister():

View file

@ -29,12 +29,15 @@ else:
info_text = ''
num_warnings = 0
num_errors = 0
def clear(clear_warnings=False):
global info_text, num_warnings
def clear(clear_warnings=False, clear_errors=False):
global info_text, num_warnings, num_errors
info_text = ''
if clear_warnings:
num_warnings = 0
if clear_errors:
num_errors = 0
def format_text(text):
return (text[:80] + '..') if len(text) > 80 else text # Limit str size
@ -54,7 +57,7 @@ def info(text):
info_text = format_text(text)
def print_warn(text):
log('Warning: ' + text, WARN)
log('WARNING: ' + text, WARN)
def warn(text):
global num_warnings
@ -62,4 +65,6 @@ def warn(text):
print_warn(text)
def error(text):
global num_errors
num_errors += 1
log('ERROR: ' + text, ERROR)

View file

@ -1,7 +1,7 @@
from arm.logicnode.arm_nodes import *
class OnVolumeTriggerNode(ArmLogicTreeNode):
"""Activates the output when the given rigid body enter, overlap or leave the given trigger.
"""Activates the output when the given object enters, overlaps or leaves the bounding box of the given trigger object. (Note: Works even if objects are not Rigid Bodies).
@input RB: this object is taken as the entering object
@input Trigger: this object is used as the volume trigger

View file

@ -1,8 +1,19 @@
from arm.logicnode.arm_nodes import *
class PickObjectNode(ArmLogicTreeNode):
"""Pickes the rigid body in the given location using the screen
coordinates (2D)."""
"""Picks the rigid body in the given location using the screen
coordinates (2D).
@seeNode Mask
@input Screen Coords: the location at which to pick, in screen
coordinates
@input Mask: a bit mask value to specify which
objects are considered
@output RB: the object that was hit
@output Hit: the hit position in world coordinates
"""
bl_idname = 'LNPickObjectNode'
bl_label = 'Pick RB'
arm_section = 'ray'
@ -11,6 +22,7 @@ class PickObjectNode(ArmLogicTreeNode):
def init(self, context):
super(PickObjectNode, self).init(context)
self.add_input('NodeSocketVector', 'Screen Coords')
self.add_input('NodeSocketInt', 'Mask', default_value=1)
self.add_output('ArmNodeSocketObject', 'RB')
self.add_output('NodeSocketVector', 'Hit')

View file

@ -9,6 +9,7 @@ import threading
import webbrowser
import shlex
import errno
import math
import bpy
@ -61,10 +62,9 @@ def remove_readonly(func, path, excinfo):
def export_data(fp, sdk_path):
wrd = bpy.data.worlds['Arm']
print('\n' + '_' * 10 + ' [Armory] Compiling ' + '_' * 10)
print('Armory v{0} ({1})'.format(wrd.arm_version, wrd.arm_commit))
if wrd.arm_verbose_output:
print('\nArmory v{0} ({1})'.format(wrd.arm_version, wrd.arm_commit))
print('OS: ' + arm.utils.get_os() + ', Target: ' + state.target + ', GAPI: ' + arm.utils.get_gapi() + ', Blender: ' + bpy.app.version_string)
print(f'Blender: {bpy.app.version_string}, Target: {state.target}, GAPI: {arm.utils.get_gapi()}')
# Clean compiled variants if cache is disabled
build_dir = arm.utils.get_fp_build()
@ -157,10 +157,10 @@ def export_data(fp, sdk_path):
cdefs = arm.utils.def_strings_to_array(wrd.compo_defs)
if wrd.arm_verbose_output:
print('Exported modules:', modules)
print('Shader flags:', defs)
print('Compositor flags:', cdefs)
print('Khafile flags:', assets.khafile_defs)
print('Exported modules:', ', '.join(modules))
print('Shader flags:', ' '.join(defs))
print('Compositor flags:', ' '.join(cdefs))
print('Khafile flags:', ' '.join(assets.khafile_defs))
# Render path is configurable at runtime
has_config = wrd.arm_write_config or os.path.exists(arm.utils.get_fp() + '/Bundled/config.arm')
@ -338,7 +338,7 @@ def build(target, is_play=False, is_publish=False, is_export=False):
if arm.utils.get_save_on_build():
bpy.ops.wm.save_mainfile()
log.clear(clear_warnings=True)
log.clear(clear_warnings=True, clear_errors=True)
# Set camera in active scene
active_scene = arm.utils.get_active_scene()
@ -431,7 +431,7 @@ def compilation_server_done():
log.error('Build failed, check console')
def build_done():
print('Finished in ' + str(time.time() - profile_time))
print('Finished in {:0.3f}s'.format(time.time() - profile_time))
if log.num_warnings > 0:
log.print_warn(f'{log.num_warnings} warning{"s" if log.num_warnings > 1 else ""} occurred during compilation')
if state.proc_build is None:

View file

@ -1,8 +1,11 @@
import os
from typing import Optional, TextIO
import bpy
import arm.utils
import arm.log
from arm.exporter import ArmoryExporter
import arm.log
import arm.utils
parsed_nodes = []
parsed_ids = dict() # Sharing node data
@ -100,7 +103,9 @@ def build_node_tree(node_group):
f.write('}')
node_group.arm_cached = True
def build_node(node: bpy.types.Node, f):
def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]:
"""Builds the given node and returns its name. f is an opened file object."""
global parsed_nodes
global parsed_ids
@ -126,7 +131,7 @@ def build_node(node: bpy.types.Node, f):
parsed_nodes.append(name)
# Create node
node_type = node.bl_idname[2:] # Discard 'LN'TimeNode prefix
node_type = node.bl_idname[2:] # Discard 'LN' prefix
f.write('\t\tvar ' + name + ' = new armory.logicnode.' + node_type + '(this);\n')
# Handle Function Nodes
@ -167,26 +172,45 @@ def build_node(node: bpy.types.Node, f):
# Create inputs
for inp in node.inputs:
# Is linked - find node
# True if the input is connected to a unlinked reroute
# somewhere down the reroute line
unconnected = False
# Is linked -> find the connected node
if inp.is_linked:
n = inp.links[0].from_node
socket = inp.links[0].from_socket
if (inp.bl_idname == 'ArmNodeSocketAction' and socket.bl_idname != 'ArmNodeSocketAction') or \
(socket.bl_idname == 'ArmNodeSocketAction' and inp.bl_idname != 'ArmNodeSocketAction'):
print('Armory Error: Sockets do not match in logic node tree "{0}" - node "{1}" - socket "{2}"'.format(group_name, node.name, inp.name))
inp_name = build_node(n, f)
for i in range(0, len(n.outputs)):
if n.outputs[i] == socket:
inp_from = i
# Follow reroutes first
while n.type == "REROUTE":
if len(n.inputs) == 0 or not n.inputs[0].is_linked:
unconnected = True
break
# Not linked - create node with default values
socket = n.inputs[0].links[0].from_socket
n = n.inputs[0].links[0].from_node
if not unconnected:
if (inp.bl_idname == 'ArmNodeSocketAction' and socket.bl_idname != 'ArmNodeSocketAction') or \
(socket.bl_idname == 'ArmNodeSocketAction' and inp.bl_idname != 'ArmNodeSocketAction'):
arm.log.warn(f'Sockets do not match in logic node tree "{group_name}": node "{node.name}", socket "{inp.name}"')
inp_name = build_node(n, f)
for i in range(0, len(n.outputs)):
if n.outputs[i] == socket:
inp_from = i
break
# Not linked -> create node with default values
else:
inp_name = build_default_node(inp)
inp_from = 0
# The input is linked to a reroute, but the reroute is unlinked
if inp_name == None:
if unconnected:
inp_name = build_default_node(inp)
inp_from = 0
# Add input
f.write('\t\t' + name + '.addInput(' + inp_name + ', ' + str(inp_from) + ');\n')

View file

@ -43,6 +43,22 @@ def add_world_defs():
if rpdat.rp_shadowmap_cascades != '1':
wrd.world_defs += '_CSM'
assets.add_khafile_def('arm_csm')
if rpdat.rp_shadowmap_atlas:
assets.add_khafile_def('arm_shadowmap_atlas')
wrd.world_defs += '_ShadowMapAtlas'
if rpdat.rp_shadowmap_atlas_single_map:
assets.add_khafile_def('arm_shadowmap_atlas_single_map')
wrd.world_defs += '_SingleAtlas'
assets.add_khafile_def('rp_shadowmap_atlas_max_size_point={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size_point)))
assets.add_khafile_def('rp_shadowmap_atlas_max_size_spot={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size_spot)))
assets.add_khafile_def('rp_shadowmap_atlas_max_size_sun={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size_sun)))
assets.add_khafile_def('rp_shadowmap_atlas_max_size={0}'.format(int(rpdat.rp_shadowmap_atlas_max_size)))
assets.add_khafile_def('rp_max_lights_cluster={0}'.format(int(rpdat.rp_max_lights_cluster)))
assets.add_khafile_def('rp_max_lights={0}'.format(int(rpdat.rp_max_lights)))
if rpdat.rp_shadowmap_atlas_lod:
assets.add_khafile_def('arm_shadowmap_atlas_lod')
assets.add_khafile_def('rp_shadowmap_atlas_lod_subdivisions={0}'.format(int(rpdat.rp_shadowmap_atlas_lod_subdivisions)))
# SS
if rpdat.rp_ssgi == 'RTGI' or rpdat.rp_ssgi == 'RTAO':
if rpdat.rp_ssgi == 'RTGI':
@ -89,9 +105,14 @@ def add_world_defs():
wrd.world_defs += '_Spot'
assets.add_khafile_def('arm_spot')
if point_lights == 1:
wrd.world_defs += '_SinglePoint'
elif point_lights > 1:
if not rpdat.rp_shadowmap_atlas:
if point_lights == 1:
wrd.world_defs += '_SinglePoint'
elif point_lights > 1:
wrd.world_defs += '_Clusters'
assets.add_khafile_def('arm_clusters')
else:
wrd.world_defs += '_SMSizeUniform'
wrd.world_defs += '_Clusters'
assets.add_khafile_def('arm_clusters')
@ -346,7 +367,21 @@ def build():
assets.add_khafile_def('rp_chromatic_aberration')
assets.add_shader_pass('chromatic_aberration_pass')
gbuffer2 = '_Veloc' in wrd.world_defs
ignoreIrr = False
for obj in bpy.data.objects:
if obj.type == "MESH":
for slot in obj.material_slots:
mat = slot.material
if mat: #Check if not NoneType
if mat.arm_ignore_irradiance:
ignoreIrr = True
if ignoreIrr:
wrd.world_defs += '_IgnoreIrr'
gbuffer2 = '_Veloc' in wrd.world_defs or '_IgnoreIrr' in wrd.world_defs
if gbuffer2:
assets.add_khafile_def('rp_gbuffer2')
wrd.world_defs += '_gbuffer2'

View file

@ -331,6 +331,7 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
'COMBXYZ': nodes_converter.parse_combxyz,
'VECT_MATH': nodes_converter.parse_vectormath,
'DISPLACEMENT': nodes_vector.parse_displacement,
'VECTOR_ROTATE': nodes_vector.parse_vectorrotate,
}
if node.type in node_parser_funcs:
@ -433,6 +434,7 @@ def parse_value(node, socket):
'SEPRGB': nodes_converter.parse_seprgb,
'SEPXYZ': nodes_converter.parse_sepxyz,
'VECT_MATH': nodes_converter.parse_vectormath,
'MAP_RANGE': nodes_converter.parse_maprange,
}
if node.type in node_parser_funcs:

View file

@ -332,3 +332,186 @@ vec3 wrap(const vec3 value, const vec3 max, const vec3 min) {
\t wrap(value.z, max.z, min.z));
}
"""
str_blackbody = """
vec3 blackbody(const float temperature){
vec3 rgb = vec3(0.0, 0.0, 0.0);
vec3 r = vec3(0.0, 0.0, 0.0);
vec3 g = vec3(0.0, 0.0, 0.0);
vec3 b = vec3(0.0, 0.0, 0.0);
float t_inv = float(1.0 / temperature);
if (temperature >= 12000.0) {
rgb = vec3(0.826270103, 0.994478524, 1.56626022);
} else if(temperature < 965.0) {
rgb = vec3(4.70366907, 0.0, 0.0);
} else {
if (temperature >= 6365.0) {
vec3 r = vec3(3.78765709e+03, 9.36026367e-06, 3.98995841e-01);
vec3 g = vec3(-5.00279505e+02, -4.59745390e-06, 1.09090465e+00);
vec4 b = vec4(6.72595954e-13, -2.73059993e-08, 4.24068546e-04, -7.52204323e-01);
rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a );
} else if (temperature >= 3315.0) {
vec3 r = vec3(4.60124770e+03, 2.89727618e-05, 1.48001316e-01);
vec3 g = vec3(-1.18134453e+03, -2.18913373e-05, 1.30656109e+00);
vec4 b = vec4(-2.22463426e-13, -1.55078698e-08, 3.81675160e-04, -7.30646033e-01);
rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a );
} else if (temperature >= 1902.0) {
vec3 r = vec3(4.66849800e+03, 2.85655028e-05, 1.29075375e-01);
vec3 g = vec3(-1.42546105e+03, -4.01730887e-05, 1.44002695e+00);
vec4 b = vec4(-2.02524603e-11, 1.79435860e-07, -2.60561875e-04, -1.41761141e-02);
rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a );
} else if (temperature >= 1449.0) {
vec3 r = vec3(4.10671449e+03, -8.61949938e-05, 6.41423749e-01);
vec3 g = vec3(-1.22075471e+03, 2.56245413e-05, 1.20753416e+00);
vec4 b = vec4(0.0, 0.0, 0.0, 0.0);
rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a );
} else if (temperature >= 1167.0) {
vec3 r = vec3(3.37763626e+03, -4.34581697e-04, 1.64843306e+00);
vec3 g = vec3(-1.00402363e+03, 1.29189794e-04, 9.08181524e-01);
vec4 b = vec4(0.0, 0.0, 0.0, 0.0);
rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a );
} else {
vec3 r = vec3(2.52432244e+03, -1.06185848e-03, 3.11067539e+00);
vec3 g = vec3(-7.50343014e+02, 3.15679613e-04, 4.73464526e-01);
vec4 b = vec4(0.0, 0.0, 0.0, 0.0);
rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a );
}
}
return rgb;
}
"""
# Adapted from https://github.com/blender/blender/blob/594f47ecd2d5367ca936cf6fc6ec8168c2b360d0/source/blender/gpu/shaders/material/gpu_shader_material_map_range.glsl
str_map_range_linear = """
float map_range_linear(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) {
if (fromMax != fromMin) {
return float(toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin));
}
else {
return float(0.0);
}
}
"""
str_map_range_stepped = """
float map_range_stepped(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax, const float steps) {
if (fromMax != fromMin) {
float factor = (value - fromMin) / (fromMax - fromMin);
factor = (steps > 0.0) ? floor(factor * (steps + 1.0)) / steps : 0.0;
return float(toMin + factor * (toMax - toMin));
}
else {
return float(0.0);
}
}
"""
str_map_range_smoothstep = """
float map_range_smoothstep(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax)
{
if (fromMax != fromMin) {
float factor = (fromMin > fromMax) ? 1.0 - smoothstep(fromMax, fromMin, value) :
smoothstep(fromMin, fromMax, value);
return float(toMin + factor * (toMax - toMin));
}
else {
return float(0.0);
}
}
"""
str_map_range_smootherstep = """
float safe_divide(float a, float b)
{
return (b != 0.0) ? a / b : 0.0;
}
float smootherstep(float edge0, float edge1, float x)
{
x = clamp(safe_divide((x - edge0), (edge1 - edge0)), 0.0, 1.0);
return x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
}
float map_range_smootherstep(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) {
if (fromMax != fromMin) {
float factor = (fromMin > fromMax) ? 1.0 - smootherstep(fromMax, fromMin, value) :
smootherstep(fromMin, fromMax, value);
return float(toMin + factor * (toMax - toMin));
}
else {
return float(0.0);
}
}
"""
str_rotate_around_axis = """
vec3 rotate_around_axis(const vec3 p, const vec3 axis, const float angle)
{
float costheta = cos(angle);
float sintheta = sin(angle);
vec3 r;
r.x = ((costheta + (1.0 - costheta) * axis.x * axis.x) * p.x) +
(((1.0 - costheta) * axis.x * axis.y - axis.z * sintheta) * p.y) +
(((1.0 - costheta) * axis.x * axis.z + axis.y * sintheta) * p.z);
r.y = (((1.0 - costheta) * axis.x * axis.y + axis.z * sintheta) * p.x) +
((costheta + (1.0 - costheta) * axis.y * axis.y) * p.y) +
(((1.0 - costheta) * axis.y * axis.z - axis.x * sintheta) * p.z);
r.z = (((1.0 - costheta) * axis.x * axis.z - axis.y * sintheta) * p.x) +
(((1.0 - costheta) * axis.y * axis.z + axis.x * sintheta) * p.y) +
((costheta + (1.0 - costheta) * axis.z * axis.z) * p.z);
return r;
}
"""
str_euler_to_mat3 = """
mat3 euler_to_mat3(vec3 euler)
{
float cx = cos(euler.x);
float cy = cos(euler.y);
float cz = cos(euler.z);
float sx = sin(euler.x);
float sy = sin(euler.y);
float sz = sin(euler.z);
mat3 mat;
mat[0][0] = cy * cz;
mat[0][1] = cy * sz;
mat[0][2] = -sy;
mat[1][0] = sy * sx * cz - cx * sz;
mat[1][1] = sy * sx * sz + cx * cz;
mat[1][2] = cy * sx;
mat[2][0] = sy * cx * cz + sx * sz;
mat[2][1] = sy * cx * sz - sx * cz;
mat[2][2] = cy * cx;
return mat;
}
"""

View file

@ -8,73 +8,43 @@ import arm.material.cycles_functions as c_functions
from arm.material.parser_state import ParserState
from arm.material.shader import floatstr, vec3str
def parse_maprange(node: bpy.types.ShaderNodeMapRange, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr:
interp = node.interpolation_type
value: str = c.parse_value_input(node.inputs[0]) if node.inputs[0].is_linked else c.to_vec1(node.inputs[0].default_value)
fromMin = c.parse_value_input(node.inputs[1])
fromMax = c.parse_value_input(node.inputs[2])
toMin = c.parse_value_input(node.inputs[3])
toMax = c.parse_value_input(node.inputs[4])
if interp == "LINEAR":
state.curshader.add_function(c_functions.str_map_range_linear)
return f'map_range_linear({value}, {fromMin}, {fromMax}, {toMin}, {toMax})'
elif interp == "STEPPED":
steps = float(c.parse_value_input(node.inputs[5]))
state.curshader.add_function(c_functions.str_map_range_stepped)
return f'map_range_stepped({value}, {fromMin}, {fromMax}, {toMin}, {toMax}, {steps})'
elif interp == "SMOOTHSTEP":
state.curshader.add_function(c_functions.str_map_range_smoothstep)
return f'map_range_smoothstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})'
elif interp == "SMOOTHERSTEP":
state.curshader.add_function(c_functions.str_map_range_smootherstep)
return f'map_range_smootherstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})'
def parse_blackbody(node: bpy.types.ShaderNodeBlackbody, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
t = float(c.parse_value_input(node.inputs[0]))
rgb = [0, 0, 0]
blackbody_table_r = [
[2.52432244e+03, -1.06185848e-03, 3.11067539e+00],
[3.37763626e+03, -4.34581697e-04, 1.64843306e+00],
[4.10671449e+03, -8.61949938e-05, 6.41423749e-01],
[4.66849800e+03, 2.85655028e-05, 1.29075375e-01],
[4.60124770e+03, 2.89727618e-05, 1.48001316e-01],
[3.78765709e+03, 9.36026367e-06, 3.98995841e-01]
]
blackbody_table_g = [
[-7.50343014e+02, 3.15679613e-04, 4.73464526e-01],
[-1.00402363e+03, 1.29189794e-04, 9.08181524e-01],
[-1.22075471e+03, 2.56245413e-05, 1.20753416e+00],
[-1.42546105e+03, -4.01730887e-05, 1.44002695e+00],
[-1.18134453e+03, -2.18913373e-05, 1.30656109e+00],
[-5.00279505e+02, -4.59745390e-06, 1.09090465e+00]
]
blackbody_table_b = [
[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0],
[-2.02524603e-11, 1.79435860e-07, -2.60561875e-04, -1.41761141e-02],
[-2.22463426e-13, -1.55078698e-08, 3.81675160e-04, -7.30646033e-01],
[6.72595954e-13, -2.73059993e-08, 4.24068546e-04, -7.52204323e-01]
]
if t >= 12000:
rgb[0] = 0.826270103
rgb[1] = 0.994478524
rgb[2] = 1.56626022
elif t < 965.0:
rgb[0] = 4.70366907
rgb[1] = 0.0
rgb[2] = 0.0
else:
if t >= 6365.0:
i = 5
elif t >= 3315.0:
i = 4
elif t >= 1902.0:
i = 3
elif t >= 1449.0:
i = 2
elif t >= 1167.0:
i = 1
else:
i = 0
r = blackbody_table_r[i]
g = blackbody_table_g[i]
b = blackbody_table_b[i]
t_inv = 1.0 / t
rgb[0] = r[0] * t_inv + r[1] * t + r[2]
rgb[1] = g[0] * t_inv + g[1] * t + g[2]
rgb[2] = ((b[0] * t + b[1]) * t + b[2]) * t + b[3]
# Pass constant
return c.to_vec3([rgb[0], rgb[1], rgb[2]])
t = c.parse_value_input(node.inputs[0])
state.curshader.add_function(c_functions.str_blackbody)
return f'blackbody({t})'
def parse_clamp(node: bpy.types.ShaderNodeClamp, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr:
value = c.parse_value_input(node.inputs['Value'])
@ -262,14 +232,21 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket,
out_val = '({0} * {1})'.format(val1, val2)
elif op == 'DIVIDE':
out_val = '({0} / {1})'.format(val1, val2)
elif op == 'MULTIPLY_ADD':
val3 = c.parse_value_input(node.inputs[2])
out_val = '({0} * {1} + {2})'.format(val1, val2, val3)
elif op == 'POWER':
out_val = 'pow({0}, {1})'.format(val1, val2)
elif op == 'LOGARITHM':
out_val = 'log({0})'.format(val1)
elif op == 'SQRT':
out_val = 'sqrt({0})'.format(val1)
elif op == 'INVERSE_SQRT':
out_val = 'inversesqrt({0})'.format(val1)
elif op == 'ABSOLUTE':
out_val = 'abs({0})'.format(val1)
elif op == 'EXPONENT':
out_val = 'exp({0})'.format(val1)
elif op == 'MINIMUM':
out_val = 'min({0}, {1})'.format(val1, val2)
elif op == 'MAXIMUM':
@ -278,6 +255,17 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket,
out_val = 'float({0} < {1})'.format(val1, val2)
elif op == 'GREATER_THAN':
out_val = 'float({0} > {1})'.format(val1, val2)
elif op == 'SIGN':
out_val = 'sign({0})'.format(val1)
elif op == 'COMPARE':
val3 = c.parse_value_input(node.inputs[2])
out_val = 'float((abs({0} - {1}) <= max({2}, 1e-5)) ? 1.0 : 0.0)'.format(val1, val2, val3)
elif op == 'SMOOTH_MIN':
val3 = c.parse_value_input(node.inputs[2])
out_val = 'float(float({2} != 0.0 ? min({0},{1}) - (max({2} - abs({0} - {1}), 0.0) / {2}) * (max({2} - abs({0} - {1}), 0.0) / {2}) * (max({2} - abs({0} - {1}), 0.0) / {2}) * {2} * (1.0 / 6.0) : min({0}, {1})))'.format(val1, val2, val3)
elif op == 'SMOOTH_MAX':
val3 = c.parse_value_input(node.inputs[2])
out_val = 'float(0-(float({2} != 0.0 ? min(-{0},-{1}) - (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * {2} * (1.0 / 6.0) : min(-{0}, (-{1})))))'.format(val1, val2, val3)
elif op == 'ROUND':
# out_val = 'round({0})'.format(val1)
out_val = 'floor({0} + 0.5)'.format(val1)
@ -285,11 +273,20 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket,
out_val = 'floor({0})'.format(val1)
elif op == 'CEIL':
out_val = 'ceil({0})'.format(val1)
elif op == 'TRUNC':
out_val = 'trunc({0})'.format(val1)
elif op == 'FRACT':
out_val = 'fract({0})'.format(val1)
elif op == 'MODULO':
# out_val = 'float({0} % {1})'.format(val1, val2)
out_val = 'mod({0}, {1})'.format(val1, val2)
elif op == 'WRAP':
val3 = c.parse_value_input(node.inputs[2])
out_val = 'float((({1}-{2}) != 0.0) ? {0} - (({1}-{2}) * floor(({0} - {2}) / ({1}-{2}))) : {2})'.format(val1, val2, val3)
elif op == 'SNAP':
out_val = 'floor(({1} != 0.0) ? {0} / {1} : 0.0) * {1}'.format(val1, val2)
elif op == 'PINGPONG':
out_val = 'float(({1} != 0.0) ? abs(fract(({0} - {1}) / ({1} * 2.0)) * {1} * 2.0 - {1}) : 0.0)'.format(val1, val2)
elif op == 'SINE':
out_val = 'sin({0})'.format(val1)
elif op == 'COSINE':
@ -304,6 +301,16 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket,
out_val = 'atan({0})'.format(val1)
elif op == 'ARCTAN2':
out_val = 'atan({0}, {1})'.format(val1, val2)
elif op == 'SINH':
out_val = 'sinh({0})'.format(val1)
elif op == 'COSH':
out_val = 'cosh({0})'.format(val1)
elif op == 'TANH':
out_val = 'tanh({0})'.format(val1)
elif op == 'RADIANS':
out_val = 'radians({0})'.format(val1)
elif op == 'DEGREES':
out_val = 'degrees({0})'.format(val1)
if node.use_clamp:
return 'clamp({0}, 0.0, 1.0)'.format(out_val)

View file

@ -4,6 +4,7 @@ import bpy
from mathutils import Euler, Vector
import arm.material.cycles as c
import arm.material.cycles_functions as c_functions
from arm.material.parser_state import ParserState
from arm.material.shader import floatstr, vec3str
@ -141,3 +142,33 @@ def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.t
scale = c.parse_value_input(node.inputs[2])
nor = c.parse_vector_input(node.inputs[3])
return f'(vec3({height}) * {scale})'
def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
type = node.rotation_type
input_vector: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[0])
input_center: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[1])
input_axis: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[2])
input_angle: bpy.types.NodeSocket = c.parse_value_input(node.inputs[3])
input_rotation: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[4])
if node.invert:
input_invert = "0"
else:
input_invert = "1"
state.curshader.add_function(c_functions.str_rotate_around_axis)
if type == 'AXIS_ANGLE':
return f'vec3( (length({input_axis}) != 0.0) ? rotate_around_axis({input_vector} - {input_center}, normalize({input_axis}), {input_angle} * {input_invert}) + {input_center} : {input_vector} )'
elif type == 'X_AXIS':
return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(1.0, 0.0, 0.0), {input_angle} * {input_invert}) + {input_center} )'
elif type == 'Y_AXIS':
return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 1.0, 0.0), {input_angle} * {input_invert}) + {input_center} )'
elif type == 'Z_AXIS':
return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 0.0, 1.0), {input_angle} * {input_invert}) + {input_center} )'
elif type == 'EULER_XYZ':
state.curshader.add_function(c_functions.str_euler_to_mat3)
return f'vec3( mat3(({input_invert} < 0.0) ? transpose(euler_to_mat3({input_rotation})) : euler_to_mat3({input_rotation})) * ({input_vector} - {input_center}) + {input_center})'
return f'(vec3(1.0, 0.0, 0.0))'

View file

@ -1,16 +1,15 @@
from typing import Dict, List
import bpy
import arm.utils
import arm.node_utils
from bpy.types import Material
from bpy.types import Object
import arm.material.cycles as cycles
import arm.material.make_shader as make_shader
import arm.material.mat_batch as mat_batch
import arm.material.mat_state as mat_state
import arm.material.cycles as cycles
import arm.node_utils
import arm.utils
def glsl_type(t): # Merge with cycles
if t == 'RGB' or t == 'RGBA' or t == 'VECTOR':
return 'vec3'
else:
return 'float'
def glsl_value(val):
if str(type(val)) == "<class 'bpy_prop_array'>":
@ -21,39 +20,27 @@ def glsl_value(val):
else:
return val
def parse(material, mat_data, mat_users, mat_armusers):
def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]], mat_armusers):
wrd = bpy.data.worlds['Arm']
rpdat = arm.utils.get_rp()
# No batch - shader data per material
if material.arm_custom_material != '':
rpasses = ['mesh']
sd = {}
sd['contexts'] = []
con = {}
con['vertex_elements'] = []
elem = {}
elem['name'] = 'pos'
elem['data'] = 'short4norm'
con['vertex_elements'].append(elem)
elem = {}
elem['name'] = 'nor'
elem['data'] = 'short2norm'
con['vertex_elements'].append(elem)
elem = {}
elem['name'] = 'tex'
elem['data'] = 'short2norm'
con['vertex_elements'].append(elem)
elem = {}
elem['name'] = 'tex1'
elem['data'] = 'short2norm'
con['vertex_elements'].append(elem)
sd['contexts'].append(con)
con = {'vertex_elements': []}
con['vertex_elements'].append({'name': 'pos', 'data': 'short4norm'})
con['vertex_elements'].append({'name': 'nor', 'data': 'short2norm'})
con['vertex_elements'].append({'name': 'tex', 'data': 'short2norm'})
con['vertex_elements'].append({'name': 'tex1', 'data': 'short2norm'})
sd = {'contexts': [con]}
shader_data_name = material.arm_custom_material
bind_constants = {}
bind_constants['mesh'] = []
bind_textures = {}
bind_textures['mesh'] = []
bind_constants = {'mesh': []}
bind_textures = {'mesh': []}
make_shader.make_instancing_and_skinning(material, mat_users)
elif not wrd.arm_batch_materials or material.name.startswith('armdefault'):
rpasses, shader_data, shader_data_name, bind_constants, bind_textures = make_shader.build(material, mat_users, mat_armusers)
sd = shader_data.sd
@ -63,39 +50,35 @@ def parse(material, mat_data, mat_users, mat_armusers):
# Material
for rp in rpasses:
c = {}
c['name'] = rp
c['bind_constants'] = [] + bind_constants[rp]
c['bind_textures'] = [] + bind_textures[rp]
c = {
'name': rp,
'bind_constants': [] + bind_constants[rp],
'bind_textures': [] + bind_textures[rp],
}
mat_data['contexts'].append(c)
if rp == 'mesh':
const = {}
const['name'] = 'receiveShadow'
const['bool'] = material.arm_receive_shadow
c['bind_constants'].append(const)
c['bind_constants'].append({'name': 'receiveShadow', 'bool': material.arm_receive_shadow})
if material.arm_material_id != 0:
const = {}
const['name'] = 'materialID'
const['int'] = material.arm_material_id
c['bind_constants'].append(const)
c['bind_constants'].append({'name': 'materialID', 'int': material.arm_material_id})
if material.arm_material_id == 2:
wrd.world_defs += '_Hair'
elif rpdat.rp_sss_state == 'On':
sss = False
sss_node = arm.node_utils.get_node_by_type(material.node_tree, 'SUBSURFACE_SCATTERING')
if sss_node != None and sss_node.outputs[0].is_linked: # Check linked node
if sss_node is not None and sss_node.outputs[0].is_linked: # Check linked node
sss = True
sss_node = arm.node_utils.get_node_by_type(material.node_tree, 'BSDF_PRINCIPLED')
if sss_node != None and sss_node.outputs[0].is_linked and (sss_node.inputs[1].is_linked or sss_node.inputs[1].default_value != 0.0):
if sss_node is not None and sss_node.outputs[0].is_linked and (sss_node.inputs[1].is_linked or sss_node.inputs[1].default_value != 0.0):
sss = True
sss_node = arm.node_utils.get_node_armorypbr(material.node_tree)
if sss_node != None and sss_node.outputs[0].is_linked and (sss_node.inputs[8].is_linked or sss_node.inputs[8].default_value != 0.0):
if sss_node is not None and sss_node.outputs[0].is_linked and (sss_node.inputs[8].is_linked or sss_node.inputs[8].default_value != 0.0):
sss = True
const = {}
const['name'] = 'materialID'
const = {'name': 'materialID'}
if sss:
const['int'] = 2
else:
@ -111,32 +94,26 @@ def parse(material, mat_data, mat_users, mat_armusers):
if node.type == 'TEX_IMAGE':
tex_name = arm.utils.safesrc(node.name)
tex = cycles.make_texture(node, tex_name)
if tex == None: # Empty texture
tex = {}
tex['name'] = tex_name
tex['file'] = ''
# Empty texture
if tex is None:
tex = {'name': tex_name, 'file': ''}
c['bind_textures'].append(tex)
# Set marked inputs as uniforms
for node in material.node_tree.nodes:
for inp in node.inputs:
if inp.is_uniform:
uname = arm.utils.safesrc(inp.node.name) + arm.utils.safesrc(inp.name) # Merge with cycles
const = {}
const['name'] = uname
const[glsl_type(inp.type)] = glsl_value(inp.default_value)
c['bind_constants'].append(const)
uname = arm.utils.safesrc(inp.node.name) + arm.utils.safesrc(inp.name) # Merge with cycles module
c['bind_constants'].append({'name': uname, cycles.glsl_type(inp.type): glsl_value(inp.default_value)})
elif rp == 'translucent':
const = {}
const['name'] = 'receiveShadow'
const['bool'] = material.arm_receive_shadow
c['bind_constants'].append(const)
c['bind_constants'].append({'name': 'receiveShadow', 'bool': material.arm_receive_shadow})
if wrd.arm_single_data_file:
mat_data['shader'] = shader_data_name
else:
ext = '' if wrd.arm_minimize else '.json'
# Make sure that custom materials are not expected to be in .arm format
ext = '' if wrd.arm_minimize and material.arm_custom_material == "" else '.json'
mat_data['shader'] = shader_data_name + ext + '/' + shader_data_name
return sd, rpasses

View file

@ -37,7 +37,6 @@ def write_norpos(con_mesh: shader.ShaderContext, vert: shader.Shader, declare=Fa
prep = ''
if declare:
prep = 'vec3 '
vert.write_pre = True
is_bone = con_mesh.is_elem('bone')
if is_bone:
make_skin.skin_pos(vert)
@ -48,4 +47,3 @@ def write_norpos(con_mesh: shader.ShaderContext, vert: shader.Shader, declare=Fa
vert.write_attrib(prep + 'wnormal = normalize(N * vec3(nor.xy, pos.w));')
if con_mesh.is_elem('ipos'):
make_inst.inst_pos(con_mesh, vert)
vert.write_pre = False

View file

@ -3,16 +3,25 @@ import bpy
def write(vert, frag):
wrd = bpy.data.worlds['Arm']
is_shadows = '_ShadowMap' in wrd.world_defs
is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs
is_single_atlas = '_SingleAtlas' in wrd.world_defs
frag.add_include('std/clusters.glsl')
frag.add_include_front('std/clusters.glsl')
frag.add_uniform('vec2 cameraProj', link='_cameraPlaneProj')
frag.add_uniform('vec2 cameraPlane', link='_cameraPlane')
frag.add_uniform('vec4 lightsArray[maxLights * 2]', link='_lightsArray')
frag.add_uniform('vec4 lightsArray[maxLights * 3]', link='_lightsArray')
frag.add_uniform('sampler2D clustersData', link='_clustersData')
if is_shadows:
frag.add_uniform('bool receiveShadow')
frag.add_uniform('vec2 lightProj', link='_lightPlaneProj', included=True)
frag.add_uniform('samplerCubeShadow shadowMapPoint[4]', included=True)
if is_shadows_atlas:
if not is_single_atlas:
frag.add_uniform('sampler2DShadow shadowMapAtlasPoint', included=True)
else:
frag.add_uniform('sampler2DShadow shadowMapAtlas', top=True)
frag.add_uniform('vec4 pointLightDataArray[maxLightsCluster]', link='_pointLightsAtlasArray', included=True)
else:
frag.add_uniform('samplerCubeShadow shadowMapPoint[4]', included=True)
vert.add_out('vec4 wvpposition')
vert.write('wvpposition = gl_Position;')
# wvpposition.z / wvpposition.w
@ -29,11 +38,15 @@ def write(vert, frag):
frag.write('int numSpots = int(texelFetch(clustersData, ivec2(clusterI, 1 + maxLightsCluster), 0).r * 255);')
frag.write('int numPoints = numLights - numSpots;')
if is_shadows:
frag.add_uniform('mat4 LWVPSpot0', link='_biasLightWorldViewProjectionMatrixSpot0', included=True)
frag.add_uniform('mat4 LWVPSpot1', link='_biasLightWorldViewProjectionMatrixSpot1', included=True)
frag.add_uniform('mat4 LWVPSpot2', link='_biasLightWorldViewProjectionMatrixSpot2', included=True)
frag.add_uniform('mat4 LWVPSpot3', link='_biasLightWorldViewProjectionMatrixSpot3', included=True)
frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True)
if is_shadows_atlas:
if not is_single_atlas:
frag.add_uniform('sampler2DShadow shadowMapAtlasSpot', included=True)
else:
frag.add_uniform('sampler2DShadow shadowMapAtlas', top=True)
else:
frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True)
# FIXME: type is actually mat4, but otherwise it will not be set as floats when writing the shaders' json files
frag.add_uniform('vec4 LWVPSpotArray[maxLightsCluster]', link='_biasLightWorldViewProjectionMatrixSpotArray', included=True)
frag.write('for (int i = 0; i < min(numLights, maxLightsCluster); i++) {')
frag.write('int li = int(texelFetch(clustersData, ivec2(clusterI, i + 1), 0).r * 255);')
@ -43,19 +56,19 @@ def write(vert, frag):
frag.write(' n,')
frag.write(' vVec,')
frag.write(' dotNV,')
frag.write(' lightsArray[li * 2].xyz,') # lp
frag.write(' lightsArray[li * 2 + 1].xyz,') # lightCol
frag.write(' lightsArray[li * 3].xyz,') # lp
frag.write(' lightsArray[li * 3 + 1].xyz,') # lightCol
frag.write(' albedo,')
frag.write(' roughness,')
frag.write(' specular,')
frag.write(' f0')
if is_shadows:
frag.write(' , li, lightsArray[li * 2].w, receiveShadow') # bias
frag.write('\t, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0') # bias
if '_Spot' in wrd.world_defs:
frag.write(' , li > numPoints - 1')
frag.write(' , lightsArray[li * 2 + 1].w') # cutoff
frag.write(' , lightsArraySpot[li].w') # cutoff - exponent
frag.write(' , lightsArraySpot[li].xyz') # spotDir
frag.write('\t, lightsArray[li * 3 + 2].y != 0.0')
frag.write('\t, lightsArray[li * 3 + 2].y') # cutoff
frag.write('\t, lightsArraySpot[li].w') # cutoff - exponent
frag.write('\t, lightsArraySpot[li].xyz') # spotDir
if '_VoxelShadow' in wrd.world_defs and '_VoxelAOvar' in wrd.world_defs:
frag.write(' , voxels, voxpos')
frag.write(');')

View file

@ -125,6 +125,10 @@ def make_base(con_mesh, parse_opacity):
if write_material_attribs_post != None:
write_material_attribs_post(con_mesh, frag)
vert.add_out('vec3 wnormal')
make_attrib.write_norpos(con_mesh, vert)
frag.write_attrib('vec3 n = normalize(wnormal);')
if not is_displacement and not vattr_written:
make_attrib.write_vertpos(vert)
@ -162,10 +166,6 @@ def make_base(con_mesh, parse_opacity):
make_tess.interpolate(tese, 'vcolor', 3, declare_out=frag.contains('vcolor'))
tese.write_pre = False
vert.add_out('vec3 wnormal')
make_attrib.write_norpos(con_mesh, vert)
frag.write_attrib('vec3 n = normalize(wnormal);')
if con_mesh.is_elem('tang'):
if tese is not None:
tese.add_out('mat3 TBN')
@ -268,6 +268,9 @@ def make_deferred(con_mesh, rpasses):
frag.write('vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5;')
frag.write('fragColor[2].rg = vec2(posa - posb);')
if mat_state.material.arm_ignore_irradiance:
frag.write('fragColor[2].b = 1.0;')
return con_mesh
def make_raytracer(con_mesh):
@ -354,6 +357,12 @@ def make_forward_mobile(con_mesh):
return
is_shadows = '_ShadowMap' in wrd.world_defs
is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs
shadowmap_sun = 'shadowMap'
if is_shadows_atlas:
is_single_atlas = '_SingleAtlas' in wrd.world_defs
shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas'
frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True)
frag.write('vec3 direct = vec3(0.0);')
if '_Sun' in wrd.world_defs:
@ -363,10 +372,10 @@ def make_forward_mobile(con_mesh):
frag.write('float sdotNL = max(dot(n, sunDir), 0.0);')
if is_shadows:
vert.add_out('vec4 lightPosition')
vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrix')
vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrixSun')
vert.write('lightPosition = LWVP * spos;')
frag.add_uniform('bool receiveShadow')
frag.add_uniform('sampler2DShadow shadowMap')
frag.add_uniform(f'sampler2DShadow {shadowmap_sun}')
frag.add_uniform('float shadowsBias', '_sunShadowsBias')
frag.write('if (receiveShadow) {')
@ -374,14 +383,14 @@ def make_forward_mobile(con_mesh):
frag.add_include('std/shadows.glsl')
frag.add_uniform('vec4 casData[shadowmapCascades * 4 + 4]', '_cascadeData', included=True)
frag.add_uniform('vec3 eye', '_cameraPosition')
frag.write('svisibility = shadowTestCascade(shadowMap, eye, wposition + n * shadowsBias * 10, shadowsBias);')
frag.write(f'svisibility = shadowTestCascade({shadowmap_sun}, eye, wposition + n * shadowsBias * 10, shadowsBias);')
else:
frag.write('if (lightPosition.w > 0.0) {')
frag.write(' vec3 lPos = lightPosition.xyz / lightPosition.w;')
if '_Legacy' in wrd.world_defs:
frag.write(' svisibility = float(texture(shadowMap, vec2(lPos.xy)).r > lPos.z - shadowsBias);')
frag.write(f' svisibility = float(texture({shadowmap_sun}, vec2(lPos.xy)).r > lPos.z - shadowsBias);')
else:
frag.write(' svisibility = texture(shadowMap, vec3(lPos.xy, lPos.z - shadowsBias)).r;')
frag.write(f' svisibility = texture({shadowmap_sun}, vec3(lPos.xy, lPos.z - shadowsBias)).r;')
frag.write('}')
frag.write('}') # receiveShadow
frag.write('direct += basecol * sdotNL * sunCol * svisibility;')
@ -404,8 +413,8 @@ def make_forward_mobile(con_mesh):
frag.write('if (receiveShadow) {')
if '_Spot' in wrd.world_defs:
vert.add_out('vec4 spotPosition')
vert.add_uniform('mat4 LWVPSpot0', link='_biasLightWorldViewProjectionMatrixSpot0')
vert.write('spotPosition = LWVPSpot0 * spos;')
vert.add_uniform('mat4 LWVPSpotArray[1]', link='_biasLightWorldViewProjectionMatrixSpotArray')
vert.write('spotPosition = LWVPSpotArray[0] * spos;')
frag.add_uniform('sampler2DShadow shadowMapSpot[1]')
frag.write('if (spotPosition.w > 0.0) {')
frag.write(' vec3 lPos = spotPosition.xyz / spotPosition.w;')
@ -532,13 +541,10 @@ def make_forward(con_mesh):
frag.add_uniform('sampler2D sltcMag', '_ltcMag', included=True)
if '_ShadowMap' in wrd.world_defs:
if '_SinglePoint' in wrd.world_defs:
frag.add_uniform('mat4 LWVPSpot0', link='_biasLightViewProjectionMatrixSpot0', included=True)
frag.add_uniform('mat4 LWVPSpot[0]', link='_biasLightViewProjectionMatrixSpot0', included=True)
frag.add_uniform('sampler2DShadow shadowMapSpot[1]', included=True)
if '_Clusters' in wrd.world_defs:
frag.add_uniform('mat4 LWVPSpot0', link='_biasLightWorldViewProjectionMatrixSpot0', included=True)
frag.add_uniform('mat4 LWVPSpot1', link='_biasLightWorldViewProjectionMatrixSpot1', included=True)
frag.add_uniform('mat4 LWVPSpot2', link='_biasLightWorldViewProjectionMatrixSpot2', included=True)
frag.add_uniform('mat4 LWVPSpot3', link='_biasLightWorldViewProjectionMatrixSpot3', included=True)
frag.add_uniform('mat4 LWVPSpotArray[4]', link='_biasLightWorldViewProjectionMatrixSpotArray', included=True)
frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True)
if not blend:
@ -570,12 +576,14 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False):
arm_discard = mat_state.material.arm_discard
make_base(con_mesh, parse_opacity=(parse_opacity or arm_discard))
blend = mat_state.material.arm_blending
vert = con_mesh.vert
frag = con_mesh.frag
tese = con_mesh.tese
if parse_opacity or arm_discard:
if arm_discard:
if arm_discard or blend:
opac = mat_state.material.arm_discard_opacity
frag.write('if (opacity < {0}) discard;'.format(opac))
elif transluc_pass:
@ -584,7 +592,6 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False):
opac = '0.9999' # 1.0 - eps
frag.write('if (opacity < {0}) discard;'.format(opac))
blend = mat_state.material.arm_blending
if blend:
frag.add_out('vec4 fragColor[1]')
if parse_opacity:
@ -605,6 +612,12 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False):
frag.add_include('std/light.glsl')
is_shadows = '_ShadowMap' in wrd.world_defs
is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs
is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs
shadowmap_sun = 'shadowMap'
if is_shadows_atlas:
shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas'
frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True)
frag.write('vec3 albedo = surfaceAlbedo(basecol, metallic);')
frag.write('vec3 f0 = surfaceF0(basecol, metallic);')
@ -661,14 +674,14 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False):
frag.write('float sdotVH = dot(vVec, sh);')
if is_shadows:
frag.add_uniform('bool receiveShadow')
frag.add_uniform('sampler2DShadow shadowMap')
frag.add_uniform(f'sampler2DShadow {shadowmap_sun}', top=True)
frag.add_uniform('float shadowsBias', '_sunShadowsBias')
frag.write('if (receiveShadow) {')
if '_CSM' in wrd.world_defs:
frag.add_include('std/shadows.glsl')
frag.add_uniform('vec4 casData[shadowmapCascades * 4 + 4]', '_cascadeData', included=True)
frag.add_uniform('vec3 eye', '_cameraPosition')
frag.write('svisibility = shadowTestCascade(shadowMap, eye, wposition + n * shadowsBias * 10, shadowsBias);')
frag.write(f'svisibility = shadowTestCascade({shadowmap_sun}, eye, wposition + n * shadowsBias * 10, shadowsBias);')
else:
if tese is not None:
tese.add_out('vec4 lightPosition')
@ -681,11 +694,11 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False):
vert.write('lightPosition = LVP * vec4(wposition, 1.0);')
else:
vert.add_out('vec4 lightPosition')
vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrix')
vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrixSun')
vert.write('lightPosition = LWVP * spos;')
frag.write('vec3 lPos = lightPosition.xyz / lightPosition.w;')
frag.write('const vec2 smSize = shadowmapSize;')
frag.write('svisibility = PCF(shadowMap, lPos.xy, lPos.z - shadowsBias, smSize);')
frag.write(f'svisibility = PCF({shadowmap_sun}, lPos.xy, lPos.z - shadowsBias, smSize);')
frag.write('}') # receiveShadow
if '_VoxelShadow' in wrd.world_defs and '_VoxelAOvar' in wrd.world_defs:
frag.write('svisibility *= 1.0 - traceShadow(voxels, voxpos, sunDir);')
@ -703,7 +716,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False):
frag.add_uniform('float pointBias', link='_pointShadowsBias')
if '_Spot' in wrd.world_defs:
# Skip world matrix, already in world-space
frag.add_uniform('mat4 LWVPSpot0', link='_biasLightViewProjectionMatrixSpot0', included=True)
frag.add_uniform('mat4 LWVPSpot[1]', link='_biasLightViewProjectionMatrixSpotArray', included=True)
frag.add_uniform('sampler2DShadow shadowMapSpot[1]', included=True)
else:
frag.add_uniform('vec2 lightProj', link='_lightPlaneProj', included=True)

View file

@ -1,31 +1,38 @@
import os
import bpy
import subprocess
import arm.utils
import arm.assets as assets
import arm.material.mat_utils as mat_utils
import arm.material.mat_state as mat_state
from arm.material.shader import ShaderData
import arm.material.cycles as cycles
import arm.material.make_mesh as make_mesh
import arm.material.make_transluc as make_transluc
import arm.material.make_overlay as make_overlay
import arm.material.make_depth as make_depth
import arm.material.make_decal as make_decal
import arm.material.make_voxel as make_voxel
from typing import Dict, List, Tuple
import bpy
from bpy.types import Material
from bpy.types import Object
import arm.api
import arm.assets as assets
import arm.exporter
import arm.log as log
import arm.material.cycles as cycles
import arm.material.make_decal as make_decal
import arm.material.make_depth as make_depth
import arm.material.make_mesh as make_mesh
import arm.material.make_overlay as make_overlay
import arm.material.make_transluc as make_transluc
import arm.material.make_voxel as make_voxel
import arm.material.mat_state as mat_state
import arm.material.mat_utils as mat_utils
from arm.material.shader import Shader, ShaderContext, ShaderData
import arm.utils
rpass_hook = None
def build(material, mat_users, mat_armusers):
def build(material: Material, mat_users: Dict[Material, List[Object]], mat_armusers) -> Tuple:
mat_state.mat_users = mat_users
mat_state.mat_armusers = mat_armusers
mat_state.material = material
mat_state.nodes = material.node_tree.nodes
mat_state.data = ShaderData(material)
mat_state.output_node = cycles.node_by_type(mat_state.nodes, 'OUTPUT_MATERIAL')
if mat_state.output_node == None:
if mat_state.output_node is None:
# Place empty material output to keep compiler happy..
mat_state.output_node = mat_state.nodes.new('ShaderNodeOutputMaterial')
@ -41,22 +48,7 @@ def build(material, mat_users, mat_armusers):
if not os.path.exists(full_path):
os.makedirs(full_path)
global_elems = []
if mat_users != None and material in mat_users:
for bo in mat_users[material]:
# GPU Skinning
if arm.utils.export_bone_data(bo):
global_elems.append({'name': 'bone', 'data': 'short4norm'})
global_elems.append({'name': 'weight', 'data': 'short4norm'})
# Instancing
if bo.arm_instanced != 'Off' or material.arm_particle_flag:
global_elems.append({'name': 'ipos', 'data': 'float3'})
if bo.arm_instanced == 'Loc + Rot' or bo.arm_instanced == 'Loc + Rot + Scale':
global_elems.append({'name': 'irot', 'data': 'float3'})
if bo.arm_instanced == 'Loc + Scale' or bo.arm_instanced == 'Loc + Rot + Scale':
global_elems.append({'name': 'iscl', 'data': 'float3'})
mat_state.data.global_elems = global_elems
make_instancing_and_skinning(material, mat_users)
bind_constants = dict()
bind_textures = dict()
@ -72,10 +64,10 @@ def build(material, mat_users, mat_armusers):
con = None
if rpdat.rp_driver != 'Armory' and arm.api.drivers[rpdat.rp_driver]['make_rpass'] != None:
if rpdat.rp_driver != 'Armory' and arm.api.drivers[rpdat.rp_driver]['make_rpass'] is not None:
con = arm.api.drivers[rpdat.rp_driver]['make_rpass'](rp)
if con != None:
if con is not None:
pass
elif rp == 'mesh':
@ -99,7 +91,7 @@ def build(material, mat_users, mat_armusers):
elif rp == 'voxel':
con = make_voxel.make(rp)
elif rpass_hook != None:
elif rpass_hook is not None:
con = rpass_hook(rp)
write_shaders(rel_path, con, rp, matname)
@ -107,7 +99,7 @@ def build(material, mat_users, mat_armusers):
shader_data_name = matname + '_data'
if wrd.arm_single_data_file:
if not 'shader_datas' in arm.exporter.current_output:
if 'shader_datas' not in arm.exporter.current_output:
arm.exporter.current_output['shader_datas'] = []
arm.exporter.current_output['shader_datas'].append(mat_state.data.get()['shader_datas'][0])
else:
@ -117,7 +109,8 @@ def build(material, mat_users, mat_armusers):
return rpasses, mat_state.data, shader_data_name, bind_constants, bind_textures
def write_shaders(rel_path, con, rpass, matname):
def write_shaders(rel_path: str, con: ShaderContext, rpass: str, matname: str) -> None:
keep_cache = mat_state.material.arm_cached
write_shader(rel_path, con.vert, 'vert', rpass, matname, keep_cache=keep_cache)
write_shader(rel_path, con.frag, 'frag', rpass, matname, keep_cache=keep_cache)
@ -125,8 +118,9 @@ def write_shaders(rel_path, con, rpass, matname):
write_shader(rel_path, con.tesc, 'tesc', rpass, matname, keep_cache=keep_cache)
write_shader(rel_path, con.tese, 'tese', rpass, matname, keep_cache=keep_cache)
def write_shader(rel_path, shader, ext, rpass, matname, keep_cache=True):
if shader == None or shader.is_linked:
def write_shader(rel_path: str, shader: Shader, ext: str, rpass: str, matname: str, keep_cache=True) -> None:
if shader is None or shader.is_linked:
return
# TODO: blend context
@ -161,3 +155,43 @@ def write_shader(rel_path, shader, ext, rpass, matname, keep_cache=True):
args.append('pos')
proc = subprocess.call(args)
os.chdir(cwd)
def make_instancing_and_skinning(mat: Material, mat_users: Dict[Material, List[Object]]) -> None:
"""Build material with instancing or skinning if enabled.
If the material is a custom material, only validation checks for instancing are performed."""
global_elems = []
if mat_users is not None and mat in mat_users:
# Whether there are both an instanced object and a not instanced object with this material
instancing_usage = [False, False]
for bo in mat_users[mat]:
if mat.arm_custom_material == '':
# GPU Skinning
if arm.utils.export_bone_data(bo):
global_elems.append({'name': 'bone', 'data': 'short4norm'})
global_elems.append({'name': 'weight', 'data': 'short4norm'})
# Instancing
inst = bo.arm_instanced
if inst != 'Off' or mat.arm_particle_flag:
instancing_usage[0] = True
if mat.arm_custom_material == '':
global_elems.append({'name': 'ipos', 'data': 'float3'})
if 'Rot' in inst:
global_elems.append({'name': 'irot', 'data': 'float3'})
if 'Scale' in inst:
global_elems.append({'name': 'iscl', 'data': 'float3'})
elif inst == 'Off':
# Ignore children of instanced objects, they are instanced even when set to 'Off'
instancing_usage[1] = bo.parent is None or bo.parent.arm_instanced == 'Off'
if instancing_usage[0] and instancing_usage[1]:
# Display a warning for invalid instancing configurations
# See https://github.com/armory3d/armory/issues/2072
log.warn(f'Material "{mat.name}" has both instanced and not instanced objects, objects might flicker!')
if mat.arm_custom_material == '':
mat_state.data.global_elems = global_elems

View file

@ -17,4 +17,4 @@ def skin_pos(vert):
def skin_nor(vert, prep):
rpdat = arm.utils.get_rp()
vert.write(prep + 'wnormal = normalize(N * (vec3(nor.xy, pos.w) + 2.0 * cross(skinA.xyz, cross(skinA.xyz, vec3(nor.xy, pos.w)) + skinA.w * vec3(nor.xy, pos.w))));')
vert.write_attrib(prep + 'wnormal = normalize(N * (vec3(nor.xy, pos.w) + 2.0 * cross(skinA.xyz, cross(skinA.xyz, vec3(nor.xy, pos.w)) + skinA.w * vec3(nor.xy, pos.w))));')

View file

@ -192,6 +192,7 @@ class Shader:
self.includes = []
self.ins = []
self.outs = []
self.uniforms_top = []
self.uniforms = []
self.constants = []
self.functions = {}
@ -218,6 +219,14 @@ class Shader:
if not self.has_include(s):
self.includes.append(s)
def add_include_front(self, s):
if not self.has_include(s):
pos = 0
# make sure compiled.inc is always on top
if len(self.includes) > 0 and self.includes[0] == 'compiled.inc':
pos = 1
self.includes.insert(pos, s)
def add_in(self, s):
if s not in self.ins:
self.ins.append(s)
@ -226,7 +235,7 @@ class Shader:
if s not in self.outs:
self.outs.append(s)
def add_uniform(self, s, link=None, included=False,
def add_uniform(self, s, link=None, included=False, top=False,
tex_addr_u=None, tex_addr_v=None,
tex_filter_min=None, tex_filter_mag=None,
tex_mipmap_filter=None):
@ -258,7 +267,10 @@ class Shader:
ar[0] = 'floats'
ar[1] = ar[1].split('[', 1)[0]
self.context.add_constant(ar[0], ar[1], link=link)
if not included and s not in self.uniforms:
if top:
if not included and s not in self.uniforms_top:
self.uniforms_top.append(s)
elif not included and s not in self.uniforms:
self.uniforms.append(s)
def add_const(self, type_str: str, name: str, value_str: str, array_size: int = 0):
@ -397,6 +409,8 @@ class Shader:
s += 'layout(triangle_strip) out;\n'
s += 'layout(max_vertices=3) out;\n'
for a in self.uniforms_top:
s += 'uniform ' + a + ';\n'
for a in self.includes:
s += '#include "' + a + '"\n'
if self.geom_passthrough:

View file

@ -11,7 +11,7 @@ import arm.proxy
import arm.utils
# Armory version
arm_version = '2020.12'
arm_version = '2021.4'
arm_commit = '$Id$'
def get_project_html5_copy(self):
@ -280,6 +280,12 @@ def init_properties():
bpy.types.Object.arm_rb_trigger = BoolProperty(name="Trigger", description="Disable contact response", default=False)
bpy.types.Object.arm_rb_deactivation_time = FloatProperty(name="Deactivation Time", description="Delay putting rigid body into sleep", default=0.0)
bpy.types.Object.arm_rb_ccd = BoolProperty(name="Continuous Collision Detection", description="Improve collision for fast moving objects", default=False)
bpy.types.Object.arm_rb_collision_filter_mask = bpy.props.BoolVectorProperty(
name="Collision Collections Filter Mask",
description="Collision collections rigid body interacts with",
default=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False),
size=20,
subtype='LAYER')
bpy.types.Object.arm_animation_enabled = BoolProperty(name="Animation", description="Enable skinning & timeline animation", default=True)
bpy.types.Object.arm_tilesheet = StringProperty(name="Tilesheet", description="Set tilesheet animation", default='')
bpy.types.Object.arm_tilesheet_action = StringProperty(name="Tilesheet Action", description="Set startup action", default='')
@ -332,6 +338,7 @@ def init_properties():
bpy.types.Material.arm_overlay = BoolProperty(name="Overlay", default=False)
bpy.types.Material.arm_decal = BoolProperty(name="Decal", default=False)
bpy.types.Material.arm_two_sided = BoolProperty(name="Two-Sided", description="Flip normal when drawing back-face", default=False)
bpy.types.Material.arm_ignore_irradiance = BoolProperty(name="Ignore Irradiance", description="Ignore irradiance for material", default=False)
bpy.types.Material.arm_cull_mode = EnumProperty(
items=[('none', 'Both', 'None'),
('clockwise', 'Front', 'Clockwise'),

View file

@ -2,29 +2,29 @@ import bpy
class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel):
bl_label = "Armory Collision Filter Mask"
bl_label = "Collections Filter Mask"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_parent_id = "ARM_PT_PhysicsPropsPanel"
@classmethod
def poll(self, context):
obj = context.object
if obj is None:
return False
return obj.rigid_body is not None
def draw(self, context):
layout = self.layout
layout.use_property_split = False
layout.use_property_decorate = False
obj = bpy.context.object
if obj is None:
return
if obj.rigid_body is not None:
layout.prop(obj, 'arm_rb_collision_filter_mask')
obj = context.object
layout.prop(obj, 'arm_rb_collision_filter_mask', text="", expand=True)
def register():
bpy.utils.register_class(ARM_PT_RbCollisionFilterMaskPanel)
bpy.types.Object.arm_rb_collision_filter_mask = bpy.props.BoolVectorProperty(
name="Collision Filter Mask",
default=[True] + [False] * 19,
size=20,
subtype='LAYER')
def unregister():

View file

@ -18,11 +18,11 @@ class ArmPropertyListItem(bpy.types.PropertyGroup):
class ARM_UL_PropertyList(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
layout.use_property_split = False
# Make sure your code supports all 3 layout types
if self.layout_type in {'DEFAULT', 'COMPACT'}:
row = layout.row()
row.prop(item, "name_prop", text="", emboss=False, icon="OBJECT_DATAMODE")
row.prop(item, item.type_prop + "_prop", text="", emboss=(item.type_prop == 'boolean'))
layout.prop(item, "name_prop", text="", emboss=False, icon="OBJECT_DATAMODE")
layout.prop(item, item.type_prop + "_prop", text="", emboss=(item.type_prop == 'boolean'))
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label(text="", icon="OBJECT_DATAMODE")
@ -155,4 +155,4 @@ def unregister():
bpy.utils.unregister_class(ARM_UL_PropertyList)
bpy.utils.unregister_class(ArmPropertyListNewItem)
bpy.utils.unregister_class(ArmPropertyListDeleteItem)
bpy.utils.unregister_class(ArmPropertyListMoveItem)
bpy.utils.unregister_class(ArmPropertyListMoveItem)

View file

@ -242,6 +242,62 @@ class ArmRPListItem(bpy.types.PropertyGroup):
rp_autoexposure: BoolProperty(name="Auto Exposure", description="Adjust exposure based on luminance", default=False, update=update_renderpath)
rp_compositornodes: BoolProperty(name="Compositor", description="Draw compositor nodes", default=True, update=update_renderpath)
rp_shadows: BoolProperty(name="Shadows", description="Enable shadow casting", default=True, update=update_renderpath)
rp_max_lights: EnumProperty(
items=[('4', '4', '4'),
('8', '8', '8'),
('16', '16', '16'),
('24', '24', '24'),
('32', '32', '32'),
('64', '64', '64'),],
name="Max Lights", description="Max number of lights that can be visible in the screen", default='16')
rp_max_lights_cluster: EnumProperty(
items=[('4', '4', '4'),
('8', '8', '8'),
('16', '16', '16'),
('24', '24', '24'),
('32', '32', '32'),
('64', '64', '64'),],
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_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_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_subdivisions: EnumProperty(
items=[('2', '2', '2'),
('3', '3', '3'),
('4', '4', '4'),
('5', '5', '5'),
('6', '6', '6'),
('7', '7', '7'),
('8', '8', '8'),],
name="LOD Subdivisions", description="Number of subdivisions of the default tile size for LOD", default='2', update=update_renderpath)
rp_shadowmap_atlas_max_size_point: EnumProperty(
items=[('1024', '1024', '1024'),
('2048', '2048', '2048'),
('4096', '4096', '4096'),
('8192', '8192', '8192'),
('16384', '16384', '16384')],
name="Max Atlas Texture Size Points", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath)
rp_shadowmap_atlas_max_size_spot: EnumProperty(
items=[('1024', '1024', '1024'),
('2048', '2048', '2048'),
('4096', '4096', '4096'),
('8192', '8192', '8192'),
('16384', '16384', '16384')],
name="Max Atlas Texture Size Spots", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath)
rp_shadowmap_atlas_max_size_sun: EnumProperty(
items=[('1024', '1024', '1024'),
('2048', '2048', '2048'),
('4096', '4096', '4096'),
('8192', '8192', '8192'),
('16384', '16384', '16384')],
name="Max Atlas Texture Size Sun", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath)
rp_shadowmap_atlas_max_size: EnumProperty(
items=[('1024', '1024', '1024'),
('2048', '2048', '2048'),
('4096', '4096', '4096'),
('8192', '8192', '8192'),
('16384', '16384', '16384')],
name="Max Atlas Texture Size", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath)
rp_shadowmap_cube: EnumProperty(
items=[('256', '256', '256'),
('512', '512', '512'),

View file

@ -328,20 +328,24 @@ class ArmEditBundledScriptButton(bpy.types.Operator):
obj = bpy.context.scene
sdk_path = arm.utils.get_sdk_path()
project_path = arm.utils.get_fp()
pkg = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package)
item = obj.arm_traitlist[obj.arm_traitlist_index]
source_hx_path = os.path.join(sdk_path , 'armory', 'Sources', 'armory', 'trait', item.class_name_prop + '.hx')
target_hx_path = os.path.join(project_path, 'Sources', pkg, item.class_name_prop + '.hx')
pkg = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package)
source_hx_path = os.path.join(sdk_path, 'armory', 'Sources', 'armory', 'trait', item.class_name_prop + '.hx')
target_dir = os.path.join(project_path, 'Sources', pkg)
target_hx_path = os.path.join(target_dir, item.class_name_prop + '.hx')
if not os.path.isfile(target_hx_path):
if not os.path.exists(target_dir):
os.makedirs(target_dir)
# Rewrite package and copy
sf = open(source_hx_path, encoding="utf-8")
sf.readline()
tf = open(target_hx_path, 'w', encoding="utf-8")
tf.write('package ' + pkg + ';\n')
shutil.copyfileobj(sf, tf)
sf.close()
tf.close()
with open(source_hx_path, encoding="utf-8") as sf:
sf.readline()
with open(target_hx_path, 'w', encoding="utf-8") as tf:
tf.write('package ' + pkg + ';\n')
shutil.copyfileobj(sf, tf)
arm.utils.fetch_script_names()
# From bundled to script
@ -861,9 +865,10 @@ def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, b
if item.arm_traitpropswarnings:
box = layout.box()
box.label(text=f"Warnings ({len(item.arm_traitpropswarnings)}):", icon="ERROR")
col = box.column(align=True)
for warning in item.arm_traitpropswarnings:
box.label(text=warning.warning)
col.label(text=f'"{warning.propName}": {warning.warning}')
propsrows = max(len(item.arm_traitpropslist), 6)
row = layout.row()

View file

@ -32,6 +32,7 @@ def filter_objects(item, b_object):
class ArmTraitPropWarning(bpy.types.PropertyGroup):
propName: StringProperty(name="Property Name")
warning: StringProperty(name="Warning")

View file

@ -1,11 +1,13 @@
import json
import os
import time
import shutil
import bpy
from bpy.props import *
import arm.api
import arm.assets as assets
from arm.exporter import ArmoryExporter
import arm.log as log
import arm.logicnode.replacement
import arm.make as make
@ -191,7 +193,19 @@ class ARM_PT_PhysicsPropsPanel(bpy.types.Panel):
if obj == None:
return
if obj.rigid_body != None:
rb = obj.rigid_body
if rb is not None:
col = layout.column()
row = col.row()
row.alignment = 'RIGHT'
rb_type = 'Dynamic'
if ArmoryExporter.rigid_body_static(rb):
rb_type = 'Static'
if rb.kinematic:
rb_type = 'Kinematic'
row.label(text=(f'Rigid Body Export Type: {rb_type}'), icon='AUTO')
layout.prop(obj, 'arm_rb_linear_factor')
layout.prop(obj, 'arm_rb_angular_factor')
layout.prop(obj, 'arm_rb_trigger')
@ -299,6 +313,146 @@ class InvalidateMaterialCacheButton(bpy.types.Operator):
context.material.signature = ''
return{'FINISHED'}
class ARM_OT_NewCustomMaterial(bpy.types.Operator):
bl_idname = "arm.new_custom_material"
bl_label = "New Custom Material"
bl_description = "Add a new custom material. This will create all the necessary files and folders"
def poll_mat_name(self, context):
project_dir = arm.utils.get_fp()
shader_dir_dst = os.path.join(project_dir, 'Shaders')
mat_name = arm.utils.safestr(self.mat_name)
self.mat_exists = os.path.isdir(os.path.join(project_dir, 'Bundled', mat_name))
vert_exists = os.path.isfile(os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl'))
frag_exists = os.path.isfile(os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl'))
self.shader_exists = vert_exists or frag_exists
mat_name: StringProperty(
name='Material Name', description='The name of the new material',
default='MyCustomMaterial',
update=poll_mat_name)
mode: EnumProperty(
name='Target RP', description='Choose for which render path mode the new material is created',
default='deferred',
items=[('deferred', 'Deferred', 'Create the material for a deferred render path'),
('forward', 'Forward', 'Create the material for a forward render path')])
mat_exists: BoolProperty(
name='Material Already Exists',
default=False,
options={'HIDDEN', 'SKIP_SAVE'})
shader_exists: BoolProperty(
name='Shaders Already Exist',
default=False,
options={'HIDDEN', 'SKIP_SAVE'})
def invoke(self, context, event):
if not bpy.data.is_saved:
self.report({'INFO'}, "Please save your file first")
return {"CANCELLED"}
# Try to set deferred/forward based on the selected render path
try:
self.mode = 'forward' if arm.utils.get_rp().rp_renderer == 'Forward' else 'deferred'
except IndexError:
# No render path, use default (deferred)
pass
self.poll_mat_name(context)
wm = context.window_manager
return wm.invoke_props_dialog(self, width=300)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, 'mat_name')
layout.prop(self, 'mode', expand=True)
if self.mat_exists:
box = layout.box()
box.alert = True
col = box.column(align=True)
col.label(text='A custom material with that name already exists,', icon='ERROR')
col.label(text='clicking on \'OK\' will override the material!', icon='BLANK1')
if self.shader_exists:
box = layout.box()
box.alert = True
col = box.column(align=True)
col.label(text='Shader file(s) with that name already exists,', icon='ERROR')
col.label(text='clicking on \'OK\' will override the shader(s)!', icon='BLANK1')
def execute(self, context):
if self.mat_name == '':
return {'CANCELLED'}
project_dir = arm.utils.get_fp()
shader_dir_src = os.path.join(arm.utils.get_sdk_path(), 'armory', 'Shaders', 'custom_mat_presets')
shader_dir_dst = os.path.join(project_dir, 'Shaders')
mat_name = arm.utils.safestr(self.mat_name)
mat_dir = os.path.join(project_dir, 'Bundled', mat_name)
os.makedirs(mat_dir, exist_ok=True)
os.makedirs(shader_dir_dst, exist_ok=True)
# Shader data
if self.mode == 'forward':
col_attachments = ['RGBA64']
constants = [{'link': '_worldViewProjectionMatrix', 'name': 'WVP', 'type': 'mat4'}]
vertex_elems = [{'name': 'pos', 'data': 'short4norm'}]
else:
col_attachments = ['RGBA64', 'RGBA64']
constants = [
{'link': '_worldViewProjectionMatrix', 'name': 'WVP', 'type': 'mat4'},
{'link': '_normalMatrix', 'name': 'N', 'type': 'mat3'}
]
vertex_elems = [
{'name': 'pos', 'data': 'short4norm'},
{'name': 'nor', 'data': 'short2norm'}
]
con = {
'color_attachments': col_attachments,
'compare_mode': 'less',
'constants': constants,
'cull_mode': 'clockwise',
'depth_write': True,
'fragment_shader': f'{mat_name}.frag',
'name': 'mesh',
'texture_units': [],
'vertex_shader': f'{mat_name}.vert',
'vertex_elements': vertex_elems
}
data = {
'shader_datas': [{
'contexts': [con],
'name': f'{mat_name}'
}]
}
# Save shader data file
with open(os.path.join(mat_dir, f'{mat_name}.json'), 'w') as datafile:
json.dump(data, datafile, indent=4, sort_keys=True)
# Copy preset shaders to project
if self.mode == 'forward':
shutil.copy(os.path.join(shader_dir_src, 'custom_mat_forward.frag.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl'))
shutil.copy(os.path.join(shader_dir_src, 'custom_mat_forward.vert.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl'))
else:
shutil.copy(os.path.join(shader_dir_src, 'custom_mat_deferred.frag.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl'))
shutil.copy(os.path.join(shader_dir_src, 'custom_mat_deferred.vert.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl'))
# True if called from the material properties tab, else it was called from the search menu
if hasattr(context, 'material') and context.material is not None:
context.material.arm_custom_material = mat_name
return{'FINISHED'}
class ARM_PT_MaterialPropsPanel(bpy.types.Panel):
bl_label = "Armory Props"
bl_space_type = "PROPERTIES"
@ -318,6 +472,7 @@ class ARM_PT_MaterialPropsPanel(bpy.types.Panel):
wrd = bpy.data.worlds['Arm']
columnb.enabled = len(wrd.arm_rplist) > 0 and arm.utils.get_rp().rp_renderer == 'Forward'
columnb.prop(mat, 'arm_receive_shadow')
layout.prop(mat, 'arm_ignore_irradiance')
layout.prop(mat, 'arm_two_sided')
columnb = layout.column()
columnb.enabled = not mat.arm_two_sided
@ -330,7 +485,9 @@ class ARM_PT_MaterialPropsPanel(bpy.types.Panel):
columnb.enabled = mat.arm_discard
columnb.prop(mat, 'arm_discard_opacity')
columnb.prop(mat, 'arm_discard_opacity_shadows')
layout.prop(mat, 'arm_custom_material')
row = layout.row(align=True)
row.prop(mat, 'arm_custom_material')
row.operator('arm.new_custom_material', text='', icon='ADD')
layout.prop(mat, 'arm_skip_context')
layout.prop(mat, 'arm_particle_fade')
layout.prop(mat, 'arm_billboard')
@ -436,7 +593,20 @@ class ARM_PT_ArmoryPlayerPanel(bpy.types.Panel):
box.alert = True
col = box.column(align=True)
col.label(text=f'{log.num_warnings} warning{"s" if log.num_warnings > 1 else ""} occurred during compilation!', icon='ERROR')
warnings = 'warnings' if log.num_warnings > 1 else 'warning'
col.label(text=f'{log.num_warnings} {warnings} occurred during compilation!', icon='ERROR')
# Blank icon to achieve the same indentation as the line before
# prevent showing "open console" twice:
if log.num_errors == 0:
col.label(text='Please open the console to get more information.', icon='BLANK1')
if log.num_errors > 0:
box = layout.box()
box.alert = True
# Less spacing between lines
col = box.column(align=True)
errors = 'errors' if log.num_errors > 1 else 'error'
col.label(text=f'{log.num_errors} {errors} occurred during compilation!', icon='CANCEL')
# Blank icon to achieve the same indentation as the line before
col.label(text='Please open the console to get more information.', icon='BLANK1')
@ -1158,6 +1328,13 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel):
rpdat = wrd.arm_rplist[wrd.arm_rplist_index]
self.layout.prop(rpdat, "rp_shadows", text="")
def compute_subdivs(self, max, subdivs):
l = [max]
for i in range(subdivs - 1):
l.append(int(max / 2))
max = max / 2
return l
def draw(self, context):
layout = self.layout
layout.use_property_split = True
@ -1177,6 +1354,55 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel):
col2.prop(rpdat, 'arm_shadowmap_split')
col.prop(rpdat, 'arm_shadowmap_bounds')
col.prop(rpdat, 'arm_pcfsize')
layout.separator()
layout.prop(rpdat, 'rp_shadowmap_atlas')
colatlas = layout.column()
colatlas.enabled = rpdat.rp_shadowmap_atlas
colatlas.prop(rpdat, 'rp_max_lights')
colatlas.prop(rpdat, 'rp_max_lights_cluster')
colatlas.prop(rpdat, 'rp_shadowmap_atlas_lod')
colatlas_lod = colatlas.column()
colatlas_lod.enabled = rpdat.rp_shadowmap_atlas_lod
colatlas_lod.prop(rpdat, 'rp_shadowmap_atlas_lod_subdivisions')
colatlas_lod_info = colatlas_lod.row()
colatlas_lod_info.alignment = 'RIGHT'
subdivs_list = self.compute_subdivs(int(rpdat.rp_shadowmap_cascade), int(rpdat.rp_shadowmap_atlas_lod_subdivisions))
subdiv_text = "Subdivisions: " + ', '.join(map(str, subdivs_list))
colatlas_lod_info.label(text=subdiv_text, icon="IMAGE_ZDEPTH")
colatlas.prop(rpdat, 'rp_shadowmap_atlas_single_map')
# show size for single texture
if rpdat.rp_shadowmap_atlas_single_map:
colatlas_single = colatlas.column()
colatlas_single.prop(rpdat, 'rp_shadowmap_atlas_max_size')
if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size):
print(rpdat.rp_shadowmap_atlas_max_size)
colatlas_warning = colatlas_single.row()
colatlas_warning.alignment = 'RIGHT'
colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR")
else:
# show size for all types
colatlas_mixed = colatlas.column()
colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_spot')
if int(rpdat.rp_shadowmap_cascade) > int(rpdat.rp_shadowmap_atlas_max_size_spot):
colatlas_warning = colatlas_mixed.row()
colatlas_warning.alignment = 'RIGHT'
colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_spot} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR")
colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_point')
if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size_point):
colatlas_warning = colatlas_mixed.row()
colatlas_warning.alignment = 'RIGHT'
colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_point} is too small for the shadowmap size: {rpdat.rp_shadowmap_cube}', icon="ERROR")
colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_sun')
if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size_sun):
colatlas_warning = colatlas_mixed.row()
colatlas_warning.alignment = 'RIGHT'
colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_sun} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR")
class ARM_PT_RenderPathVoxelsPanel(bpy.types.Panel):
bl_label = "Voxel AO"
@ -2210,6 +2436,7 @@ def register():
bpy.utils.register_class(ARM_PT_WorldPropsPanel)
bpy.utils.register_class(InvalidateCacheButton)
bpy.utils.register_class(InvalidateMaterialCacheButton)
bpy.utils.register_class(ARM_OT_NewCustomMaterial)
bpy.utils.register_class(ARM_PT_MaterialPropsPanel)
bpy.utils.register_class(ARM_PT_MaterialBlendingPropsPanel)
bpy.utils.register_class(ARM_PT_MaterialDriverPropsPanel)
@ -2297,6 +2524,7 @@ def unregister():
bpy.utils.unregister_class(ARM_PT_ScenePropsPanel)
bpy.utils.unregister_class(InvalidateCacheButton)
bpy.utils.unregister_class(InvalidateMaterialCacheButton)
bpy.utils.unregister_class(ARM_OT_NewCustomMaterial)
bpy.utils.unregister_class(ARM_PT_MaterialDriverPropsPanel)
bpy.utils.unregister_class(ARM_PT_MaterialBlendingPropsPanel)
bpy.utils.unregister_class(ARM_PT_MaterialPropsPanel)
@ -2349,4 +2577,4 @@ def unregister():
bpy.utils.unregister_class(scene.TLM_PT_Filtering)
bpy.utils.unregister_class(scene.TLM_PT_Encoding)
bpy.utils.unregister_class(scene.TLM_PT_Utility)
bpy.utils.unregister_class(scene.TLM_PT_Additional)
bpy.utils.unregister_class(scene.TLM_PT_Additional)

View file

@ -4,7 +4,7 @@ import os
import platform
import re
import subprocess
from typing import Any
from typing import Any, Dict, List, Optional, Tuple
import webbrowser
import shlex
import locale
@ -156,11 +156,11 @@ def get_sdk_path():
addon_prefs = get_arm_preferences()
p = bundled_sdk_path()
if use_local_sdk:
return get_fp() + '/armsdk/'
return os.path.normpath(get_fp() + '/armsdk/')
elif os.path.exists(p) and addon_prefs.sdk_bundled:
return p
return os.path.normpath(p)
else:
return addon_prefs.sdk_path
return os.path.normpath(addon_prefs.sdk_path)
def get_last_commit():
p = get_sdk_path() + 'armory/.git/refs/heads/master'
@ -292,116 +292,97 @@ def fetch_bundled_script_names():
for file in glob.glob('*.hx'):
wrd.arm_bundled_scripts_list.add().name = file.rsplit('.', 1)[0]
script_props = {}
script_props_defaults = {}
script_warnings = {}
def fetch_script_props(file):
with open(file, encoding="utf-8") as f:
name = file.rsplit('.', 1)[0]
if 'Sources' in name:
name = name[name.index('Sources') + 8:]
if '/' in name:
name = name.replace('/', '.')
if '\\' in file:
name = name.replace('\\', '.')
script_warnings: Dict[str, List[Tuple[str, str]]] = {} # Script name -> List of (identifier, warning message)
script_props[name] = []
script_props_defaults[name] = []
script_warnings[name] = []
# See https://regex101.com/r/bbrCzN/8
RX_MODIFIERS = r'(?P<modifiers>(?:public\s+|private\s+|static\s+|inline\s+|final\s+)*)?' # Optional modifiers
RX_IDENTIFIER = r'(?P<identifier>[_$a-z]+[_a-z0-9]*)' # Variable name, follow Haxe rules
RX_TYPE = r'(?::\s+(?P<type>[_a-z]+[\._a-z0-9]*))?' # Optional type annotation
RX_VALUE = r'(?:\s*=\s*(?P<value>(?:\".*\")|(?:[^;]+)|))?' # Optional default value
lines = f.read().splitlines()
PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}(?P<attr_type>var|final)\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};'
PROP_REGEX = re.compile(PROP_REGEX_RAW, re.IGNORECASE)
def fetch_script_props(filename: str):
"""Parses @prop declarations from the given Haxe script."""
with open(filename, 'r', encoding='utf-8') as sourcefile:
source = sourcefile.read()
# Read next line
read_prop = False
for lineno, line in enumerate(lines):
# enumerate() starts with 0
lineno += 1
if source == '':
return
if not read_prop:
read_prop = line.lstrip().startswith('@prop')
name = filename.rsplit('.', 1)[0]
# Convert the name into a package path relative to the "Sources" dir
if 'Sources' in name:
name = name[name.index('Sources') + 8:]
if '/' in name:
name = name.replace('/', '.')
if '\\' in filename:
name = name.replace('\\', '.')
script_props[name] = []
script_props_defaults[name] = []
script_warnings[name] = []
for match in re.finditer(PROP_REGEX, source):
p_modifiers: Optional[str] = match.group('modifiers')
p_identifier: str = match.group('identifier')
p_type: Optional[str] = match.group('type')
p_default_val: Optional[str] = match.group('value')
if p_modifiers is not None:
if 'static' in p_modifiers:
script_warnings[name].append((p_identifier, '`static` modifier might cause unwanted behaviour!'))
if 'inline' in p_modifiers:
script_warnings[name].append((p_identifier, '`inline` modifier is not supported!'))
continue
if 'final' in p_modifiers or match.group('attr_type') == 'final':
script_warnings[name].append((p_identifier, '`final` properties are not supported!'))
continue
if read_prop:
if 'var ' in line:
# Line of code
code_ref = line.split('var ')[1].split(';')[0]
else:
script_warnings[name].append(f"Line {lineno - 1}: Unused @prop")
read_prop = line.lstrip().startswith('@prop')
continue
# Property type is annotated
if p_type is not None:
if p_type.startswith("iron.object."):
p_type = p_type[12:]
elif p_type.startswith("iron.math."):
p_type = p_type[10:]
valid_prop = False
type_default_val = get_type_default_value(p_type)
if type_default_val is None:
script_warnings[name].append((p_identifier, f'unsupported type `{p_type}`!'))
continue
# Declaration = Assignment;
var_sides = code_ref.split('=')
# DeclarationName: DeclarationType
decl_sides = var_sides[0].split(':')
# Default value exists
if p_default_val is not None:
# Remove string quotes
p_default_val = p_default_val.replace('\'', '').replace('"', '')
else:
p_default_val = type_default_val
prop_name = decl_sides[0].strip()
# Default value is given instead, try to infer the properties type from it
elif p_default_val is not None:
p_type = get_prop_type_from_value(p_default_val)
if 'static ' in line:
# Static properties can be overwritten multiple times
# from multiple property lists
script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Static properties may result in undefined behaviours!")
# Type is not recognized
if p_type is None:
script_warnings[name].append((p_identifier, 'could not infer property type from given value!'))
continue
if p_type == "String":
p_default_val = p_default_val.replace('\'', '').replace('"', '')
# If the prop type is annotated in the code
# (= declaration has two parts)
if len(decl_sides) > 1:
prop_type = decl_sides[1].strip()
if prop_type.startswith("iron.object."):
prop_type = prop_type[12:]
elif prop_type.startswith("iron.math."):
prop_type = prop_type[10:]
else:
script_warnings[name].append((p_identifier, 'missing type or default value!'))
continue
# Default value exists
if len(var_sides) > 1 and var_sides[1].strip() != "":
# Type is not supported
if get_type_default_value(prop_type) is None:
script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Type {prop_type} is not supported!")
read_prop = False
continue
# Register prop
prop = (p_identifier, p_type)
script_props[name].append(prop)
script_props_defaults[name].append(p_default_val)
prop_value = var_sides[1].replace('\'', '').replace('"', '').strip()
else:
prop_value = get_type_default_value(prop_type)
# Type is not supported
if prop_value is None:
script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Type {prop_type} is not supported!")
read_prop = False
continue
valid_prop = True
# Default value exists
elif len(var_sides) > 1 and var_sides[1].strip() != "":
prop_value = var_sides[1].strip()
prop_type = get_prop_type_from_value(prop_value)
# Type is not recognized
if prop_type is None:
script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Property type not recognized!")
read_prop = False
continue
if prop_type == "String":
prop_value = prop_value.replace('\'', '').replace('"', '')
valid_prop = True
else:
script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Not a valid property!")
read_prop = False
continue
prop = (prop_name, prop_type)
# Register prop
if valid_prop:
script_props[name].append(prop)
script_props_defaults[name].append(prop_value)
read_prop = False
def get_prop_type_from_value(value: str):
"""
@ -555,7 +536,8 @@ def fetch_prop(o):
item.arm_traitpropswarnings.clear()
for warning in warnings:
entry = item.arm_traitpropswarnings.add()
entry.warning = warning
entry.propName = warning[0]
entry.warning = warning[1]
def fetch_bundled_trait_props():
# Bundled script props
@ -594,7 +576,7 @@ def safesrc(s):
def safestr(s: str) -> str:
"""Outputs a string where special characters have been replaced with
'_', which can be safely used in file and path names."""
for c in r'[]/\;,><&*:%=+@!#^()|?^':
for c in r'''[]/\;,><&*:%=+@!#^()|?^'"''':
s = s.replace(c, '_')
return ''.join([i if ord(i) < 128 else '_' for i in s])
@ -1096,4 +1078,4 @@ def register(local_sdk=False):
use_local_sdk = local_sdk
def unregister():
pass
pass

View file

@ -254,6 +254,9 @@ project.addSources('Sources');
assets.add_khafile_def('arm_debug')
khafile.write(add_shaders(sdk_path + "/armory/Shaders/debug_draw/**", rel_path=do_relpath_sdk))
if not is_publish and state.target == 'html5':
khafile.write("project.addParameter('--debug');\n")
if wrd.arm_verbose_output:
khafile.write("project.addParameter('--times');\n")
@ -517,6 +520,7 @@ def write_indexhtml(w, h, is_publish):
add_compiledglsl = ''
def write_compiledglsl(defs, make_variants):
rpdat = arm.utils.get_rp()
wrd = bpy.data.worlds['Arm']
shadowmap_size = arm.utils.get_cascade_size(rpdat) if rpdat.rp_shadows else 0
with open(arm.utils.build_dir() + '/compiled/Shaders/compiled.inc', 'w') as f:
f.write(
@ -533,6 +537,9 @@ def write_compiledglsl(defs, make_variants):
#endif
""")
if state.target == 'html5' or arm.utils.get_gapi() == 'direct3d11':
f.write("#define _FlipY\n")
f.write("""const float PI = 3.1415926535;
const float PI2 = PI * 2.0;
const vec2 shadowmapSize = vec2(""" + str(shadowmap_size) + """, """ + str(shadowmap_size) + """);
@ -689,6 +696,22 @@ const float voxelgiAperture = """ + str(round(rpdat.arm_voxelgi_aperture * 100)
if rpdat.arm_skin == 'On':
f.write(
"""const int skinMaxBones = """ + str(rpdat.arm_skin_max_bones) + """;
""")
if '_Clusters' in wrd.world_defs:
max_lights = "4"
max_lights_clusters = "4"
if rpdat.rp_shadowmap_atlas:
max_lights = str(rpdat.rp_max_lights)
max_lights_clusters = str(rpdat.rp_max_lights_cluster)
# prevent max lights cluster being higher than max lights
if (int(max_lights_clusters) > int(max_lights)):
max_lights_clusters = max_lights
f.write(
"""const int maxLights = """ + max_lights + """;
const int maxLightsCluster = """ + max_lights_clusters + """;
const float clusterNear = 3.0;
""")
f.write(add_compiledglsl + '\n') # External defined constants