Compare commits

...

38 Commits

Author SHA1 Message Date
365c624d73 Tilesheets To Objects 2026-09-09 02:33:00 -07:00
384a8a7ee8 Voxel Shadow - No Temporal 2026-09-09 00:35:34 -07:00
4e2b59a1d6 Merge pull request 'main' (#138) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#138
2026-09-09 05:42:48 +00:00
6d07e70b11 Hashlink Compile - Curve/Console 2026-09-08 22:34:51 -07:00
42fdff4757 TriVoxel: Burley + (EON, Gotanda, Chan) Diffuse Models 2026-09-08 22:02:48 -07:00
d44fd9993a Get Transform Update 2026-09-07 11:33:15 -07:00
3fd1b395fb Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-09-07 11:13:11 -07:00
40bca62f96 Get Transform Node Replacement Update 2026-09-07 11:12:57 -07:00
e5082835c4 Merge pull request 'Reflections / Refractions' (#137) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#137
2026-09-06 08:39:04 +00:00
bc700a6374 Reflections / Refractions 2026-09-06 01:37:13 -07:00
1e500993d9 Merge pull request 'main' (#136) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#136
2026-09-04 21:02:18 +00:00
d842744aeb 9 Tap PCF 2026-09-04 13:59:07 -07:00
ada18b4648 Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-09-04 13:51:26 -07:00
5004efe046 Transparent Shadow Fix 2026-09-04 13:50:03 -07:00
1015e0df34 Merge pull request 'main' (#135) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#135
2026-09-04 17:49:34 +00:00
949b355255 merge upstream 2026-09-04 17:48:06 +00:00
0460b9d587 Repe [T3DU] Update - 31f26d171bba0355ce2a77031e3aad4c64dbc7e9 2026-09-04 10:46:13 -07:00
88cebe4f49 Merge pull request 'main' (#134) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#134
2026-08-28 16:17:33 +00:00
c915312901 Voxel Viewport 2026-08-27 23:45:53 -07:00
0f9d693bb7 Pre-invalidated 2026-08-27 23:02:25 -07:00
ce09e510e9 Cleanup 2026-08-27 22:41:52 -07:00
7319040624 Update Export 2026-08-27 15:14:57 -07:00
542773bd79 Merge pull request 'main' (#133) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#133
2026-08-10 17:45:06 +00:00
fe017dd874 Full BSDF 2026-08-07 02:04:01 -07:00
7aeebf2008 Extend BRDF 2026-07-27 12:10:11 -07:00
0b4184ccc2 Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-07-24 17:02:15 -07:00
ddc12e8607 BSDF Shaders 2026-07-24 17:02:00 -07:00
6ad647ae56 Merge pull request 'main' (#132) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#132
2026-07-24 09:22:31 +00:00
b77aca926a Refactor shader sockets 2026-07-24 01:59:51 -07:00
c2bb20f905 Moises Jpelaez: 5.2 Updates 2026-07-23 23:41:13 -07:00
14c6a7be03 Merge pull request 'main' (#131) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#131
2026-07-24 06:13:01 +00:00
85d63e8413 Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-07-23 23:08:56 -07:00
78452aaf67 Finished Viewport 2026-07-23 23:02:14 -07:00
1f72636350 Merge pull request 'main' (#130) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#130
2026-07-23 07:00:06 +00:00
62433ce86a merge upstream 2026-07-23 06:58:51 +00:00
2be36398f7 Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-07-22 21:04:07 -07:00
2675138ddc Windows RunT/Krom WebView2 DX11 and OpenGL 2026-07-22 21:03:43 -07:00
572665e8e6 Merge pull request 'main' (#129) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#129
2026-07-22 04:01:11 +00:00
524 changed files with 31710 additions and 4511 deletions

Binary file not shown.

Binary file not shown.

View File

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

View File

@ -0,0 +1,17 @@
{
"contexts": [
{
"name": "add_pass",
"depth_write": false,
"compare_mode": "always",
"cull_mode": "none",
"blend_source": "source_alpha",
"blend_destination": "inverse_source_alpha",
"blend_operation": "add",
"links": [],
"texture_params": [],
"vertex_shader": "../include/pass.vert.glsl",
"fragment_shader": "../include/pass_copy.frag.glsl"
}
]
}

View File

@ -6,6 +6,7 @@
uniform sampler2D tex;
uniform sampler2D gbuffer0; // Roughness
uniform sampler2D gbufferD; // Depth
uniform vec2 dirInv;
@ -14,19 +15,46 @@ out vec4 fragColor;
void main() {
float roughness = textureLod(gbuffer0, texCoord, 0.0).b;
// if (roughness == 0.0) { // Always blur for now, non blured output can produce noise
// fragColor.rgb = textureLod(tex, texCoord).rgb;
// return;
// }
if (roughness >= 0.8) { // No reflections
if (roughness >= 0.8) {
fragColor.rgb = textureLod(tex, texCoord, 0.0).rgb;
return;
}
fragColor.rgb = textureLod(tex, texCoord + dirInv * 2.5, 0.0).rgb;
fragColor.rgb += textureLod(tex, texCoord + dirInv * 1.5, 0.0).rgb;
fragColor.rgb += textureLod(tex, texCoord, 0.0).rgb;
fragColor.rgb += textureLod(tex, texCoord - dirInv * 1.5, 0.0).rgb;
fragColor.rgb += textureLod(tex, texCoord - dirInv * 2.5, 0.0).rgb;
fragColor.rgb /= vec3(5.0);
if (roughness < 0.01) {
fragColor.rgb = textureLod(tex, texCoord, 0.0).rgb;
return;
}
float blurRadius = 1.0 + roughness * 4.0;
vec3 center = textureLod(tex, texCoord, 0.0).rgb;
float centerDepth = textureLod(gbufferD, texCoord, 0.0).r;
float w0 = 1.0 / (1.0 + roughness * 2.0);
float w1 = 1.0 / (1.0 + roughness);
float w2 = 1.0 / (1.0 + roughness * 0.5);
float totalW = w0;
fragColor.rgb = center * w0;
vec2 offsets[4];
offsets[0] = dirInv * blurRadius * 2.5;
offsets[1] = dirInv * blurRadius * 1.5;
offsets[2] = -dirInv * blurRadius * 1.5;
offsets[3] = -dirInv * blurRadius * 2.5;
float weights[4];
weights[0] = w2;
weights[1] = w1;
weights[2] = w1;
weights[3] = w2;
for (int i = 0; i < 4; i++) {
vec2 sampleTC = texCoord + offsets[i];
float sampleDepth = textureLod(gbufferD, sampleTC, 0.0).r;
float depthWeight = exp(-abs(centerDepth - sampleDepth) * 100.0);
float w = weights[i] * depthWeight;
fragColor.rgb += textureLod(tex, sampleTC, 0.0).rgb * w;
totalW += w;
}
fragColor.rgb /= vec3(totalW);
}

View File

@ -11,6 +11,7 @@
#endif
uniform sampler2D tex;
#ifdef _CDepth
uniform sampler2D gbufferD;
#endif
@ -67,6 +68,7 @@ uniform vec3 PPComp14;
uniform vec4 PPComp15;
uniform vec4 PPComp16;
uniform vec4 PPComp18;
uniform vec4 PPComp19;
#endif
// #ifdef _CPos
@ -230,6 +232,45 @@ vec3 lensflare(vec2 uv, vec2 pos) {
}
#endif
#ifdef _CDistort
float distortHash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float distortValueNoise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
float a = distortHash(i);
float b = distortHash(i + vec2(1.0, 0.0));
float c = distortHash(i + vec2(0.0, 1.0));
float d = distortHash(i + vec2(1.0, 1.0));
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
vec2 distortSmoothNoise(vec2 p) {
return vec2(
distortValueNoise(p),
distortValueNoise(p + vec2(5.2, 1.3))
);
}
vec2 distortUV(vec2 uv, vec2 nUV, float t, float strength) {
float intensity = 0.01 * strength;
float scale = 4.0;
float speed = 0.25;
nUV.x += t * speed;
nUV.y += t * speed;
vec2 noise = distortSmoothNoise(nUV * scale);
uv += (-1.0 + noise * 2.0) * intensity;
return uv;
}
#endif
void main() {
vec2 texCo = texCoord;
#ifdef _DynRes
@ -252,22 +293,25 @@ void main() {
#ifdef _CFishEye
#ifdef _CPostprocess
const float fishEyeStrength = -(PPComp2.y);
float fishEyeStrength = PPComp2.y;
#else
const float fishEyeStrength = -0.01;
float fishEyeStrength = compoFisheyeStrength;
#endif
const vec2 m = vec2(0.5, 0.5);
vec2 d = texCo - m;
float r = sqrt(dot(d, d));
float power = (2.0 * PI / (2.0 * sqrt(dot(m, m)))) * fishEyeStrength;
float bind;
if (power > 0.0) { bind = sqrt(dot(m, m)); }
else { bind = m.x; }
if (power > 0.0) {
texCo = m + normalize(d) * tan(r * power) * bind / tan(bind * power);
}
else {
texCo = m + normalize(d) * atan(r * -power * 10.0) * bind / atan(-power * bind * 10.0);
if (abs(fishEyeStrength) > 0.0001) {
const vec2 m = vec2(0.5, 0.5);
vec2 d = texCo - m;
float r = sqrt(dot(d, d));
float power = - (2.0 * PI / (2.0 * sqrt(dot(m, m)))) * fishEyeStrength;
float bind;
if (power > 0.0) { bind = sqrt(dot(m, m)); }
else { bind = m.x; }
if (power > 0.0) {
texCo = m + normalize(d) * tan(r * power) * bind / tan(bind * power);
}
else {
texCo = m + normalize(d) * atan(r * -power * 10.0) * bind / atan(-power * bind * 10.0);
}
}
#endif
@ -277,9 +321,28 @@ void main() {
#else
float strengthDistort = compoDistortStrength;
#endif
float uX = time * strengthDistort;
texCo.y = texCo.y + (sin(texCo.x*4.0+uX*2.0)*0.01);
texCo.x = texCo.x + (cos(texCo.y*4.0+uX*2.0)*0.01);
vec2 nUV = texCo;
texCo = distortUV(texCo, nUV, time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.1, nUV.y + 0.1), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.2, nUV.y + 0.2), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.3, nUV.y + 0.3), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.4, nUV.y + 0.4), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.5, nUV.y + 0.5), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.6, nUV.y + 0.6), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.7, nUV.y + 0.7), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.8, nUV.y + 0.8), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.9, nUV.y + 0.9), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.15, nUV.y + 0.15), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.25, nUV.y + 0.25), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.35, nUV.y + 0.35), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.45, nUV.y + 0.45), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.55, nUV.y + 0.55), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.65, nUV.y + 0.65), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.75, nUV.y + 0.75), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.85, nUV.y + 0.85), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.95, nUV.y + 0.95), time, strengthDistort);
#endif
#ifdef _CDepth
@ -343,6 +406,7 @@ void main() {
float compoDistance = PPComp3.x;
float compoLength = PPComp3.y;
float compoStop = PPComp3.z;
vec2 focus = vec2(PPComp19.x, PPComp19.y);
if (PPComp2.z == 1){
compoAutoFocus = true;
@ -350,9 +414,9 @@ void main() {
compoAutoFocus = false;
}
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, compoAutoFocus, compoDistance, compoLength, compoStop);
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, compoAutoFocus, compoDistance, compoLength, compoStop, focus, PPComp19.z);
#else
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, true, compoDOFDistance, compoDOFLength, compoDOFFstop);
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, true, compoDOFDistance, compoDOFLength, compoDOFFstop, vec2(0.5, 0.5), 1.0);
#endif
#else
fragColor = textureLod(tex, texCo, 0.0);
@ -382,8 +446,10 @@ void main() {
vec3 col4 = textureLod(tex, texCo + vec2(texStep.x, texStep.y) * SharpenSize, 0.0).rgb;
vec3 colavg = (col1 + col2 + col3 + col4) * 0.25;
float edgeMagnitude = length(fragColor.rgb - colavg);
fragColor.rgb = mix(fragColor.rgb, SharpenColor, min(edgeMagnitude * strengthSharpen * 2.0, 1.0));
float edgeMagnitude = length(fragColor.rgb - colavg);
float luma = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114));
float sharpenMask = 1.0 - smoothstep(0.5, 0.8, luma);
fragColor.rgb = mix(fragColor.rgb, SharpenColor, min(edgeMagnitude * strengthSharpen * 2.0, 1.0) * sharpenMask);
#endif
#ifdef _CFog

View File

@ -245,6 +245,11 @@
"name": "PPComp18",
"link": "_PPComp18",
"ifdef": ["_CPostprocess"]
},
{
"name": "PPComp19",
"link": "_PPComp19",
"ifdef": ["_CPostprocess"]
}
],
"texture_params": [],

View File

@ -17,10 +17,12 @@ in vec3 wnormal;
+-------------------+-----------------++--------------+--------------+-----------------+--------------------+
| GBUF_IDX_1 | || base color (RGB) | occlusion/specular |
+-------------------+-----------------++--------------+--------------+-----------------+--------------------+
| GBUF_IDX_2 | _gbuffer2 || velocity (XY) | ignore radiance | unused |
| GBUF_IDX_2 | _gbuffer2 || velocity (XY) | ignore radiance | tangent angle |
+-------------------+-----------------++--------------+--------------+-----------------+--------------------+
| GBUF_IDX_EMISSION | _EmissionShaded || emission color (RGB) | unused |
+-------------------+-----------------++--------------+--------------+-----------------+--------------------+
| GBUF_IDX_REFRACTION | _SSRefraction || packed IOR | transmittance | surfaceDepth | unused |
| | _VoxelRefract || (0-1 range) | | | |
The indices as well as the GBUF_SIZE define are defined in "compiled.inc".
*/
@ -52,6 +54,10 @@ void main() {
#endif
#ifdef _SSRefraction
fragColor[GBUF_IDX_REFRACTION] = vec4(ior, opacity, 0.0, 0.0);
fragColor[GBUF_IDX_REFRACTION] = vec4(packIOR(ior), opacity, 0.0, 1.0);
#endif
#ifdef _Anisotropy
fragColor[GBUF_IDX_2].a = -1.0;
#endif
}

View File

@ -7,9 +7,13 @@ out vec4 fragColor[GBUF_SIZE];
void main() {
fragColor[GBUF_IDX_0] = vec4(1.0, 1.0, 0.0, 1.0);
#if GBUF_SIZE > 1
fragColor[GBUF_IDX_1] = vec4(color, 1.0);
#else
fragColor[GBUF_IDX_0] = vec4(color, 1.0);
#endif
#ifdef _EmissionShaded
fragColor[GBUF_IDX_EMISSION] = vec4(0.0);
fragColor[GBUF_IDX_EMISSION] = vec4(color, 1.0);
#endif
}

View File

@ -8,12 +8,10 @@
#ifdef _Irr
#include "std/shirr.glsl"
#endif
#ifdef _SSS
#include "std/sss.glsl"
#endif
#ifdef _SSRS
#include "std/ssrs.glsl"
#endif
#include "std/brdf.glsl"
uniform sampler2D gbufferD;
uniform sampler2D gbuffer0;
@ -25,6 +23,9 @@ uniform sampler2D gbuffer1;
#ifdef _EmissionShaded
uniform sampler2D gbufferEmission;
#endif
#ifdef _ClearCoat
uniform sampler2D gbufferCoatNormal;
#endif
#ifdef _VoxelGI
uniform sampler2D voxels_diffuse;
@ -91,7 +92,7 @@ uniform mat4 invVP;
#ifdef _SinglePoint
//!uniform sampler2DShadow shadowMapSpot[1];
//!uniform sampler2D shadowMapSpotTransparent[1];
//!uniform mat4 LWVPSpot[1];
//!uniform mat4 LWVPSpotArray[1];
#endif
#ifdef _Clusters
//!uniform sampler2DShadow shadowMapSpot[4];
@ -136,7 +137,7 @@ uniform vec2 cameraPlane;
#ifdef _ShadowMapTransparent
//!uniform sampler2D shadowMapSpotTransparent[1];
#endif
//!uniform mat4 LWVPSpot[1];
//!uniform mat4 LWVPSpotArray[1];
#else
//!uniform samplerCubeShadow shadowMapPoint[1];
#ifdef _ShadowMapTransparent
@ -199,6 +200,7 @@ uniform vec3 sunCol;
uniform sampler2D shadowMapAtlasSunTransparent;
#endif
#endif
//!uniform vec4 tileBoundsSunArray[maxLights * shadowmapCascades];
#else
uniform sampler2DShadow shadowMap;
#ifdef _ShadowMapTransparent
@ -235,6 +237,9 @@ uniform float time;
#endif
#include "std/light.glsl"
#ifdef _SSS
#include "std/sss.glsl"
#endif
in vec2 texCoord;
in vec3 viewRay;
@ -254,13 +259,41 @@ void main() {
float metallic;
uint matid;
unpackFloatInt16(g0.a, metallic, matid);
#ifdef _ExtBRDF
matid = min(matid, uint(MAX_MATERIALS - 1));
//!uniform vec4 materialParams[MAX_MATERIALS * 8];
vec4 matp0 = vec4(0.0), matp1 = vec4(0.0), matp2 = vec4(0.0), matp3 = vec4(0.0);
vec4 matp4 = vec4(0.0), matp5 = vec4(0.0), matp6 = vec4(0.0), matp7 = vec4(0.0);
// TODO: coatIOR=1.5, ior=1.45, thinWall=1.0 move to python make files
matp1.z = 1.5;
matp3.x = 1.45;
matp3.y = 1.0;
if (matid >= 3u) {
getMaterialParams(matid, matp0, matp1, matp2, matp3, matp4, matp5, matp6, matp7);
}
#ifdef _ClearCoat
vec3 coatTintCol = vec3(matp1.w, matp2.x, matp2.y);
#endif
#ifdef _Sheen
vec3 sheenTintCol = vec3(matp5.z, matp5.w, matp6.x);
#endif
#ifdef _SSS
vec3 sssColorVal = vec3(matp4.w, matp5.x, matp5.y);
vec3 sssRadiusBase = vec3(matp4.x, matp4.y, matp4.z);
float sssRadiusScalar = max(max(matp4.x, matp4.y), matp4.z) * matp7.x;
#endif
#endif
vec2 occspec = unpackFloat2(g1.a);
// re-investigate clamp basecolor to prevent extreme values causing glitches
vec3 basecolor = min(g1.rgb, vec3(2.0));
vec3 albedo = surfaceAlbedo(basecolor, metallic);
vec3 f0 = surfaceF0(basecolor, metallic);
#ifdef _ExtBRDF
f0 = mix(f0, basecolor, vec3(matp6.y, matp6.z, matp6.w));
#endif
#ifdef _VRStereo
bool isLeftEye = texCoord.x < 0.5;
vec3 eyePos = isLeftEye ? eyeLeft : eyeRight;
@ -279,10 +312,26 @@ void main() {
#endif
float dotNV = max(dot(n, v), 0.0);
#ifdef _ClearCoat
vec4 gCoat = textureLod(gbufferCoatNormal, texCoord, 0.0);
vec3 nCoat;
nCoat.z = 1.0 - abs(gCoat.x) - abs(gCoat.y);
nCoat.xy = nCoat.z >= 0.0 ? gCoat.xy : octahedronWrap(gCoat.xy);
nCoat = normalize(nCoat);
#endif
#ifdef _gbuffer2
vec4 g2 = textureLod(gbuffer2, texCoord, 0.0);
#endif
#ifdef _Anisotropy
#ifdef _gbuffer2
vec3 wTangent = decodeTangent(g2.a, n);
#else
vec3 wTangent = vec3(0.0);
#endif
#endif
#ifdef _MicroShadowing
occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel
@ -295,6 +344,44 @@ void main() {
vec3 F = f0;
#endif
#ifdef _ExtBRDF
float iblSheenWeight = 1.0;
float iblCoatWeight = 1.0;
float iblLayerWeight = 1.0;
vec3 coatTintAbsorb = vec3(1.0);
#ifdef _Sheen
float sheenAlb = sheenIBLAlbedo(matp0.z, matp0.w, dotNV);
iblSheenWeight = max(1.0 - sheenAlb *
max(max(sheenTintCol.r, sheenTintCol.g), sheenTintCol.b), 0.0);
#endif
#ifdef _ClearCoat
float dotNVCoat = max(dot(nCoat, v), 0.0);
float coatF = coatIBLFresnel(matp1.x, matp1.z, dotNVCoat);
iblCoatWeight = max(1.0 - coatF, 0.0);
coatTintAbsorb = mix(vec3(1.0), clamp(coatTintCol, 0.0, 1.0),
clamp(1.0 / max(dotNVCoat, 0.3) * 0.2, 0.0, 1.0));
#endif
iblLayerWeight = iblSheenWeight * iblCoatWeight;
brdf_sheenWeight = iblSheenWeight;
brdf_coatWeight = iblCoatWeight;
brdf_coatTintAbsorb = coatTintAbsorb;
#ifdef _Sheen
brdf_sheenAlbedo = sheenAlb;
#endif
#ifdef _ClearCoat
brdf_coatF0 = (matp1.z - 1.0) / (matp1.z + 1.0);
brdf_coatF0 = brdf_coatF0 * brdf_coatF0;
#endif
#ifdef _Transmission
brdf_transmissionF0 = (matp3.x - 1.0) / (matp3.x + 1.0);
brdf_transmissionF0 = brdf_transmissionF0 * brdf_transmissionF0;
#endif
#endif // _ExtBRDF
#ifndef _VoxelAOvar
#ifndef _VoxelGI
// Envmap
@ -302,9 +389,7 @@ void main() {
vec3 envl = shIrradiance(n, shirr);
#ifdef _gbuffer2
if (g2.b < 0.5) {
envl = envl;
} else {
if (g2.b >= 0.5) {
envl = vec3(0.0);
}
#endif
@ -317,20 +402,25 @@ void main() {
#endif
#ifdef _Rad
#ifdef _Anisotropy
vec3 reflectionWorld = anisotropicIBLDirection(n, v, wTangent,
matp0.x, roughness);
#else
vec3 reflectionWorld = reflect(-v, n);
#endif
float lod = getMipFromRoughness(roughness, envmapNumMipmaps);
vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(reflectionWorld), lod).rgb;
prefilteredColor = min(prefilteredColor, vec3(20.0));
#endif
#ifdef _EnvLDR
envl.rgb = pow(envl.rgb, vec3(2.2));
envl.rgb = srgbToLinear(envl.rgb);
#ifdef _Rad
prefilteredColor = pow(prefilteredColor, vec3(2.2));
prefilteredColor = srgbToLinear(prefilteredColor);
#endif
#endif
envl.rgb *= albedo;
envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV);
#ifdef _Brdf
envl.rgb *= 1.0 - F; //LV: We should take refracted light into account
@ -344,6 +434,68 @@ void main() {
#endif
#endif
#ifdef _ExtBRDF
envl.rgb *= iblLayerWeight;
#ifdef _Transmission
float transF = transmissionIBLFresnel(matp3.x, dotNV);
float transmittance = 1.0 - transF;
#ifdef _Rad
if (matp2.z > 0.0 && transmittance > 0.0) {
vec3 refrDir;
if (matp3.y > 0.5) {
refrDir = reflect(-v, n);
} else {
refrDir = transmissionIBLDirection(n, v, matp3.x);
}
float transLod = getMipFromRoughness(matp2.w, envmapNumMipmaps);
vec3 transColor = textureLod(senvmapRadiance,
envMapEquirect(refrDir), transLod).rgb;
transColor = min(transColor, vec3(20.0));
#ifdef _EnvLDR
transColor = srgbToLinear(transColor);
#endif
envl.rgb += albedo * matp2.z * transmittance * transColor * dotNV
* iblLayerWeight;
}
#endif
#endif
#ifdef _ClearCoat
envl.rgb *= coatTintAbsorb;
#ifdef _Rad
if (coatF > 0.0) {
float coatLod = getMipFromRoughness(matp1.y, envmapNumMipmaps);
vec3 coatRefl = reflect(-v, nCoat);
vec3 coatColor = textureLod(senvmapRadiance,
envMapEquirect(coatRefl), coatLod).rgb;
coatColor = min(coatColor, vec3(20.0));
#ifdef _EnvLDR
coatColor = srgbToLinear(coatColor);
#endif
envl.rgb += coatColor * coatF * iblSheenWeight;
}
#endif
#endif
#ifdef _Sheen
#ifdef _Rad
if (sheenAlb > 0.0) {
float sheenLod = getMipFromRoughness(matp0.w, envmapNumMipmaps);
vec3 sheenRefl = reflect(-v, n);
vec3 sheenColor = textureLod(senvmapRadiance,
envMapEquirect(sheenRefl), sheenLod).rgb;
sheenColor = min(sheenColor, vec3(20.0));
#ifdef _EnvLDR
sheenColor = srgbToLinear(sheenColor);
#endif
envl.rgb += sheenColor * sheenTintCol * sheenAlb;
}
#endif
#endif
#endif // _ExtBRDF
envl.rgb *= envmapStrength * occspec.x;
fragColor.rgb = envl;
@ -352,11 +504,30 @@ void main() {
#ifdef _VoxelGI
fragColor.rgb = textureLod(voxels_diffuse, texCoord, 0.0).rgb * voxelgiDiff;
if(roughness < 1.0 && occspec.y > 0.0)
fragColor.rgb += textureLod(voxels_specular, texCoord, 0.0).rgb * occspec.y * voxelgiRefl;
if(roughness < 1.0) {
fragColor.rgb += textureLod(voxels_specular, texCoord, 0.0).rgb * F * voxelgiRefl * occspec.y;
}
#ifdef _Rad
vec3 iblReflection = reflect(-v, n);
float iblLod = getMipFromRoughness(roughness, envmapNumMipmaps);
vec3 iblPrefiltered = textureLod(senvmapRadiance, envMapEquirect(iblReflection), iblLod).rgb;
iblPrefiltered = min(iblPrefiltered, vec3(20.0));
#ifdef _EnvLDR
iblPrefiltered = srgbToLinear(iblPrefiltered);
#endif
#ifdef _ExtBRDF
iblPrefiltered *= iblLayerWeight;
iblPrefiltered *= coatTintAbsorb;
#endif
fragColor.rgb += iblPrefiltered * F * envmapStrength * occspec.x;
#else
#ifdef _EnvCol
fragColor.rgb += backgroundCol * F * envmapStrength * occspec.x;
#endif
#endif
#else
#ifdef _VoxelAOvar
fragColor.rgb = textureLod(voxels_ao, texCoord, 0.0).rgb * voxelgiOcc;
fragColor.rgb = textureLod(voxels_ao, texCoord, 0.0).rgb;
#endif
#endif
@ -406,10 +577,42 @@ void main() {
float sdotVH = max(0.0, dot(v, sh));
float sdotNL = max(0.0, dot(n, sunDir));
vec3 svisibility = vec3(1.0);
vec3 sdirect = lambertDiffuseBRDF(albedo, sdotNL) +
#ifdef _Anisotropy
vec3 sdirect;
if (abs(matp0.x) > 0.001 && dot(wTangent, wTangent) > 0.001) {
vec3 sbitangent = normalize(cross(n, wTangent));
sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
anisotropicBRDF(f0, roughness, matp0.x, matp0.y,
wTangent, sbitangent, n, sunDir, v, sdotNL, dotNV) * occspec.y;
} else {
sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y;
}
#else
vec3 sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y;
#endif
#ifdef _ExtBRDF
float sunLayerWeight;
sdirect = applyExtBRDFLayers(sdirect, albedo, f0, roughness,
sdotNL, dotNV, sdotNH, sdotVH, n, sunDir, v, sh
#ifdef _ClearCoat
, matp1.x, matp1.y, matp1.z, coatTintCol, nCoat
#endif
#ifdef _Sheen
, matp0.z, matp0.w, sheenTintCol
#endif
#ifdef _Transmission
, matp2.z, matp2.w, matp3.x, matp3.y
#endif
, sunLayerWeight);
#endif
#ifdef _ShadowMap
#ifdef _ShadowMapAtlas
tileBounds = tileBoundsSunArray[0];
#endif
#ifdef _CSM
svisibility = shadowTestCascade(
#ifdef _ShadowMapAtlas
@ -494,23 +697,17 @@ void main() {
fragColor.rgb += sdirect * sunCol * svisibility;
// #ifdef _Hair // Aniso
// if (matid == 2) {
// const float shinyParallel = roughness;
// const float shinyPerpendicular = 0.1;
// const vec3 v = vec3(0.99146, 0.11664, 0.05832);
// vec3 T = abs(dot(n, v)) > 0.99999 ? cross(n, vec3(0.0, 1.0, 0.0)) : cross(n, v);
// fragColor.rgb = orenNayarDiffuseBRDF(albedo, roughness, dotNV, dotNL, dotVH) + wardSpecular(n, h, dotNL, dotNV, dotNH, T, shinyParallel, shinyPerpendicular) * spec;
// }
// #endif
#ifdef _SSS
if (matid == 2) {
#ifdef _ExtBRDF
if (matid >= 3u && matp3.z > 0.0) {
#ifdef _CSM
int casi, casindex;
mat4 LWVP = getCascadeMat(distance(eye, p), casi, casindex);
#endif
fragColor.rgb += fragColor.rgb * SSSSTransmittance(
vec3 sssColor = sssColorVal;
float sssRadius = sssRadiusScalar;
float sssStrength = matp3.z;
vec3 sssResult = SSSSTransmittance(
LWVP, p, n, sunDir, lightPlane.y,
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
@ -521,12 +718,26 @@ void main() {
#else
shadowMap
#endif
);//TODO implement transparent shadowmaps into the SSSSTransmittance()
, sssColor, sssRadius
#ifdef _ShadowMapAtlas
#ifdef _CSM
, tileBoundsSunArray[casi]
#else
, tileBoundsSunArray[0]
#endif
#endif
);
fragColor.rgb += sunCol * sssStrength * sssResult;
}
#endif
#endif
#endif // _Sun
#ifdef _ShadowMapAtlas
tileBounds = vec4(0.0, 0.0, 1.0, 1.0);
#endif
#ifdef _SinglePoint
#ifdef _VRStereo
@ -555,12 +766,53 @@ void main() {
#ifdef _SSRS
, gbufferD, invVP, eye
#endif
#ifdef _ClearCoat
, matp1.x, matp1.y, matp1.z, coatTintCol, nCoat
#endif
#ifdef _Sheen
, matp0.z, matp0.w, sheenTintCol
#endif
#ifdef _Anisotropy
, matp0.x, matp0.y, wTangent
#endif
#ifdef _SSS
, matp3.z, sssColorVal, sssRadiusBase * matp7.x, matp3.w
#endif
#ifdef _Transmission
, matp2.z, matp2.w, matp3.x, matp3.y
#endif
);
#ifdef _Spot
#ifdef _SSS
#ifdef _ShadowMap
if (matid == 2) fragColor.rgb += fragColor.rgb * SSSSTransmittance(LWVPSpot[0], p, n, normalize(lightPos - p), lightPlane.y, shadowMapSpot[0]);//TODO implement transparent shadowmaps into the SSSSTransmittance()
#ifdef _ExtBRDF
if (matid >= 3u && matp3.z > 0.0) {
vec3 sssColorSpot = sssColorVal;
float sssRadiusSpot = sssRadiusScalar;
float sssStrengthSpot = matp3.z;
fragColor.rgb += pointCol * sssStrengthSpot * SSSSTransmittance(LWVPSpotArray[0], p, n, normalize(lightPos - p), lightPlane.y, shadowMapSpot[0], sssColorSpot, sssRadiusSpot
#ifdef _ShadowMapAtlas
, vec4(0.0, 0.0, 1.0, 1.0)
#endif
);//TODO implement transparent shadowmaps into the SSSSTransmittance()
}
#endif
#endif
#endif
#endif
#ifndef _Spot
#ifdef _SSS
#ifdef _ShadowMap
#ifdef _ExtBRDF
if (matid >= 3u && matp3.z > 0.0) {
vec3 sssColorPoint = sssColorVal;
float sssRadiusPoint = sssRadiusScalar;
float sssStrengthPoint = matp3.z;
fragColor.rgb += pointCol * sssStrengthPoint * SSSSTransmittanceCube(shadowMapPoint[0], lightPos, p, n, normalize(lightPos - p), lightPlane.y, lightProj, sssColorPoint, sssRadiusPoint);
}
#endif
#endif
#endif
#endif
@ -618,7 +870,96 @@ void main() {
#ifdef _SSRS
, gbufferD, invVP, eye
#endif
#ifdef _ClearCoat
, matp1.x, matp1.y, matp1.z, coatTintCol, nCoat
#endif
#ifdef _Sheen
, matp0.z, matp0.w, sheenTintCol
#endif
#ifdef _Anisotropy
, matp0.x, matp0.y, wTangent
#endif
#ifdef _SSS
, matp3.z, sssColorVal, sssRadiusBase * matp7.x, matp3.w
#endif
#ifdef _Transmission
, matp2.z, matp2.w, matp3.x, matp3.y
#endif
);
#ifdef _SSS
#ifdef _ShadowMap
#ifdef _ExtBRDF
if (matid >= 3u && matp3.z > 0.0) {
vec3 sssColorCL = sssColorVal;
float sssRadiusCL = sssRadiusScalar;
float sssStrengthCL = matp3.z;
vec3 cLightPos = lightsArray[li * 3].xyz;
vec3 cLightCol = lightsArray[li * 3 + 1].xyz;
vec3 cLightDir = normalize(cLightPos - p);
#ifdef _Spot
bool isSpotLight = lightsArray[li * 3 + 2].y != 0.0;
if (isSpotLight) {
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[li], p, n, cLightDir, lightPlane.y, shadowMapAtlasSpot, sssColorCL, sssRadiusCL, tileBoundsSpotArray[li]);
#else
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[li], p, n, cLightDir, lightPlane.y, shadowMapAtlas, sssColorCL, sssRadiusCL, tileBoundsSpotArray[li]);
#endif
#else
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[0], p, n, cLightDir, lightPlane.y, shadowMapSpot[0], sssColorCL, sssRadiusCL
#ifdef _ShadowMapAtlas
, vec4(0.0, 0.0, 1.0, 1.0)
#endif
);
else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[1], p, n, cLightDir, lightPlane.y, shadowMapSpot[1], sssColorCL, sssRadiusCL
#ifdef _ShadowMapAtlas
, vec4(0.0, 0.0, 1.0, 1.0)
#endif
);
else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[2], p, n, cLightDir, lightPlane.y, shadowMapSpot[2], sssColorCL, sssRadiusCL
#ifdef _ShadowMapAtlas
, vec4(0.0, 0.0, 1.0, 1.0)
#endif
);
else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[3], p, n, cLightDir, lightPlane.y, shadowMapSpot[3], sssColorCL, sssRadiusCL
#ifdef _ShadowMapAtlas
, vec4(0.0, 0.0, 1.0, 1.0)
#endif
);
#endif
} else {
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCubeAtlas(shadowMapAtlasPoint, cLightPos, p, n, cLightDir, lightPlane.y, lightProj, li, sssColorCL, sssRadiusCL);
#else
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCubeAtlas(shadowMapAtlas, cLightPos, p, n, cLightDir, lightPlane.y, lightProj, li, sssColorCL, sssRadiusCL);
#endif
#else
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[0], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[1], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[2], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[3], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
#endif
}
#else
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCubeAtlas(shadowMapAtlasPoint, cLightPos, p, n, cLightDir, lightPlane.y, lightProj, li, sssColorCL, sssRadiusCL);
#else
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCubeAtlas(shadowMapAtlas, cLightPos, p, n, cLightDir, lightPlane.y, lightProj, li, sssColorCL, sssRadiusCL);
#endif
#else
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[0], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[1], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[2], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[3], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
#endif
#endif
}
#endif
#endif
#endif
}
#endif // _Clusters

View File

@ -138,6 +138,16 @@
"link": "_cascadeData",
"ifdef": ["_Sun", "_ShadowMap", "_CSM"]
},
{
"name": "tileBoundsSunArray",
"link": "_tileBoundsSunArray",
"ifdef": ["_Sun", "_ShadowMap", "_ShadowMapAtlas"]
},
{
"name": "tileBoundsSpotArray",
"link": "_tileBoundsSpotArray",
"ifdef": ["_Clusters", "_Spot", "_ShadowMap", "_ShadowMapAtlas"]
},
{
"name": "lightPlane",
"link": "_lightPlane",
@ -277,8 +287,15 @@
"link": "_biasLightWorldViewProjectionMatrixSpot3",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "materialParams",
"link": "_materialParams",
"type": "floats",
"ifdef": ["_ExtBRDF"]
}
],
"texture_units": [],
"vertex_shader": "../include/pass_viewray.vert.glsl",
"fragment_shader": "deferred_light.frag.glsl",
"color_attachments": ["RGBA64"]

View File

@ -3,6 +3,7 @@
#include "compiled.inc"
#include "std/gbuffer.glsl"
#include "std/math.glsl"
#include "std/brdf.glsl"
#ifdef _Clusters
#include "std/clusters.glsl"
#endif
@ -13,6 +14,12 @@
uniform sampler2D gbufferD;
uniform sampler2D gbuffer0;
uniform sampler2D gbuffer1;
#ifdef _gbuffer2
uniform sampler2D gbuffer2;
#endif
#ifdef _ClearCoat
uniform sampler2D gbufferCoatNormal;
#endif
uniform float envmapStrength;
#ifdef _Irr
@ -49,7 +56,7 @@ uniform vec2 cameraPlane;
#ifdef _SinglePoint
#ifdef _Spot
//!uniform sampler2DShadow shadowMapSpot[1];
//!uniform mat4 LWVPSpot[1];
//!uniform mat4 LWVPSpotArray[1];
#else
//!uniform samplerCubeShadow shadowMapPoint[1];
//!uniform vec2 lightProj;
@ -91,6 +98,7 @@ uniform vec3 sunCol;
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSun;
#endif
//!uniform vec4 tileBoundsSunArray[maxLights * shadowmapCascades];
#else
uniform sampler2DShadow shadowMap;
#endif
@ -132,17 +140,61 @@ void main() {
float metallic;
uint matid;
unpackFloatInt16(g0.a, metallic, matid);
#ifdef _ExtBRDF
matid = min(matid, uint(MAX_MATERIALS - 1));
//!uniform vec4 materialParams[MAX_MATERIALS * 8];
vec4 matp0 = vec4(0.0), matp1 = vec4(0.0), matp2 = vec4(0.0), matp3 = vec4(0.0);
vec4 matp4 = vec4(0.0), matp5 = vec4(0.0), matp6 = vec4(0.0), matp7 = vec4(0.0);
// TODO: coatIOR=1.5, ior=1.45, thinWall=1.0 move to python make files
matp1.z = 1.5;
matp3.x = 1.45;
matp3.y = 1.0;
if (matid >= 3u) {
getMaterialParams(matid, matp0, matp1, matp2, matp3, matp4, matp5, matp6, matp7);
}
#ifdef _ClearCoat
vec3 coatTintCol = vec3(matp1.w, matp2.x, matp2.y);
#endif
#ifdef _Sheen
vec3 sheenTintCol = vec3(matp5.z, matp5.w, matp6.x);
#endif
#ifdef _SSS
vec3 sssColorVal = vec3(matp4.w, matp5.x, matp5.y);
vec3 sssRadiusScaled = vec3(matp4.x, matp4.y, matp4.z) * matp7.x;
#endif
#endif
vec4 g1 = textureLod(gbuffer1, texCoord, 0.0); // Basecolor.rgb, spec/occ
vec2 occspec = unpackFloat2(g1.a);
vec3 albedo = surfaceAlbedo(g1.rgb, metallic); // g1.rgb - basecolor
vec3 f0 = surfaceF0(g1.rgb, metallic);
#ifdef _ExtBRDF
f0 = mix(f0, min(g1.rgb, vec3(2.0)), vec3(matp6.y, matp6.z, matp6.w));
#endif
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
vec3 p = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj);
vec3 v = normalize(eye - p);
float dotNV = max(dot(n, v), 0.0);
#ifdef _ClearCoat
vec4 gCoat = textureLod(gbufferCoatNormal, texCoord, 0.0);
vec3 nCoat;
nCoat.z = 1.0 - abs(gCoat.x) - abs(gCoat.y);
nCoat.xy = nCoat.z >= 0.0 ? gCoat.xy : octahedronWrap(gCoat.xy);
nCoat = normalize(nCoat);
#endif
#ifdef _Anisotropy
#ifdef _gbuffer2
vec4 g2 = textureLod(gbuffer2, texCoord, 0.0);
vec3 wTangent = decodeTangent(g2.a, n);
#else
vec3 wTangent = vec3(0.0);
#endif
#endif
#ifdef _Brdf
vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(dotNV, 1.0 - roughness) * 256.0), 0).xy;
#endif
@ -158,19 +210,24 @@ void main() {
#endif
#ifdef _Rad
#ifdef _Anisotropy
vec3 reflectionWorld = anisotropicIBLDirection(n, v, wTangent,
matp0.x, roughness);
#else
vec3 reflectionWorld = reflect(-v, n);
#endif
float lod = getMipFromRoughness(roughness, envmapNumMipmaps);
vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(reflectionWorld), lod).rgb;
#endif
#ifdef _EnvLDR
envl.rgb = pow(envl.rgb, vec3(2.2));
envl.rgb = srgbToLinear(envl.rgb);
#ifdef _Rad
prefilteredColor = pow(prefilteredColor, vec3(2.2));
prefilteredColor = srgbToLinear(prefilteredColor);
#endif
#endif
envl.rgb *= albedo;
envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV);
#ifdef _Rad // Indirect specular
envl.rgb += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5 * occspec.y;
@ -180,6 +237,98 @@ void main() {
#endif
#endif
#ifdef _ExtBRDF
float iblSheenWeight = 1.0;
float iblCoatWeight = 1.0;
vec3 coatTintAbsorb = vec3(1.0);
#ifdef _Sheen
float sheenAlb = sheenIBLAlbedo(matp0.z, matp0.w, dotNV);
iblSheenWeight = max(1.0 - sheenAlb *
max(max(sheenTintCol.r, sheenTintCol.g), sheenTintCol.b), 0.0);
#endif
#ifdef _ClearCoat
float dotNVCoat = max(dot(nCoat, v), 0.0);
float coatF = coatIBLFresnel(matp1.x, matp1.z, dotNVCoat);
iblCoatWeight = max(1.0 - coatF, 0.0);
if (matp1.x > 0.0) {
coatTintAbsorb = mix(vec3(1.0), clamp(coatTintCol, 0.0, 1.0),
clamp(1.0 / max(dotNVCoat, 0.3) * 0.2, 0.0, 1.0));
}
#endif
float iblLayerWeight = iblSheenWeight * iblCoatWeight;
envl.rgb *= iblLayerWeight;
brdf_sheenWeight = iblSheenWeight;
brdf_coatWeight = iblCoatWeight;
brdf_coatTintAbsorb = coatTintAbsorb;
#ifdef _Sheen
brdf_sheenAlbedo = sheenAlb;
#endif
#ifdef _ClearCoat
brdf_coatF0 = (matp1.z - 1.0) / (matp1.z + 1.0);
brdf_coatF0 = brdf_coatF0 * brdf_coatF0;
#endif
#ifdef _Transmission
brdf_transmissionF0 = (matp3.x - 1.0) / (matp3.x + 1.0);
brdf_transmissionF0 = brdf_transmissionF0 * brdf_transmissionF0;
#endif
#ifdef _Transmission
float transF = transmissionIBLFresnel(matp3.x, dotNV);
float transmittance = 1.0 - transF;
#ifdef _Rad
if (matp2.z > 0.0 && transmittance > 0.0) {
vec3 refrDir = transmissionIBLDirection(n, v, matp3.x);
float transLod = getMipFromRoughness(matp2.w, envmapNumMipmaps);
vec3 transColor = textureLod(senvmapRadiance,
envMapEquirect(refrDir), transLod).rgb;
transColor = min(transColor, vec3(20.0));
#ifdef _EnvLDR
transColor = srgbToLinear(transColor);
#endif
envl.rgb += albedo * matp2.z * transmittance * transColor * dotNV
* iblLayerWeight;
}
#endif
#endif
#ifdef _ClearCoat
envl.rgb *= coatTintAbsorb;
#ifdef _Rad
if (coatF > 0.0) {
float coatLod = getMipFromRoughness(matp1.y, envmapNumMipmaps);
vec3 coatRefl = reflect(-v, nCoat);
vec3 coatColor = textureLod(senvmapRadiance,
envMapEquirect(coatRefl), coatLod).rgb;
coatColor = min(coatColor, vec3(20.0));
#ifdef _EnvLDR
coatColor = srgbToLinear(coatColor);
#endif
envl.rgb += coatColor * coatF * iblSheenWeight;
}
#endif
#endif
#ifdef _Sheen
#ifdef _Rad
if (sheenAlb > 0.0) {
float sheenLod = getMipFromRoughness(matp0.w, envmapNumMipmaps);
vec3 sheenRefl = reflect(-v, n);
vec3 sheenColor = textureLod(senvmapRadiance,
envMapEquirect(sheenRefl), sheenLod).rgb;
sheenColor = min(sheenColor, vec3(20.0));
#ifdef _EnvLDR
sheenColor = srgbToLinear(sheenColor);
#endif
envl.rgb += sheenColor * sheenTintCol * sheenAlb;
}
#endif
#endif
#endif // _ExtBRDF
envl.rgb *= envmapStrength * occspec.x;
fragColor.rgb = envl;
@ -189,10 +338,50 @@ void main() {
float sdotVH = max(0.0, dot(v, sh));
float sdotNL = max(0.0, dot(n, sunDir));
float svisibility = 1.0;
vec3 sdirect = lambertDiffuseBRDF(albedo, sdotNL) +
#ifdef _Anisotropy
vec3 sdirect;
if (abs(matp0.x) > 0.001 && dot(wTangent, wTangent) > 0.001) {
vec3 sbitangent = normalize(cross(n, wTangent));
sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
anisotropicBRDF(f0, roughness, matp0.x, matp0.y,
wTangent, sbitangent, n, sunDir, v, sdotNL, dotNV) * occspec.y;
} else {
sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y;
}
#else
vec3 sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y;
#endif
float sunSheenWeight = brdf_sheenWeight;
float sunCoatWeight = brdf_coatWeight;
#ifdef _Sheen
vec3 sunSheen = sheenBRDF(matp0.z, matp0.w, sheenTintCol, sdotNL, sdotNH, dotNV);
#endif
#ifdef _ClearCoat
vec3 sunCoat = clearcoatBRDF(matp1.x, matp1.y, matp1.z, nCoat, sunDir, v, sh);
#endif
float sunLayerWeight = sunSheenWeight * sunCoatWeight;
sdirect *= sunLayerWeight;
#ifdef _Transmission
sdirect += transmissionBRDF(albedo, matp2.z, matp2.w, matp3.x, matp3.y, sdotNL, dotNV, sdotVH) * sunLayerWeight;
#endif
#ifdef _ClearCoat
sdirect *= brdf_coatTintAbsorb;
sdirect += sunCoat * sunSheenWeight;
#endif
#ifdef _Sheen
sdirect += sunSheen;
#endif
#ifdef _ShadowMap
#ifdef _ShadowMapAtlas
tileBounds = tileBoundsSunArray[0];
#endif
#ifdef _CSM
svisibility = shadowTestCascade(
#ifdef _ShadowMapAtlas
@ -235,6 +424,21 @@ void main() {
#ifdef _Spot
, true, spotData.x, spotData.y, spotDir, spotData.zw, spotRight // TODO: Test!
#endif
#ifdef _ClearCoat
, matp1.x, matp1.y, matp1.z, coatTintCol, nCoat
#endif
#ifdef _Sheen
, matp0.z, matp0.w, sheenTintCol
#endif
#ifdef _Anisotropy
, matp0.x, matp0.y, wTangent
#endif
#ifdef _SSS
, matp3.z, sssColorVal, sssRadiusScaled, matp3.w
#endif
#ifdef _Transmission
, matp2.z, matp2.w, matp3.x, matp3.y
#endif
);
#endif
@ -277,7 +481,29 @@ void main() {
, vec2(lightsArray[li * 3].w, lightsArray[li * 3 + 1].w) // scale
, lightsArraySpot[li * 2 + 1].xyz // right
#endif
#ifdef _ClearCoat
, matp1.x, matp1.y, matp1.z, coatTintCol, nCoat
#endif
#ifdef _Sheen
, matp0.z, matp0.w, sheenTintCol
#endif
#ifdef _Anisotropy
, matp0.x, matp0.y, wTangent
#endif
#ifdef _SSS
, matp3.z, sssColorVal, sssRadiusScaled, matp3.w
#endif
#ifdef _Transmission
, matp2.z, matp2.w, matp3.x, matp3.y
#endif
);
}
#endif // _Clusters
fragColor.rgb = clamp(fragColor.rgb, vec3(0.0), vec3(65504.0));
if (any(isnan(fragColor.rgb)) || any(isinf(fragColor.rgb))) {
fragColor.rgb = vec3(0.0);
}
fragColor.a = 1.0; // Mark as opaque
}

View File

@ -97,6 +97,16 @@
"link": "_cascadeData",
"ifdef": ["_Sun", "_ShadowMap", "_CSM"]
},
{
"name": "tileBoundsSunArray",
"link": "_tileBoundsSunArray",
"ifdef": ["_Sun", "_ShadowMap", "_ShadowMapAtlas"]
},
{
"name": "tileBoundsSpotArray",
"link": "_tileBoundsSpotArray",
"ifdef": ["_Clusters", "_Spot", "_ShadowMap", "_ShadowMapAtlas"]
},
{
"name": "eyeLookRight",
"link": "_eyeLookRight",
@ -214,6 +224,12 @@
"link": "_biasLightWorldViewProjectionMatrixSpot3",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "materialParams",
"link": "_materialParams",
"type": "floats",
"ifdef": ["_ExtBRDF"]
}
],
"vertex_shader": "../include/pass_viewray.vert.glsl",

View File

@ -0,0 +1,8 @@
#version 450
in vec4 color;
out vec4 fragColor;
void main() {
fragColor = vec4(color);
}

View File

@ -0,0 +1,12 @@
#version 450
in vec3 pos;
in vec4 col;
uniform mat4 ViewProjection;
out vec4 color;
void main() {
color = col;
gl_Position = ViewProjection * vec4(pos, 1.0);
}

View File

@ -0,0 +1,15 @@
#version 450
#include "compiled.inc"
in vec4 color;
out vec4 fragColor[GBUF_SIZE];
void main() {
fragColor[GBUF_IDX_0] = vec4(1.0, 1.0, 0.0, 1.0);
fragColor[GBUF_IDX_1] = vec4(color);
#ifdef _EmissionShaded
fragColor[GBUF_IDX_EMISSION] = vec4(color);
#endif
}

View File

@ -11,6 +11,7 @@ uniform sampler2D gbuffer1; // basecol, spec
uniform mat4 P;
uniform mat3 V3;
uniform vec2 cameraProj;
uniform vec2 screenSize;
#ifdef _CPostprocess
uniform vec3 PPComp9;
@ -24,8 +25,8 @@ out vec4 fragColor;
vec3 hitCoord;
float depth;
const int numBinarySearchSteps = 7;
const int maxSteps = int(ceil(1.0 / ssrRayStep) * ssrSearchDist);
const int numBinarySearchSteps = 8;
const int maxSteps = 50;
vec2 getProjectedCoord(const vec3 hit) {
vec4 projectedCoord = P * vec4(hit, 1.0);
@ -38,44 +39,58 @@ vec2 getProjectedCoord(const vec3 hit) {
}
float getDeltaDepth(const vec3 hit) {
depth = textureLod(gbufferD, getProjectedCoord(hit), 0.0).r * 2.0 - 1.0;
vec2 tc = getProjectedCoord(hit);
if (tc.x < 0.0 || tc.x > 1.0 || tc.y < 0.0 || tc.y > 1.0)
return -1.0;
depth = textureLod(gbufferD, tc, 0.0).r * 2.0 - 1.0;
vec3 viewPos = getPosView(viewRay, depth, cameraProj);
return viewPos.z - hit.z;
}
vec4 binarySearch(vec3 dir) {
vec4 binarySearch(vec3 dir, float stepSize) {
float ddepth;
for (int i = 0; i < numBinarySearchSteps; i++) {
dir *= 0.5;
hitCoord -= dir;
stepSize *= 0.5;
hitCoord -= dir * stepSize;
ddepth = getDeltaDepth(hitCoord);
if (ddepth < 0.0) hitCoord += dir;
if (ddepth < 0.0) hitCoord += dir * stepSize;
}
// Ugly discard of hits too far away
#ifdef _CPostprocess
if (abs(ddepth) > PPComp9.z / 500) return vec4(0.0);
float maxDist = PPComp9.z;
#else
if (abs(ddepth) > ssrSearchDist / 500) return vec4(0.0);
float maxDist = ssrSearchDist;
#endif
return vec4(getProjectedCoord(hitCoord), 0.0, 1.0);
if (abs(ddepth) > maxDist * 0.005) return vec4(0.0);
vec2 hitTC = getProjectedCoord(hitCoord);
if (hitTC.x < 0.0 || hitTC.x > 1.0 || hitTC.y < 0.0 || hitTC.y > 1.0)
return vec4(0.0);
return vec4(hitTC, 0.0, 1.0);
}
vec4 rayCast(vec3 dir) {
#ifdef _CPostprocess
dir *= PPComp9.x;
float baseStep = PPComp9.x;
float maxDist = PPComp9.z;
#else
dir *= ssrRayStep;
float baseStep = ssrRayStep;
float maxDist = ssrSearchDist;
#endif
float stepSize = baseStep * max(1.0, -viewRay.z * 0.1);
vec3 startPos = hitCoord;
for (int i = 0; i < maxSteps; i++) {
hitCoord += dir;
if (getDeltaDepth(hitCoord) > 0.0) return binarySearch(dir);
hitCoord += dir * stepSize;
float dist = length(hitCoord - startPos);
if (dist > maxDist) break;
float ddepth = getDeltaDepth(hitCoord);
if (ddepth > 0.0) return binarySearch(dir, stepSize);
stepSize *= 1.03;
}
return vec4(0.0);
}
void main() {
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
float roughness = unpackFloat(g0.b).y;
float roughness = g0.b;
if (roughness == 1.0) { fragColor.rgb = vec3(0.0); return; }
float spec = fract(textureLod(gbuffer1, texCoord, 0.0).a);
@ -92,30 +107,54 @@ void main() {
vec3 viewNormal = V3 * n;
vec3 viewPos = getPosView(viewRay, d, cameraProj);
vec3 reflected = reflect(viewPos, viewNormal);
float NdotV = clamp(dot(viewNormal, -normalize(viewPos)), 0.0, 1.0);
vec3 reflected = reflect(normalize(viewPos), viewNormal);
hitCoord = viewPos;
#ifdef _CPostprocess
vec3 dir = reflected * (1.0 - rand(texCoord) * PPComp10.y * roughness) * 2.0;
#else
vec3 dir = reflected * (1.0 - rand(texCoord) * ssrJitter * roughness) * 2.0;
#endif
vec3 dir = reflected;
// * max(ssrMinRayStep, -viewPos.z)
vec4 coords = rayCast(dir);
vec2 deltaCoords = abs(vec2(0.5, 0.5) - coords.xy);
float screenEdgeFactor = clamp(1.0 - (deltaCoords.x + deltaCoords.y), 0.0, 1.0);
if (coords.w <= 0.0) {
fragColor.rgb = vec3(0.0);
return;
}
vec2 deltaCoords = abs(vec2(0.5, 0.5) - coords.xy);
float screenEdgeFactor = smoothstep(0.5, 0.15, deltaCoords.x)
* smoothstep(0.5, 0.15, deltaCoords.y);
screenEdgeFactor = max(screenEdgeFactor, 0.15);
float hitDepth = textureLod(gbufferD, coords.xy, 0.0).r * 2.0 - 1.0;
vec3 hitViewPos = getPosView(viewRay, hitDepth, cameraProj);
vec3 hitDir = normalize(hitViewPos - viewPos);
float hitNdotV = clamp(dot(viewNormal, -hitDir), 0.0, 1.0);
float hitBackFace = smoothstep(-0.15, 0.3, hitNdotV);
float reflectivity = 1.0 - roughness;
#ifdef _CPostprocess
float intensity = pow(reflectivity, PPComp10.x) * screenEdgeFactor * clamp(-reflected.z, 0.0, 1.0) * clamp((PPComp9.z - length(viewPos - hitCoord)) * (1.0 / PPComp9.z), 0.0, 1.0) * coords.w;
float falloffExp = PPComp10.x;
float maxDist = PPComp9.z;
#else
float intensity = pow(reflectivity, ssrFalloffExp) * screenEdgeFactor * clamp(-reflected.z, 0.0, 1.0) * clamp((ssrSearchDist - length(viewPos - hitCoord)) * (1.0 / ssrSearchDist), 0.0, 1.0) * coords.w;
float falloffExp = ssrFalloffExp;
float maxDist = ssrSearchDist;
#endif
float distAttenuation = 1.0 - clamp(length(viewPos - hitCoord) / maxDist, 0.0, 1.0);
distAttenuation = pow(distAttenuation, 1.5);
float fresnel = pow(1.0 - NdotV, 5.0);
fresnel = mix(0.04, 1.0, fresnel);
float intensity = pow(reflectivity, falloffExp) * screenEdgeFactor
* smoothstep(0.0, 0.1, -reflected.z)
* distAttenuation
* hitBackFace
* coords.w;
intensity = clamp(intensity, 0.0, 1.0);
vec3 reflCol = textureLod(tex, coords.xy, 0.0).rgb;
reflCol = clamp(reflCol, 0.0, 1.0);
fragColor.rgb = reflCol * intensity * 0.5;
fragColor.rgb = reflCol * intensity * mix(0.5, 1.0, fresnel);
}

View File

@ -22,6 +22,10 @@
"name": "cameraProj",
"link": "_cameraPlaneProj"
},
{
"name": "screenSize",
"link": "_screenSize"
},
{
"name": "PPComp9",
"link": "_PPComp9",

View File

@ -12,6 +12,7 @@ uniform sampler2D tex1;
uniform sampler2D gbufferD;
uniform sampler2D gbuffer0;
uniform sampler2D gbufferD1;
uniform sampler2D gbuffer1;
uniform sampler2D gbuffer_refraction; // ior\opacity
uniform mat4 P;
@ -26,7 +27,7 @@ vec3 hitCoord;
float depth;
const int numBinarySearchSteps = 7;
const int maxSteps = int(ceil(1.0 / ss_refractionRayStep) * ss_refractionSearchDist);
const int maxSteps = 50;
vec2 getProjectedCoord(const vec3 hit) {
vec4 projectedCoord = P * vec4(hit, 1.0);
@ -39,45 +40,60 @@ vec2 getProjectedCoord(const vec3 hit) {
}
float getDeltaDepth(const vec3 hit) {
depth = textureLod(gbufferD1, getProjectedCoord(hit), 0.0).r * 2.0 - 1.0;
vec2 tc = getProjectedCoord(hit);
if (tc.x < 0.0 || tc.x > 1.0 || tc.y < 0.0 || tc.y > 1.0)
return -1.0;
depth = textureLod(gbufferD1, tc, 0.0).r * 2.0 - 1.0;
vec3 viewPos = getPosView(viewRay, depth, cameraProj);
return viewPos.z - hit.z;
}
vec4 binarySearch(vec3 dir) {
vec4 binarySearch(vec3 dir, float stepSize) {
float ddepth;
for (int i = 0; i < numBinarySearchSteps; i++) {
dir *= 0.5;
hitCoord -= dir;
stepSize *= 0.5;
hitCoord -= dir * stepSize;
ddepth = getDeltaDepth(hitCoord);
if (ddepth < 0.0) hitCoord += dir;
if (ddepth < 0.0) hitCoord += dir * stepSize;
}
if (abs(ddepth) > ss_refractionSearchDist) return vec4(0.0);
return vec4(getProjectedCoord(hitCoord), 0.0, 1.0);
if (abs(ddepth) > ss_refractionSearchDist * 0.005) return vec4(0.0);
vec2 hitTC = getProjectedCoord(hitCoord);
if (hitTC.x < 0.0 || hitTC.x > 1.0 || hitTC.y < 0.0 || hitTC.y > 1.0)
return vec4(0.0);
return vec4(hitTC, 0.0, 1.0);
}
vec4 rayCast(vec3 dir) {
float ddepth;
dir *= ss_refractionRayStep;
float stepSize = ss_refractionRayStep * max(1.0, -viewRay.z * 0.1);
vec3 startPos = hitCoord;
for (int i = 0; i < maxSteps; i++) {
hitCoord += dir;
ddepth = getDeltaDepth(hitCoord);
if (ddepth > 0.0) return binarySearch(dir);
hitCoord += dir * stepSize;
float dist = length(hitCoord - startPos);
if (dist > ss_refractionSearchDist) break;
float ddepth = getDeltaDepth(hitCoord);
if (ddepth > 0.0) return binarySearch(dir, stepSize);
stepSize *= 1.03;
}
return vec4(texCoord, 0.0, 0.0);
}
void main() {
vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0);
float ior = gr.x;
float ior = unpackIOR(gr.x);
float transmittance = gr.y;
float surfaceDepth = gr.z;
float d = surfaceDepth * 2.0 - 1.0;
vec4 sceneSample = textureLod(tex, texCoord, 0.0);
if (surfaceDepth == 0.0 || transmittance == 0.0 || ior == 1.0) {
if (surfaceDepth == 0.0 || surfaceDepth == 1.0) {
fragColor = sceneSample;
return;
}
vec4 g1 = textureLod(gbuffer1, texCoord, 0.0);
if (transmittance == 0.0 || ior == 1.0) {
vec3 background = textureLod(tex1, texCoord, 0.0).rgb;
fragColor.rgb = sceneSample.rgb + background * (1.0 - sceneSample.a);
fragColor.rgb = g1.rgb + background * transmittance;
fragColor.a = 1.0;
return;
}
@ -96,18 +112,18 @@ void main() {
vec3 refracted = refract(incident, viewNormal, 1.0 / ior);
if (length(refracted) < 0.001) {
vec3 background = textureLod(tex1, texCoord, 0.0).rgb;
fragColor.rgb = sceneSample.rgb + background * (1.0 - sceneSample.a);
fragColor.rgb = g1.rgb + background * transmittance;
fragColor.a = 1.0;
return;
}
hitCoord = viewPos;
vec3 dir = refracted * (1.0 - rand(texCoord) * ss_refractionJitter * roughness) * 2.0;
vec3 dir = normalize(refracted);
vec4 coords = rayCast(dir);
vec2 screenEdge = smoothstep(0.0, 0.1, coords.xy) * smoothstep(0.0, 0.1, 1.0 - coords.xy);
float screenEdgeFactor = screenEdge.x * screenEdge.y;
vec2 screenEdge = smoothstep(0.0, 0.05, coords.xy) * smoothstep(0.0, 0.05, 1.0 - coords.xy);
float screenEdgeFactor = max(screenEdge.x * screenEdge.y, 0.05);
float refractivity = 1.0 - roughness;
float intensity = pow(refractivity, ss_refractionFalloffExp) * screenEdgeFactor * coords.w;
@ -118,6 +134,6 @@ void main() {
vec3 behindColor = mix(straightBackground, refractedBackground, intensity);
fragColor.rgb = sceneSample.rgb + behindColor * (1.0 - sceneSample.a);
fragColor.rgb = g1.rgb + behindColor * transmittance;
fragColor.a = 1.0;
}

View File

@ -40,100 +40,148 @@
uniform sampler2D gbufferD;
uniform sampler2D gbuffer0;
uniform sampler2D gbuffer1;
uniform sampler2D tex;
uniform vec2 dir;
uniform vec2 cameraProj;
uniform mat4 projectionMatrix;
#ifdef _ExtBRDF
//!uniform vec4 materialParams[MAX_MATERIALS * 8];
#endif
in vec2 texCoord;
out vec4 fragColor;
const vec3 SKIN_SSS_RADIUS = vec3(4.8, 2.4, 1.5);
const float SSS_DISTANCE_SCALE = 0.001;
// TODO: finish the SSS
const float SSS_SCALE = 0.05;
const float DEPTH_THRESHOLD = 0.05;
// Temp hash func -
float hash13(vec3 p3) {
p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
vec4 SSSSBlur() {
const int SSSS_N_SAMPLES = 15;
vec4 SSSSBlur(vec3 sssRadius, float sssWeight) {
const int SSSS_N_SAMPLES = 11;
vec4 kernel[SSSS_N_SAMPLES];
kernel[0] = vec4(0.233, 0.455, 0.649, 0.0); // Center sample
kernel[1] = vec4(0.100, 0.336, 0.344, 0.37); // +0.37mm
kernel[2] = vec4(0.118, 0.198, 0.0, 0.97); // +0.97mm
kernel[3] = vec4(0.113, 0.007, 0.007, 1.93); // +1.93mm
kernel[4] = vec4(0.358, 0.004, 0.0, 3.87); // +3.87mm
kernel[5] = vec4(0.078, 0.0, 0.0, 6.53); // +6.53mm (red only)
kernel[6] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
kernel[7] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
kernel[8] = vec4(0.100, 0.336, 0.344, -0.37); // -0.37mm
kernel[9] = vec4(0.118, 0.198, 0.0, -0.97); // -0.97mm
kernel[10] = vec4(0.113, 0.007, 0.007, -1.93); // -1.93mm
kernel[11] = vec4(0.358, 0.004, 0.0, -3.87); // -3.87mm
kernel[12] = vec4(0.078, 0.0, 0.0, -6.53); // -6.53mm (red only)
kernel[13] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
kernel[14] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
vec4 colorM = textureLod(tex, texCoord, 0.0);
float depth = textureLod(gbufferD, texCoord, 0.0).r;
kernel[0] = vec4(0.233, 0.455, 0.649, 0.0); // Center sample
kernel[1] = vec4(0.100, 0.336, 0.344, 0.37); // +0.37
kernel[2] = vec4(0.118, 0.198, 0.0, 0.97); // +0.97
kernel[3] = vec4(0.113, 0.007, 0.007, 1.93); // +1.93
kernel[4] = vec4(0.358, 0.004, 0.0, 3.87); // +3.87
kernel[5] = vec4(0.078, 0.0, 0.0, 6.53); // +6.53 (red only)
kernel[6] = vec4(0.100, 0.336, 0.344, -0.37); // -0.37
kernel[7] = vec4(0.118, 0.198, 0.0, -0.97); // -0.97
kernel[8] = vec4(0.113, 0.007, 0.007, -1.93); // -1.93
kernel[9] = vec4(0.358, 0.004, 0.0, -3.87); // -3.87
kernel[10] = vec4(0.078, 0.0, 0.0, -6.53); // -6.53 (red only)
vec2 texSize = vec2(textureSize(tex, 0));
ivec2 texelCoord = ivec2(texCoord * texSize);
vec4 colorM = texelFetch(tex, texelCoord, 0);
vec3 albedo = texelFetch(gbuffer1, texelCoord, 0).rgb;
vec3 irradianceM = colorM.rgb / max(albedo, vec3(0.00001));
float depth = texelFetch(gbufferD, texelCoord, 0).r;
float depthM = cameraProj.y / (depth - cameraProj.x);
float distanceScale = 1.0 / max(depthM, 0.1);
vec2 finalStep = sssWidth * distanceScale * dir * SSS_DISTANCE_SCALE;
float blurWidth = max(max(sssRadius.r, sssRadius.g), sssRadius.b);
float projScale = dot(dir, vec2(projectionMatrix[0][0], projectionMatrix[1][1]));
vec2 finalStep = blurWidth * (1.0 / depthM) * dir * projScale * SSS_SCALE;
vec3 jitterSeed = vec3(texCoord.xy * 1000.0, fract(cameraProj.x * 0.0001));
float jitterOffset = (hash13(jitterSeed) * 2.0 - 1.0) * 0.15;
finalStep *= (1.0 + jitterOffset);
vec3 colorBlurred = vec3(0.0);
vec3 weightSum = vec3(0.0);
colorBlurred += colorM.rgb * kernel[0].rgb;
weightSum += kernel[0].rgb;
vec3 colorBlurred = irradianceM * kernel[0].rgb;
vec3 weightSum = kernel[0].rgb;
for (int i = 1; i < SSSS_N_SAMPLES; i++) {
float sampleJitter = hash13(vec3(texCoord.xy * 720.0, float(i) * 37.45)) * 0.1 - 0.05;
vec2 offset = texCoord + (kernel[i].a + sampleJitter) * finalStep;
vec4 color = textureLod(tex, offset, 0.0);
const float DEPTH_THRESHOLD = 0.05;
float sampleDepth = textureLod(gbufferD, offset, 0.0).r;
float sampleDepthM = cameraProj.y / (sampleDepth - cameraProj.x);
float depthDiff = abs(depthM - sampleDepthM);
float depthWeight = exp(-depthDiff * 10.0);
if (depthDiff > DEPTH_THRESHOLD) {
color.rgb = mix(colorM.rgb, color.rgb, depthWeight);
vec3 irradiance = irradianceM;
float s = 0.0;
if (all(greaterThanEqual(offset, vec2(0.0))) && all(lessThan(offset, vec2(1.0)))) {
ivec2 sampleTexel = ivec2(offset * texSize);
float sampleDepth = texelFetch(gbufferD, sampleTexel, 0).r;
float sampleDepthM = cameraProj.y / (sampleDepth - cameraProj.x);
float depthDiff = abs(depthM - sampleDepthM);
if (depthDiff < 1.0) {
vec4 sampleG0 = texelFetch(gbuffer0, sampleTexel, 0);
float sampleMetallic;
uint sampleMatid;
unpackFloatInt16(sampleG0.a, sampleMetallic, sampleMatid);
bool sampleIsSSS = false;
#ifdef _ExtBRDF
if (sampleMatid >= 3u && sampleMatid < uint(MAX_MATERIALS)) {
if (materialParams[sampleMatid * 8u + 3u].z > 0.0) sampleIsSSS = true;
}
#endif
if (sampleIsSSS) {
vec3 sampleColor = texelFetch(tex, sampleTexel, 0).rgb;
vec3 sampleAlbedo = texelFetch(gbuffer1, sampleTexel, 0).rgb;
irradiance = sampleColor / max(sampleAlbedo, vec3(0.00001));
}
if (depthDiff <= DEPTH_THRESHOLD) {
s = 1.0;
} else {
s = exp(-depthDiff * 10.0);
}
}
}
colorBlurred += color.rgb * kernel[i].rgb;
colorBlurred += kernel[i].rgb * mix(irradianceM, irradiance, s);
weightSum += kernel[i].rgb;
}
vec3 normalizedColor = colorBlurred / max(weightSum, vec3(0.00001));
vec3 normalizedIrradiance = colorBlurred / max(weightSum, vec3(0.00001));
float dither = hash13(vec3(texCoord * 1333.0, 0.0)) * 0.003 - 0.0015;
normalizedColor = max(normalizedColor + vec3(dither), vec3(0.0));
return vec4(normalizedColor, colorM.a);
normalizedIrradiance = max(normalizedIrradiance + vec3(dither), vec3(0.0));
vec3 blurredColor = normalizedIrradiance * albedo;
vec3 result = mix(colorM.rgb, blurredColor, sssWeight);
return vec4(result, colorM.a);
}
void main() {
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
vec2 texSize0 = vec2(textureSize(gbuffer0, 0));
ivec2 texelCoord0 = ivec2(texCoord * texSize0);
vec4 g0 = texelFetch(gbuffer0, texelCoord0, 0);
float metallic;
uint matid;
unpackFloatInt16(g0.a, metallic, matid);
if (matid == 2u) {
vec4 originalColor = textureLod(tex, texCoord, 0.0);
vec4 blurredColor = SSSSBlur();
vec4 sssContribution = blurredColor - originalColor;
vec4 combined = originalColor + max(vec4(0.0), sssContribution) * 0.8;
fragColor = max(vec4(0.0), min(combined, vec4(10.0)));
bool applySSS = false;
vec3 sssRadius = vec3(1.0);
float sssWeight = 1.0;
vec4 matp0, matp1, matp2, matp3, matp4, matp5, matp6, matp7;
#ifdef _ExtBRDF
if (matid >= 3u && matid < uint(MAX_MATERIALS)) {
getMaterialParams(matid, matp0, matp1, matp2, matp3, matp4, matp5, matp6, matp7);
// matp3.z = subsurface, matp4.xyz = subsurfaceRadiusRGB, matp7.x = subsurfaceScale
if (matp3.z > 0.0) {
applySSS = true;
sssRadius = matp4.xyz * matp7.x;
sssWeight = matp3.z;
}
}
#endif
if (applySSS) {
fragColor = SSSSBlur(sssRadius, sssWeight);
} else {
fragColor = textureLod(tex, texCoord, 0.0);
vec2 texSizeMain = vec2(textureSize(tex, 0));
ivec2 texelCoordMain = ivec2(texCoord * texSizeMain);
fragColor = texelFetch(tex, texelCoordMain, 0);
}
}

View File

@ -13,6 +13,16 @@
{
"name": "cameraProj",
"link": "_cameraPlaneProj"
},
{
"name": "projectionMatrix",
"link": "_projectionMatrix"
},
{
"name": "materialParams",
"link": "_materialParams",
"type": "floats",
"ifdef": ["_ExtBRDF"]
}
],
"texture_params": [],
@ -32,6 +42,16 @@
{
"name": "cameraProj",
"link": "_cameraPlaneProj"
},
{
"name": "projectionMatrix",
"link": "_projectionMatrix"
},
{
"name": "materialParams",
"link": "_materialParams",
"type": "floats",
"ifdef": ["_ExtBRDF"]
}
],
"texture_params": [],

View File

@ -1,10 +1,30 @@
#ifndef _BRDF_GLSL_
#define _BRDF_GLSL_
#ifndef PI
#define PI 3.1415926535
#endif
#ifndef INV_PI
#define INV_PI 0.3183098861
#endif
#ifndef INV_TWO_PI
#define INV_TWO_PI 0.1591549430
#endif
#ifndef SCHLICK_A
#define SCHLICK_A -5.55473
#endif
#ifndef SCHLICK_B
#define SCHLICK_B -6.98316
#endif
#ifndef SRGB_GAMMA
#define SRGB_GAMMA 2.2
#endif
#define srgbToLinear(x) pow(x, vec3(SRGB_GAMMA))
// http://xlgames-inc.github.io/posts/improvedibl/
// http://blog.selfshadow.com/publications/s2013-shading-course/
vec3 f_schlick(const vec3 f0, const float vh) {
return f0 + (1.0 - f0) * exp2((-5.55473 * vh - 6.98316) * vh);
return f0 + (1.0 - f0) * exp2((SCHLICK_A * vh + SCHLICK_B) * vh);
}
float v_smithschlick(const float nl, const float nv, const float a) {
@ -31,7 +51,7 @@ float d_ggx(const float nh, const float a) {
float a2 = a * a;
float denom = nh * nh * (a2 - 1.0) + 1.0;
denom = max(denom * denom, 0.00006103515625 /* 2^-14 = smallest possible half float value, prevent div by zero */);
return a2 * (1.0 / 3.1415926535) / denom;
return a2 * INV_PI / denom;
}
vec3 specularBRDF(const vec3 f0, const float roughness, const float nl, const float nh, const float nv, const float vh) {
@ -44,11 +64,10 @@ vec3 specularBRDF(const vec3 f0, const float roughness, const float nl, const fl
// http://filmicworlds.com/blog/optimizing-ggx-shaders-with-dotlh/
vec3 specularBRDFb(const vec3 f0, const float roughness, const float dotNL, const float dotNH, const float dotLH) {
// D
const float pi = 3.1415926535;
float alpha = roughness * roughness;
float alphaSqr = alpha * alpha;
float denom = dotNH * dotNH * (alphaSqr - 1.0) + 1.0;
float D = alphaSqr / (pi * denom * denom);
float D = alphaSqr / (PI * denom * denom);
// F
const float F_a = 1.0;
float F_b = pow(1.0 - dotLH, 5.0);
@ -65,21 +84,201 @@ vec3 specularBRDFb(const vec3 f0, const float roughness, const float dotNL, cons
return specular / 4.0; // TODO: get rid of / 4.0
}
vec3 orenNayarDiffuseBRDF(const vec3 albedo, const float roughness, const float nv, const float nl, const float vh) {
float a = roughness * roughness;
float s = a;
float s2 = s * s;
float vl = 2.0 * vh * vh - 1.0; // Double angle identity
float Cosri = vl - nv * nl;
float C1 = 1.0 - 0.5 * s2 / (s2 + 0.33);
float test = 1.0;
if (Cosri >= 0.0) test = (1.0 / (max(nl, nv)));
float C2 = 0.45 * s2 / (s2 + 0.09) * Cosri * test;
return albedo * max(0.0, nl) * (C1 + C2) * (1.0 + roughness * 0.5);
vec3 lambertDiffuseBRDF(const vec3 albedo, const float nl) {
return albedo * INV_PI * nl;
}
vec3 lambertDiffuseBRDF(const vec3 albedo, const float nl) {
return albedo * (1.0 / 3.1415926535) * nl;
#ifdef _BurleyDiffuse
vec3 burleyDiffuseBRDF(const vec3 albedo, const float roughness,
const float dotNL, const float dotNV, const float dotVH) {
float nl = clamp(dotNL, 0.0, 1.0);
float nv = clamp(dotNV, 0.0, 1.0);
float energyBias = mix(0.0, 0.5, roughness);
float energyFactor = mix(1.0, 1.0 / 1.51, roughness);
float fd90 = energyBias + 2.0 * roughness * dotVH * dotVH;
float lightScatter = 1.0 + (fd90 - 1.0) * pow(1.0 - nl, 5.0);
float viewScatter = 1.0 + (fd90 - 1.0) * pow(1.0 - nv, 5.0);
return albedo * INV_PI * lightScatter * viewScatter * energyFactor * nl;
}
#endif
#ifdef _EONDiffuse
const float constant1_FON = 0.5 - 2.0 / (3.0 * PI);
const float constant2_FON = 2.0 / 3.0 - 28.0 / (15.0 * PI);
float E_FON_approx(float mu, float r) {
float mucomp = 1.0 - mu;
const float g1 = 0.0571085289;
const float g2 = 0.491881867;
const float g3 = -0.332181442;
const float g4 = 0.0714429953;
float GoverPi = mucomp * (g1 + mucomp * (g2 + mucomp * (g3 + mucomp * g4)));
return (1.0 + r * GoverPi) / (1.0 + constant1_FON * r);
}
vec3 eonDiffuseBRDF(const vec3 albedo, const float roughness,
const float dotNL, const float dotNV, const float dotVH) {
float r = roughness;
float mu_i = clamp(dotNL, 0.0, 1.0);
float mu_o = clamp(dotNV, 0.0, 1.0);
if (mu_i < 1.0e-7 || mu_o < 1.0e-7) return vec3(0.0);
float dotLV = 2.0 * dotVH * dotVH - 1.0;
float s = dotLV - mu_i * mu_o;
float sovertF = s > 0.0 ? s / max(mu_i, mu_o) : s;
float AF = 1.0 / (1.0 + constant1_FON * r);
vec3 f_ss = (albedo * INV_PI) * AF * (1.0 + r * sovertF);
float EFo = E_FON_approx(mu_o, r);
float EFi = E_FON_approx(mu_i, r);
float avgEF = AF * (1.0 + constant2_FON * r);
vec3 rho_ms = (albedo * albedo) * avgEF
/ max(vec3(1.0) - albedo * (1.0 - avgEF), vec3(1.0e-7));
const float eps = 1.0e-7;
vec3 f_ms = (rho_ms * INV_PI)
* max(eps, 1.0 - EFo)
* max(eps, 1.0 - EFi)
/ max(eps, 1.0 - avgEF);
return (f_ss + f_ms) * mu_i;
}
#endif
#ifdef _GotandaDiffuse
vec3 gotandaDiffuseBRDF(const vec3 albedo, const float roughness, const vec3 f0,
const float dotNL, const float dotNV, const float dotVH) {
float nl = clamp(dotNL, 0.0, 1.0);
float nv = clamp(dotNV, 0.0, 1.0);
float a = roughness * roughness;
float a2 = a * a;
float dotLV = 2.0 * dotVH * dotVH - 1.0;
float Cosri = dotLV - nv * nl;
float a2_13 = a2 + 1.36053;
float Fr = (1.0 - (0.542026 * a2 + 0.303573 * a) / a2_13)
* (1.0 - pow(1.0 - nv, 5.0 - 4.0 * a2) / a2_13)
* ((-0.733996 * a2 * a + 1.50912 * a2 - 1.16402 * a)
* pow(1.0 - nv, 1.0 + 1.0 / (39.0 * a2 * a2 + 1.0)) + 1.0);
float Lm = (max(1.0 - 2.0 * a, 0.0) * (1.0 - pow(1.0 - nl, 5.0))
+ min(2.0 * a, 1.0)) * (1.0 - 0.5 * a * (nl - 1.0)) * nl;
float Vd = (a2 / ((a2 + 0.09) * (1.31072 + 0.995584 * nv)))
* (1.0 - pow(1.0 - nl,
(1.0 - 0.3726732 * nv * nv)
/ (0.188566 + 0.38841 * nv)));
float Bp = Cosri < 0.0 ? 1.4 * nv * nl * Cosri : Cosri;
vec3 Lr = (21.0 / 20.0) * (1.0 - f0) * (Fr * Lm + Vd + Bp);
return max(albedo * INV_PI * Lr, vec3(0.0));
}
#endif
#ifdef _ChanDiffuse
vec3 chanDiffuseBRDF(const vec3 albedo, const float roughness,
const float dotNL, const float dotNV, const float dotVH) {
float nl = clamp(dotNL, 0.0, 1.0);
float nv = clamp(dotNV, 0.0, 1.0);
float vh = clamp(dotVH, 0.0, 1.0);
float a = roughness * roughness;
float a2 = a * a;
float g = clamp((1.0 / 18.0) * log2(2.0 / max(a2, 1e-7) - 1.0), 0.0, 1.0);
float dotNH = clamp((nl + nv) / max(2.0 * vh, 1e-5), 0.0, 1.0);
float F0 = vh + pow(1.0 - vh, 5.0);
float FdV = 1.0 - 0.75 * pow(1.0 - nv, 5.0);
float FdL = 1.0 - 0.75 * pow(1.0 - nl, 5.0);
float Fd = mix(F0, FdV * FdL, clamp(2.2 * g - 0.5, 0.0, 1.0));
float Fb = ((34.5 * g - 59.0) * g + 24.5) * vh
* exp2(-max(73.2 * g - 21.2, 8.9) * sqrt(dotNH));
float Lobe = clamp(Fd + Fb, 0.0, 1.0);
return albedo * INV_PI * Lobe * nl;
}
#endif
vec3 diffuseBRDF(const vec3 albedo, const float roughness, const vec3 f0,
const float dotNL, const float dotNV, const float dotVH) {
#ifdef _BurleyDiffuse
return burleyDiffuseBRDF(albedo, roughness, dotNL, dotNV, dotVH);
#elif defined(_EONDiffuse)
return eonDiffuseBRDF(albedo, roughness, dotNL, dotNV, dotVH);
#elif defined(_GotandaDiffuse)
return gotandaDiffuseBRDF(albedo, roughness, f0, dotNL, dotNV, dotVH);
#elif defined(_ChanDiffuse)
return chanDiffuseBRDF(albedo, roughness, dotNL, dotNV, dotVH);
#else
return lambertDiffuseBRDF(albedo, dotNL);
#endif
}
vec3 lambertDiffuseIBL(const vec3 albedo) {
return albedo;
}
#ifdef _BurleyDiffuse
vec3 burleyDiffuseIBL(const vec3 albedo, const float roughness, const float dotNV) {
float nv = clamp(dotNV, 0.0, 1.0);
float energyBias = mix(0.0, 0.5, roughness);
float energyFactor = mix(1.0, 1.0 / 1.51, roughness);
float fd90 = energyBias + 2.0 * roughness * nv * nv;
float viewScatter = 1.0 + (fd90 - 1.0) * pow(1.0 - nv, 5.0);
return albedo * viewScatter * energyFactor;
}
#endif
#ifdef _EONDiffuse
vec3 eonDiffuseIBL(const vec3 albedo, const float roughness, const float dotNV) {
float r = roughness;
float AF = 1.0 / (1.0 + constant1_FON * r);
float EF = E_FON_approx(clamp(dotNV, 0.0, 1.0), r);
float avgEF = AF * (1.0 + constant2_FON * r);
vec3 rho_ms = (albedo * albedo) * avgEF
/ max(vec3(1.0) - albedo * (1.0 - avgEF), vec3(1.0e-7));
return max(albedo * EF + rho_ms * (1.0 - EF), vec3(0.0));
}
#endif
#ifdef _GotandaDiffuse
vec3 gotandaDiffuseIBL(const vec3 albedo, const float roughness, const vec3 f0, const float dotNV) {
float nv = clamp(dotNV, 0.0, 1.0);
float a = roughness * roughness;
float a2 = a * a;
float a2_13 = a2 + 1.36053;
float Fr = (1.0 - (0.542026 * a2 + 0.303573 * a) / a2_13)
* (1.0 - pow(1.0 - nv, 5.0 - 4.0 * a2) / a2_13)
* ((-0.733996 * a2 * a + 1.50912 * a2 - 1.16402 * a)
* pow(1.0 - nv, 1.0 + 1.0 / (39.0 * a2 * a2 + 1.0)) + 1.0);
float Lm = (max(1.0 - 2.0 * a, 0.0) * (1.0 - pow(1.0 - nv, 5.0))
+ min(2.0 * a, 1.0)) * (1.0 - 0.5 * a * (nv - 1.0)) * nv;
float Vd = (a2 / ((a2 + 0.09) * (1.31072 + 0.995584 * nv)))
* (1.0 - pow(1.0 - nv,
(1.0 - 0.3726732 * nv * nv)
/ (0.188566 + 0.38841 * nv)));
float Cosri = 1.0 - nv * nv;
float Bp = Cosri;
vec3 Lr = (21.0 / 20.0) * (1.0 - f0) * (Fr * Lm + Vd + Bp);
return max(albedo * Lr, vec3(0.0));
}
#endif
#ifdef _ChanDiffuse
vec3 chanDiffuseIBL(const vec3 albedo, const float roughness, const float dotNV) {
float nv = clamp(dotNV, 0.0, 1.0);
float a = roughness * roughness;
float a2 = a * a;
float g = clamp((1.0 / 18.0) * log2(2.0 / max(a2, 1e-7) - 1.0), 0.0, 1.0);
float FdV = 1.0 - 0.75 * pow(1.0 - nv, 5.0);
float Fd = mix(1.0, FdV * FdV, clamp(2.2 * g - 0.5, 0.0, 1.0));
float Fb = ((34.5 * g - 59.0) * g + 24.5)
* exp2(-max(73.2 * g - 21.2, 8.9) * sqrt(nv));
return albedo * clamp(Fd + Fb, 0.0, 1.0);
}
#endif
vec3 diffuseIBL(const vec3 albedo, const float roughness, const vec3 f0, const float dotNV) {
#ifdef _BurleyDiffuse
return burleyDiffuseIBL(albedo, roughness, dotNV);
#elif defined(_EONDiffuse)
return eonDiffuseIBL(albedo, roughness, dotNV);
#elif defined(_GotandaDiffuse)
return gotandaDiffuseIBL(albedo, roughness, f0, dotNV);
#elif defined(_ChanDiffuse)
return chanDiffuseIBL(albedo, roughness, dotNV);
#else
return lambertDiffuseIBL(albedo);
#endif
}
vec3 surfaceAlbedo(const vec3 baseColor, const float metalness) {
@ -95,24 +294,6 @@ float getMipFromRoughness(const float roughness, const float numMipmaps) {
return roughness * numMipmaps;
}
float wardSpecular(vec3 N, vec3 H, float dotNL, float dotNV, float dotNH, vec3 fiberDirection, float shinyParallel, float shinyPerpendicular) {
if(dotNL < 0.0 || dotNV < 0.0) {
return 0.0;
}
// fiberDirection - parse from rotation
// shinyParallel - roughness
// shinyPerpendicular - anisotropy
vec3 fiberParallel = normalize(fiberDirection);
vec3 fiberPerpendicular = normalize(cross(N, fiberDirection));
float dotXH = dot(fiberParallel, H);
float dotYH = dot(fiberPerpendicular, H);
const float PI = 3.1415926535;
float coeff = sqrt(dotNL/dotNV) / (4.0 * PI * shinyParallel * shinyPerpendicular);
float theta = (pow(dotXH/shinyParallel, 2.0) + pow(dotYH/shinyPerpendicular, 2.0)) / (1.0 + dotNH);
return clamp(coeff * exp(-2.0 * theta), 0.0, 1.0);
}
// https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile
// vec3 EnvBRDFApprox(vec3 SpecularColor, float Roughness, float NoV) {
// const vec4 c0 = { -1, -0.0275, -0.572, 0.022 };
@ -138,4 +319,207 @@ float D_Approx(const float Roughness, const float RoL) {
return rcp_a2 * exp2( c * RoL - c );
}
#ifdef _ClearCoat
float brdf_coatF0;
vec3 clearcoatBRDF(const float clearcoat, const float clearcoat_rough,
const float coat_ior, const vec3 coatN, const vec3 l, const vec3 v, const vec3 h) {
if (clearcoat <= 0.0) return vec3(0.0);
float cdotNL = max(0.0, dot(coatN, l));
float cdotNH = max(0.0, dot(coatN, h));
float cdotNV = max(0.0, dot(coatN, v));
float cdotVH = max(0.0, dot(v, h));
float a = clearcoat_rough * clearcoat_rough;
float F = brdf_coatF0 + (1.0 - brdf_coatF0) * exp2((SCHLICK_A * cdotVH + SCHLICK_B) * cdotVH);
float D = d_ggx(cdotNH, a);
float G = g2_approx(cdotNL, cdotNV, a);
return vec3(clearcoat * D * G * F / max(4.0 * cdotNV, 1e-5));
}
float coatAttenuation(const float clearcoat,
const float coat_ior, const vec3 coatN, const vec3 v) {
if (clearcoat <= 0.0) return 1.0;
float cdotNV = max(0.0, dot(coatN, v));
float F = brdf_coatF0 + (1.0 - brdf_coatF0) * exp2((SCHLICK_A * cdotNV + SCHLICK_B) * cdotNV);
return max(1.0 - F * clearcoat, 0.0);
}
vec3 coatTintAttenuation(const float clearcoat, const vec3 coat_tint, const vec3 coatN, const vec3 v) {
if (clearcoat <= 0.0) return vec3(1.0);
float cdotNV = max(0.0, dot(coatN, v));
float absorption = 1.0 / max(cdotNV, 0.3);
return mix(vec3(1.0), clamp(coat_tint, 0.0, 1.0), clamp(absorption * 0.2, 0.0, 1.0));
}
#endif
#ifdef _Sheen
float brdf_sheenAlbedo;
// based on Blender sheen model/Frostbite PBR
vec3 sheenBRDF(const float sheen, const float sheen_rough,
const vec3 sheen_tint, const float dotNL, const float dotNH, const float dotNV) {
if (sheen <= 0.0) return vec3(0.0);
float rough = clamp(sheen_rough, 1e-3, 1.0);
float a = rough * rough;
float sinNH2 = 1.0 - dotNH * dotNH;
float a2 = a * a;
float denom = 1.0 + a2 * sinNH2;
float D = (2.0 + a2) * sinNH2 * INV_TWO_PI / (denom * denom);
float V = 1.0 / (4.0 * dotNL * dotNV + 1e-5);
return sheen_tint * sheen * D * V * dotNL * brdf_sheenAlbedo;
}
float sheenAttenuation(const float sheen, const float sheen_rough,
const vec3 sheen_tint, const float dotNV) {
if (sheen <= 0.0) return 1.0;
float maxComp = sheen * max(max(sheen_tint.r, sheen_tint.g), sheen_tint.b) * brdf_sheenAlbedo;
return max(1.0 - maxComp, 0.0);
}
#endif
#ifdef _Anisotropy
// anisotropic GGX Burley 2012
vec3 anisotropicBRDF(const vec3 f0, const float roughness, const float anisotropy,
const float aniso_rot, const vec3 tangent, const vec3 bitangent,
const vec3 n, const vec3 l, const vec3 v,
const float dotNL, const float dotNV) {
if (abs(anisotropy) <= 0.001) return vec3(0.0);
float rot = aniso_rot * PI * 2.0;
float cr = cos(rot);
float sr = sin(rot);
vec3 t = normalize(tangent * cr + bitangent * sr);
vec3 b = normalize(bitangent * cr - tangent * sr);
float aniso_abs = abs(anisotropy);
float at = max(roughness * (1.0 + aniso_abs), 1e-5);
float ab = max(roughness * (1.0 - aniso_abs), 1e-5);
if (anisotropy < 0.0) { vec3 tmp = t; t = b; b = tmp; }
float at2 = at * at;
float ab2 = ab * ab;
vec3 h = normalize(l + v);
float dotTH = dot(t, h);
float dotBH = dot(b, h);
float dotTV = dot(t, v);
float dotBV = dot(b, v);
float dotTL = dot(t, l);
float dotBL = dot(b, l);
float denom = max(dotTH * dotTH / at2 + dotBH * dotBH / ab2, 1e-7);
float D = INV_PI / (at * ab * denom * denom);
float V = 1.0 / max(dotNL * (dotTL / at + dotBL / ab) * (dotTV / at + dotBV / ab), 1e-5);
float dotVH = max(dot(v, h), 0.0);
vec3 F = f_schlick(f0, dotVH);
return D * V * F / max(4.0 * dotNV, 1e-5);
}
#endif
#ifdef _Transmission
float brdf_transmissionF0;
// Blenders microfacet glass/refraction model
vec3 transmissionBRDF(const vec3 albedo, const float transmission,
const float trans_rough, const float ior, const float thin_wall,
const float dotNL, const float dotNV, const float dotVH) {
if (transmission <= 0.0) return vec3(0.0);
float F = brdf_transmissionF0 + (1.0 - brdf_transmissionF0) * exp2((SCHLICK_A * dotVH + SCHLICK_B) * dotVH);
float transmittance = 1.0 - F;
if (thin_wall > 0.5) {
return albedo * transmission * transmittance * dotNL;
}
float a = trans_rough * trans_rough;
float rough_atten = min(mix(1.0, 1.0 / max(dotNV, 0.1), a), 4.0);
return albedo * transmission * transmittance * rough_atten * dotNL;
}
#endif
#ifdef _ExtBRDF
float brdf_sheenWeight = 1.0;
float brdf_coatWeight = 1.0;
vec3 brdf_coatTintAbsorb = vec3(1.0);
vec3 applyExtBRDFLayers(
const vec3 direct,
const vec3 albedo,
const vec3 f0,
const float roughness,
const float dotNL, const float dotNV, const float dotNH, const float dotVH,
const vec3 n, const vec3 l, const vec3 v, const vec3 h,
#ifdef _ClearCoat
const float clearcoat, const float clearcoatRough, const float coatIOR,
const vec3 coatTint, const vec3 coatN,
#endif
#ifdef _Sheen
const float sheen, const float sheenRough, const vec3 sheenTint,
#endif
#ifdef _Transmission
const float transmission, const float transRough, const float ior, const float thinWall,
#endif
out float layerWeight
) {
float sheenWeight = brdf_sheenWeight;
float coatWeight = brdf_coatWeight;
#ifdef _Sheen
vec3 sheenContrib = sheenBRDF(sheen, sheenRough, sheenTint, dotNL, dotNH, dotNV);
#endif
#ifdef _ClearCoat
vec3 coatContrib = clearcoatBRDF(clearcoat, clearcoatRough, coatIOR, coatN, l, v, h);
#endif
layerWeight = sheenWeight * coatWeight;
vec3 result = direct * layerWeight;
#ifdef _Transmission
result += transmissionBRDF(albedo, transmission, transRough, ior, thinWall, dotNL, dotNV, dotVH) * layerWeight;
#endif
#ifdef _ClearCoat
result *= brdf_coatTintAbsorb;
result += coatContrib * sheenWeight;
#endif
#ifdef _Sheen
result += sheenContrib;
#endif
return result;
}
#endif
#ifdef _ClearCoat
float coatIBLFresnel(const float clearcoat, const float coat_ior,
const float dotNV_coat) {
if (clearcoat <= 0.0) return 0.0;
float F = brdf_coatF0 + (1.0 - brdf_coatF0) * exp2((SCHLICK_A * dotNV_coat + SCHLICK_B) * dotNV_coat);
return F * clearcoat;
}
#endif
#ifdef _Sheen
float sheenIBLAlbedo(const float sheen, const float sheen_rough,
const float dotNV) {
if (sheen <= 0.0) return 0.0;
float rough = clamp(sheen_rough, 1e-3, 1.0);
return sheen * (1.0 - 0.5 * rough) * mix(1.0, dotNV, 0.5);
}
#endif
#ifdef _Anisotropy
vec3 anisotropicIBLDirection(const vec3 n, const vec3 v, const vec3 tangent,
const float anisotropy, const float roughness) {
if (abs(anisotropy) <= 0.001 || dot(tangent, tangent) < 0.001)
return reflect(-v, n);
vec3 bitangent = normalize(cross(n, tangent));
vec3 r = reflect(-v, n);
float aniso_abs = abs(anisotropy);
vec3 stretchDir = anisotropy > 0.0 ? tangent : bitangent;
float stretchAmt = aniso_abs * roughness;
return normalize(r + stretchDir * stretchAmt * dot(r, stretchDir) * 0.5);
}
#endif
#ifdef _Transmission
float transmissionIBLFresnel(const float ior, const float dotNV) {
return brdf_transmissionF0 + (1.0 - brdf_transmissionF0) * exp2((SCHLICK_A * dotNV + SCHLICK_B) * dotNV);
}
vec3 transmissionIBLDirection(const vec3 n, const vec3 v, const float ior) {
float eta = 1.0 / ior;
vec3 refrDir = refract(-v, n, eta);
if (dot(refrDir, refrDir) < 0.001) {
refrDir = reflect(-v, n);
}
return refrDir;
}
#endif
#endif

View File

@ -34,17 +34,19 @@ THE SOFTWARE.
// https://research.nvidia.com/sites/default/files/publications/GIVoxels-pg2011-authors.pdf
const float MAX_DISTANCE = voxelgiRange;
const int MAX_CONE_STEPS = 32;
#ifdef _VoxelGI
uniform sampler3D dummy;
vec4 sampleVoxel(sampler3D voxels, vec3 P, const float clipmaps[voxelgiClipmapCount * 10], const float clipmap_index, const float step_dist, const int precomputed_direction, const vec3 face_offset, const vec3 direction_weight) {
vec4 col = vec4(0.0);
vec3 tc = (P - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
int base = int(clipmap_index * 10);
float voxelSize = float(clipmaps[base]);
vec3 tc = (P - vec3(clipmaps[base + 4], clipmaps[base + 5], clipmaps[base + 6])) / (voxelSize * voxelgiResolution);
vec3 half_texel = vec3(0.5) / voxelgiResolution;
tc = tc * 0.5 + 0.5;
tc = clamp(tc, half_texel, 1.0 - half_texel);
tc.x = (tc.x + precomputed_direction) / (6 + DIFFUSE_CONE_COUNT);
tc.x = (tc.x + precomputed_direction) / (6 + diffuseConeCount);
tc.y = (tc.y + clipmap_index) / voxelgiClipmapCount;
if (precomputed_direction == 0) {
@ -55,7 +57,7 @@ vec4 sampleVoxel(sampler3D voxels, vec3 P, const float clipmaps[voxelgiClipmapCo
else
col = textureLod(voxels, tc, 0);
col *= step_dist / float(clipmaps[int(clipmap_index * 10)]);
col *= step_dist / voxelSize;
return col;
}
@ -64,11 +66,13 @@ vec4 sampleVoxel(sampler3D voxels, vec3 P, const float clipmaps[voxelgiClipmapCo
#ifdef _VoxelAOvar
float sampleVoxel(sampler3D voxels, vec3 P, const float clipmaps[voxelgiClipmapCount * 10], const float clipmap_index, const float step_dist, const int precomputed_direction, const vec3 face_offset, const vec3 direction_weight) {
float opac = 0.0;
vec3 tc = (P - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
int base = int(clipmap_index * 10);
float voxelSize = float(clipmaps[base]);
vec3 tc = (P - vec3(clipmaps[base + 4], clipmaps[base + 5], clipmaps[base + 6])) / (voxelSize * voxelgiResolution);
vec3 half_texel = vec3(0.5) / voxelgiResolution;
tc = tc * 0.5 + 0.5;
tc = clamp(tc, half_texel, 1.0 - half_texel);
tc.x = (tc.x + precomputed_direction) / (6 + DIFFUSE_CONE_COUNT);
tc.x = (tc.x + precomputed_direction) / (6 + diffuseConeCount);
tc.y = (tc.y + clipmap_index) / voxelgiClipmapCount;
if (precomputed_direction == 0) {
@ -79,7 +83,7 @@ float sampleVoxel(sampler3D voxels, vec3 P, const float clipmaps[voxelgiClipmapC
else
opac = textureLod(voxels, tc, 0).r;
opac *= step_dist / float(clipmaps[int(clipmap_index * 10)]);
opac *= step_dist / voxelSize;
return opac;
}
@ -92,7 +96,7 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
float dist = voxelSize0;
float step_dist = dist;
vec3 samplePos;
vec3 start_pos = origin + n * voxelSize0;
vec3 start_pos = origin + n * voxelSize0 * voxelgiOffset;
int clipmap_index0 = 0;
vec3 aniso_direction = -dir;
@ -100,12 +104,14 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
aniso_direction.x > 0.0 ? 0.0 : 1.0,
aniso_direction.y > 0.0 ? 2.0 : 3.0,
aniso_direction.z > 0.0 ? 4.0 : 5.0
) / (6 + DIFFUSE_CONE_COUNT);
) / (6 + diffuseConeCount);
vec3 direction_weight = abs(dir);
float coneCoefficient = 2.0 * tan(aperture * 0.5);
while (sampleCol.a < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount) {
const vec3 half_texel = vec3(0.5) / voxelgiResolution;
int steps = 0;
while (sampleCol.a < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount && steps < MAX_CONE_STEPS) {
vec4 mipSample = vec4(0.0);
float diam = max(voxelSize0, dist * coneCoefficient);
float lod = clamp(log2(diam / voxelSize0), clipmap_index0, voxelgiClipmapCount - 1);
@ -113,7 +119,9 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
float clipmap_blend = smoothstep(0.0, 1.0, fract(lod));
vec3 p0 = start_pos + dir * dist;
samplePos = (p0 - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
int base = int(clipmap_index * 10);
float voxelSize = float(clipmaps[base]);
samplePos = (p0 - vec3(clipmaps[base + 4], clipmaps[base + 5], clipmaps[base + 6])) / (voxelSize * voxelgiResolution);
samplePos = samplePos * 0.5 + 0.5;
if (any(notEqual(samplePos, clamp(samplePos, 0.0, 1.0)))) {
@ -129,8 +137,11 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, precomputed_direction, face_offset, direction_weight);
if(totalBlend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
if(totalBlend > 0.05 && clipmap_index < voxelgiClipmapCount - 1) {
vec4 mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, precomputed_direction, face_offset, direction_weight);
int baseNext = int((clipmap_index + 1.0) * 10);
float voxelSizeCoarse = float(clipmaps[baseNext]);
mipSampleNext *= voxelSizeCoarse / voxelSize;
mipSample = mix(mipSample, mipSampleNext, totalBlend);
}
@ -138,8 +149,6 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
float stepSizeCurrent = step_size;
if (use_sdf) {
// half texel correction is applied to avoid sampling over current clipmap:
const vec3 half_texel = vec3(0.5) / voxelgiResolution;
vec3 tc0 = clamp(samplePos, half_texel, 1 - half_texel);
tc0.y = (tc0.y + clipmap_index) / voxelgiClipmapCount; // remap into clipmap
float sdf = textureLod(voxelsSDF, tc0, 0).r;
@ -147,6 +156,7 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
}
step_dist = diam * stepSizeCurrent;
dist += step_dist;
steps++;
}
return sampleCol;
}
@ -154,13 +164,13 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
vec4 traceDiffuse(const vec3 origin, const vec3 normal, const sampler3D voxels, const float clipmaps[voxelgiClipmapCount * 10]) {
float sum = 0.0;
vec4 amount = vec4(0.0);
for (int i = 0; i < DIFFUSE_CONE_COUNT; ++i) {
vec3 coneDir = DIFFUSE_CONE_DIRECTIONS[i];
for (int i = 0; i < diffuseConeCount; ++i) {
vec3 coneDir = diffuseConeDirections[i];
const float cosTheta = dot(normal, coneDir);
if (cosTheta <= 0)
continue;
int precomputed_direction = 6 + i;
amount += traceCone(voxels, dummy, origin, normal, coneDir, precomputed_direction, false, DIFFUSE_CONE_APERTURE, 1.0, clipmaps) * cosTheta;
amount += traceCone(voxels, voxels, origin, normal, coneDir, precomputed_direction, false, diffuseConeAperture, 1.0, clipmaps) * cosTheta;
sum += cosTheta;
}
@ -191,7 +201,7 @@ vec4 traceRefraction(const vec3 origin, const vec3 normal, sampler3D voxels, sam
amount.rgb = max(vec3(0.0), amount.rgb);
amount.a = clamp(amount.a, 0.0, 1.0);
return amount * voxelgiOcc;
return amount * voxelgiOcc * voxelgiRefr;
}
#endif
@ -202,7 +212,7 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
float dist = voxelSize0;
float step_dist = dist;
vec3 samplePos;
vec3 start_pos = origin + n * voxelSize0;
vec3 start_pos = origin + n * voxelSize0 * voxelgiOffset;
int clipmap_index0 = 0;
vec3 aniso_direction = -dir;
@ -210,12 +220,13 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
aniso_direction.x > 0.0 ? 0.0 : 1.0,
aniso_direction.y > 0.0 ? 2.0 : 3.0,
aniso_direction.z > 0.0 ? 4.0 : 5.0
) / (6 + DIFFUSE_CONE_COUNT);
) / (6 + diffuseConeCount);
vec3 direction_weight = abs(dir);
float coneCoefficient = 2.0 * tan(aperture * 0.5);
while (sampleCol < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount) {
int steps = 0;
while (sampleCol < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount && steps < MAX_CONE_STEPS) {
float mipSample = 0.0;
float diam = max(voxelSize0, dist * coneCoefficient);
float lod = clamp(log2(diam / voxelSize0), clipmap_index0, voxelgiClipmapCount - 1);
@ -223,7 +234,9 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
float clipmap_blend = smoothstep(0.0, 1.0, fract(lod));
vec3 p0 = start_pos + dir * dist;
samplePos = (p0 - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
int base = int(clipmap_index * 10);
float voxelSize = float(clipmaps[base]);
samplePos = (p0 - vec3(clipmaps[base + 4], clipmaps[base + 5], clipmaps[base + 6])) / (voxelSize * voxelgiResolution);
samplePos = samplePos * 0.5 + 0.5;
if ((any(notEqual(clamp(samplePos, 0.0, 1.0), samplePos)))) {
@ -239,8 +252,11 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, precomputed_direction, face_offset, direction_weight);
if(totalBlend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
if(totalBlend > 0.05 && clipmap_index < voxelgiClipmapCount - 1) {
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, precomputed_direction, face_offset, direction_weight);
int baseNext = int((clipmap_index + 1.0) * 10);
float voxelSizeCoarse = float(clipmaps[baseNext]);
mipSampleNext *= voxelSizeCoarse / voxelSize;
mipSample = mix(mipSample, mipSampleNext, totalBlend);
}
@ -248,6 +264,7 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
step_dist = diam * step_size;
dist += step_dist;
steps++;
}
return sampleCol;
}
@ -256,18 +273,18 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
float traceAO(const vec3 origin, const vec3 normal, const sampler3D voxels, const float clipmaps[voxelgiClipmapCount * 10]) {
float sum = 0.0;
float amount = 0.0;
for (int i = 0; i < DIFFUSE_CONE_COUNT; i++) {
vec3 coneDir = DIFFUSE_CONE_DIRECTIONS[i];
for (int i = 0; i < diffuseConeCount; i++) {
vec3 coneDir = diffuseConeDirections[i];
int precomputed_direction = 6 + i;
const float cosTheta = dot(normal, coneDir);
if (cosTheta <= 0)
continue;
amount += traceConeAO(voxels, origin, normal, coneDir, precomputed_direction, DIFFUSE_CONE_APERTURE, 1.0, clipmaps) * cosTheta;
amount += traceConeAO(voxels, origin, normal, coneDir, precomputed_direction, diffuseConeAperture, 1.0, clipmaps) * cosTheta;
sum += cosTheta;
}
amount /= max(sum, 0.0001);
amount = clamp(amount, 0.0, 1.0);
return amount * voxelgiOcc;
return amount;
}
#endif
@ -278,7 +295,7 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
float dist = voxelSize0;
float step_dist = dist;
vec3 samplePos;
vec3 start_pos = origin + n * voxelSize0;
vec3 start_pos = origin + n * voxelSize0 * voxelgiOffset;
int clipmap_index0 = 0;
vec3 aniso_direction = -dir;
@ -286,11 +303,13 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
aniso_direction.x > 0.0 ? 0.0 : 1.0,
aniso_direction.y > 0.0 ? 2.0 : 3.0,
aniso_direction.z > 0.0 ? 4.0 : 5.0
) / (6 + DIFFUSE_CONE_COUNT);
) / (6 + diffuseConeCount);
vec3 direction_weight = abs(dir);
float coneCoefficient = 2.0 * tan(aperture * 0.5);
while (sampleCol < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount) {
const vec3 half_texel = vec3(0.5) / voxelgiResolution;
int steps = 0;
while (sampleCol < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount && steps < MAX_CONE_STEPS) {
float mipSample = 0.0;
float diam = max(voxelSize0, dist * coneCoefficient);
float lod = clamp(log2(diam / voxelSize0), clipmap_index0, voxelgiClipmapCount - 1);
@ -298,7 +317,9 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
float clipmap_blend = smoothstep(0.0, 1.0, fract(lod));
vec3 p0 = start_pos + dir * dist;
samplePos = (p0 - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
int base = int(clipmap_index * 10);
float voxelSize = float(clipmaps[base]);
samplePos = (p0 - vec3(clipmaps[base + 4], clipmaps[base + 5], clipmaps[base + 6])) / (voxelSize * voxelgiResolution);
samplePos = samplePos * 0.5 + 0.5;
if ((any(notEqual(samplePos, clamp(samplePos, 0.0, 1.0))))) {
@ -318,11 +339,14 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, 0, face_offset, direction_weight).a;
#endif
if(totalBlend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
if(totalBlend > 0.05 && clipmap_index < voxelgiClipmapCount - 1) {
int baseNext = int((clipmap_index + 1.0) * 10);
float voxelSizeCoarse = float(clipmaps[baseNext]);
float scaleRatio = voxelSizeCoarse / voxelSize;
#ifdef _VoxelAOvar
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, 0, face_offset, direction_weight);
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, 0, face_offset, direction_weight) * scaleRatio;
#else
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, 0, face_offset, direction_weight).a;
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, 0, face_offset, direction_weight).a * scaleRatio;
#endif
mipSample = mix(mipSample, mipSampleNext, totalBlend);
}
@ -331,8 +355,6 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
float stepSizeCurrent = step_size;
// half texel correction is applied to avoid sampling over current clipmap:
const vec3 half_texel = vec3(0.5) / voxelgiResolution;
vec3 tc0 = clamp(samplePos, half_texel, 1 - half_texel);
tc0.y = (tc0.y + clipmap_index) / voxelgiClipmapCount; // remap into clipmap
float sdf = textureLod(voxelsSDF, tc0, 0.0).r;
@ -340,6 +362,7 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
step_dist = diam * stepSizeCurrent;
dist += step_dist;
steps++;
}
return sampleCol;
}
@ -347,7 +370,7 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
float traceShadow(const vec3 origin, const vec3 normal, const sampler3D voxels, const sampler3D voxelsSDF, const vec3 dir, const float clipmaps[voxelgiClipmapCount * 10], const vec2 pixel, const vec2 velocity) {
vec3 P = origin + dir * (BayerMatrix8[int(pixel.x + velocity.x) % 8][int(pixel.y + velocity.y) % 8] - 0.5) * voxelgiStep;
float amount = traceConeShadow(voxels, voxelsSDF, P, normal, dir, SHADOW_CONE_APERTURE, voxelgiStep, clipmaps);
float amount = traceConeShadow(voxels, voxelsSDF, P, normal, dir, voxelgiAperture, voxelgiStep, clipmaps);
amount = clamp(amount, 0.0, 1.0);
return amount * voxelgiOcc;
}

View File

@ -20,13 +20,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
const int DIFFUSE_CONE_COUNT = 16;
const float diffuseConeAperture = radians(39.0);
const float SHADOW_CONE_APERTURE = radians(15.0);
const float DIFFUSE_CONE_APERTURE = 1.0;
const vec3 DIFFUSE_CONE_DIRECTIONS[16] = vec3[](
const vec3 diffuseConeDirections[16] = vec3[](
vec3( 0.3480, 0.0000, 0.9375),
vec3(-0.4299, 0.3938, 0.8125),
vec3( 0.0635, -0.7234, 0.6875),

View File

@ -10,9 +10,9 @@
const int samples = 8; // Samples on the first ring
const int rings = 6; // Ring count
const vec2 focus = vec2(0.5, 0.5);
//const vec2 focus = vec2(0.5, 0.5);
const float coc = 0.03; // Circle of confusion size in mm (35mm film = 0.03mm)
const float maxblur = 1.0;
//const float maxblur = 1.0;
const float threshold = 0.5; // Highlight threshold
const float gain = 2.0; // Highlight gain
const float bias = 0.5; // Bokeh edge bias
@ -41,7 +41,9 @@ vec3 dof(
const bool autoFocus,
const float DOFDistance,
const float DOFLength,
const float DOFFStop) {
const float DOFFStop,
const vec2 focus,
const float maxblur) {
float depth = linearize(gdepth, cameraProj);
float fDepth = 0.0;
@ -85,7 +87,6 @@ vec3 dof(
float pw = (cos(float(j) * step) * float(i));
float ph = (sin(float(j) * step) * float(i));
float p = 1.0;
// if (pentagon) p = penta(vec2(pw, ph));
blurredCol += color(texCoord + vec2(pw * w, ph * h), blur, tex, texStep) * mix(1.0, (float(i)) / (float(rings)), bias) * p;
s += 1.0 * mix(1.0, (float(i)) / (float(rings)), bias) * p;
}

View File

@ -170,4 +170,88 @@ void unpackFloatInt16(float val, out float f, out uint i) {
f = (bitsValue & ~(0xF << numBitFloat)) / maxValFloat;
}
#ifdef _ExtBRDF
// extended material parameters by material slot ID returns vec4s (28 floats) of extended BRDF parameters
void getMaterialParams(uint matid, out vec4 p0, out vec4 p1, out vec4 p2, out vec4 p3,
out vec4 p4, out vec4 p5, out vec4 p6, out vec4 p7) {
uint base = matid * 8u;
#if defined(_Anisotropy) || defined(_Sheen)
p0 = materialParams[base];
#else
p0 = vec4(0.0);
#endif
#if defined(_ClearCoat)
p1 = materialParams[base + 1u];
#else
p1 = vec4(0.0);
#endif
#if defined(_ClearCoat) || defined(_Transmission)
p2 = materialParams[base + 2u];
#else
p2 = vec4(0.0);
#endif
#if defined(_Transmission) || defined(_SSS)
p3 = materialParams[base + 3u];
#else
p3 = vec4(0.0);
#endif
#if defined(_SSS)
p4 = materialParams[base + 4u];
#else
p4 = vec4(0.0);
#endif
#if defined(_Sheen) || defined(_SSS)
p5 = materialParams[base + 5u];
#else
p5 = vec4(0.0);
#endif
#if defined(_ExtBRDF)
p6 = materialParams[base + 6u];
#else
p6 = vec4(0.0);
#endif
#if defined(_SSS)
p7 = materialParams[base + 7u];
#else
p7 = vec4(0.0);
#endif
}
#endif
float packIOR(float ior) {
return clamp((ior - 1.0) / 1.5, 0.0, 1.0);
}
float unpackIOR(float packed) {
return packed * 1.5 + 1.0;
}
#ifndef PI
#define PI 3.1415926535
#endif
#ifndef PI2
#define PI2 6.2831853071
#endif
float encodeTangent(vec3 tangent, vec3 normal) {
if (length(tangent) < 0.5) return -1.0;
vec3 t = normalize(tangent);
vec3 n = normalize(normal);
vec3 ref = abs(n.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
vec3 r = normalize(ref - n * dot(ref, n));
vec3 b = cross(n, r);
float angle = atan(dot(t, b), dot(t, r));
return (angle / (2.0 * PI) + 0.5);
}
vec3 decodeTangent(float enc, vec3 normal) {
if (enc < 0.0) return vec3(1.0, 0.0, 0.0);
vec3 n = normalize(normal);
vec3 ref = abs(n.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
vec3 r = normalize(ref - n * dot(ref, n));
vec3 b = cross(n, r);
float angle = (enc - 0.5) * 2.0 * PI;
return normalize(r * cos(angle) + b * sin(angle));
}
#endif

View File

@ -3,7 +3,9 @@ uniform sampler2D texIES;
float iesAttenuation(vec3 l) {
const float PI = 3.1415926535;
#ifndef PI
#define PI 3.1415926535
#endif
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
// Sample direction into light space
// vec3 iesSampleDirection = mul(light.worldToLight , -L);

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
#ifdef _SinglePoint
#ifdef _Spot
uniform sampler2DShadow shadowMapSpot[1];
uniform mat4 LWVPSpot[1];
uniform mat4 LWVPSpotArray[1];
#else
uniform samplerCubeShadow shadowMapPoint[1];
uniform vec2 lightProj;
@ -24,7 +24,9 @@
#ifdef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlas;
#endif
#ifndef _SinglePoint
uniform vec2 lightProj;
#endif
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasPoint;
@ -53,19 +55,64 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _Spot
, bool isSpot, float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right
#endif
#ifdef _ClearCoat
, float clearcoat, float clearcoatRough, float coatIOR, vec3 coatTint, vec3 coatN
#endif
#ifdef _Sheen
, float sheen, float sheenRough, vec3 sheenTint
#endif
#ifdef _Anisotropy
, float anisotropy, float anisoRot, vec3 tangent
#endif
#ifdef _SSS
, float subsurface, vec3 sssColor, vec3 sssRadius, float sssAnisotropy
#endif
#ifdef _Transmission
, float transmission, float transRough, float ior, float thinWall
#endif
) {
vec3 ld = lp - p;
vec3 l = normalize(ld);
float dist = length(ld);
vec3 l = ld / dist;
vec3 h = normalize(v + l);
float dotNH = max(0.0, dot(n, h));
float dotVH = max(0.0, dot(v, h));
float dotNL = max(0.0, dot(n, l));
vec3 direct = lambertDiffuseBRDF(albedo, dotNL) +
specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec;
#ifdef _Anisotropy
vec3 direct;
if (abs(anisotropy) > 0.001 && dot(tangent, tangent) > 0.001) {
vec3 bitangent = normalize(cross(n, tangent));
direct = diffuseBRDF(albedo, rough, f0, dotNL, dotNV, dotVH) +
anisotropicBRDF(f0, rough, anisotropy, anisoRot,
tangent, bitangent, n, l, v, dotNL, dotNV) * spec;
} else {
direct = diffuseBRDF(albedo, rough, f0, dotNL, dotNV, dotVH) +
specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec;
}
#else
vec3 direct = diffuseBRDF(albedo, rough, f0, dotNL, dotNV, dotVH) +
specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec;
#endif
#ifdef _ExtBRDF
float layerWeight;
direct = applyExtBRDFLayers(direct, albedo, f0, rough, dotNL, dotNV, dotNH, dotVH, n, l, v, h
#ifdef _ClearCoat
, clearcoat, clearcoatRough, coatIOR, coatTint, coatN
#endif
#ifdef _Sheen
, sheen, sheenRough, sheenTint
#endif
#ifdef _Transmission
, transmission, transRough, ior, thinWall
#endif
, layerWeight
);
#endif
direct *= lightCol;
direct *= attenuate(distance(p, lp));
direct *= attenuate(dist);
#ifdef _Spot
if (isSpot) {
@ -74,12 +121,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _ShadowMap
if (receiveShadow) {
#ifdef _SinglePoint
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
#endif
#ifdef _Clusters
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
#ifdef _ShadowMapAtlas
tileBounds = tileBoundsSpotArray[index];
direct *= shadowTest(
#ifndef _SingleAtlas
shadowMapAtlasSpot
@ -132,4 +180,41 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
return direct;
}
// Backward-compatible overload for generated shaders that don't pass extended BRDF params
#ifdef _ExtBRDF
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
#ifdef _ShadowMap
, int index, float bias, bool receiveShadow
#endif
#ifdef _Spot
, bool isSpot, float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right
#endif
) {
return sampleLight(p, n, v, dotNV, lp, lightCol, albedo, rough, spec, f0
#ifdef _ShadowMap
, index, bias, receiveShadow
#endif
#ifdef _Spot
, isSpot, spotSize, spotBlend, spotDir, scale, right
#endif
#ifdef _ClearCoat
, 0.0, 0.0, 1.5, vec3(1.0), n
#endif
#ifdef _Sheen
, 0.0, 0.0, vec3(1.0)
#endif
#ifdef _Anisotropy
, 0.0, 0.0, vec3(0.0)
#endif
#ifdef _SSS
, 0.0, vec3(0.0), vec3(0.0), 0.0
#endif
#ifdef _Transmission
, 0.0, 0.0, 1.45, 1.0
#endif
);
}
#endif // _ExtBRDF
#endif

View File

@ -1,41 +1,81 @@
/*
https://github.com/JonasFolletete/glsl-triplanar-mapping
vec4 boxProjection(sampler2D image, vec3 normal, vec3 coord, float blend) {
vec3 n = normalize(normal);
vec3 N = abs(n);
vec4 color1, color2, color3;
MIT License
vec2 uv = coord.yz;
if (n.x < 0.0) {
uv.x = 1.0 - uv.x;
}
color1 = texture(image, uv);
Copyright (c) 2018 Jonas Folletête
uv = coord.xz;
if (n.y > 0.0) {
uv.x = 1.0 - uv.x;
}
color2 = texture(image, uv);
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
uv = vec2(coord.y, 1.0 - coord.x);
if (n.z > 0.0) {
uv.x = 1.0 - uv.x;
}
color3 = texture(image, uv);
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
N /= max(dot(N, vec3(1.0)), 1e-8);
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
float limit = 0.5 + 0.5 * clamp(blend, 0.0, 1.0);
vec3 weight;
weight = N.xyz / (N.xyx + N.yzz);
weight = clamp((weight - 0.5 * (1.0 - clamp(blend, 0.0, 1.0))) / max(1e-8, clamp(blend, 0.0, 1.0)), 0.0, 1.0);
vec3 blendNormal(vec3 normal) {
vec3 blending = abs(normal);
blending = normalize(max(blending, 0.00001));
blending /= vec3(blending.x + blending.y + blending.z);
return blending;
if (N.z < (1.0 - limit) * (N.y + N.x)) {
weight.z = 0.0;
weight.y = 1.0 - weight.x;
}
else if (N.x < (1.0 - limit) * (N.y + N.z)) {
weight.x = 0.0;
weight.z = 1.0 - weight.y;
}
else if (N.y < (1.0 - limit) * (N.x + N.z)) {
weight.y = 0.0;
weight.x = 1.0 - weight.z;
}
else {
weight = ((2.0 - limit) * N + (limit - 1.0)) / max(1e-8, clamp(blend, 0.0, 1.0));
}
return color1 * weight.x + color2 * weight.y + color3 * weight.z;
}
vec3 triplanarMapping (sampler2D ImageTexture, vec3 normal, vec3 position) {
vec3 normalBlend = blendNormal(normal);
vec3 xColor = texture(ImageTexture, position.yz).rgb;
vec3 yColor = texture(ImageTexture, position.xz).rgb;
vec3 zColor = texture(ImageTexture, position.xy).rgb;
return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
vec2 sphericalMapping(vec3 coord) {
vec3 vin = coord * 2.0 - vec3(1.0);
float len = length(vin);
float v, u;
if (len > 0.0) {
if (vin.x == 0.0 && vin.y == 0.0) {
u = 0.0;
}
else {
u = (1.0 - atan(vin.x, vin.y) / PI) * 0.5;
}
v = acos(clamp(vin.z / len, -1.0, 1.0)) / PI;
}
else {
v = u = 0.0;
}
return vec2(u, v);
}
vec2 tubeMapping(vec3 coord) {
vec3 vin = coord * 2.0 - vec3(1.0);
float u, v;
v = - (vin.z + 1.0) * 0.5;
float len = sqrt(vin.x * vin.x + vin.y * vin.y);
if (len > 0.0) {
u = (1.0 - (atan(vin.x / len, vin.y / len) / PI)) * 0.5;
}
else {
v = u = 0.0;
}
return vec2(u, v);
}

View File

@ -8,13 +8,25 @@ float hash(const vec2 p) {
}
vec2 envMapEquirect(const vec3 normal) {
const float PI = 3.1415926535;
const float PI2 = PI * 2.0;
#ifndef PI
#define PI 3.1415926535
#endif
#ifndef PI2
#define PI2 6.2831853071
#endif
float phi = acos(normal.z);
float theta = atan(-normal.y, normal.x) + PI;
return vec2(theta / PI2, phi / PI);
}
vec2 envMapMirror(const vec3 co) {
vec3 nco = normalize(co);
nco.y -= 1.0;
float div = 2.0 * sqrt(max(-0.5 * nco.y, 0.0));
nco /= max(1e-8, div);
return 0.5 * nco.xz + 0.5;
}
float rand(const vec2 co) { // Unreliable
return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
}

View File

@ -4,6 +4,36 @@ uniform vec2 morphScaleOffset;
uniform vec2 morphDataDim;
uniform vec4 morphWeights[8];
void getMorphedVertex(vec2 uvCoord, inout vec3 A, vec4 imorph) {
vec3 morph = texture(morphDataPos, uvCoord).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += imorph.x * morph;
morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += imorph.y * morph;
morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - 2.0 * morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += imorph.z * morph;
morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - 3.0 * morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += imorph.w * morph;
}
void getMorphedNormal(vec2 uvCoord, vec3 oldNor, inout vec3 morphNor, vec4 imorph) {
vec3 norm = oldNor + imorph.x * (texture(morphDataNor, uvCoord).rgb * 2.0 - 1.0);
morphNor += norm;
norm = oldNor + imorph.y * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - morphDataDim.y)).rgb * 2.0 - 1.0);
morphNor += norm;
norm = oldNor + imorph.z * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - 2.0 * morphDataDim.y)).rgb * 2.0 - 1.0);
morphNor += norm;
norm = oldNor + imorph.w * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - 3.0 * morphDataDim.y)).rgb * 2.0 - 1.0);
morphNor += norm;
morphNor = normalize(morphNor);
}
void getMorphedVertex(vec2 uvCoord, inout vec3 A){
vec3 totalDelta = vec3(0.0);
for(int i = 0; i<8; i++ )

View File

@ -22,6 +22,14 @@ uniform vec2 smSizeUniform;
#endif
#endif
#ifdef _ShadowMapAtlas
uniform vec4 tileBoundsSunArray[maxLights * shadowmapCascades];
#if defined(_Clusters) && defined(_Spot) && defined(_ShadowMap)
uniform vec4 tileBoundsSpotArray[maxLightsCluster];
#endif
vec4 tileBounds = vec4(0.0, 0.0, 1.0, 1.0);
#endif
#ifdef _ShadowMapAtlas
// PCF that clamps samples to tile boundaries to prevent bleeding
vec3 PCFTileAware(sampler2DShadow shadowMap,
@ -67,9 +75,15 @@ vec3 PCFTileAware(sampler2DShadow shadowMap,
#ifdef _ShadowMapTransparent
if (transparent == false) {
vec4 shadowmap_transparent = texture(shadowMapTransparent, uv);
if (shadowmap_transparent.a < compare)
result *= shadowmap_transparent.rgb;
vec3 transResult = vec3(0.0);
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
vec4 smt = texture(shadowMapTransparent,
clamp(uv + vec2(x, y) / smSize, tileMin, tileMax));
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
}
}
result *= transResult / 9.0;
}
#endif
@ -134,9 +148,15 @@ vec3 PCF(sampler2DShadow shadowMap,
#ifdef _ShadowMapTransparent
if (transparent == false) {
vec4 shadowmap_transparent = texture(shadowMapTransparent, uv);
if (shadowmap_transparent.a < compare)
result *= shadowmap_transparent.rgb;
vec3 transResult = vec3(0.0);
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
vec4 smt = texture(shadowMapTransparent,
uv + vec2(x, y) / smSize);
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
}
}
result *= transResult / 9.0;
}
#endif
@ -179,9 +199,19 @@ vec3 PCFCube(samplerCubeShadow shadowMapCube,
#ifdef _ShadowMapTransparent
if (transparent == false) {
vec4 shadowmap_transparent = texture(shadowMapCubeTransparent, ml);
if (shadowmap_transparent.a < compare)
result *= shadowmap_transparent.rgb;
vec3 transResult = vec3(0.0);
vec4 smt = texture(shadowMapCubeTransparent, ml);
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
for (int x = -1; x <= 1; x += 2) {
for (int y = -1; y <= 1; y += 2) {
for (int z = -1; z <= 1; z += 2) {
smt = texture(shadowMapCubeTransparent,
ml + vec3(x, y, z) * s);
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
}
}
}
result *= transResult / 9.0;
}
#endif
@ -291,13 +321,13 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
, const bool transparent
#endif
) {
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
const vec2 smSize = smSizeUniform; // TODO: incorrect...
vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
@ -377,9 +407,15 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
#ifdef _ShadowMapTransparent
if (transparent == false) {
vec4 shadowmap_transparent = texture(shadowMapTransparent, uvtiled);
if (shadowmap_transparent.a < compare)
result *= shadowmap_transparent.rgb;
vec3 transResult = vec3(0.0);
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
vec4 smt = texture(shadowMapTransparent,
clamp(uvtiled + vec2(x, y) / smSize, 0.0, 1.0));
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
}
}
result *= transResult / 9.0;
}
#endif
@ -387,10 +423,6 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
}
#endif
#ifdef _ShadowMapAtlas
uniform vec4 tileBounds;
#endif
vec3 shadowTest(sampler2DShadow shadowMap,
#ifdef _ShadowMapTransparent
sampler2D shadowMapTransparent,
@ -405,9 +437,9 @@ vec3 shadowTest(sampler2DShadow shadowMap,
#ifdef _ShadowMapAtlas
// use tile PCF
#ifdef _SMSizeUniform
vec2 smSizeAtlas = smSizeUniform;
vec2 smSizeAtlas = smSizeUniform * (tileBounds.zw - tileBounds.xy);
#else
const vec2 smSizeAtlas = shadowmapSize;
vec2 smSizeAtlas = shadowmapSize * (tileBounds.zw - tileBounds.xy);
#endif
return PCFTileAware(shadowMap,
#ifdef _ShadowMapTransparent
@ -455,7 +487,7 @@ mat4 getCascadeMat(const float d, out int casi, out int casIndex) {
float(d > casData[c * 4].y),
float(d > casData[c * 4].z),
float(d > casData[c * 4].w));
casi = int(min(dot(ci, comp), c));
casi = int(min(dot(ci, comp), float(c - 1)));
// Get cascade mat
casIndex = casi * 4;
return mat4(
@ -479,8 +511,12 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
#ifdef _SMSizeUniform
vec2 smSize = smSizeUniform;
#else
#ifdef _ShadowMapAtlas
vec2 smSize = shadowmapSize * (tileBoundsSunArray[0].zw - tileBoundsSunArray[0].xy);
#else
const vec2 smSize = shadowmapSize * vec2(shadowmapCascades, 1.0);
#endif
#endif
const int c = shadowmapCascades;
float d = distance(eye, p);
int casi;
@ -489,16 +525,35 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
vec4 lPos = LWVP * vec4(p, 1.0);
lPos.xyz /= lPos.w;
#ifdef _ShadowMapAtlas
tileBounds = tileBoundsSunArray[casi];
#endif
vec3 visibility = vec3(1.0);
if (lPos.w > 0.0) visibility = PCF(shadowMap,
#ifdef _ShadowMapTransparent
shadowMapTransparent,
#endif
lPos.xy, lPos.z - shadowsBias, smSize
#ifdef _ShadowMapTransparent
, transparent
#endif
);
if (lPos.w > 0.0) {
#ifdef _ShadowMapAtlas
visibility = PCFTileAware(shadowMap,
#ifdef _ShadowMapTransparent
shadowMapTransparent,
#endif
lPos.xy, lPos.z - shadowsBias, smSize,
tileBounds.xy, tileBounds.zw
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#else
visibility = PCF(shadowMap,
#ifdef _ShadowMapTransparent
shadowMapTransparent,
#endif
lPos.xy, lPos.z - shadowsBias, smSize
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif
}
// Blend cascade
// https://github.com/TheRealMJP/Shadows
@ -518,15 +573,33 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
lPos2.xyz /= lPos2.w;
vec3 visibility2 = vec3(1.0);
// use lPos2 coordinates for second cascade, not lPos
if (lPos2.w > 0.0) visibility2 = PCF(shadowMap,
#ifdef _ShadowMapTransparent
shadowMapTransparent,
#endif
lPos2.xy, lPos2.z - shadowsBias, smSize
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#ifdef _ShadowMapAtlas
tileBounds = tileBoundsSunArray[casi + 1];
#endif
if (lPos2.w > 0.0) {
#ifdef _ShadowMapAtlas
visibility2 = PCFTileAware(shadowMap,
#ifdef _ShadowMapTransparent
shadowMapTransparent,
#endif
lPos2.xy, lPos2.z - shadowsBias, smSize,
tileBounds.xy, tileBounds.zw
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#else
visibility2 = PCF(shadowMap,
#ifdef _ShadowMapTransparent
shadowMapTransparent,
#endif
lPos2.xy, lPos2.z - shadowsBias, smSize
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif
}
float lerpAmt = smoothstep(0.0, blendThres, splitDist);
return mix(visibility2, visibility, lerpAmt);

View File

@ -26,7 +26,7 @@ uniform sampler2D singleScatterLUT;
uniform vec2 skyDensity;
#ifndef PI
#define PI 3.141592
#define PI 3.1415926535
#endif
#ifndef HALF_PI
#define HALF_PI 1.570796

View File

@ -1,15 +1,25 @@
// Separable SSS Transmittance Function, ref to sss_pass
vec3 SSSSTransmittance(mat4 LWVP, vec3 p, vec3 n, vec3 l, float lightFar, sampler2DShadow shadowMap) {
const float translucency = 1.0;
vec3 SSSSTransmittance(mat4 LWVP, vec3 p, vec3 n, vec3 l, float lightFar, sampler2DShadow shadowMap, vec3 sssColor, float sssRadius
#ifdef _ShadowMapAtlas
, vec4 tileBounds
#endif
) {
const float translucency = 0.85;
vec4 shrinkedPos = vec4(p - 0.005 * n, 1.0);
vec4 shadowPos = LWVP * shrinkedPos;
float scale = 8.25 * (1.0 - translucency) / (sssWidth / 10.0);
float d1 = texture(shadowMap, vec3(shadowPos.xy / shadowPos.w, shadowPos.z)).r; // 'd1' has a range of 0..1
float d2 = shadowPos.z; // 'd2' has a range of 0..'lightFarPlane'
d1 *= lightFar; // So we scale 'd1' accordingly:
float d = scale * abs(d1 - d2);
vec2 shadowUV = shadowPos.xy / shadowPos.w;
#ifdef _ShadowMapAtlas
shadowUV = clamp(shadowUV, tileBounds.xy, tileBounds.zw);
#endif
float scale = 2.5 * (1.0 - translucency) / max(sssRadius, 0.001);
float d1 = texture(shadowMap, vec3(shadowUV, shadowPos.z)).r;
float d2 = shadowPos.z;
d1 *= lightFar;
d2 *= lightFar;
float d = scale * abs(d1 - d2) * 1000.0;
if (d > 10.0) return vec3(0.0);
float dd = -d * d;
vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) +
vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) +
@ -17,10 +27,70 @@ vec3 SSSSTransmittance(mat4 LWVP, vec3 p, vec3 n, vec3 l, float lightFar, sample
vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) +
vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) +
vec3(0.078, 0.0, 0.0) * exp(dd / 7.41);
return profile * clamp(0.3 + dot(l, -n), 0.0, 1.0);
profile *= mix(vec3(1.0), sssColor, 0.8);
return profile * clamp(0.5 + dot(l, -n), 0.0, 1.0);
}
vec3 SSSSTransmittanceCube(float translucency, vec4 shadowPos, vec3 n, vec3 l, float lightFar) {
// TODO
return vec3(0.0);
#ifdef _ShadowMapAtlas
vec3 SSSSTransmittanceCubeAtlas(sampler2DShadow shadowMap, vec3 lightPos, vec3 p, vec3 n, vec3 l, float lightFar, vec2 lightProj, int index, vec3 sssColor, float sssRadius) {
const float translucency = 0.85;
vec3 shrinkedPos = p - 0.005 * n;
vec3 ld = normalize(shrinkedPos - lightPos);
#ifdef _InvY
ld.y = -ld.y;
#endif
float d2 = lpToDepth(ld, lightProj);
int faceIndex = 0;
int lightIndex = index * 6;
vec2 uv = sampleCube(ld, faceIndex);
vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex];
vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y;
#endif
float d1 = texture(shadowMap, vec3(uvtiled, d2)).r;
d1 *= lightFar;
d2 *= lightFar;
float scale = 2.5 * (1.0 - translucency) / max(sssRadius, 0.001);
// d1/d2 are in meters, sssRadius is in mm, exponential constants are in mm^2
float d = scale * abs(d1 - d2) * 1000.0; // Convert distance to mm
if (d > 10.0) return vec3(0.0);
float dd = -d * d;
vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) +
vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) +
vec3(0.118, 0.198, 0.0) * exp(dd / 0.187) +
vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) +
vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) +
vec3(0.078, 0.0, 0.0) * exp(dd / 7.41);
profile *= mix(vec3(1.0), sssColor, 0.8);
return profile * clamp(0.5 + dot(l, -n), 0.0, 1.0);
}
#endif
vec3 SSSSTransmittanceCube(samplerCubeShadow shadowMapCube, vec3 lightPos, vec3 p, vec3 n, vec3 l, float lightFar, vec2 lightProj, vec3 sssColor, float sssRadius) {
const float translucency = 0.85;
vec3 shrinkedPos = p - 0.005 * n;
vec3 ld = normalize(shrinkedPos - lightPos);
#ifdef _InvY
ld.y = -ld.y;
#endif
float d2 = lpToDepth(ld, lightProj);
float d1 = texture(shadowMapCube, vec4(ld, d2)).r;
d1 *= lightFar;
d2 *= lightFar;
float scale = 2.5 * (1.0 - translucency) / max(sssRadius, 0.001);
// d1/d2 are in meters, sssRadius is in mm, exponential constants are in mm^2
float d = scale * abs(d1 - d2) * 1000.0; // Convert distance to mm
if (d > 10.0) return vec3(0.0);
float dd = -d * d;
vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) +
vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) +
vec3(0.118, 0.198, 0.0) * exp(dd / 0.187) +
vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) +
vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) +
vec3(0.078, 0.0, 0.0) * exp(dd / 7.41);
profile *= mix(vec3(1.0), sssColor, 0.8);
return profile * clamp(0.5 + dot(l, -n), 0.0, 1.0);
}

View File

@ -38,7 +38,6 @@ uniform layout(r8) image3D voxelsOut;
#endif
uniform int clipmapLevel;
uniform float voxelBlend;
uniform float clipmaps[voxelgiClipmapCount * 10];
@ -47,7 +46,7 @@ void main() {
ivec3 src = ivec3(gl_GlobalInvocationID.xyz);
src.y += clipmapLevel * res;
for (int i = 0; i < 6 + DIFFUSE_CONE_COUNT; i++)
for (int i = 0; i < 6 + diffuseConeCount; i++)
{
vec4 col = vec4(0.0);

View File

@ -151,7 +151,7 @@ void main() {
#endif
#endif
envl.rgb *= albedo;
envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV);
#ifdef _Brdf
envl.rgb *= 1.0 - F; //LV: We should take refracted light into account
@ -165,7 +165,7 @@ void main() {
#endif
#endif
envl.rgb *= envmapStrength * occspec.x;
envl.rgb *= envmapStrength * voxelgiEnv * occspec.x;
vec3 occ = envl * (1.0 - traceAO(P, n, voxels, clipmaps));

View File

@ -53,13 +53,6 @@ uniform float shirr[7 * 4];
#ifdef _Brdf
uniform sampler2D senvmapBrdf;
#endif
#ifdef _Rad
uniform sampler2D senvmapRadiance;
uniform int envmapNumMipmaps;
#endif
#ifdef _EnvCol
uniform vec3 backgroundCol;
#endif
void main() {
const vec2 pixel = gl_GlobalInvocationID.xy;
@ -140,37 +133,20 @@ void main() {
vec3 envl = vec3(0.0);
#endif
#ifdef _Rad
vec3 reflectionWorld = reflect(-v, n);
float lod = getMipFromRoughness(roughness, envmapNumMipmaps);
vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(reflectionWorld), lod).rgb;
#endif
#ifdef _EnvLDR
envl.rgb = pow(envl.rgb, vec3(2.2));
#ifdef _Rad
prefilteredColor = pow(prefilteredColor, vec3(2.2));
#endif
#endif
envl.rgb *= albedo;
envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV);
#ifdef _Brdf
envl.rgb *= 1.0 - F; //LV: We should take refracted light into account
#endif
#ifdef _Rad // Indirect specular
envl.rgb += prefilteredColor * F; //LV: Removed "1.5 * occspec.y". Specular should be weighted only by FV LUT
#else
#ifdef _EnvCol
envl.rgb += backgroundCol * F; //LV: Eh, what's the point of weighting it only by F0?
#endif
#endif
envl.rgb *= envmapStrength * occspec.x;
envl.rgb *= envmapStrength * voxelgiEnv * occspec.x;
vec4 trace = traceDiffuse(P, n, voxels, clipmaps);
vec3 color = trace.rgb * albedo * (1.0 - F);
vec3 color = trace.rgb * diffuseIBL(albedo, roughness, f0, dotNV) * (1.0 - F);
color += envl * (1.0 - trace.a);
imageStore(voxels_diffuse, ivec2(pixel), vec4(color, 1.0));

View File

@ -48,13 +48,15 @@ void main() {
const vec2 pixel = gl_GlobalInvocationID.xy;
vec2 uv = (pixel + 0.5) / postprocess_resolution;
#ifdef _InvY
uv.y = 1.0 - uv.y
uv.y = 1.0 - uv.y;
#endif
float depth = textureLod(gbufferD, uv, 0.0).r * 2.0 - 1.0;
if (depth == 0) return;
vec2 ior_opac = textureLod(gbuffer_refraction, uv, 0.0).xy;
float ior = unpackIOR(ior_opac.x);
float opacity = ior_opac.y;
float x = uv.x * 2 - 1;
float y = uv.y * 2 - 1;
@ -72,8 +74,8 @@ void main() {
n = normalize(n);
vec3 color = vec3(0.0);
if(ior_opac.y < 1.0)
color = traceRefraction(P, n, voxels, voxelsSDF, normalize(eye - P), ior_opac.x, g0.b, clipmaps, pixel).rgb;
if(opacity < 1.0)
color = traceRefraction(P, n, voxels, voxelsSDF, normalize(eye - P), ior, g0.b, clipmaps, pixel).rgb;
imageStore(voxels_refraction, ivec2(pixel), vec4(color, 1.0));
}

View File

@ -69,7 +69,7 @@ void main() {
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
n = normalize(n);
float occ = 1.0 - traceShadow(P, n, voxels, voxelsSDF, normalize(lPos - P), clipmaps, pixel);
float occ = 1.0 - traceShadow(P, n, voxels, voxelsSDF, normalize(lPos - P), clipmaps, pixel, vec2(0.0));
imageStore(voxels_shadows, ivec2(pixel), vec4(occ));
}

View File

@ -66,9 +66,13 @@ void main() {
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
n = normalize(n);
float roughness = g0.b;
vec3 v = normalize(eye - P);
vec2 velocity = -textureLod(sveloc, uv, 0.0).rg;
vec3 color = traceSpecular(P, n, voxels, voxelsSDF, normalize(eye - P), g0.z * g0.z, clipmaps, pixel, velocity).rgb;
vec3 color = traceSpecular(P, n, voxels, voxelsSDF, v, roughness * roughness, clipmaps, pixel, velocity).rgb;
imageStore(voxels_specular, ivec2(pixel), vec4(color, 1.0));
}

View File

@ -79,7 +79,7 @@ void main() {
float aniso_colors[6];
#endif
for (int i = 0; i < 6 + DIFFUSE_CONE_COUNT; i++)
for (int i = 0; i < 6 + diffuseConeCount; i++)
{
ivec3 src = ivec3(gl_GlobalInvocationID.xyz);
src.x += i * res;
@ -136,7 +136,7 @@ void main() {
radiance = basecol;
vec4 trace = traceDiffuse(wposition, wnormal, voxelsSampler, clipmaps);
vec3 indirect = trace.rgb + envl.rgb * (1.0 - trace.a);
radiance.rgb *= light.rgb + indirect.rgb;
radiance.rgb *= light.rgb * INV_PI + indirect.rgb;
radiance.rgb += emission.rgb;
}
@ -195,7 +195,7 @@ void main() {
}
else {
// precompute cone sampling:
vec3 coneDirection = DIFFUSE_CONE_DIRECTIONS[i - 6];
vec3 coneDirection = diffuseConeDirections[i - 6];
vec3 aniso_direction = -coneDirection;
uvec3 face_offsets = uvec3(
aniso_direction.x > 0 ? 0 : 1,

View File

@ -237,8 +237,24 @@ class App {
traitRenders.remove(f);
}
public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void) {
traitRenders2D.push(f);
public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void, index: Int = -1) {
if (index < 0 || index >= traitRenders2D.length) {
traitRenders2D.push(f);
} else {
traitRenders2D.insert(index, f);
}
}
public static function moveRender2D(f: kha.graphics2.Graphics->Void, newIndex: Int) {
var oldIndex = traitRenders2D.indexOf(f);
if (oldIndex != -1) {
traitRenders2D.splice(oldIndex, 1);
if (newIndex >= traitRenders2D.length) {
traitRenders2D.push(f);
} else {
traitRenders2D.insert(newIndex, f);
}
}
}
public static function removeRender2D(f: kha.graphics2.Graphics->Void) {

View File

@ -304,10 +304,13 @@ class RenderPath {
currentD = 1;
currentFace = -1;
meshesSorted = false;
sun = null;
for (l in Scene.active.lights) {
if (l.visible) l.buildMatrix(Scene.active.camera);
if (l.data.raw.type == "sun") sun = l;
if (l.data.raw.type == "sun") {
if (sun == null || (!sun.data.raw.cast_shadow && l.data.raw.cast_shadow)) sun = l;
}
else point = l;
}
light = Scene.active.lights[0];
@ -500,7 +503,7 @@ class RenderPath {
}
public function drawMeshes(context: String) {
var isShadows = context == "shadowmap";
var isShadows = context == "shadowmap" || context == "shadowmap_transparent";
if (isShadows) {
// Disabled shadow casting for this light
if (light == null || !light.data.raw.cast_shadow || !light.visible || light.data.raw.strength == 0) return;
@ -528,13 +531,11 @@ class RenderPath {
if (!drawn) submitDraw(context);
#if lnx_debug
// Callbacks to specific context
if (contextEvents != null) {
var ar = contextEvents.get(context);
if (ar != null) for (i in 0...ar.length) ar[i](currentG, i, ar.length);
}
#end
end();
}
@ -594,7 +595,6 @@ class RenderPath {
}
}
#if lnx_debug
static var contextEvents: Map<String, Array<Graphics->Int->Int->Void>> = null;
public static function notifyOnContext(name: String, onContext: Graphics->Int->Int->Void) {
if (contextEvents == null) contextEvents = new Map();
@ -605,7 +605,13 @@ class RenderPath {
}
ar.push(onContext);
}
#end
public static function removeNotifyOnContext(name: String, onContext: Graphics->Int->Int->Void) {
if (contextEvents != null) {
var ar = contextEvents.get(name);
if (ar != null) ar.remove(onContext);
}
}
#if rp_decals
public function drawDecals(context: String) {

View File

@ -14,6 +14,7 @@ import iron.object.SpeakerObject;
import iron.object.DecalObject;
import iron.object.ProbeObject;
import iron.object.Tilesheet;
import iron.object.CurveObject;
import iron.data.CameraData;
import iron.data.MeshData;
import iron.data.LightData;
@ -64,6 +65,7 @@ class Scene {
#end
public var empties: Array<Object>;
public var animations: Array<Animation>;
public var tilesheets: Array<Tilesheet>;
#if lnx_skin
public var armatures: Array<Armature>;
#end
@ -71,6 +73,13 @@ class Scene {
public var embedded: Map<String, kha.Image>;
#if (rp_renderer == "Deferred")
public static inline var MAX_MATERIALS = 16;
public static inline var FLOATS_PER_MATERIAL_PARAM = 32; // 8 vec4s per material
public var materialParamsBuffer: kha.arrays.Float32Array;
public var materialParamsDirty: Bool = true;
#end
public var ready: Bool; // Async in progress
public var traitInits: Array<Void->Void> = [];
@ -103,10 +112,14 @@ class Scene {
#end
empties = [];
animations = [];
tilesheets = [];
#if lnx_skin
armatures = [];
#end
embedded = new Map();
#if (rp_renderer == "Deferred")
materialParamsBuffer = new kha.arrays.Float32Array(MAX_MATERIALS * FLOATS_PER_MATERIAL_PARAM);
#end
root = new Object();
root.name = "Root";
traitInits = [];
@ -125,6 +138,14 @@ class Scene {
// Startup scene
active.addScene(format.name, null, function(sceneObject: Object) {
if (format.properties != null) {
sceneObject.properties = new Map();
for (p in format.properties) {
sceneObject.properties.set(p.name, cleanValue(p.value));
}
}
// Create traits bottom-up (children first, then parents)
createTraitsBottomUp(sceneObject);
@ -204,6 +225,89 @@ class Scene {
root.remove();
}
#if (rp_renderer == "Deferred")
public function markMaterialParamsDirty() {
materialParamsDirty = true;
}
public function updateMaterialParams() {
if (!materialParamsDirty) return;
materialParamsDirty = false;
var buf = materialParamsBuffer;
for (m in meshes) {
if (m.materials == null) continue;
for (mat in m.materials) {
if (mat == null) continue;
if (mat.contexts == null) continue;
var slot = -1;
var bc = null;
for (ctx in mat.contexts) {
if (ctx == null || ctx.raw == null) continue;
if (ctx.raw.name == "mesh" && ctx.raw.bind_constants != null) {
bc = ctx.raw.bind_constants;
for (c in bc) {
if (c != null && c.name == "materialID" && c.intValue != null) {
slot = c.intValue;
}
}
break;
}
}
if (slot < 0 || slot >= MAX_MATERIALS) continue;
var base = slot * FLOATS_PER_MATERIAL_PARAM;
if (base + FLOATS_PER_MATERIAL_PARAM - 1 >= buf.length) continue;
if (bc == null) continue;
for (i in 0...FLOATS_PER_MATERIAL_PARAM) buf[base + i] = 0.0;
buf[base + 6] = 1.5; // coatIOR
buf[base + 12] = 1.45; // ior
buf[base + 13] = 1.0; // thinWall
for (c in bc) {
if (c == null || c.name == null || c.floatValue == null) continue;
switch (c.name) {
// matp0: vec4(anisotropy, anisoRot, sheen, sheenRough)
case "anisotropy": buf[base + 0] = c.floatValue;
case "anisoRot": buf[base + 1] = c.floatValue;
case "sheen": buf[base + 2] = c.floatValue;
case "sheenRough": buf[base + 3] = c.floatValue;
// matp1: vec4(clearcoat, clearcoatRough, coatIOR, coatTintR)
case "clearcoat": buf[base + 4] = c.floatValue;
case "clearcoatRough": buf[base + 5] = c.floatValue;
case "coatIOR": buf[base + 6] = c.floatValue;
case "coatTintR": buf[base + 7] = c.floatValue;
// matp2: vec4(coatTintG, coatTintB, transmission, transRough)
case "coatTintG": buf[base + 8] = c.floatValue;
case "coatTintB": buf[base + 9] = c.floatValue;
case "transmission": buf[base + 10] = c.floatValue;
case "transmissionRough": buf[base + 11] = c.floatValue;
// matp3: vec4(ior, thinWall, subsurface, subsurfaceAnisotropy)
case "ior": buf[base + 12] = c.floatValue;
case "thinWall": buf[base + 13] = c.floatValue;
case "subsurface": buf[base + 14] = c.floatValue;
case "subsurfaceAnisotropy": buf[base + 15] = c.floatValue;
// matp4: vec4(subsurfaceRadiusR, subsurfaceRadiusG, subsurfaceRadiusB, subsurfaceColorR)
case "subsurfaceRadiusR": buf[base + 16] = c.floatValue;
case "subsurfaceRadiusG": buf[base + 17] = c.floatValue;
case "subsurfaceRadiusB": buf[base + 18] = c.floatValue;
case "subsurfaceColorR": buf[base + 19] = c.floatValue;
// matp5: vec4(subsurfaceColorG, subsurfaceColorB, sheenTintR, sheenTintG)
case "subsurfaceColorG": buf[base + 20] = c.floatValue;
case "subsurfaceColorB": buf[base + 21] = c.floatValue;
case "sheenTintR": buf[base + 22] = c.floatValue;
case "sheenTintG": buf[base + 23] = c.floatValue;
// matp6: vec4(sheenTintB, specularTintR, specularTintG, specularTintB)
case "sheenTintB": buf[base + 24] = c.floatValue;
case "specularTintR": buf[base + 25] = c.floatValue;
case "specularTintG": buf[base + 26] = c.floatValue;
case "specularTintB": buf[base + 27] = c.floatValue;
// matp7: vec4(subsurfaceScale, 0, 0, 0)
case "subsurfaceScale": buf[base + 28] = c.floatValue;
}
}
}
}
}
#end
static var framePassed = true;
public static function setActive(sceneName: String, done: Object->Void = null) {
if (!framePassed) return;
@ -249,6 +353,7 @@ class Scene {
if (terrainStream != null) terrainStream.update(active.camera);
#end
for (anim in animations) anim.update(Time.delta);
for (tilesheet in tilesheets) tilesheet.update();
for (e in empties) if (e != null && e.parent != null) e.transform.update();
}
@ -348,9 +453,23 @@ class Scene {
return g;
}
public function removeFromGroups(object: Object) {
if (groups == null) return;
for (name in groups.keys()) getGroup(name).remove(object);
}
public function addMeshObject(data: MeshData, materials: Vector<MaterialData>, parent: Object = null): MeshObject {
var object = new MeshObject(data, materials);
parent != null ? object.setParent(parent) : object.setParent(root);
#if (rp_renderer == "Deferred")
markMaterialParamsDirty();
#end
return object;
}
public function addCurveObject(data: TCurveData, parent: Object = null): CurveObject {
var object = new CurveObject(data);
parent != null ? object.setParent(parent) : object.setParent(root);
return object;
}
@ -613,6 +732,10 @@ class Scene {
else done(ro);
});
}
else if (o.type == "curve_object") {
var object = addCurveObject(Data.getCurveRawByName(format.curve_datas, o.data_ref), parent);
returnObject(object, o, done);
}
else done(null);
}
@ -793,11 +916,13 @@ class Scene {
}
else { #end // lnx_skin
#if lnx_stream
streamMeshObject(
if ((o.particle_refs == null || o.particle_refs.length == 0) && o.is_particle == null && parent != null)
streamMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
else
returnMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
#else
returnMeshObject(
returnMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
#end
object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
#if lnx_skin
}
#end
@ -872,20 +997,24 @@ class Scene {
#end
if (o.properties != null) {
object.properties = new Map();
for (p in o.properties) object.properties.set(p.name, p.value);
for (p in o.properties) {
object.properties.set(p.name, cleanValue(p.value));
}
}
if (o.vertex_groups != null) {
object.vertex_groups = new Map();
for (p in o.vertex_groups){
var verts = [];
for(i in 0...Std.int(p.value.length/3)){
var x = Std.parseFloat(p.value[i*3]);
var y = Std.parseFloat(p.value[i*3+1]);
var z = Std.parseFloat(p.value[i*3+2]);
verts.push(new iron.math.Vec4(x, y, z, 1));
cast(object, MeshObject).vertexGroups = new Map();
for (p in o.vertex_groups) {
var verts:Array<iron.math.Vec4> = [];
var data:kha.arrays.Float32Array = cast p.value;
if (data != null) {
for (i in 0...Std.int(data.length / 3)) {
verts.push(new iron.math.Vec4(data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 1.0));
}
}
object.vertex_groups.set(p.name, verts);
cast(object, MeshObject).vertexGroups.set(p.name, verts);
}
}
@ -1049,4 +1178,16 @@ class Scene {
public function notifyOnRemove(f: Void->Void) {
traitRemoves.push(f);
}
static function cleanValue(val: Dynamic): Dynamic {
if (val == null) return null;
if (untyped val.buffer != null) {
var data: kha.arrays.Float32Array = cast val;
return [for (i in 0...data.length) data[i]];
}
if (Std.isOfType(val, Array)) {
return [for (item in (cast val: Array<Dynamic>)) cleanValue(item)];
}
return val;
}
}

View File

@ -125,10 +125,10 @@ class Trait {
/**
Add 2D render handler.
**/
public function notifyOnRender2D(f: kha.graphics2.Graphics->Void) {
public function notifyOnRender2D(f: kha.graphics2.Graphics->Void, index: Int = -1) {
if (_render2D == null) _render2D = [];
_render2D.push(f);
App.notifyOnRender2D(f);
App.notifyOnRender2D(f, index);
}
/**

View File

@ -311,7 +311,7 @@ class Data {
loadingSceneRaws.set(file, [done]);
// If no extension specified, set to .arm
// If no extension specified, set to .lnx
var compressed = file.endsWith(".lz4");
var isJson = file.endsWith(".json");
var ext = (compressed || isJson || file.endsWith(".lnx")) ? "" : ".lnx";
@ -405,6 +405,13 @@ class Data {
}
#end
public static function getCurveRawByName(datas: Array<TCurveData>, name: String): TCurveData {
if (datas == null || datas.length == 0) return null;
if (name == "") return datas[0];
for (dat in datas) if (dat.name == name) return dat;
return null;
}
// Raw assets
public static function getBlob(file: String, done: kha.Blob->Void) {
var cached = cachedBlobs.get(file); // Is already cached

View File

@ -40,6 +40,8 @@ class Geometry {
public var instancedVB: VertexBuffer = null;
public var instanced = false;
public var instanceCount = 0;
public var instanceElements: Array<{name: String, data: String}> = [];
public var instanceStride: Int = 0;
public var positions: TVertexArray;
public var normals: TVertexArray;
@ -130,11 +132,39 @@ class Geometry {
structure.add("iscl", kha.graphics4.VertexData.Float3);
}
if (instanceElements != null && instanceElements.length > 0) {
for (elem in instanceElements) {
if (StringTools.startsWith(elem.name, "i") && elem.name != "ipos" && elem.name != "irot" && elem.name != "iscl") {
var vdata = VertexData.Float1;
var dataStr: String = Reflect.field(elem, "data");
switch (dataStr) {
case "float1": vdata = VertexData.Float1;
case "float2": vdata = VertexData.Float2;
case "float3": vdata = VertexData.Float3;
case "float4": vdata = VertexData.Float4;
}
structure.add(elem.name, vdata);
}
}
}
this.instanceStride = Std.int(structure.byteSize() / 4);
instanceCount = Std.int(data.length / Std.int(structure.byteSize() / 4));
instancedVB = new VertexBuffer(instanceCount, structure, usage, 1);
var vertices = instancedVB.lock();
for (i in 0...Std.int(vertices.byteLength / 4)) vertices.setFloat32(i * 4, data[i]);
instancedVB.unlock();
}
public function updateInstanced(data: Float32Array) {
if (instancedVB == null) return;
var vertices = instancedVB.lock();
for (i in 0...Std.int(vertices.byteLength / 4)) {
vertices.setFloat32(i * 4, data[i]);
}
instancedVB.unlock();
}
public function copyVertices(vertices: ByteArray, offset = 0, fakeUVs = false) {

View File

@ -40,6 +40,8 @@ typedef TSceneFormat = {
@:optional public var irradiance: Float32Array; // Blob with spherical harmonics, bands 0,1,2
@:optional public var terrain_datas: Array<TTerrainData>;
@:optional public var terrain_ref: String;
@:optional public var properties: Array<TProperty>;
@:optional public var curve_datas: Array<TCurveData>;
}
#if js
@ -432,6 +434,7 @@ typedef TParticleData = {
// Velocity
public var object_align_factor: Float32Array;
public var factor_random: FastFloat;
public var normal_factor: FastFloat;
// Rotation
public var use_rotations: Bool;
public var rotation_mode: Int; // 0 - None, 1 - Normal, 2 - Normal-Tangent, 3 - Velocity/Hair, 4 - Global X, 5 - Global Y, 6 - Global Z, 7 - Object X, 8 - Object Y, 9 - Object Z
@ -525,7 +528,7 @@ typedef TVertex_groups = {
@:structInit class TVertex_groups {
#end
public var name: String;
public var value: Dynamic;
public var value: Float32Array;
}
#if js
@ -564,6 +567,21 @@ typedef TConstraint = {
@:optional public var invert_z: Null<Bool>;
@:optional public var use_offset: Null<Bool>;
@:optional public var influence: Null<FastFloat>;
@:optional public var use_min_x: Null<Bool>;
@:optional public var use_max_x: Null<Bool>;
@:optional public var use_min_y: Null<Bool>;
@:optional public var use_max_y: Null<Bool>;
@:optional public var use_min_z: Null<Bool>;
@:optional public var use_max_z: Null<Bool>;
@:optional public var use_limit_x: Null<Bool>;
@:optional public var use_limit_y: Null<Bool>;
@:optional public var use_limit_z: Null<Bool>;
@:optional public var min_x: Null<FastFloat>;
@:optional public var max_x: Null<FastFloat>;
@:optional public var min_y: Null<FastFloat>;
@:optional public var max_y: Null<FastFloat>;
@:optional public var min_z: Null<FastFloat>;
@:optional public var max_z: Null<FastFloat>;
}
#if js
@ -622,3 +640,48 @@ typedef TTrack = {
public var values: Float32Array; // sampled - full matrix transforms, non-sampled - values
@:optional public var ref_values: Array<Array<String>>; // ref values
}
#if js
typedef TBezierPoint = {
#else
@:structInit class TBezierPoint {
#end
public var co: Float32Array;
public var handle_left: Float32Array;
public var handle_right: Float32Array;
}
#if js
typedef TSpline = {
#else
@:structInit class TSpline {
#end
public var closed: Bool;
public var resolution: Int;
public var points: Array<TBezierPoint>;
public var material_index: Int;
}
#if js
typedef TShapeKey = {
#else
@:structInit class TShapeKey {
#end
public var name: String;
public var value: Float;
public var points: Array<TBezierPoint>;
}
#if js
typedef TCurveData = {
#else
@:structInit class TCurveData {
#end
public var name: String;
public var object: String;
public var splines: Array<TSpline>;
public var strength: Float;
public var color: Float32Array;
@:optional public var material_refs: Array<String>;
@:optional public var shape_keys: Array<TShapeKey>;
}

View File

@ -79,6 +79,8 @@ class ShaderContext {
var structure: VertexStructure;
var instancingType = 0;
var instanceElements: Array<{name: String, data: String}> = [];
var instanceStride: Int = 0;
public function new(raw: TShaderContext, done: ShaderContext->Void, overrideContext: TShaderOverride = null) {
this.raw = raw;
@ -108,6 +110,11 @@ class ShaderContext {
if (instancingType == 3 || instancingType == 4) {
instStruct.add("iscl", VertexData.Float3);
}
for (e in instanceElements)
instStruct.add(e.name, parseData(e.data));
this.instanceStride = Std.int(instStruct.byteSize() / 4);
instStruct.instanced = true;
pipeState.inputLayout = [structure, instStruct];
}
@ -268,10 +275,12 @@ class ShaderContext {
if (Reflect.field(elem, "name") == "ipos") { ipos = true; continue; }
if (Reflect.field(elem, "name") == "irot") { irot = true; continue; }
if (Reflect.field(elem, "name") == "iscl") { iscl = true; continue; }
if (Reflect.field(elem, "name").startsWith("i")) { instanceElements.push(elem); continue; }
#else
if (elem.name == "ipos") { ipos = true; continue; }
if (elem.name == "irot") { irot = true; continue; }
if (elem.name == "iscl") { iscl = true; continue; }
if (elem.name.startsWith("i")) { instanceElements.push(elem); continue; }
#end
structure.add(elem.name, parseData(elem.data));
}

View File

@ -0,0 +1,357 @@
package iron.format.gif;
import haxe.io.Bytes;
/**
* Gif data.
*/
typedef Data =
{
/**
* Gif version. There is only 2 Gif version exists. 87a and 89a.
* 87a have less features and does not support any extensions.
* Unknown version is adviced to be interpreted as newest (89a) official version.
*/
var version:Version;
/**
* Information about logical screen of Gif that provides basic information about Gif.
*/
var logicalScreenDescriptor:LogicalScreenDescriptor;
/**
* Global color table used for Gif. Present only if Logical Screen Descriptor contained global color table flag.
* Note that this color table not always present since frames can contain local color tables that overrides global color table.
*/
@:optional var globalColorTable:Null<ColorTable>;
/**
* List of Gif data blocks.
*/
var blocks:List<Block>;
}
/**
* Gif data block. Custom blocks are not supported.
*/
enum Block
{
/**
* Gif frame block.
* Note that this block does not contain link to graphic control extension of Frame even if it is present. GraphicControl extension Block commonly present right before frame Block.
*/
BFrame(frame:Frame);
/**
* Additional extension block. This Block does not supported in 87a Gif specification version.
*/
BExtension(extension:Extension);
/**
* End of File block. Represents end of Gif data.
*/
BEOF;
}
/**
* Extension block contains additional data about Gif image. This block does not supported by 87a version.
*/
enum Extension
{
/**
* Graphic Control extension gives additional control over next frame, like frame delay, disposal method, alpha channel and other information.
*/
EGraphicControl(gce:GraphicControlExtension);
/**
* Commentary extension. Not show up as any visual, just a text in file.
*/
EComment(text:String);
/**
* Text extension. Must work as text rendering on the image, but ignored by all major Gif decoders.
*/
EText(pte:PlainTextExtension);
/**
* Application extension allow to insert additional application data into Gif. Mostly used app extension is NETSCAPE2.0 looping extension, used to set up amount of loops in frame.
*/
EApplicationExtension(ext:ApplicationExtension);
/**
* Unknown extension.
*/
EUnknown(id:Int, data:Bytes);
}
/**
* Application extension. Mostly used only for one reason - setting up loops count. There is exist other app extensions but they are really rare.
*/
enum ApplicationExtension
{
/**
* NETSCAPE2.0 looping extension. Contains only amount of animation repeats.
* Note that there is two NETSCAPE2.0 app extensions for Gif format and the type of extension is stored in first byte of data. Looping extension have ID 1.
*/
AENetscapeLooping(loops:Int);
/**
* Unknown or unsupported app extension.
*/
AEUnknown(name:String, version:String, data:Bytes);
}
/**
* Typical color table for Gif image.
* Can contain 2, 4, 8, 16, 32, 64, 128 or 256 colors.
* Data stored in RGB format. Information about alpha channel provided by Graohic Control Extension.
*/
typedef ColorTable = Bytes;
/**
* Single frame of the image.
* Actually it's a merge of 3 consequent blocks:
* 1. Image Descriptor.
* Contains frame informations like position, size, existing of local color table and interlaced flag.
* 2. [Local color table].
* Only present if Image Descriptor contains local color table flag. Overrides global color table.
* 3. Pixel data blocks.
* LZW compressed pixel data.
*/
typedef Frame =
{
/**
* X position of image on the Logical Screen
*/
var x:Int;
/**
* Y position of image on the Logical Screen
*/
var y:Int;
/**
* Width of image in pixels
*/
var width:Int;
/**
* Height of image in pixels
*/
var height:Int;
/**
* Is this image uses local color table?
*/
var localColorTable:Bool;
/**
* Is this image written in interlace mode?
* Note: The pixel data already deinterlaced and this flag presented only for information purpose (and for Writer when there is one).
*/
var interlaced:Bool;
/**
* Is local color table sorted in order of decreasing priority?
*/
var sorted:Bool;
/**
* Size of local color table
*/
var localColorTableSize:Int;
/**
* Pixel data of frame. Stored as Indexed colors, 1 byte per pixel.
*/
var pixels:Bytes;
/**
* Local color table used by frame. Stored as 3-byte RGB colors. If value is null, must be used global color table.
*/
var colorTable:ColorTable;
}
/**
* Graphic Control Extension block, used for setting up disposal method, transparency, delay and user input.
*/
typedef GraphicControlExtension =
{
/**
* Disposal method of frame.
*/
var disposalMethod:DisposalMethod;
/**
* Is image must wait for user input, before dispose?
* This flag may be used by user-defined program but absolutely ignored by any Gif players.
*/
var userInput:Bool;
/**
* Is image have transparency?
*/
var hasTransparentColor:Bool;
/**
* Delay, before next image appears. Delay is in centiseconds (1 centisecond = 1/100 seconds).
* Note: Some players (like FastStone) cut fraction of elapsed time when progressing to next frame which results in small timing error.
* Recommended to use `time -= delay` instead of `time = 0`.
*/
var delay:Int;
/**
* Index in color table that used as transparent.
*/
var transparentIndex:Int;
}
/**
* Extension for rendering text on Gif logical screen. It does not supported by major Gif decoders.
* Font and text size decision is left to decoder. (recommended to decide based on grid/cell size)
* Text must be rendered with one character at cell.
* It's recommended to replace any characters less than 0x20 and greater than 0xf7 to be rendered as Space (0x20)
*/
typedef PlainTextExtension =
{
/**
* X position of text grid on Logical Screen.
*/
var textGridX:Int;
/**
* Y position of text grid on Logical Screen.
*/
var textGridY:Int;
/**
* Width of text grid in pixels.
*/
var textGridWidth:Int;
/**
* Height of text grid in pixels.
*/
var textGridHeight:Int;
/**
* Width of character cell in text grid.
*/
var charCellWidth:Int;
/**
* Height of character cell in text grid.
*/
var charCellHeight:Int;
/**
* Foreground/character color index.
*/
var textForegroundColorIndex:Int;
/**
* Background color index.
*/
var textBackgroundColorIndex:Int;
/**
* Text to render.
*/
var text:String;
}
/**
* Logical screen descriptor of GIF file.
* Contains very basic information about Gif.
*/
typedef LogicalScreenDescriptor =
{
/**
* Width of GIF image in pixels
*/
var width:Int;
/**
* Height of GIF image in pixels
*/
var height:Int;
/**
* Is this file uses global color table?
*/
var hasGlobalColorTable:Bool;
/**
* Specification:
* Number of bits per primary color available
to the original image, minus 1. This value represents the size of
the entire palette from which the colors in the graphic were
selected, not the number of colors actually used in the graphic.
For example, if the value in this field is 3, then the palette of
the original image had 4 bits per primary color available to create
the image. This value should be set to indicate the richness of
the original palette, even if not every color from the whole
palette is available on the source machine.
*/
var colorResolution:Int;
/**
* Specification:
* Indicates whether the Global Color Table is sorted.
If the flag is set, the Global Color Table is sorted, in order of
decreasing importance. Typically, the order would be decreasing
frequency, with most frequent color first. This assists a decoder,
with fewer available colors, in choosing the best subset of colors;
the decoder may use an initial segment of the table to render the
graphic.
*/
var sorted:Bool;
/**
* Size of global color table.
*/
var globalColorTableSize:Int;
/**
* Background color index in global color table
*/
var backgroundColorIndex:Int;
/**
* Factor used to compute an approximation of the aspect ratio of the pixel in the original image.
*/
var pixelAspectRatio:Float;
}
/**
* Version of Gif file.
* The only 2 official versions is GIF87a and GIF89a.
*/
enum Version
{
/**
* First version of Gif file format from May 1987.
*
* Note: The checking of unsupported blocks disabled by default to save some time. To enable supported blocks check set `yagp_strict_version_check` debug variable.
*/
GIF87a;
/**
* Second and actual version of Gif file format from July 1989.
*/
GIF89a;
/**
* Unknown version of Gif file.
*/
Unknown(version:String);
}
/**
* Disposal method of GIF frame.
*/
enum DisposalMethod
{
/**
* The disposal method is unspecified. Action on demand of viewer.
*
* Mostly interpreted as NO_ACTION.
*/
UNSPECIFIED;
/**
* No action required.
*/
NO_ACTION;
/**
* Fill frame rectangle with background color.
*
* Usage note:
* Most renderers clears to transparency instead of filling background color, when frame's transparent color index not equals to background color index.
*/
FILL_BACKGROUND;
/**
* Render previous state of gif as it before rendering disposing frame.
*/
RENDER_PREVIOUS;
/**
* Reserved disposal methods.
*/
UNDEFINED(index:Int);
}

View File

@ -0,0 +1,359 @@
package iron.format.gif;
/*
* No copyright asserted on the source code of this class. May be used
* for any purpose.
*
* Original code by Kevin Weiner, FM Software.
* Adapted by Thomas Hourdel (https://github.com/Chman/Moments)
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
*/
import haxe.io.UInt8Array;
import haxe.io.BytesOutput;
@:enum abstract GifRepeat(Int)
from Int to Int {
var None = 0;
var Infinite = -1;
}
@:enum abstract GifQuality(Int)
from Int to Int {
var Best = 1;
var VeryHigh = 10;
var QuiteHigh = 20;
var High = 35;
var Mid = 50;
var Low = 65;
var QuiteLow = 80;
var VeryLow = 90;
var Worst = 100;
}
class GifEncoder {
var width: Int;
var height: Int;
var framerate: Float = 24; // used if frame.delay < 0
var repeat: Int = -1; // -1: infinite, 0: none, >0: repeat count
var colorDepth: Int = 8; // Number of bit planes
var paletteSize: Int = 7; // Color table size (bits-1)
var sampleInterval: Int = 10; // Default sample interval for quantizer
//caches
var pixels: UInt8Array;
var indexedPixels: UInt8Array; // Converted frame indexed to palette
var colorTab: UInt8Array; // RGB palette
var usedEntry: Array<Bool>; // Active palette entries
//
var nq: NeuQuant;
var lzwEncoder: LzwEncoder;
//internal
var started: Bool = false;
var first_frame: Bool = true;
//:todo: error handling could be better - but throw inside of another thread on cpp is too quiet
/** Allows a custom print handler for error messages.
Defaults to Sys.println on sys targets, and trace otherwise. */
public var print: Dynamic->Void;
// Public API
/** Construct a gif encoder with options:
frame width/height:
Default is 0, required
framerate:
This is used if an added frame has a delay that is negative.
repeat:
Default is 0 (no repeat); -1 means play indefinitely.
Use GifRepeat for clarity
quality:
Sets quality of color quantization (conversion of images to
the maximum 256 colors allowed by the GIF specification). Lower values (minimum = 1)
produce better colors, but slow processing significantly. Higher values will speed
up the quantization pass at the cost of lower image quality (maximum = 100). */
public function new(
_frame_width:Int,
_frame_height:Int,
_framerate:Float,
_repeat:Int = GifRepeat.Infinite,
_quality:Int = 10
) {
#if sys
print = Sys.println;
#else
print = function(v) { trace(v); }
#end
width = _frame_width;
height = _frame_height;
framerate = _framerate;
repeat = _repeat;
sampleInterval = Std.int(clamp(_quality, 1, 100));
usedEntry = [for (i in 0...256) false];
pixels = new UInt8Array(width * height * 3);
indexedPixels = new UInt8Array(width * height);
nq = new NeuQuant();
lzwEncoder = new LzwEncoder();
} //new
public function start(output:BytesOutput) : Void {
if(output == null) {
print("gif: start() output must not be null.");
return;
}
output.writeString("GIF89a");
write_LSD(output);
started = true;
} //start
public function add(output:BytesOutput, frame:GifFrame) : Void {
if(output == null) {
print("gif: add() output must not be null.");
return;
}
if(!started) {
print("gif: add() requires start to be called before adding frames.");
return;
}
var pixels = get_pixels(frame);
analyze(pixels);
if(first_frame) {
write_palette(output);
if(repeat != GifRepeat.None) {
write_NetscapeExt(output);
}
first_frame = false;
} //first_frame
var delay = if(frame.delay < 0) {
1.0/framerate;
} else {
frame.delay;
}
write_GraphicControlExt(output, delay);
write_image_desc(output, first_frame);
if(!first_frame) {
write_palette(output);
}
write_pixels(output);
} //add
public function commit(output:BytesOutput) : Void {
if(output == null) {
print("gif: commit() output must be not null.");
return;
}
if(!started) {
print("gif: commit() called without start() being called first.");
return;
}
output.writeByte(0x3b); // Gif trailer
output.flush();
output.close();
started = false;
first_frame = true;
} //commit
//helpers
function get_pixels(frame:GifFrame):UInt8Array {
//if not flipped we can use the data as is
if (!frame.flippedY) return frame.data;
//otherwise flip it, and return the cached array
var stride = width * 3;
for(y in 0...height) {
var begin = (height - 1 - y) * stride;
pixels.view.buffer.blit(y * stride, frame.data.view.buffer, begin, stride);
}
return pixels;
} //get_pixels
function analyze(pixels:UInt8Array) {
// Create reduced palette
nq.reset(pixels, pixels.length, sampleInterval);
colorTab = nq.process();
// Map image pixels to new palette
var k:Int = 0;
for (i in 0...(width * height)) {
var r = pixels[k++] & 0xff;
var g = pixels[k++] & 0xff;
var b = pixels[k++] & 0xff;
var index = nq.map(r, g,b);
usedEntry[index] = true;
indexedPixels[i] = index;
}
} //analyze
//writers
//
/** Writes Logical Screen Descriptor. */
function write_LSD(output:BytesOutput) {
//
// Logical screen size
output.writeInt16(width);
output.writeInt16(height);
// Packed fields
output.writeByte(0x80 | // 1 : global color table flag = 1 (gct used)
0x70 | // 2-4 : color resolution = 7
0x00 | // 5 : gct sort flag = 0
paletteSize); // 6-8 : gct size
output.writeByte(0); // Background color index
output.writeByte(0); // Pixel aspect ratio - assume 1:1
} //write_LSD
/** Writes Netscape application extension to define repeat count. */
function write_NetscapeExt(output:BytesOutput):Void {
var repeats = repeat;
if(repeats == GifRepeat.Infinite || repeats < 0) repeats = 0;
if(repeats == GifRepeat.None) repeats = -1;
output.writeByte(0x21); // Extension introducer
output.writeByte(0xff); // App extension label
output.writeByte(11); // Block size
output.writeString("NETSCAPE" + "2.0"); // App id + auth code
output.writeByte(3); // Sub-block size
output.writeByte(1); // Loop sub-block id
output.writeInt16(repeats); // Loop count (extra iterations, 0=repeat forever)
output.writeByte(0); // Block terminator
} //write_NetscapeExt
/** Write color table. */
function write_palette(output:BytesOutput):Void {
output.write(colorTab.view.buffer);
var n:Int = (3 * 256) - colorTab.length;
for (i in 0...n) {
output.writeByte(0);
}
} //write_palette
/** Encodes and writes pixel data. */
function write_pixels(output:BytesOutput):Void {
lzwEncoder.reset(indexedPixels, colorDepth);
lzwEncoder.encode(output);
} //write_pixels
/** Writes Image Descriptor. */
function write_image_desc(output:BytesOutput, first:Bool):Void {
output.writeByte(0x2c); // Image separator
output.writeInt16(0); // Image position x = 0
output.writeInt16(0); // Image position y = 0
output.writeInt16(width); // Image width
output.writeInt16(height); // Image height
//Write LCT, or GCT
if(first) {
output.writeByte(0); // No LCT - GCT is used for first (or only) frame
} else {
output.writeByte(0x80 | // 1 local color table 1=yes
0 | // 2 interlace - 0=no
0 | // 3 sorted - 0=no
0 | // 4-5 reserved
paletteSize); // 6-8 size of color table
} //else
} //write_image_desc
/** Writes Graphic Control Extension. Delay is in seconds, floored and converted to 1/100 of a second */
function write_GraphicControlExt(output:BytesOutput, delay:Float):Void {
output.writeByte(0x21); // Extension introducer
output.writeByte(0xf9); // GCE label
output.writeByte(4); // data block size
// Packed fields
output.writeByte(0 | // 1:3 reserved
0 | // 4:6 disposal
0 | // 7 user input - 0 = none
0 ); // 8 transparency flag
//convert to 1/100 sec
var delay_val = Math.floor(delay * 100);
output.writeInt16(delay_val); // Delay x 1/100 sec
output.writeByte(0); // Transparent color index
output.writeByte(0); // Block terminator
} //write_GraphicControlExt
/** Clamp a value between a and b and return the clamped version */
static inline public function clamp(value:Float, a:Float, b:Float):Float
{
return ( value < a ) ? a : ( ( value > b ) ? b : value );
}
} //GifEncoder
typedef GifFrame = {
/** Delay of the frame in seconds. This value gets floored
when encoded due to gif format requirements. If this value is negative,
the default encoder frame rate will be used. */
var delay: Float;
/** Whether or not this frame should be flipped on the Y axis */
var flippedY: Bool;
/** Pixels data in unsigned bytes, rgb format */
var data: UInt8Array;
}

View File

@ -0,0 +1,350 @@
 package iron.format.gif;
/*
* No copyright asserted on the source code of this class. May be used
* for any purpose, however, refer to the Unisys LZW patent for restrictions
* on use of the associated LZWEncoder class :
*
* The Unisys patent expired on 20 June 2003 in the USA, in Europe it expired
* on 18 June 2004, in Japan the patent expired on 20 June 2004 and in Canada
* it expired on 7 July 2004. The U.S. IBM patent expired 11 August 2006, The
* Software Freedom Law Center says that after 1 October 2006, there will be
* no significant patent claims interfering with employment of the GIF format.
*
* Original code by Kevin Weiner, FM Software.
* Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
*
*/
import haxe.io.Int32Array;
import haxe.io.UInt8Array;
class LzwEncoder {
static var EOF(default, never):Int = -1;
var pixAry:UInt8Array;
var initCodeSize:Int;
var curPixel:Int;
// GIFCOMPR.C - GIF Image compression routines
//
// Lempel-Ziv compression based on 'compress'. GIF modifications by
// David Rowley (mgardi@watdcsu.waterloo.edu)
// General DEFINEs
static var BITS(default, never):Int = 12;
static var HSIZE(default, never):Int = 5003; // 80% occupancy
// GIF Image compression - modified 'compress'
//
// Based on: compress.c - File compression ala IEEE Computer, June 1984.
//
// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
// Jim McKie (decvax!mcvax!jim)
// Steve Davies (decvax!vax135!petsd!peora!srd)
// Ken Turkowski (decvax!decwrl!turtlevax!ken)
// James A. Woods (decvax!ihnp4!ames!jaw)
// Joe Orost (decvax!vax135!petsd!joe)
var n_bits:Int; // number of bits/code
var maxbits:Int = BITS; // user settable max # bits/code
var maxcode:Int; // maximum code, given n_bits
var maxmaxcode:Int = 1 << BITS; // should NEVER generate this code
var htab:Int32Array;
var codetab:Int32Array;
var hsize:Int = HSIZE; // for dynamic table sizing
var free_ent:Int = 0; // first unused entry
// block compression parameters -- after all codes are used up,
// and compression rate changes, start over.
var clear_flg:Bool = false;
// Algorithm: use open addressing double hashing (no chaining) on the
// prefix code / next character combination. We do a variant of Knuth's
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
// secondary probe. Here, the modular division first probe is gives way
// to a faster exclusive-or manipulation. Also do block compression with
// an adaptive reset, whereby the code table is cleared when the compression
// ratio decreases, but after the table fills. The variable-length output
// codes are re-sized at this point, and a special CLEAR code is generated
// for the decompressor. Late addition: construct the table according to
// file size for noticeable speed improvement on small files. Please direct
// questions about this implementation to ames!jaw.
var g_init_bits:Int;
var ClearCode:Int;
var EOFCode:Int;
// output
//
// output the given code.
// Inputs:
// code: A n_bits-bit integer. If == -1, then EOF. This assumes
// that n_bits =< wordsize - 1.
// outputs:
// outputs code to the file.
// Assumptions:
// Chars are 8 bits long.
// Algorithm:
// Maintain a BITS character long buffer (so that 8 codes will
// fit in it exactly). Use the VAX insv instruction to insert each
// code in turn. When the buffer fills up empty it and start over.
var cur_accum:Int = 0;
var cur_bits:Int = 0;
var masks:Array<Int> =
[
0x0000,
0x0001,
0x0003,
0x0007,
0x000F,
0x001F,
0x003F,
0x007F,
0x00FF,
0x01FF,
0x03FF,
0x07FF,
0x0FFF,
0x1FFF,
0x3FFF,
0x7FFF,
0xFFFF ];
// Number of characters so far in this 'packet'
var a_count:Int;
// Define the storage for the packet accumulator
var accum:UInt8Array;
//----------------------------------------------------------------------------
public function new()
{
htab = new Int32Array(HSIZE);
codetab = new Int32Array(HSIZE);
accum = new UInt8Array(256);
}
//Reset the encoder to new pixel data and default values
public function reset(pixels:UInt8Array, color_depth:Int) { //width and height used to be passed in though they were never used
pixAry = pixels;
initCodeSize = Std.int(Math.max(2, color_depth));
maxbits = BITS;
maxmaxcode = 1 << BITS;
hsize = HSIZE;
free_ent = 0;
clear_flg = false;
cur_accum = 0;
cur_bits = 0;
}
// add a character to the end of the current packet, and if it is 254
// characters, flush the packet to disk.
function add(c:UInt, out:haxe.io.Output):Void
{
accum[a_count++] = c;
if (a_count >= 254)
flush(out);
}
// Clear out the hash table
// table clear for block compress
function clearTable(out:haxe.io.Output):Void
{
resetCodeTable(hsize);
free_ent = ClearCode + 2;
clear_flg = true;
output(ClearCode, out);
}
// reset code table
function resetCodeTable(hsize:Int):Void
{
for (i in 0...hsize)
htab[i] = -1;
}
function compress(init_bits:Int, out:haxe.io.Output):Void
{
var fcode:Int;
var i:Int /* = 0 */;
var c:Int;
var ent:Int;
var disp:Int;
var hsize_reg:Int;
var hshift:Int;
// Set up the globals: g_init_bits - initial number of bits
g_init_bits = init_bits;
// Set up the necessary values
clear_flg = false;
n_bits = g_init_bits;
maxcode = maxCode(n_bits);
ClearCode = 1 << (init_bits - 1);
EOFCode = ClearCode + 1;
free_ent = ClearCode + 2;
a_count = 0; // clear packet
ent = nextPixel();
hshift = 0;
fcode = hsize;
while (fcode < 65536) {
++hshift;
fcode *= 2;
}
hshift = 8 - hshift; // set hash code range bound
hsize_reg = hsize;
resetCodeTable(hsize_reg); // clear hash table
output(ClearCode, out);
while ((c = nextPixel()) != EOF)
{
fcode = (c << maxbits) + ent;
i = (c << hshift) ^ ent; // xor hashing
if (htab[i] == fcode)
{
ent = codetab[i];
continue;
}
else if (htab[i] >= 0) // non-empty slot
{
disp = hsize_reg - i; // secondary hash (after G. Knott)
if (i == 0)
disp = 1;
do
{
if ((i -= disp) < 0)
i += hsize_reg;
if (htab[i] == fcode)
{
ent = codetab[i];
break;
}
} while (htab[i] >= 0);
if (htab[i] == fcode) continue;
}
output(ent, out);
ent = c;
if (free_ent < maxmaxcode)
{
codetab[i] = free_ent++; // code -> hashtable
htab[i] = fcode;
}
else
clearTable(out);
}
// Put out the final code.
output(ent, out);
output(EOFCode, out);
}
//----------------------------------------------------------------------------
public function encode(os:haxe.io.Output):Void
{
os.writeByte( initCodeSize ); // write "initial code size" byte
curPixel = 0;
compress(initCodeSize + 1, os); // compress and write the pixel data
os.writeByte(0); // write block terminator
}
// flush the packet to disk, and reset the accumulator
function flush(out:haxe.io.Output):Void
{
if (a_count > 0)
{
out.writeByte(a_count);
out.writeBytes(accum.view.buffer, 0, a_count);
a_count = 0;
}
}
inline function maxCode(n_bits:Int):Int
{
return (1 << n_bits) - 1;
}
//----------------------------------------------------------------------------
// Return the next pixel from the image
//----------------------------------------------------------------------------
function nextPixel():Int
{
if (curPixel == pixAry.length)
return EOF;
curPixel++;
return pixAry[curPixel - 1] & 0xff;
}
function output(code:Int, out:haxe.io.Output):Void
{
cur_accum &= masks[cur_bits];
if (cur_bits > 0)
cur_accum |= (code << cur_bits);
else
cur_accum = code;
cur_bits += n_bits;
while (cur_bits >= 8)
{
add(cur_accum & 0xff, out);
cur_accum >>= 8;
cur_bits -= 8;
}
// If the next entry is going to be too big for the code size,
// then increase it, if possible.
if (free_ent > maxcode || clear_flg)
{
if (clear_flg)
{
maxcode = maxCode(n_bits = g_init_bits);
clear_flg = false;
}
else
{
++n_bits;
if (n_bits == maxbits)
maxcode = maxmaxcode;
else
maxcode = maxCode(n_bits);
}
}
if (code == EOFCode)
{
// At EOF, write the rest of the buffer.
while (cur_bits > 0)
{
add(cur_accum & 0xff, out);
cur_accum >>= 8;
cur_bits -= 8;
}
flush(out);
}
}
}

View File

@ -0,0 +1,541 @@
package iron.format.gif;
/*
* Copyright (c) 1994 Anthony Dekker
* Ported to Java by Kevin Weiner, FM Software
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
*
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
* See "Kohonen neural networks for optimal colour quantization"
* in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
* for a discussion of the algorithm.
*
* Any party obtaining a copy of these files from the author, directly or
* indirectly, is granted, free of charge, a full and unrestricted irrevocable,
* world-wide, paid up, royalty-free, nonexclusive right and license to deal
* in this software and documentation files (the "Software"), including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons who receive
* copies from any such party to do so, with the only requirement being
* that this copyright notice remain intact.
*
*/
import haxe.io.Int32Array;
import haxe.io.UInt8Array;
class NeuQuant {
inline static var netsize : Int = 256; // Number of colours used
// Four primes near 500 - assume no image has a length so large that it is divisible by all four primes
inline static var prime1 : Int = 499;
inline static var prime2 : Int = 491;
inline static var prime3 : Int = 487;
inline static var prime4 : Int = 503;
inline static var minpicturebytes : Int = (3 * prime4); // Minimum size for input image
// Network Definitions
inline static var netbiasshift : Int = 4; // Bias for colour values
inline static var ncycles : Int = 100; // No. of learning cycles
// Defs for freq and bias
inline static var intbiasshift : Int = 16; // Bias for fractions
inline static var intbias : Int = (1 << intbiasshift);
inline static var gammashift : Int = 10; // Gamma = 1024
inline static var gamma : Int = (1 << gammashift);
inline static var betashift : Int = 10;
inline static var beta : Int = (intbias >> betashift); // Beta = 1/1024
inline static var betagamma : Int = (intbias << (gammashift - betashift));
// Defs for decreasing radius factor
inline static var initrad : Int = (netsize >> 3); // For 256 cols, radius starts
inline static var radiusbiasshift : Int = 6; // At 32.0 biased by 6 bits
inline static var radiusbias : Int = (1 << radiusbiasshift);
inline static var initradius : Int = (initrad * radiusbias); // And decreases by a
inline static var radiusdec : Int = 30; // Factor of 1/30 each cycle
// Defs for decreasing alpha factor
inline static var alphabiasshift : Int = 10; /* alpha starts at 1.0 */
inline static var initalpha : Int = (1 << alphabiasshift);
// Radbias and alpharadbias used for radpower calculation
inline static var radbiasshift : Int = 8;
inline static var radbias : Int = (1 << radbiasshift);
inline static var alpharadbshift : Int = (alphabiasshift + radbiasshift);
inline static var alpharadbias : Int = (1 << alpharadbshift);
var alphadec:Int; // Biased by 10 bits
// Types and Global Variables
var thepicture: UInt8Array; // The input image itself
var lengthcount: Int; // Lengthcount = H*W*3
var samplefac: Int; // Sampling factor 1..30
var network: Int32Array; // The network itself - [netsize][4]
var netindex: Int32Array; // For network lookup - really 256
var bias: Int32Array; // Bias array for learning
var freq: Int32Array; // Frequency array for learning
var radpower: Int32Array; // Radpower for precomputation
var colormap_map: UInt8Array; // Cached color map array
var colormap_index: Int32Array; // Cached color map index
public function new()
{
netindex = new Int32Array(256);
bias = new Int32Array(netsize);
freq = new Int32Array(netsize);
radpower = new Int32Array(initrad);
network = new Int32Array(netsize * 4);
colormap_map = new UInt8Array(3 * netsize);
colormap_index = new Int32Array(netsize);
}
// Reset network in range (0,0,0) to (255,255,255) and set parameters
public function reset(thepic:UInt8Array, len:Int, sample:Int):Void {
thepicture = thepic;
lengthcount = len;
samplefac = sample;
for (i in 0...netsize) {
network[i*4 + 0] = network[i*4 + 1] = network[i*4 + 2] = Std.int((i << (netbiasshift + 8)) / netsize);
freq[i] = Std.int(intbias / netsize); // 1 / netsize
bias[i] = 0; // allocated to zero?
}
}
public function colormap():UInt8Array
{
for(i in 0...netsize) {
colormap_index[network[i * 4 + 3]] = i;
}
var k:Int = 0;
for (i in 0...netsize)
{
var j = colormap_index[i];
colormap_map[k++] = network[j * 4];
colormap_map[k++] = network[j * 4 + 1];
colormap_map[k++] = network[j * 4 + 2];
}
return colormap_map;
}
// Insertion sort of network and building of netindex[0..255] (to do after unbias)
public function inxbuild():Void
{
var i:Int;
var j:Int;
var smallpos:Int;
var smallval:Int;
var previouscol:Int;
var startpos:Int;
previouscol = 0;
startpos = 0;
for (i in 0...netsize)
{
smallpos = i;
smallval = network[i*4 + 1]; // Index on g
// Find smallest in i..netsize-1
for (j in (i + 1)...netsize)
{
if (network[j*4 + 1] < smallval)
{
smallpos = j;
smallval = network[j*4 + 1]; // Index on g
}
}
// Swap p (i) and q (smallpos) entries
if (i != smallpos)
{
j = network[smallpos*4 + 0];
network[smallpos*4 + 0] = network[i*4 + 0];
network[i*4 + 0] = j;
j = network[smallpos*4 + 1];
network[smallpos*4 + 1] = network[i*4 + 1];
network[i*4 + 1] = j;
j = network[smallpos*4 + 2];
network[smallpos*4 + 2] = network[i*4 + 2];
network[i*4 + 2] = j;
j = network[smallpos*4 + 3];
network[smallpos*4 + 3] = network[i*4 + 3];
network[i*4 + 3] = j;
}
// Smallval entry is now in position i
if (smallval != previouscol)
{
netindex[previouscol] = (startpos + i) >> 1;
for (j in (previouscol + 1)...smallval)
netindex[j] = i;
previouscol = smallval;
startpos = i;
}
}
var maxnetpos = netsize - 1;
netindex[previouscol] = (startpos + maxnetpos) >> 1;
for (j in (previouscol + 1)...256)
netindex[j] = maxnetpos;
}
// Main learning Loop
public function learn():Void
{
var i:Int;
var j:Int;
var b:Int;
var g:Int;
var r:Int;
var radius:Int;
var rad:Int;
var alpha:Int;
var step:Int;
var delta:Int;
var samplepixels:Int;
var p:UInt8Array;
var pix:Int;
var lim:Int;
if (lengthcount < minpicturebytes)
samplefac = 1;
alphadec = 30 + Std.int((samplefac - 1) / 3);
p = thepicture;
pix = 0;
lim = lengthcount;
samplepixels = Std.int(lengthcount / (3 * samplefac));
delta = Std.int(samplepixels / ncycles);
alpha = initalpha;
radius = initradius;
rad = radius >> radiusbiasshift;
if (rad <= 1)
rad = 0;
for (i in 0...rad)
radpower[i] = Std.int(alpha * (((rad * rad - i * i) * radbias) / (rad * rad)));
if (lengthcount < minpicturebytes)
{
step = 3;
}
else if ((lengthcount % prime1) != 0)
{
step = 3 * prime1;
}
else
{
if ((lengthcount % prime2) != 0)
{
step = 3 * prime2;
}
else
{
if ((lengthcount % prime3) != 0)
step = 3 * prime3;
else
step = 3 * prime4;
}
}
i = 0;
while (i < samplepixels)
{
b = (p[pix + 0] & 0xff) << netbiasshift;
g = (p[pix + 1] & 0xff) << netbiasshift;
r = (p[pix + 2] & 0xff) << netbiasshift;
j = contest(b, g, r);
altersingle(alpha, j, b, g, r);
if (rad != 0)
alterneigh(rad, j, b, g, r); // Alter neighbours
pix += step;
if (pix >= lim)
pix -= lengthcount;
i++;
if (delta == 0)
delta = 1;
if (i % delta == 0)
{
alpha -= Std.int(alpha / alphadec);
radius -= Std.int(radius / radiusdec);
rad = radius >> radiusbiasshift;
if (rad <= 1)
rad = 0;
for (j in 0...rad)
radpower[j] = Std.int(alpha * (((rad * rad - j * j) * radbias) / (rad * rad)));
}
}
}
// Search for BGR values 0..255 (after net is unbiased) and return colour index
public function map(b:Int, g:Int, r:Int):Int
{
var i:Int;
var j:Int;
var dist:Int;
var a:Int;
var bestd:Int;
var best:Int;
bestd = 1000; // Biggest possible dist is 256*3
best = -1;
i = netindex[g]; // Index on g
j = i - 1; // Start at netindex[g] and work outwards
while ((i < netsize) || (j >= 0))
{
if (i < netsize)
{
dist = network[i*4 + 1] - g; // Inx key
if (dist >= bestd)
{
i = netsize; // Stop iter
}
else
{
if (dist < 0)
dist = -dist;
a = network[i*4 + 0] - b;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
a = network[i*4 + 2] - r;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
bestd = dist;
best = network[i*4 + 3];
}
}
i++;
}
}
if (j >= 0)
{
dist = g - network[j*4 + 1]; // Inx key - reverse dif
if (dist >= bestd)
{
j = -1; // Stop iter
}
else
{
if (dist < 0)
dist = -dist;
a = network[j*4 + 0] - b;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
a = network[j*4 + 2] - r;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
bestd = dist;
best = network[j*4 + 3];
}
}
j--;
}
}
}
return best;
}
public function process():UInt8Array
{
learn();
unbiasnet();
inxbuild();
return colormap();
}
// Unbias network to give byte values 0..255 and record position i to prepare for sort
public function unbiasnet():Void
{
for (i in 0...netsize)
{
network[i*4] >>= netbiasshift;
network[i*4 + 1] >>= netbiasshift;
network[i*4 + 2] >>= netbiasshift;
network[i*4 + 3] = i; // Record colour no
}
}
// Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
function alterneigh(rad:Int, i:Int, b:Int, g:Int, r:Int):Void
{
var j:Int;
var k:Int;
var lo:Int;
var hi:Int;
var a:Int;
var m:Int;
lo = i - rad;
if (lo < -1)
lo = -1;
hi = i + rad;
if (hi > netsize)
hi = netsize;
j = i + 1;
k = i - 1;
m = 1;
while ((j < hi) || (k > lo))
{
a = radpower[m++];
if (j < hi)
{
network[j * 4 + 0] -= Std.int((a * (network[j * 4 + 0] - b)) / alpharadbias);
network[j * 4 + 1] -= Std.int((a * (network[j * 4 + 1] - g)) / alpharadbias);
network[j * 4 + 2] -= Std.int((a * (network[j * 4 + 2] - r)) / alpharadbias);
j++;
}
if (k > lo)
{
network[k * 4 + 0] -= Std.int((a * (network[k * 4 + 0] - b)) / alpharadbias);
network[k * 4 + 1] -= Std.int((a * (network[k * 4 + 1] - g)) / alpharadbias);
network[k * 4 + 2] -= Std.int((a * (network[k * 4 + 2] - r)) / alpharadbias);
k--;
}
}
}
// Move neuron i towards biased (b,g,r) by factor alpha
function altersingle(alpha:Int, i:Int, b:Int, g:Int, r:Int):Void
{
/* Alter hit neuron */
network[i*4 + 0] -= Std.int((alpha * (network[i*4 + 0] - b)) / initalpha);
network[i*4 + 1] -= Std.int((alpha * (network[i*4 + 1] - g)) / initalpha);
network[i*4 + 2] -= Std.int((alpha * (network[i*4 + 2] - r)) / initalpha);
}
inline function make_abs(value:Int) : Int {
var tmp = value >> 31;
value ^= tmp;
value += tmp & 1;
return value;
}
// Search for biased BGR values
static inline var bestd_init = ~(1 << 31);
function contest(b:Int, g:Int, r:Int):Int
{
// Finds closest neuron (min dist) and updates freq
// Finds best neuron (min dist-bias) and returns position
// For frequently chosen neurons, freq[i] is high and bias[i] is negative
// bias[i] = gamma*((1/netsize)-freq[i])
var i:Int;
var dist:Int;
var a:Int;
var biasdist:Int;
var betafreq:Int;
var bestpos:Int;
var bestbiaspos:Int;
var bestd:Int;
var bestbiasd:Int;
bestd = bestd_init;
bestbiasd = bestd;
bestpos = -1;
bestbiaspos = bestpos;
for (i in 0...netsize)
{
var i_n = i * 4;
var b_i = i_n + 0;
var g_i = i_n + 1;
var r_i = i_n + 2;
var b_a = network[b_i];
var g_a = network[g_i];
var r_a = network[r_i];
b_a = make_abs(b_a - b);
g_a = make_abs(g_a - g);
r_a = make_abs(r_a - r);
dist = b_a + g_a + r_a;
if (dist < bestd)
{
bestd = dist;
bestpos = i;
}
biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
if (biasdist < bestbiasd)
{
bestbiasd = biasdist;
bestbiaspos = i;
}
betafreq = (freq[i] >> betashift);
freq[i] -= betafreq;
bias[i] += (betafreq << gammashift);
}
freq[bestpos] += beta;
bias[bestpos] -= betagamma;
return bestbiaspos;
}
}

View File

@ -0,0 +1,346 @@
package iron.format.gif;
import iron.format.gif.Data;
import haxe.io.Bytes;
import haxe.io.BytesOutput;
import haxe.io.Input;
/**
* ...
* @author Yanrishatum
*/
class Reader
{
private var i:Input;
public function new(i:Input)
{
this.i = i;
i.bigEndian = false;
}
public function read():Data
{
for (b in [71, 73, 70])
{
if (i.readByte() != b) throw "Invalid header";
}
var gifVer:String = i.readString(3);
var version:Version = Version.GIF89a;
switch(gifVer)
{
case "87a": version = Version.GIF87a;
case "89a": version = Version.GIF89a;
default: version = Version.Unknown(gifVer);
}
// Logical screen descriptor.
var width:Int = i.readUInt16();
var height:Int = i.readUInt16();
var packedField:Int = i.readByte();
var bgIndex:Int = i.readByte();
var pixelAspectRatio:Float = i.readByte();
if (pixelAspectRatio != 0) pixelAspectRatio = (pixelAspectRatio + 15) / 64;
else pixelAspectRatio = 1;
var lsd:LogicalScreenDescriptor =
{
width: width,
height: height,
hasGlobalColorTable: (packedField & 128) == 128,
colorResolution: (packedField & 112) >>> 4,
sorted: (packedField & 8) == 8,
globalColorTableSize: 2 << (packedField & 7),
backgroundColorIndex: bgIndex,
pixelAspectRatio: pixelAspectRatio
}
var gct:ColorTable = null;
if (lsd.hasGlobalColorTable) gct = readColorTable(lsd.globalColorTableSize);
var blocks:List<Block> = new List();
while (true)
{
var b:Block = readBlock();
blocks.add(b);
if (b == Block.BEOF) break;
}
return
{
version: version,
logicalScreenDescriptor: lsd,
globalColorTable: gct,
blocks: blocks
}
}
private function readBlock():Block
{
var blockID:Int = i.readByte();
switch(blockID)
{
case 0x2C:
// Image
return readImage();
case 0x21:
// Extension
return readExtension();
case 0x3B:
return Block.BEOF;
}
// The behaviour of taking unknown block ID is unspecified.
return Block.BEOF;
}
private function readImage():Block
{
var x:Int = i.readUInt16();
var y:Int = i.readUInt16();
var width:Int = i.readUInt16();
var height:Int = i.readUInt16();
var packed:Int = i.readByte();
var localColorTable:Bool = (packed & 128) == 128;
var interlaced:Bool = (packed & 64) == 64;
var sorted:Bool = (packed & 32) == 32;
var localColorTableSize:Int = 2 << (packed & 7);
var lct:ColorTable = null;
if (localColorTable) lct = readColorTable(localColorTableSize);
return Block.BFrame(
{
x: x,
y: y,
width: width,
height: height,
localColorTable: localColorTable,
interlaced:interlaced,
sorted:sorted,
localColorTableSize:localColorTableSize,
pixels:readPixels(width, height, interlaced),
colorTable:lct
});
}
private function readPixels(width:Int, height:Int, interlaced:Bool):Bytes
{
var input:Input = this.i;
var pixelsCount:Int = width * height;
var pixels:Bytes = Bytes.alloc(pixelsCount);
var minCodeSize:Int = input.readByte();
var blockSize:Int = input.readByte() - 1;
var bits:Int = input.readByte();
var bitsCount:Int = 8;
var clearCode:Int = 1 << minCodeSize;
var eoiCode:Int = clearCode + 1;
var codeSize:Int = minCodeSize + 1;
var codeSizeLimit:Int = 1 << codeSize;
var codeMask = codeSizeLimit - 1;
var baseDict:Array<Array<Int>> = new Array();
for (i in 0...clearCode) baseDict[i] = [i];
var dict:Array<Array<Int>> = new Array();
var dictLen:Int = clearCode + 2;
var newRecord:Array<Int>;
var i:Int = 0;
var code:Int = 0;
var last:Int;
while (i < pixelsCount)
{
last = code;
while (bitsCount < codeSize)
{
if (blockSize == 0) break;
bits |= input.readByte() << bitsCount;
bitsCount += 8;
blockSize--;
if (blockSize == 0) blockSize = input.readByte();
}
code = bits & codeMask;
bits >>= codeSize;
bitsCount -= codeSize;
if (code == clearCode)
{
dict = baseDict.copy();
dictLen = clearCode + 2;
codeSize = minCodeSize + 1;
codeSizeLimit = (1 << codeSize);
codeMask = codeSizeLimit - 1;
continue;
}
if (code == eoiCode) break;
if (code < dictLen)
{
if (last != clearCode)
{
newRecord = dict[last].copy();
newRecord.push(dict[code][0]);
dict[dictLen++] = newRecord;
}
}
else
{
if (code != dictLen) throw 'Invalid LZW code. Excepted: $dictLen, got: $code';
newRecord = dict[last].copy();
newRecord.push(newRecord[0]);
dict[dictLen++] = newRecord;
}
newRecord = dict[code];
for (item in newRecord) pixels.set(i++, item);
if (dictLen == codeSizeLimit && codeSize < 12)
{
codeSize++;
codeSizeLimit = (1 << codeSize);
codeMask = codeSizeLimit - 1;
}
}
// Just in case
while (blockSize > 0)
{
input.readByte();
blockSize--;
if (blockSize == 0) blockSize = input.readByte();
}
while (i < pixelsCount) pixels.set(i++, 0);
if (interlaced)
{
var buffer:Bytes = Bytes.alloc(pixelsCount);
var offset:Int = deinterlace(pixels, buffer, 8, 0, 0 , width, height); // Every 8 line with start at 0
offset = deinterlace(pixels, buffer, 8, 4, offset, width, height); // Every 8 line with start at 4
offset = deinterlace(pixels, buffer, 4, 2, offset, width, height); // Every 4 line with start at 2
deinterlace(pixels, buffer, 2, 1, offset, width, height); // Every 2 line with start at 1
pixels = buffer;
}
return pixels;
}
private function deinterlace(input:Bytes, output:Bytes, step:Int, y:Int, offset:Int, width:Int, height:Int):Int
{
while (y < height)
{
output.blit(y * width, input, offset, width);
offset += width;
y += step;
}
return offset;
}
private function readExtension():Block
{
var subId:Int = i.readByte();
switch(subId)
{
case 0xF9:
// Graphics Control Extension
if (i.readByte() != 4) throw "Incorrect Graphic Control Extension block size!";
var packed:Int = i.readByte();
var disposalMethod:DisposalMethod = switch ( (packed & 28) >> 2)
{
case 0: DisposalMethod.UNSPECIFIED;
case 1: DisposalMethod.NO_ACTION;
case 2: DisposalMethod.FILL_BACKGROUND;
case 3: DisposalMethod.RENDER_PREVIOUS;
default: DisposalMethod.UNDEFINED((packed & 28) >> 2);
};
var b:Block = Block.BExtension(Extension.EGraphicControl(
{
disposalMethod:disposalMethod,
userInput: (packed & 2) == 2,
hasTransparentColor: (packed & 1) == 1,
delay: i.readUInt16(),
transparentIndex: i.readByte()
}));
i.readByte(); // Terminator
return b;
case 0x01:
// Text block
// Exists only on paper, nobody ever used it.
if (i.readByte() != 12) throw "Incorrect size of Plain Text Extension introducer block.";
return Block.BExtension(Extension.EText(
{
textGridX: i.readUInt16(),
textGridY: i.readUInt16(),
textGridWidth: i.readUInt16(),
textGridHeight: i.readUInt16(),
charCellWidth: i.readByte(),
charCellHeight: i.readByte(),
textForegroundColorIndex: i.readByte(),
textBackgroundColorIndex: i.readByte(),
text: readBlocks().toString()
}));
case 0xFE:
// Commentary
return Block.BExtension(Extension.EComment(readBlocks().toString()));
case 0xFF:
// Application extension
return readApplicationExtension();
default:
return Block.BExtension(Extension.EUnknown(subId, readBlocks()));
}
}
private function readApplicationExtension():Block
{
if (i.readByte() != 11) throw "Incorrect size of Application Extension introducer block.";
var name:String = i.readString(8);
var version:String = i.readString(3);
var data:Bytes = readBlocks();
if (name == "NETSCAPE" && version == "2.0" && data.get(0) == 1)
{
return Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AENetscapeLooping(data.get(1) | (data.get(2) << 8))));
}
return Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AEUnknown(name, version, data)));
}
private inline function readBlocks():Bytes
{
var buffer:BytesOutput = new BytesOutput();
var bytes:Bytes = Bytes.alloc(255);
var len:Int = i.readByte();
while (len != 0)
{
i.readBytes(bytes, 0, len);
buffer.writeBytes(bytes, 0, len);
len = i.readByte();
}
buffer.flush();
bytes = buffer.getBytes();
buffer.close();
return bytes;
}
private function readColorTable(size:Int):ColorTable
{
size *= 3;
var output:ColorTable = ColorTable.alloc(size);
var c:Int = 0;
while (c < size)
{
output.set(c , i.readByte()); // R
output.set(c + 1, i.readByte()); // G
output.set(c + 2, i.readByte()); // B
c += 3;
}
return output;
}
}

View File

@ -0,0 +1,390 @@
package iron.format.gif;
import iron.format.gif.Data;
import haxe.io.Bytes;
import haxe.io.BytesData;
/**
* Tools for gif data.
* @author Yanrishatum
*/
class Tools
{
/**
* Returns amount of frames in Gif data.
*/
public static function framesCount(data:Data):Int
{
var frames:Int = 0;
for (block in data.blocks)
{
switch(block)
{
case Block.BFrame(_):
frames++;
default :
}
}
return frames;
}
/**
* Returns frame at given index.
* @param data Gif data.
* @param frameIndex Index of frame.
* @return Frame at given index or null, if there is no frame at that index.
*/
public static function frame(data:Data, frameIndex:Int):Frame
{
var counter:Int = 0;
for (block in data.blocks)
{
switch (block)
{
case Block.BFrame(frame):
if (counter == frameIndex) return frame;
counter++;
default :
}
}
return null;
}
/**
* Returns Graphic Control extension for frame at given index.
* @param data Gif data.
* @param frameIndex Index of frame.
* @return GCE extension if it is exists for given frame, null otherwise.
*/
public static function graphicControl(data:Data, frameIndex:Int):GraphicControlExtension
{
var counter:Int = 0;
var gce:GraphicControlExtension = null;
for (block in data.blocks)
{
switch (block)
{
case Block.BFrame(frame):
if (counter == frameIndex) return gce;
gce = null;
counter++;
case Block.BExtension(Extension.EGraphicControl(g)):
gce = g;
default :
}
}
return null;
}
//==========================================================
// Extracting.
//==========================================================
/**
* Extracts frame pixel data in Blue-Green-Red-Alpha pixel format.
* This function extracts only exact frame and does put previous frame pixel data into resulting Bytes. Note that frame size may not equal to Gif logical screen size.
* @param data Gif data.
* @param frameIndex Frame index.
* @return BGRA pixel data with dimensions equals to specified Frame size. If frame does not present in Gif data returns null.
*/
public static function extractBGRA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
if (frameCaret == frameIndex)
{
var bytes:Bytes = Bytes.alloc(frame.width * frame.height * 4);
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var writeCaret:Int = 0;
for (i in 0...frame.pixels.length)
{
var index:Int = frame.pixels.get(i) * 3;
bytes.set(writeCaret , ct.get(index + 2)); // B
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index )); // R
if (transparentIndex == index) bytes.set(writeCaret + 3, 0); // A = 0
else bytes.set(writeCaret + 3, 0xFF); // A = FF
writeCaret += 4;
}
return bytes;
}
frameCaret++;
gce = null;
default:
}
}
return null;
}
/**
* Extracts frame pixel data in Red-Green-Blue-Alpha pixel format.
* This function extracts only exact frame and does put previous frame pixel data into resulting Bytes. Note that frame size may not equal to Gif logical screen size.
* @param data Gif data.
* @param frameIndex Frame index.
* @return RGBA pixel data with dimensions equals to specified Frame size. If frame does not present in Gif data returns null.
*/
public static function extractRGBA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
if (frameCaret == frameIndex)
{
var bytes:Bytes = Bytes.alloc(frame.width * frame.height * 4);
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var writeCaret:Int = 0;
for (i in 0...frame.pixels.length)
{
var index:Int = frame.pixels.get(i) * 3;
bytes.set(writeCaret , ct.get(index )); // R
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index + 2)); // B
if (transparentIndex == index) bytes.set(writeCaret + 3, 0); // A = 0
else bytes.set(writeCaret + 3, 0xFF); // A = FF
writeCaret += 4;
}
return bytes;
}
frameCaret++;
gce = null;
default:
}
}
return null;
}
/**
* Extracts full Gif pixel data to specified frame in Blue-Green-Red-Alpha pixel format.
* This functions returns full representation of frame including rendering of all other frames before.
* @param data Gif data.
* @param frameIndex Frame index.
* @return BGRA pixel data with dimensions equals to Gif logical screen with full pixel data of Gif image at specified frame.
*/
public static function extractFullBGRA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
var bytes:Bytes = Bytes.alloc(data.logicalScreenDescriptor.width* data.logicalScreenDescriptor.height * 4);
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var pixels:Bytes = frame.pixels;
var x:Int = 0;
var writeCaret:Int = (frame.y * data.logicalScreenDescriptor.width + frame.x) * 4;
var lineSkip:Int = (data.logicalScreenDescriptor.width - frame.width) * 4 + 4;
var disposalMethod:DisposalMethod = frameCaret != frameIndex && gce != null ? gce.disposalMethod : DisposalMethod.NO_ACTION;
switch (disposalMethod)
{
case DisposalMethod.RENDER_PREVIOUS:
// Do not render frame at all
case DisposalMethod.FILL_BACKGROUND:
for (i in 0...pixels.length)
{
bytes.set(writeCaret , 0); // B
bytes.set(writeCaret + 1, 0); // G
bytes.set(writeCaret + 2, 0); // R
bytes.set(writeCaret + 3, 0); // A
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
default:
for (i in 0...pixels.length)
{
var index:Int = pixels.get(i) * 3;
if (transparentIndex != index) // Render only if pixel non-transparent
{
bytes.set(writeCaret , ct.get(index + 2)); // B
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index )); // R
bytes.set(writeCaret + 3, 0xFF); // A
}
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
}
if (frameCaret == frameIndex) return bytes;
frameCaret++;
gce = null;
default:
}
}
return bytes;
}
/**
* Extracts full Gif pixel data to specified frame in Red-Green-Blue-Alpha pixel format.
* This functions returns full representation of frame including rendering of all other frames before.
* @param data Gif data.
* @param frameIndex Frame index.
* @return RGBA pixel data with dimensions equals to Gif logical screen with full pixel data of Gif image at specified frame.
*/
public static function extractFullRGBA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
var bytes:Bytes = Bytes.alloc(data.logicalScreenDescriptor.width* data.logicalScreenDescriptor.height * 4);
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var pixels:Bytes = frame.pixels;
var x:Int = 0;
var writeCaret:Int = (frame.y * data.logicalScreenDescriptor.width + frame.x) * 4;
var lineSkip:Int = (data.logicalScreenDescriptor.width - frame.width) * 4 + 4;
var disposalMethod:DisposalMethod = frameCaret != frameIndex && gce != null ? gce.disposalMethod : DisposalMethod.NO_ACTION;
switch (disposalMethod)
{
case DisposalMethod.RENDER_PREVIOUS:
// Do not render frame at all
case DisposalMethod.FILL_BACKGROUND:
for (i in 0...pixels.length)
{
bytes.set(writeCaret , 0); // R
bytes.set(writeCaret + 1, 0); // G
bytes.set(writeCaret + 2, 0); // B
bytes.set(writeCaret + 3, 0); // A
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
default:
for (i in 0...pixels.length)
{
var index:Int = pixels.get(i) * 3;
if (transparentIndex != index) // Render only if pixel non-transparent
{
bytes.set(writeCaret , ct.get(index )); // R
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index + 2)); // B
bytes.set(writeCaret + 3, 0xFF); // A
}
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
}
if (frameCaret == frameIndex) return bytes;
frameCaret++;
gce = null;
default:
}
}
return bytes;
}
/**
* Returns amount of animation repeats stored in Gif data.
* This is link to Netscape Looping application extension. If this extension does not present amount of loops equals to 1.
* @param data Gif data.
* @return Amount of animation repeats. Zero equals to infinite amount of repeats.
*/
public static function loopCount(data:Data):Int
{
for (block in data.blocks)
{
switch(block)
{
case Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AENetscapeLooping(loops))): return loops;
default :
}
}
return 1;
}
//==========================================================
// In-Dev writer tools.
//==========================================================
//public static function buildFrameFromTrueColor(pixels:Bytes, width:Int, height:Int):Void
//{
//
//}
private static var LN2:Float = Math.log(2);
@:noCompletion public static inline function log2(val:Float):Float
{
return Math.log(val) / LN2;
}
}

View File

@ -0,0 +1,525 @@
package format.gif;
import format.gif.Data;
import haxe.ds.Vector;
import haxe.io.Bytes;
import haxe.io.Output;
import haxe.io.UInt8Array;
/**
* ...
* @author Yanrishatum
*/
class Writer
{
private var o:Output;
private var lzw:LZWEncoder;
private var gctSize:Int;
public function new(o:Output)
{
this.o = o;
this.lzw = new LZWEncoder();
o.bigEndian = false;
}
/**
* Write entire Data at once.
* @param data Input gif file data
*/
public function write(data:Data):Void
{
// Header
writeHeader(data.version);
// Logical screen descriptor.
writeLogicalScreenDescriptor(data.logicalScreenDescriptor, data.globalColorTable);
for (block in data.blocks)
{
switch (block)
{
case Block.BEOF:
writeEOF();
return;
case Block.BExtension(ext):
switch (ext)
{
case Extension.EUnknown(id, bytes):
writeUnknownExtension(id, bytes);
case Extension.EComment(text):
writeComment(text);
case Extension.EText(textExt):
writeText(textExt);
case Extension.EGraphicControl(gce):
writeGraphicControl(gce);
case Extension.EApplicationExtension(appExt):
writeAppExtension(appExt);
}
case Block.BFrame(frame):
writeFrame(frame);
}
}
writeEOF(); // If we doesn't encountered EOF block - write it.
}
/**
* Writes header of Gif file. Must be first.
* @param version
*/
public function writeHeader(version:Version):Void
{
o.writeString("GIF");
switch(version)
{
case Version.GIF87a: o.writeString("87a");
case Version.GIF89a: o.writeString("89a");
case Version.Unknown(v):
if (v.length == 3) o.writeString(v);
else if (v.length > 3) o.writeString(v.substr(0, 3));
else
{
while (v.length < 3) v += "-";
o.writeString(v);
}
}
}
/**
* Writes Logical Screen Descriptor block. Must go right after header.
* @param lsd Logical Screen Descriptor object.
* @param globalColorTable Global color table. Required only if LSD contains hasGlobalColorTable flag.
* Color table must be a RGB-aligned Bytes with 3 bytes per color.
*/
public function writeLogicalScreenDescriptor(lsd:LogicalScreenDescriptor, globalColorTable:Bytes = null):Void
{
o.writeUInt16(lsd.width);
o.writeUInt16(lsd.height);
var packed:Int = 0;
if (lsd.hasGlobalColorTable) packed |= 128;
packed |= (lsd.colorResolution << 4) & 112;
if (lsd.sorted) packed |= 8;
packed |= Math.round(Tools.log2(lsd.globalColorTableSize) - 1) & 7;
o.writeByte(packed);
o.writeByte(lsd.backgroundColorIndex);
if (lsd.pixelAspectRatio == 1) o.writeByte(0);
else o.writeByte(Std.int(lsd.pixelAspectRatio) * 64 - 15);
if (lsd.hasGlobalColorTable)
{
if (globalColorTable != null)
{
o.writeBytes(globalColorTable, 0, globalColorTable.length);
gctSize = lsd.globalColorTableSize;
}
else throw "hasGlobalColorTable flag present, but there is no global color table!";
}
}
public function writeComment(text:String):Void
{
o.writeByte(0x21);
o.writeByte(0xFE);
writeStringBlocks(text);
}
public function writeText(textExt:PlainTextExtension):Void
{
o.writeByte(0x21);
o.writeByte(0x01);
o.writeByte(12);
o.writeUInt16(textExt.textGridX);
o.writeUInt16(textExt.textGridY);
o.writeUInt16(textExt.textGridWidth);
o.writeUInt16(textExt.textGridHeight);
o.writeByte(textExt.charCellWidth);
o.writeByte(textExt.charCellHeight);
o.writeByte(textExt.textForegroundColorIndex);
o.writeByte(textExt.textForegroundColorIndex);
writeStringBlocks(textExt.text);
}
public function writeGraphicControl(gce:GraphicControlExtension):Void
{
o.writeByte(0x21);
o.writeByte(0xF9);
o.writeByte(4);
var packed:Int = 0;
switch (gce.disposalMethod)
{
case DisposalMethod.UNSPECIFIED: // 0
case DisposalMethod.NO_ACTION: packed |= 4;
case DisposalMethod.FILL_BACKGROUND: packed |= 8;
case DisposalMethod.RENDER_PREVIOUS: packed |= 12;
case DisposalMethod.UNDEFINED(idx): packed |= (idx & 7) << 2;
}
if (gce.userInput) packed |= 2;
if (gce.hasTransparentColor) packed |= 1;
o.writeByte(packed);
o.writeUInt16(gce.delay);
o.writeByte(gce.transparentIndex);
o.writeByte(0); // Terminator
}
public function writeAppExtension(appExt:ApplicationExtension):Void
{
o.writeByte(0x21);
o.writeByte(0xFF);
o.writeByte(11);
switch (appExt)
{
case ApplicationExtension.AENetscapeLooping(loops):
o.writeString("NETSCAPE2.0");
o.writeByte(3);
o.writeByte(1); // Looping
o.writeUInt16(loops);
o.writeByte(0);
case ApplicationExtension.AEUnknown(name, version, bytes):
o.writeString(name);
o.writeString(version);
writeBlocks(bytes);
}
}
public function writeUnknownExtension(id:Int, bytes:Bytes):Void
{
o.writeByte(0x21);
o.writeByte(id);
writeBlocks(bytes);
}
public function writeFrame(frame:Frame):Void
{
o.writeByte(0x2C);
o.writeUInt16(frame.x);
o.writeUInt16(frame.y);
o.writeUInt16(frame.width);
o.writeUInt16(frame.height);
var packed:Int = 0;
if (frame.localColorTable) packed |= 128;
if (frame.interlaced) packed |= 64;
if (frame.sorted) packed |= 32;
packed |= Math.round(Tools.log2(frame.localColorTableSize) - 1) & 7;
o.writeByte(packed);
if (frame.localColorTable)
{
if (frame.colorTable != null) o.writeBytes(frame.colorTable, 0, frame.colorTable.length);
else throw "localColorTable flag is set, but there is no local color table!";
}
lzw.encode(frame.width, frame.height, frame.pixels, frame.localColorTable ? frame.localColorTableSize : gctSize, o, frame.interlaced);
}
/**
* Writes EndOfFile block.
*/
public function writeEOF():Void
{
o.writeByte(0x3B);
}
private function writeStringBlocks(text:String):Void
{
var len:Int;
var caret:Int = 0;
while (caret < text.length)
{
len = text.length - caret;
if (len > 0xFF) len = 0xFF;
o.writeByte(len);
for (i in 0...len) o.writeByte(text.charCodeAt(i + caret));
caret += len;
}
o.writeByte(0);
}
private function writeBlocks(bytes:Bytes):Void
{
var len:Int;
var caret:Int = 0;
while (caret < bytes.length)
{
len = bytes.length - caret;
if (len > 0xFF) len = 0xFF;
o.writeByte(len);
o.writeBytes(bytes, caret, len);
caret += 0xFF;
}
o.writeByte(0); // Terminator
}
}
class LZWEncoder
{
private var EOF:Int = -1;
private static inline var BITS:Int = 12;
private static inline var HSIZE:Int = 5003;
private var masks:Array<Int> = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF,
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF];
private var out:Output;
private var bits:Int;
private var bitsCount:Int;
private var minCodeSize:Int;
private var codeSize:Int;
private var codeSizeLimit:Int;
private var clearFlag:Bool;
private var clearCode:Int;
private var eofCode:Int;
// Dict
private var htab:Vector<Int>;
private var codetab:Vector<Int>;
private var freeEnt:Int;
// Block buffer
private var blockBuffer:Bytes;
private var blockBufferCaret:Int;
// Input data
private var pixels:Bytes;
private var width:Int;
private var height:Int;
private var remaining:Int;
// Non-interlaced
private var pixelsCaret:Int;
// Interlaced
private var interlaced:Bool;
private var pixelsX:Int;
private var pixelsY:Int;
private var interlacingStage:Int;
private var interlacingStep:Int;
public function new()
{
blockBuffer = Bytes.alloc(256);
}
public function encode(width:Int, height:Int, pixels:Bytes, colorsCount:Int, out:Output, interlaced:Bool):Void
{
minCodeSize = Math.round(Tools.log2(colorsCount));
this.pixels = pixels;
this.width = width;
this.height = height;
this.out = out;
htab = new Vector(HSIZE);
codetab = new Vector(HSIZE);
blockBufferCaret = 0;
bits = 0;
bitsCount = 0;
clearCode = 1 << minCodeSize;
eofCode = clearCode + 1;
freeEnt = clearCode + 2;
out.writeByte(minCodeSize);
remaining = width * height;
this.interlaced = interlaced;
if (interlaced)
{
pixelsX = 0;
pixelsY = 0;
interlacingStage = 0;
interlacingStep = 8;
}
else pixelsCaret = 0;
compress();
out.writeByte(0);
}
private function char_out(c:Int):Void
{
blockBuffer.set(blockBufferCaret++, c);
if (blockBufferCaret >= 254) flush_char();
}
private function cl_block():Void
{
cl_hash(HSIZE);
freeEnt = clearCode + 2;
clearFlag = true;
output(clearCode);
}
private function cl_hash(hsize:Int):Void
{
for (i in 0...hsize) htab[i] = -1;
}
private function compress():Void
{
var disp:Int;
var i:Int;
clearFlag = false;
codeSize = minCodeSize + 1;
codeSizeLimit = MAXCODE(codeSize);
var ent:Int = nextPixel();
var hshift:Int = 0;
var fcode:Int = HSIZE;
while (fcode < 65536)
{
++hshift;
fcode *= 2;
}
hshift = 8 - hshift;
var hsize_reg:Int = HSIZE;
cl_hash(hsize_reg);
output(clearCode);
var c:Int;
while ((c = nextPixel()) != EOF)
{
fcode = (c << BITS) + ent;
i = (c << hshift) ^ ent;
if (htab[i] == fcode)
{
ent = codetab[i];
continue;
}
else if (htab[i] >= 0)
{
disp = hsize_reg - i;
if (i == 0) disp = 1;
var skip:Bool = false;
do
{
if ((i -= disp) < 0) i += hsize_reg;
if (htab[i] == fcode)
{
ent = codetab[i];
skip = true;
break;
}
}
while (htab[i] >= 0);
if (skip) continue;
}
output(ent);
ent = c;
if (freeEnt < (1 << BITS))
{
codetab[i] = freeEnt++;
htab[i] = fcode;
}
else
{
cl_block();
}
}
output(ent);
output(eofCode);
}
private function flush_char():Void
{
if (blockBufferCaret > 0)
{
out.writeByte(blockBufferCaret);
out.writeBytes(blockBuffer, 0, blockBufferCaret);
blockBufferCaret = 0;
}
}
private inline function MAXCODE(n_bits:Int):Int
{
return (1 << n_bits) - 1;
}
private function nextPixel():Int
{
if (remaining == 0) return EOF;
remaining--;
if (interlaced)
{
if (++pixelsX == width)
{
pixelsX = 0;
pixelsY += interlacingStep;
if (pixelsY >= height)
{
switch (interlacingStage)
{
// first: Every 8 line with start at 0
case 0: pixelsY = 4; // Every 8 line with start at 4
case 1: pixelsY = 2; interlacingStep = 4; // Every 4 line with start at 2
case 2: pixelsY = 1; interlacingStep = 2; // Every 2 line with start at 1
default: return -1; // EOF
}
interlacingStage++;
}
}
return pixels.get(pixelsY * width + pixelsX);
}
else
{
return pixels.get(pixelsCaret++);
}
}
private function output(code:Int):Void
{
bits &= masks[bitsCount];
if (bitsCount > 0) bits |= (code << bitsCount);
else bits = code;
bitsCount += codeSize;
while (bitsCount >= 8)
{
char_out(bits & 0xFF);
bits >>= 8;
bitsCount -= 8;
}
if (freeEnt > codeSizeLimit || clearFlag)
{
if (clearFlag)
{
codeSizeLimit = MAXCODE(codeSize = minCodeSize + 1);
clearFlag = false;
}
else
{
codeSize++;
if (codeSize == BITS) codeSizeLimit = 1 << BITS;
else codeSizeLimit = MAXCODE(codeSize);
}
}
if (code == eofCode)
{
while (bitsCount > 0)
{
char_out(bits & 0xFF);
bits >>= 8;
bitsCount -= 8;
}
flush_char();
}
}
}

View File

@ -0,0 +1,37 @@
/*
* format - Haxe File Formats
*
* JPG File Format
* Copyright (C) 2007-2009 Trevor McCauley, Baluta Cristian (hx port) & Robert Sköld (format conversion)
*
* Copyright (c) 2009, The Haxe Project Contributors
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package iron.format.jpg;
typedef Data = {
var width : Int;
var height : Int;
var quality : Float;
var pixels : haxe.io.Bytes;
}

View File

@ -0,0 +1,657 @@
package iron.format.jpg;
class Writer {
var ZigZag: Array<Int>;
// Static table initialization
function initZigZag() {
ZigZag = [
0, 1, 5, 6,14,15,27,28,
2, 4, 7,13,16,26,29,42,
3, 8,12,17,25,30,41,43,
9,11,18,24,31,40,44,53,
10,19,23,32,39,45,52,54,
20,22,33,38,46,51,55,60,
21,34,37,47,50,56,59,61,
35,36,48,49,57,58,62,63
];
}
var YTable: Array<Int>;
var UVTable: Array<Int>;
var fdtbl_Y: Array<Float>;
var fdtbl_UV: Array<Float>;
function initQuantTables(sf: Int) {
var YQT: Array<Int> = [
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68,109,103, 77,
24, 35, 55, 64, 81,104,113, 92,
49, 64, 78, 87,103,121,120,101,
72, 92, 95, 98,112,100,103, 99
];
for (i in 0...64) {
var t: Int = Math.floor( (YQT[i] * sf + 50) / 100 );
if( t < 1 ) t = 1;
else if( t > 255 ) t = 255;
YTable[ ZigZag[i] ] = t;
}
var UVQT: Array<Int> = [
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
];
for( j in 0...64 ) {
var u: Int = Math.floor( (UVQT[j] * sf + 50) / 100 );
if( u < 1 ) u = 1;
else if( u > 255 ) u = 255;
UVTable[ ZigZag[j] ] = u;
}
var aasf: Array<Float> = [
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379
];
var k = 0;
for( row in 0...8 ) {
for( col in 0...8 ) {
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
k++;
}
}
}
var std_dc_luminance_nrcodes: Array<Int>;
var std_dc_luminance_values: haxe.io.Bytes;
var std_ac_luminance_nrcodes: Array<Int>;
var std_ac_luminance_values: haxe.io.Bytes;
function initLuminance() {
std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
std_dc_luminance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
std_ac_luminance_values = strIntsToBytes(
'0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,' +
'0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,' +
'0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,' +
'0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,' +
'0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,' +
'0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,' +
'0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,' +
'0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,' +
'0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,' +
'0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,' +
'0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,' +
'0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,' +
'0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,' +
'0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,' +
'0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,' +
'0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,' +
'0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,' +
'0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,' +
'0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,' +
'0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
'0xf9,0xfa'
);
}
function strIntsToBytes( s: String ) {
var len = s.length;
var b = new haxe.io.BytesBuffer();
var val = 0;
var i = 0;
for( j in 0...len ) {
if( s.charAt( j ) == ',' ) {
val = Std.parseInt( s.substr(i, j - i) );
b.addByte( val );
i = j + 1;
}
}
if( i < len ) {
val = Std.parseInt( s.substr(i) );
b.addByte( val );
}
return b.getBytes();
}
var std_dc_chrominance_nrcodes: Array<Int>;
var std_dc_chrominance_values: haxe.io.Bytes;
var std_ac_chrominance_nrcodes: Array<Int>;
var std_ac_chrominance_values: haxe.io.Bytes;
function initChrominance() {
std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
std_dc_chrominance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
std_ac_chrominance_values = strIntsToBytes(
'0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,' +
'0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,' +
'0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,' +
'0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,' +
'0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,' +
'0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,' +
'0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,' +
'0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,' +
'0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,' +
'0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,' +
'0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,' +
'0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,' +
'0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,' +
'0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,' +
'0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,' +
'0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,' +
'0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,' +
'0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,' +
'0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,' +
'0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
'0xf9,0xfa'
);
}
var YDC_HT: Map<Int,BitString>;
var UVDC_HT: Map<Int,BitString>;
var YAC_HT: Map<Int,BitString>;
var UVAC_HT: Map<Int,BitString>;
// Función para crear la tabla Huffman (Helper)
function computeHuffmanTbl(nrcodes: Array<Int>, std_table: haxe.io.Bytes): Map<Int,BitString> {
var codevalue = 0;
var pos_in_table = 0;
var HT: Map<Int,BitString> = new Map();
for( k in 1...17 ) {
var end = nrcodes[k];
for( j in 0...end ) {
var idx: Int = std_table.get( pos_in_table );
HT.set( idx, new BitString( k, codevalue ) );
pos_in_table++;
codevalue++;
}
codevalue *= 2;
}
return HT;
}
function initHuffmanTbl() {
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values);
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values);
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values);
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values);
// CORRECCIÓN DE TABLAS: Asegurar la existencia de EOB (0x00) y ZRL (0xF0)
// Esto es necesario para evitar fallos si el 'computeHuffmanTbl' no incluye estos valores por algún motivo.
if (YAC_HT.get(0x00) == null) YAC_HT.set(0x00, new BitString(4, 0x00));
if (UVAC_HT.get(0x00) == null) UVAC_HT.set(0x00, new BitString(4, 0x00));
if (YAC_HT.get(0xF0) == null) YAC_HT.set(0xF0, new BitString(11, 0x1E));
if (UVAC_HT.get(0xF0) == null) UVAC_HT.set(0xF0, new BitString(11, 0x1E));
}
var bitcode: Map<Int,BitString>;
var category: Map<Int,Int>;
function initCategoryNumber() {
var nrlower = 1;
var nrupper = 2;
var idx: Int;
for (cat in 1...16) {
//Positive numbers
for( nr in nrlower...nrupper ) {
idx = 32767 + nr;
category.set( idx, cat );
bitcode.set( idx, new BitString( cat, nr ) );
}
//Negative numbers
var nrneg: Int = -(nrupper - 1);
while( nrneg <= -nrlower ) {
idx = 32767 + nrneg;
category.set( idx, cat );
bitcode.set( idx, new BitString( cat, nrupper - 1 + nrneg ) );
nrneg++;
}
nrlower <<= 1;
nrupper <<= 1;
}
}
// IO functions
var byteout: haxe.io.Output;
var bytenew: Int;
var bytepos: Int;
function writeBits(bs: BitString) {
// Se confía en que bs no es nulo gracias al clamping y las correcciones de tablas.
var value: Int = bs.val;
var posval: Int = bs.len - 1;
while( posval >= 0 ) {
if( (value & (1 << posval)) != 0 ) {
bytenew |= (1 << bytepos);
}
posval--;
bytepos--;
if( bytepos < 0 ) {
if( bytenew == 0xFF ) {
b(0xFF);
b(0);
}
else {
b(bytenew);
}
bytepos = 7;
bytenew = 0;
}
}
}
function writeWord( val: Int ) {
b( (val >> 8) & 0xFF );
b( val & 0xFF );
}
// DCT & quantization core
function fDCTQuant(data: Array<Float>, fdtbl: Array<Float>): Array<Float> {
/* Pass 1: process rows. */
var dataOff = 0;
for (i in 0...8) {
var tmp0: Float = data[dataOff + 0] + data[dataOff + 7];
var tmp7: Float = data[dataOff + 0] - data[dataOff + 7];
var tmp1: Float = data[dataOff + 1] + data[dataOff + 6];
var tmp6: Float = data[dataOff + 1] - data[dataOff + 6];
var tmp2: Float = data[dataOff + 2] + data[dataOff + 5];
var tmp5: Float = data[dataOff + 2] - data[dataOff + 5];
var tmp3: Float = data[dataOff + 3] + data[dataOff + 4];
var tmp4: Float = data[dataOff + 3] - data[dataOff + 4];
/* Even part */
var tmp10: Float = tmp0 + tmp3; /* phase 2 */
var tmp13: Float = tmp0 - tmp3;
var tmp11: Float = tmp1 + tmp2;
var tmp12: Float = tmp1 - tmp2;
data[dataOff + 0] = tmp10 + tmp11; /* phase 3 */
data[dataOff + 4] = tmp10 - tmp11;
var z1: Float = (tmp12 + tmp13) * 0.707106781; /* c4 */
data[dataOff + 2] = tmp13 + z1; /* phase 5 */
data[dataOff + 6] = tmp13 - z1;
/* Odd part */
tmp10 = tmp4 + tmp5; /* phase 2 */
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5: Float = (tmp10 - tmp12) * 0.382683433; /* c6 */
var z2: Float = 0.541196100 * tmp10 + z5; /* c2-c6 */
var z4: Float = 1.306562965 * tmp12 + z5; /* c2+c6 */
var z3: Float = tmp11 * 0.707106781; /* c4 */
var z11: Float = tmp7 + z3; /* phase 5 */
var z13: Float = tmp7 - z3;
data[dataOff + 5] = z13 + z2; /* phase 6 */
data[dataOff + 3] = z13 - z2;
data[dataOff + 1] = z11 + z4;
data[dataOff + 7] = z11 - z4;
dataOff += 8; /* advance pointer to next row */
}
/* Pass 2: process columns. */
dataOff = 0;
for (j in 0...8) {
var tmp0p2: Float = data[dataOff+ 0] + data[dataOff+56];
var tmp7p2: Float = data[dataOff+ 0] - data[dataOff+56];
var tmp1p2: Float = data[dataOff+ 8] + data[dataOff+48];
var tmp6p2: Float = data[dataOff+ 8] - data[dataOff+48];
var tmp2p2: Float = data[dataOff+16] + data[dataOff+40];
var tmp5p2: Float = data[dataOff+16] - data[dataOff+40];
var tmp3p2: Float = data[dataOff+24] + data[dataOff+32];
var tmp4p2: Float = data[dataOff+24] - data[dataOff+32];
/* Even part */
var tmp10p2: Float = tmp0p2 + tmp3p2; /* phase 2 */
var tmp13p2: Float = tmp0p2 - tmp3p2;
var tmp11p2: Float = tmp1p2 + tmp2p2;
var tmp12p2: Float = tmp1p2 - tmp2p2;
data[dataOff+ 0] = tmp10p2 + tmp11p2; /* phase 3 */
data[dataOff+32] = tmp10p2 - tmp11p2;
var z1p2: Float = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
data[dataOff+48] = tmp13p2 - z1p2;
/* Odd part */
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
tmp11p2 = tmp5p2 + tmp6p2;
tmp12p2 = tmp6p2 + tmp7p2;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5p2: Float = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
var z2p2: Float = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
var z4p2: Float = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
var z3p2: Float= tmp11p2 * 0.707106781; /* c4 */
var z11p2: Float = tmp7p2 + z3p2; /* phase 5 */
var z13p2: Float = tmp7p2 - z3p2;
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
data[dataOff+24] = z13p2 - z2p2;
data[dataOff+ 8] = z11p2 + z4p2;
data[dataOff+56] = z11p2 - z4p2;
dataOff++; /* advance pointer to next column */
}
// Quantize/descale the coefficients
for (k in 0...64) {
// Apply the quantization and scaling factor & Round to nearest integer
data[k] = Math.round(data[k] * fdtbl[k]);
}
return data;
}
// Chunk writing
inline function b(v) {
byteout.writeByte(v);
}
function writeAPP0() {
b(0xFF); b(0xE0); //<- marker 0xFFE0
b(0); b(16); //<- length
b("J".code); // J
b("F".code);
b("I".code);
b("F".code);
b(0);
b(1); // versionhi
b(1); // versionlo
b(0); // xyunits
b(0); b(1); // xdensity
b(0); b(1); // ydensity
b(0); // thumbnwidth
b(0); // thumbnheight
}
function writeDQT() {
b(0xFF); b(0xDB); //<- marker 0xFFDB
b(0); b(132); //<- length
b(0);
for( j in 0...64 )
b(YTable[j]);
b(1);
for( j in 0...64 )
b(UVTable[j]);
}
function writeSOF0(width: Int, height: Int) {
b(0xFF); b(0xC0); //<- marker 0xFFC0
b(0); b(17); //<- length, truecolor YUV JPG
b(8); // precision
b( (height>>8) & 0xFF );
b( height & 0xFF );
b( (width>>8) & 0xFF );
b( width & 0xFF );
b(3); // nrofcomponents
b(1); // IdY
b(0x11); // HVY
b(0); // QTY
b(2); // IdU
b(0x11); // HVU
b(1); // QTU
b(3); // IdV
b(0x11); // HVV
b(1); // QTV
}
function writeDHT() {
b(0xFF); b(0xC4); //<- marker 0xFFC4
b(0x01); b(0xA2); //<- length
b(0); // HTYDCinfo
for( j in 1...17 )
b(std_dc_luminance_nrcodes[j]);
byteout.write(std_dc_luminance_values);
b(0x10); // HTYACinfo
for( j in 1...17 )
b(std_ac_luminance_nrcodes[j]);
byteout.write(std_ac_luminance_values);
b(1); // HTUDCinfo
for( j in 1...17 )
b(std_dc_chrominance_nrcodes[j]);
byteout.write(std_dc_chrominance_values);
b(0x11); // HTUACinfo
for( j in 1...17 )
b(std_ac_chrominance_nrcodes[j]);
byteout.write(std_ac_chrominance_values);
}
function writeSOS() {
b(0xFF); b(0xDA); //<- marker 0xFFDA
b(0); b(12); //<- length
b(3); // nrofcomponents
b(1); // IdY
b(0); // HTY
b(2); // IdU
b(0x11); // HTU
b(3); // IdV
b(0x11); // HTV
b(0); // Ss
b(0x3F); // Se
b(0); // Bf
}
// Core processing
var DU: Array<Float>;
function processDU(CDU: Array<Float>, fdtbl: Array<Float>, DC: Float, HTDC: Map<Int,BitString>, HTAC: Map<Int,BitString>): Float {
var EOB: BitString = HTAC.get( 0x00 );
var M16zeroes: BitString = HTAC.get( 0xF0 );
var DU_DCT: Array<Float> = fDCTQuant(CDU, fdtbl);
//ZigZag reorder
for (i in 0...64) {
DU[ ZigZag[i] ] = DU_DCT[i];
}
var idx: Int;
var Diff = Std.int( DU[0] - DC );
DC = DU[0];
// CORRECCIÓN DE RANGO: Clamping de la diferencia DC (previene accesos a `category` fuera de rango)
if (Diff > 16383) Diff = 16383;
if (Diff < -16383) Diff = -16383;
//Encode DC
if( Diff == 0 ) {
writeBits( HTDC.get(0) );
} else {
idx = 32767 + Diff;
writeBits(HTDC.get( category.get( idx ) ));
writeBits( bitcode.get( idx ) );
}
//Encode ACs
var end0pos = 63;
while( (end0pos > 0) && ( DU[end0pos] == 0.0 ) ) end0pos--;
//end0pos = first element in reverse order !=0
if ( end0pos == 0 ) {
writeBits(EOB);
return DC;
}
var i = 1;
while ( i <= end0pos ) {
var startpos = i;
while( ( DU[i] == 0.0 ) && ( i <= end0pos ) ) i++;
// Chequeo de seguridad si 'i' saltó más allá
if (i > end0pos) break;
var nrzeroes: Int = i - startpos;
if ( nrzeroes >= 16 ) {
for( nrmarker in 0...(nrzeroes >> 4) ) writeBits(M16zeroes);
nrzeroes &= 0xF;
}
// CORRECCIÓN DE RANGO: Clamping del coeficiente AC
var du_val = Std.int( DU[i] );
if (du_val > 16383) du_val = 16383;
if (du_val < -16383) du_val = -16383;
// LÓGICA DE SALTO: Si el clamping forzó el valor a 0, saltamos.
if (du_val == 0) {
i++;
continue;
}
idx = 32767 + du_val;
var cat = category.get( idx );
// Si 'cat' es nulo, significa que el valor de 'du_val' está fuera del rango -16383..16383, lo cual el clamping debería haber prevenido.
var index_ac = nrzeroes * 16 + cat;
writeBits( HTAC.get( index_ac ) );
writeBits( bitcode.get( idx ) );
i++;
}
if( end0pos != 63 ) writeBits(EOB);
return DC;
}
var YDU: Array<Float>;
var UDU: Array<Float>;
var VDU: Array<Float>;
function RGB2YUV(img: haxe.io.Bytes, width : Int, xpos: Int, ypos: Int) {
var pos = 0;
for( y in 0...8 ) {
var offset = ((y + ypos) * width + xpos) << 2;
for( x in 0...8 ) {
offset++; // skip alpha
var R = img.get(offset++);
var G = img.get(offset++);
var B = img.get(offset++);
YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) -128;
UDU[pos] = (((-0.16874) * R + (-0.33126) * G + ( 0.50000) * B));
VDU[pos] = ((( 0.50000) * R + (-0.41869) * G + (-0.08131) * B));
pos++;
}
}
}
public function new( out : haxe.io.Output ) {
//begin : lines added to initialize variables
YTable = new Array<Int>();
UVTable = new Array<Int>();
fdtbl_Y = new Array<Float>();
fdtbl_UV = new Array<Float>();
for (i in 0...64) {
YTable.push(0); UVTable.push(0);
fdtbl_Y.push(0.0); fdtbl_UV.push(0.0);
}
bitcode = new Map();
category = new Map();
byteout = out;
bytenew = 0;
bytepos = 7;
YDC_HT = new Map();
UVDC_HT = new Map();
YAC_HT = new Map();
UVAC_HT = new Map();
YDU = new Array<Float>();
UDU = new Array<Float>();
VDU = new Array<Float>();
DU = new Array<Float>();
for (i in 0...64) {
YDU.push(0.0); UDU.push(0.0); VDU.push(0.0); DU.push(0.0);
}
initZigZag();
initLuminance();
initChrominance();
//end : lines added to initialize variables
// Create tables
initHuffmanTbl();
initCategoryNumber();
}
public function write( image : Data ) {
// init quality table
var quality = image.quality;
if( quality <= 0 ) quality = 1;
if( quality > 100 ) quality = 100;
var sf =
if( quality < 50 ) Std.int( 5000 / quality )
else Std.int( 200 - quality * 2 );
initQuantTables(sf);
// Initialize bit writer
bytenew = 0;
bytepos = 7;
var width = image.width;
var height = image.height;
// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0( width, height );
writeDHT();
writeSOS();
// Encode 8x8 macroblocks
var DCY = 0.0;
var DCU = 0.0;
var DCV = 0.0;
bytenew = 0;
bytepos = 7;
var ypos = 0;
while( ypos < height ) {
var xpos = 0;
while( xpos < width ) {
// CORRECCIÓN CRÍTICA DE ESTADO: Limpieza de arreglos para evitar arrastre de valores (lo que el 'trace' estaba enmascarando)
// Se asegura que los buffers sean cero antes de llenarlos con RGB2YUV si no se llenan completamente.
for (k in 0...64) { YDU[k] = 0.0; UDU[k] = 0.0; VDU[k] = 0.0; }
RGB2YUV(image.pixels, width, xpos, ypos);
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
xpos += 8;
}
ypos += 8;
}
// Do the bit alignment of the EOI marker
if( bytepos >= 0 ) {
var fillbits = new BitString( bytepos + 1, ( 1 << (bytepos + 1) ) - 1 );
writeBits(fillbits);
}
writeWord(0xFFD9); //EOI
}
}
private class BitString {
public var len: Int;
public var val: Int;
public function new( l: Int, v: Int ) {
len = l;
val = v;
}
}

View File

@ -0,0 +1,651 @@
/*
* format - Haxe File Formats
*
* JPG File Format
* Copyright (C) 2007-2009 Thibault Imbert, AS3-to-Haxe by Michel Oster
*
* Copyright (c) 2009, The Haxe Project Contributors
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package iron.format.jpg;
class Writer {
var ZigZag: Array<Int>;
// Static table initialization
function initZigZag() {
ZigZag = [
0, 1, 5, 6,14,15,27,28,
2, 4, 7,13,16,26,29,42,
3, 8,12,17,25,30,41,43,
9,11,18,24,31,40,44,53,
10,19,23,32,39,45,52,54,
20,22,33,38,46,51,55,60,
21,34,37,47,50,56,59,61,
35,36,48,49,57,58,62,63
];
}
var YTable: Array<Int>; // = new Array(64);
var UVTable: Array<Int>; // = new Array(64);
var fdtbl_Y: Array<Float>; // = new Array(64);
var fdtbl_UV: Array<Float>; // = new Array(64);
function initQuantTables(sf: Int) {
var YQT: Array<Int> = [
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68,109,103, 77,
24, 35, 55, 64, 81,104,113, 92,
49, 64, 78, 87,103,121,120,101,
72, 92, 95, 98,112,100,103, 99
];
for (i in 0...64) {
var t: Int = Math.floor( (YQT[i] * sf + 50) / 100 );
if( t < 1 ) t = 1;
else if( t > 255 ) t = 255;
YTable[ ZigZag[i] ] = t;
}
var UVQT: Array<Int> = [
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
];
for( j in 0...64 ) {
var u: Int = Math.floor( (UVQT[j] * sf + 50) / 100 );
if( u < 1 ) u = 1;
else if( u > 255 ) u = 255;
UVTable[ ZigZag[j] ] = u;
}
var aasf: Array<Float> = [
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379
];
var k = 0;
for( row in 0...8 ) {
for( col in 0...8 ) {
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
k++;
}
}
}
var std_dc_luminance_nrcodes: Array<Int>;
var std_dc_luminance_values: haxe.io.Bytes;
var std_ac_luminance_nrcodes: Array<Int>;
var std_ac_luminance_values: haxe.io.Bytes;
function initLuminance() {
std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
std_dc_luminance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
std_ac_luminance_values = strIntsToBytes(
'0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,' +
'0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,' +
'0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,' +
'0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,' +
'0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,' +
'0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,' +
'0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,' +
'0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,' +
'0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,' +
'0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,' +
'0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,' +
'0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,' +
'0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,' +
'0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,' +
'0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,' +
'0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,' +
'0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,' +
'0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,' +
'0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,' +
'0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
'0xf9,0xfa'
);
}
function strIntsToBytes( s: String ) {
var len = s.length;
var b = new haxe.io.BytesBuffer();
var val = 0;
var i = 0;
for( j in 0...len ) {
if( s.charAt( j ) == ',' ) {
val = Std.parseInt( s.substr(i, j - i) );
b.addByte( val );
i = j + 1;
}
}
if( i < len ) {
val = Std.parseInt( s.substr(i) );
b.addByte( val );
}
return b.getBytes();
}
var std_dc_chrominance_nrcodes: Array<Int>;
var std_dc_chrominance_values: haxe.io.Bytes;
var std_ac_chrominance_nrcodes: Array<Int>;
var std_ac_chrominance_values: haxe.io.Bytes;
function initChrominance() {
std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
std_dc_chrominance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
std_ac_chrominance_values = strIntsToBytes(
'0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,' +
'0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,' +
'0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,' +
'0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,' +
'0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,' +
'0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,' +
'0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,' +
'0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,' +
'0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,' +
'0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,' +
'0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,' +
'0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,' +
'0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,' +
'0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,' +
'0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,' +
'0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,' +
'0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,' +
'0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,' +
'0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,' +
'0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
'0xf9,0xfa'
);
}
var YDC_HT: Map<Int,BitString>;
var UVDC_HT: Map<Int,BitString>;
var YAC_HT: Map<Int,BitString>;
var UVAC_HT: Map<Int,BitString>;
function initHuffmanTbl() {
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values);
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values);
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values);
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values);
}
function computeHuffmanTbl(nrcodes: Array<Int>, std_table: haxe.io.Bytes): Map<Int,BitString> {
var codevalue = 0;
var pos_in_table = 0;
var HT: Map<Int,BitString> = new Map();
for( k in 1...17 ) {
var end = nrcodes[k];
for( j in 0...end ) {
var idx: Int = std_table.get( pos_in_table );
HT.set( idx, new BitString( k, codevalue ) );
pos_in_table++;
codevalue++;
}
codevalue *= 2;
}
return HT;
}
var bitcode: Map<Int,BitString>;
var category: Map<Int,Int>;
function initCategoryNumber() {
var nrlower = 1;
var nrupper = 2;
var idx: Int;
for (cat in 1...16) {
//Positive numbers
for( nr in nrlower...nrupper ) {
idx = 32767 + nr;
category.set( idx, cat );
bitcode.set( idx, new BitString( cat, nr ) );
}
//Negative numbers
var nrneg: Int = -(nrupper - 1);
while( nrneg <= -nrlower ) {
idx = 32767 + nrneg;
category.set( idx, cat );
bitcode.set( idx, new BitString( cat, nrupper - 1 + nrneg ) );
nrneg++;
}
nrlower <<= 1;
nrupper <<= 1;
}
}
// IO functions
var byteout: haxe.io.Output;
var bytenew: Int;
var bytepos: Int;
function writeBits(bs: BitString) {
var value: Int = bs.val;
var posval: Int = bs.len - 1;
while( posval >= 0 ) {
//if (value & uint(1 << posval) ) {
if( (value & (1 << posval)) != 0 ) { //<- CORRECT ?
//bytenew |= uint(1 << bytepos);
bytenew |= (1 << bytepos);
}
posval--;
bytepos--;
if( bytepos < 0 ) {
if( bytenew == 0xFF ) {
b(0xFF);
b(0);
}
else {
b(bytenew);
}
bytepos = 7;
bytenew = 0;
}
}
}
function writeWord( val: Int ) {
b( (val >> 8) & 0xFF );
b( val & 0xFF );
}
// DCT & quantization core
function fDCTQuant(data: Array<Float>, fdtbl: Array<Float>): Array<Float> {
/* Pass 1: process rows. */
var dataOff = 0;
for (i in 0...8) {
var tmp0: Float = data[dataOff + 0] + data[dataOff + 7];
var tmp7: Float = data[dataOff + 0] - data[dataOff + 7];
var tmp1: Float = data[dataOff + 1] + data[dataOff + 6];
var tmp6: Float = data[dataOff + 1] - data[dataOff + 6];
var tmp2: Float = data[dataOff + 2] + data[dataOff + 5];
var tmp5: Float = data[dataOff + 2] - data[dataOff + 5];
var tmp3: Float = data[dataOff + 3] + data[dataOff + 4];
var tmp4: Float = data[dataOff + 3] - data[dataOff + 4];
/* Even part */
var tmp10: Float = tmp0 + tmp3; /* phase 2 */
var tmp13: Float = tmp0 - tmp3;
var tmp11: Float = tmp1 + tmp2;
var tmp12: Float = tmp1 - tmp2;
data[dataOff + 0] = tmp10 + tmp11; /* phase 3 */
data[dataOff + 4] = tmp10 - tmp11;
var z1: Float = (tmp12 + tmp13) * 0.707106781; /* c4 */
data[dataOff + 2] = tmp13 + z1; /* phase 5 */
data[dataOff + 6] = tmp13 - z1;
/* Odd part */
tmp10 = tmp4 + tmp5; /* phase 2 */
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5: Float = (tmp10 - tmp12) * 0.382683433; /* c6 */
var z2: Float = 0.541196100 * tmp10 + z5; /* c2-c6 */
var z4: Float = 1.306562965 * tmp12 + z5; /* c2+c6 */
var z3: Float = tmp11 * 0.707106781; /* c4 */
var z11: Float = tmp7 + z3; /* phase 5 */
var z13: Float = tmp7 - z3;
data[dataOff + 5] = z13 + z2; /* phase 6 */
data[dataOff + 3] = z13 - z2;
data[dataOff + 1] = z11 + z4;
data[dataOff + 7] = z11 - z4;
dataOff += 8; /* advance pointer to next row */
}
/* Pass 2: process columns. */
dataOff = 0;
for (j in 0...8) {
var tmp0p2: Float = data[dataOff+ 0] + data[dataOff+56];
var tmp7p2: Float = data[dataOff+ 0] - data[dataOff+56];
var tmp1p2: Float = data[dataOff+ 8] + data[dataOff+48];
var tmp6p2: Float = data[dataOff+ 8] - data[dataOff+48];
var tmp2p2: Float = data[dataOff+16] + data[dataOff+40];
var tmp5p2: Float = data[dataOff+16] - data[dataOff+40];
var tmp3p2: Float = data[dataOff+24] + data[dataOff+32];
var tmp4p2: Float = data[dataOff+24] - data[dataOff+32];
/* Even part */
var tmp10p2: Float = tmp0p2 + tmp3p2; /* phase 2 */
var tmp13p2: Float = tmp0p2 - tmp3p2;
var tmp11p2: Float = tmp1p2 + tmp2p2;
var tmp12p2: Float = tmp1p2 - tmp2p2;
data[dataOff+ 0] = tmp10p2 + tmp11p2; /* phase 3 */
data[dataOff+32] = tmp10p2 - tmp11p2;
var z1p2: Float = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
data[dataOff+48] = tmp13p2 - z1p2;
/* Odd part */
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
tmp11p2 = tmp5p2 + tmp6p2;
tmp12p2 = tmp6p2 + tmp7p2;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5p2: Float = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
var z2p2: Float = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
var z4p2: Float = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
var z3p2: Float= tmp11p2 * 0.707106781; /* c4 */
var z11p2: Float = tmp7p2 + z3p2; /* phase 5 */
var z13p2: Float = tmp7p2 - z3p2;
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
data[dataOff+24] = z13p2 - z2p2;
data[dataOff+ 8] = z11p2 + z4p2;
data[dataOff+56] = z11p2 - z4p2;
dataOff++; /* advance pointer to next column */
}
// Quantize/descale the coefficients
for (k in 0...64) {
// Apply the quantization and scaling factor & Round to nearest integer
data[k] = Math.round(data[k] * fdtbl[k]);
}
return data;
}
// Chunk writing
inline function b(v) {
byteout.writeByte(v);
}
function writeAPP0() {
b(0xFF); b(0xE0); //<- marker 0xFFE0
b(0); b(16); //<- length
b("J".code); // J
b("F".code);
b("I".code);
b("F".code);
b(0);
b(1); // versionhi
b(1); // versionlo
b(0); // xyunits
b(0); b(1); // xdensity
b(0); b(1); // ydensity
b(0); // thumbnwidth
b(0); // thumbnheight
}
function writeDQT() {
b(0xFF); b(0xDB); //<- marker 0xFFDB
b(0); b(132); //<- length
b(0);
for( j in 0...64 )
b(YTable[j]);
b(1);
for( j in 0...64 )
b(UVTable[j]);
}
function writeSOF0(width: Int, height: Int) {
b(0xFF); b(0xC0); //<- marker 0xFFC0
b(0); b(17); //<- length, truecolor YUV JPG
b(8); // precision
b( (height>>8) & 0xFF );
b( height & 0xFF );
b( (width>>8) & 0xFF );
b( width & 0xFF );
b(3); // nrofcomponents
b(1); // IdY
b(0x11); // HVY
b(0); // QTY
b(2); // IdU
b(0x11); // HVU
b(1); // QTU
b(3); // IdV
b(0x11); // HVV
b(1); // QTV
}
function writeDHT() {
b(0xFF); b(0xC4); //<- marker 0xFFC4
b(0x01); b(0xA2); //<- length
b(0); // HTYDCinfo
for( j in 1...17 )
b(std_dc_luminance_nrcodes[j]);
byteout.write(std_dc_luminance_values);
b(0x10); // HTYACinfo
for( j in 1...17 )
b(std_ac_luminance_nrcodes[j]);
byteout.write(std_ac_luminance_values);
b(1); // HTUDCinfo
for( j in 1...17 )
b(std_dc_chrominance_nrcodes[j]);
byteout.write(std_dc_chrominance_values);
b(0x11); // HTUACinfo
for( j in 1...17 )
b(std_ac_chrominance_nrcodes[j]);
byteout.write(std_ac_chrominance_values);
}
function writeSOS() {
b(0xFF); b(0xDA); //<- marker 0xFFDA
b(0); b(12); //<- length
b(3); // nrofcomponents
b(1); // IdY
b(0); // HTY
b(2); // IdU
b(0x11); // HTU
b(3); // IdV
b(0x11); // HTV
b(0); // Ss
b(0x3F); // Se
b(0); // Bf
}
// Core processing
var DU: Array<Float>; //<- initialized in function new JPEGEncoder()
function processDU(CDU: Array<Float>, fdtbl: Array<Float>, DC: Float, HTDC: Map<Int,BitString>, HTAC: Map<Int,BitString>): Float {
var EOB: BitString = HTAC.get( 0x00 );
var M16zeroes: BitString = HTAC.get( 0xF0 );
var DU_DCT: Array<Float> = fDCTQuant(CDU, fdtbl);
//ZigZag reorder
for (i in 0...64) {
DU[ ZigZag[i] ] = DU_DCT[i];
}
var idx: Int;
var Diff = Std.int( DU[0] - DC );
DC = DU[0];
//Encode DC
if( Diff == 0 ) {
writeBits( HTDC.get(0) ); // Diff might be 0
} else {
idx = 32767 + Diff;
writeBits(HTDC.get( category.get( idx ) ));
writeBits( bitcode.get( idx ) );
}
//Encode ACs
var end0pos = 63;
//for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) { };
while( (end0pos > 0) && ( DU[end0pos] == 0.0 ) ) end0pos--;
//end0pos = first element in reverse order !=0
if ( end0pos == 0 ) {
writeBits(EOB);
return DC;
}
var i = 1;
while ( i <= end0pos ) {
var startpos = i;
//for (; (DU[i]==0) && (i<=end0pos); i++) { }; <- it's a 'while' loop
while( ( DU[i] == 0.0 ) && ( i <= end0pos ) ) i++;
var nrzeroes: Int = i - startpos;
if ( nrzeroes >= 16 ) {
//for (var nrmarker: Int=1; nrmarker <= nrzeroes/16; nrmarker++) {
for( nrmarker in 0...(nrzeroes >> 4) ) writeBits(M16zeroes);
nrzeroes &= 0xF;
}
idx = 32767 + Std.int( DU[i] ); //<- line added
writeBits( HTAC.get( nrzeroes * 16 + category.get( idx ) ) );
writeBits( bitcode.get( idx ) );
i++;
}
if( end0pos != 63 ) writeBits(EOB);
return DC;
}
var YDU: Array<Float>;
var UDU: Array<Float>;
var VDU: Array<Float>;
function RGB2YUV(img: haxe.io.Bytes, width : Int, xpos: Int, ypos: Int) {
var pos = 0;
for( y in 0...8 ) {
var offset = ((y + ypos) * width + xpos) << 2;
for( x in 0...8 ) {
offset++; // skip alpha
var R = img.get(offset++);
var G = img.get(offset++);
var B = img.get(offset++);
YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) -128;
UDU[pos] = (((-0.16874) * R + (-0.33126) * G + ( 0.50000) * B));
VDU[pos] = ((( 0.50000) * R + (-0.41869) * G + (-0.08131) * B));
pos++;
}
}
}
public function new( out : haxe.io.Output ) {
//begin : lines added to initialize variables
YTable = new Array<Int>();
UVTable = new Array<Int>();
fdtbl_Y = new Array<Float>();
fdtbl_UV = new Array<Float>();
for (i in 0...64) {
YTable.push(0); UVTable.push(0);
fdtbl_Y.push(0.0); fdtbl_UV.push(0.0);
}
bitcode = new Map(); //<- 65535 elements <BitString>
category = new Map(); //<- 65535 elements <Int>
byteout = out;
bytenew = 0;
bytepos = 7;
YDC_HT = new Map();
UVDC_HT = new Map();
YAC_HT = new Map();
UVAC_HT = new Map();
YDU = new Array<Float>(); //<- 64 elements
UDU = new Array<Float>();
VDU = new Array<Float>();
DU = new Array<Float>();
for (i in 0...64) {
YDU.push(0.0); UDU.push(0.0); VDU.push(0.0); DU.push(0.0);
}
initZigZag();
initLuminance();
initChrominance();
//end : lines added to initialize variables
// Create tables
initHuffmanTbl();
initCategoryNumber();
}
public function write( image : Data ) {
// init quality table
var quality = image.quality;
if( quality <= 0 ) quality = 1;
if( quality > 100 ) quality = 100;
var sf =
if( quality < 50 ) Std.int( 5000 / quality )
else Std.int( 200 - quality * 2 );
initQuantTables(sf);
// Initialize bit writer
bytenew = 0;
bytepos = 7;
var width = image.width;
var height = image.height;
// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0( width, height );
writeDHT();
writeSOS();
// Encode 8x8 macroblocks
var DCY = 0.0;
var DCU = 0.0;
var DCV = 0.0;
bytenew = 0;
bytepos = 7;
var ypos = 0;
while( ypos < height ) {
var xpos = 0;
while( xpos < width ) {
RGB2YUV(image.pixels, width, xpos, ypos);
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
xpos += 8;
}
ypos += 8;
}
// Do the bit alignment of the EOI marker
if( bytepos >= 0 ) {
var fillbits = new BitString( bytepos + 1, ( 1 << (bytepos + 1) ) - 1 );
writeBits(fillbits);
}
writeWord(0xFFD9); //EOI
}
}
private class BitString {
public var len: Int;
public var val: Int;
public function new( l: Int, v: Int ) {
len = l;
val = v;
}
}

View File

@ -0,0 +1,56 @@
/*
* format - Haxe File Formats
*
* WAVE File Format
* Copyright (C) 2009 Robin Palotai
*
* Copyright (c) 2009, The Haxe Project Contributors
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package iron.format.wav;
typedef WAVE = {
header : WAVEHeader,
data : haxe.io.Bytes,
cuePoints : Array<CuePoint>
}
typedef WAVEHeader = {
format : WAVEFormat,
channels : Int,
samplingRate : Int,
byteRate : Int, // samplingRate * channels * bitsPerSample / 8
blockAlign : Int, // channels * bitsPerSample / 8
bitsPerSample : Int
}
typedef CuePoint = {
id : Int,
sampleOffset : Int
}
enum WAVEFormat {
WF_PCM;
}

View File

@ -0,0 +1,155 @@
/*
* format - Haxe File Formats
*
* WAVE File Format
* Copyright (C) 2009 Robin Palotai
*
* Copyright (c) 2009, The Haxe Project Contributors
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package iron.format.wav;
import iron.format.wav.Data;
class Reader {
var i : haxe.io.Input;
var version : Int;
public function new(i) {
this.i = i;
i.bigEndian = false;
}
inline function readInt() {
#if haxe3
return i.readInt32();
#else
return i.readUInt30();
#end
}
public function read() : WAVE {
if (i.readString(4) != "RIFF")
throw "RIFF header expected";
var len = readInt();
if (i.readString(4) != "WAVE")
throw "WAVE signature not found";
var fmt = i.readString(4);
while(fmt != "fmt ") {
switch( fmt ) {
case "JUNK": //protool
var junkLen = i.readInt32();
i.read(junkLen);
fmt = i.readString(4);
case "bext":
var bextLen = i.readInt32();
i.read(bextLen);
fmt = i.readString(4);
default:
break;
}
}
if ( fmt != "fmt " )
throw "unsupported wave chunk "+fmt;
var fmtlen = readInt();
var format = switch (i.readUInt16()) {
case 1,3: WF_PCM;
default: throw "only PCM (uncompressed) WAV files are supported";
}
var channels = i.readUInt16();
var samplingRate = readInt();
var byteRate = readInt();
var blockAlign = i.readUInt16();
var bitsPerSample = i.readUInt16();
if (fmtlen > 16)
i.read(fmtlen - 16);
var nextChunk = i.readString (4);
while (nextChunk != "data") {
// read past other subchunks
i.read(readInt());
nextChunk = i.readString (4);
}
// data
if (nextChunk != "data")
throw "expected data subchunk";
var datalen = readInt();
var data : haxe.io.Bytes;
try {
data = i.read(datalen);
} catch (e : haxe.io.Eof) {
throw "Invalid chunk data length";
}
var cuePoints = new Array<CuePoint>();
try {
while (true) {
var nextChunk = i.readString (4);
switch (nextChunk) {
case "cue ":
readInt();
var nbCuePoints = readInt();
for (_ in 0...nbCuePoints) {
var cueId = readInt();
readInt();
i.readString(4);
readInt();
readInt();
var cueSampleOffset = readInt();
cuePoints.push({ id : cueId, sampleOffset: cueSampleOffset });
}
default:
var n = readInt();
if( n < 0 ) break;
i.read(n);
}
}
} catch (e : haxe.io.Eof) { }
return {
header: {
format: format,
channels: channels,
samplingRate: samplingRate,
byteRate: byteRate,
blockAlign: blockAlign,
bitsPerSample: bitsPerSample
},
data: data,
cuePoints: cuePoints
}
}
}

View File

@ -0,0 +1,72 @@
/*
* format - Haxe File Formats
*
* WAVE File Format
* Copyright (C) 2009 Robin Palotai
*
* Copyright (c) 2009, The Haxe Project Contributors
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package iron.format.wav;
import iron.format.wav.Data;
class Writer {
var o : haxe.io.Output;
public function new(output : haxe.io.Output) {
o = output;
o.bigEndian = false;
}
public function write(wav : WAVE) {
var hdr = wav.header;
o.writeString("RIFF");
writeInt(36 + wav.data.length);
o.writeString("WAVE");
o.writeString("fmt ");
writeInt(16);
o.writeUInt16(1);
o.writeUInt16(hdr.channels);
writeInt(hdr.samplingRate);
writeInt(hdr.byteRate);
o.writeUInt16(hdr.blockAlign);
o.writeUInt16(hdr.bitsPerSample);
o.writeString("data");
writeInt(wav.data.length);
o.write(wav.data);
}
inline function writeInt( v : Int ) {
#if haxe3
o.writeInt32(v);
#else
o.writeUInt30(v);
#end
}
}

View File

@ -66,32 +66,12 @@ class Quat {
}
public inline function fromAxisAngle(axis: Vec4, angle: FastFloat): Quat {
//var s: FastFloat = Math.sin(angle * 0.5);
//x = axis.x * s;
//y = axis.y * s;
//z = axis.z * s;
//w = Math.cos(angle * 0.5);
//return normalize();
// Normalize the axis vector first
var axisLen = Math.sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z);
if (axisLen > 0.00001) {
var aL = 1.0 / axisLen;
var nX = axis.x * aL;
var nY = axis.y * aL;
var nZ = axis.z * aL;
var halfAngle = angle * 0.5;
var s: FastFloat = Math.sin(halfAngle);
x = nX * s;
y = nY * s;
z = nZ * s;
w = Math.cos(halfAngle);
} else {
x = 0.0;
y = 0.0;
z = 0.0;
w = 1.0;
}
return this;
var s: FastFloat = Math.sin(angle * 0.5);
x = axis.x * s;
y = axis.y * s;
z = axis.z * s;
w = Math.cos(angle * 0.5);
return normalize();
}
public inline function toAxisAngle(axis: Vec4): FastFloat {
@ -399,33 +379,17 @@ class Quat {
@return This quaternion.
**/
public inline function fromEulerOrdered(e: Vec4, order: String): Quat {
var mappedAngles = new Vec4();
switch (order) {
case "XYZ":
mappedAngles.set(e.x, e.y, e.z);
case "XZY":
mappedAngles.set(e.x, e.z, e.y);
case "YXZ":
mappedAngles.set(e.y, e.x, e.z);
case "YZX":
mappedAngles.set(e.y, e.z, e.x);
case "ZXY":
mappedAngles.set(e.z, e.x, e.y);
case "ZYX":
mappedAngles.set(e.z, e.y, e.x);
}
var c1 = Math.cos(mappedAngles.x / 2);
var c2 = Math.cos(mappedAngles.y / 2);
var c3 = Math.cos(mappedAngles.z / 2);
var s1 = Math.sin(mappedAngles.x / 2);
var s2 = Math.sin(mappedAngles.y / 2);
var s3 = Math.sin(mappedAngles.z / 2);
var c1 = Math.cos(e.x / 2);
var c2 = Math.cos(e.y / 2);
var c3 = Math.cos(e.z / 2);
var s1 = Math.sin(e.x / 2);
var s2 = Math.sin(e.y / 2);
var s3 = Math.sin(e.z / 2);
var qx = new Quat(s1, 0, 0, c1);
var qy = new Quat(0, s2, 0, c2);
var qz = new Quat(0, 0, s3, c3);
// Original multiplication sequence (implements reverse of 'order')
if (order.charAt(2) == 'X')
this.setFrom(qx);
else if (order.charAt(2) == 'Y')
@ -445,12 +409,6 @@ class Quat {
else
this.mult(qz);
// TO DO quick fix somethings wrong..
this.x = -this.x;
this.y = -this.y;
this.z = -this.z;
this.w = -this.w;
return this;
}

View File

@ -1,31 +1,119 @@
package iron.object;
import iron.data.SceneFormat;
import iron.math.Vec4;
import iron.math.Quat;
class Constraint {
var raw: TConstraint;
var target: Transform = null;
public function new(constr: TConstraint) {
raw = constr;
public function new(constraint: TConstraint) {
raw = constraint;
}
public function apply(transform: Transform) {
if (target == null && raw.target != null) target = Scene.active.getChild(raw.target).transform;
if (target == null && raw.type != "LIMIT_LOCATION" && raw.type != "LIMIT_ROTATION" && raw.type != "LIMIT_SCALE") return;
if (raw.type == "COPY_LOCATION") {
if (raw.use_x) {
transform.world._30 = target.loc.x;
if (raw.use_offset) transform.world._30 += transform.loc.x;
if (raw.use_offset) {
if (raw.use_x) transform.world._30 += target.world._30;
if (raw.use_y) transform.world._31 += target.world._31;
if (raw.use_z) transform.world._32 += target.world._32;
}
if (raw.use_y) {
transform.world._31 = target.loc.y;
if (raw.use_offset) transform.world._31 += transform.loc.y;
else {
if (raw.use_x) transform.world._30 = target.world._30;
if (raw.use_y) transform.world._31 = target.world._31;
if (raw.use_z) transform.world._32 = target.world._32;
}
if (raw.use_z) {
transform.world._32 = target.loc.z;
if (raw.use_offset) transform.world._32 += transform.loc.z;
}
else if (raw.type == "COPY_ROTATION") {
var tq = target.rot;
var mq = transform.rot;
if (raw.use_offset) {
mq.mult(tq);
}
else {
if (raw.use_x) mq.x = tq.x;
if (raw.use_y) mq.y = tq.y;
if (raw.use_z) mq.z = tq.z;
mq.w = tq.w;
}
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
var scale = transform.scale;
transform.world.compose(loc, mq, scale);
}
else if (raw.type == "COPY_SCALE") {
var ts = target.scale;
if (raw.use_offset) {
if (raw.use_x) transform.scale.x *= ts.x;
if (raw.use_y) transform.scale.y *= ts.y;
if (raw.use_z) transform.scale.z *= ts.z;
}
else {
if (raw.use_x) transform.scale.x = ts.x;
if (raw.use_y) transform.scale.y = ts.y;
if (raw.use_z) transform.scale.z = ts.z;
}
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
transform.world.compose(loc, transform.rot, transform.scale);
}
else if (raw.type == "COPY_TRANSFORMS") {
transform.world.setFrom(target.world);
}
else if (raw.type == "LIMIT_LOCATION") {
if (raw.use_min_x && transform.world._30 < raw.min_x) transform.world._30 = raw.min_x;
if (raw.use_max_x && transform.world._30 > raw.max_x) transform.world._30 = raw.max_x;
if (raw.use_min_y && transform.world._31 < raw.min_y) transform.world._31 = raw.min_y;
if (raw.use_max_y && transform.world._31 > raw.max_y) transform.world._31 = raw.max_y;
if (raw.use_min_z && transform.world._32 < raw.min_z) transform.world._32 = raw.min_z;
if (raw.use_max_z && transform.world._32 > raw.max_z) transform.world._32 = raw.max_z;
}
else if (raw.type == "LIMIT_ROTATION") {
var euler = transform.rot.getEuler();
var changed = false;
if (raw.use_limit_x) {
if (euler.x < raw.min_x) { euler.x = raw.min_x; changed = true; }
if (euler.x > raw.max_x) { euler.x = raw.max_x; changed = true; }
}
if (raw.use_limit_y) {
if (euler.y < raw.min_y) { euler.y = raw.min_y; changed = true; }
if (euler.y > raw.max_y) { euler.y = raw.max_y; changed = true; }
}
if (raw.use_limit_z) {
if (euler.z < raw.min_z) { euler.z = raw.min_z; changed = true; }
if (euler.z > raw.max_z) { euler.z = raw.max_z; changed = true; }
}
if (changed) {
transform.rot.fromEuler(euler.x, euler.y, euler.z);
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
transform.world.compose(loc, transform.rot, transform.scale);
}
}
else if (raw.type == "LIMIT_SCALE") {
if (raw.use_min_x && transform.scale.x < raw.min_x) transform.scale.x = raw.min_x;
if (raw.use_max_x && transform.scale.x > raw.max_x) transform.scale.x = raw.max_x;
if (raw.use_min_y && transform.scale.y < raw.min_y) transform.scale.y = raw.min_y;
if (raw.use_max_y && transform.scale.y > raw.max_y) transform.scale.y = raw.max_y;
if (raw.use_min_z && transform.scale.z < raw.min_z) transform.scale.z = raw.min_z;
if (raw.use_max_z && transform.scale.z > raw.max_z) transform.scale.z = raw.max_z;
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
transform.world.compose(loc, transform.rot, transform.scale);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,12 @@ import iron.object.CameraObject;
class LightObject extends Object {
public var data: LightData;
public var color: Vec4;
public var strength: Float;
#if lnx_spot
public var size: Float;
public var blend: Float;
#end
#if rp_shadowmap
#if lnx_shadowmap_atlas
@ -79,6 +85,15 @@ class LightObject extends Object {
super();
this.data = data;
this.color = new Vec4(data.raw.color[0], data.raw.color[1], data.raw.color[2]);
this.strength = data.raw.strength;
#if lnx_spot
if (data.raw.type == "spot"){
this.size = data.raw.spot_size;
this.blend = data.raw.spot_blend;
}
#end
var type = data.raw.type;
var fov = data.raw.fov;
@ -245,7 +260,12 @@ class LightObject extends Object {
// Snap to texel coords - fix translation swim
var smsize = data.raw.shadowmap_size;
#if lnx_csm // Cascades
smsize = Std.int(smsize / 4);
#if lnx_shadowmap_atlas
var ts = cascade < tileScale.length ? tileScale[cascade] : 1.0;
smsize = Std.int(Math.max(ts * data.raw.shadowmap_size, 1.0));
#else
smsize = Std.int(smsize / cascadeCount);
#end
#end
var worldPerTexelX = (maxx - minx) / smsize;
var worldPerTexelY = (maxy - miny) / smsize;
@ -369,7 +389,7 @@ class LightObject extends Object {
// Centralize discarding conditions when iterating over lights
// Important to avoid issues later with "misaligned" data in uniforms (lightsArray, clusterData, LWVPSpotArray)
public inline static function discardLight(light: LightObject) {
return !light.visible || light.data.raw.strength == 0.0 || light.data.raw.type == "sun";
return !light.visible || light.strength == 0.0 || light.data.raw.type == "sun";
}
// Discarding conditions but with culling included
public inline static function discardLightCulled(light: LightObject) {
@ -452,7 +472,7 @@ class LightObject extends Object {
lpos.set(l.transform.worldx(), l.transform.worldy(), l.transform.worldz());
lpos.applymat4(camera.V);
lpos.z *= -1.0;
var radius = getRadius(l.data.raw.strength);
var radius = getRadius(l.strength);
var minX = 0;
var minY = 0;
var minZ = 0;
@ -547,10 +567,10 @@ class LightObject extends Object {
lightsArray[i * 12 + 3] = 0.0; // padding or spot scale x
// light color
var f = l.data.raw.strength;
lightsArray[i * 12 + 4] = l.data.raw.color[0] * f;
lightsArray[i * 12 + 5] = l.data.raw.color[1] * f;
lightsArray[i * 12 + 6] = l.data.raw.color[2] * f;
var f = l.strength;
lightsArray[i * 12 + 4] = l.color.x * f;
lightsArray[i * 12 + 5] = l.color.y * f;
lightsArray[i * 12 + 6] = l.color.z * f;
lightsArray[i * 12 + 7] = 0.0; // padding or spot scale y
// other data
@ -561,13 +581,13 @@ class LightObject extends Object {
#if lnx_spot
if (l.data.raw.type == "spot") {
lightsArray[i * 12 + 9] = l.data.raw.spot_size;
lightsArray[i * 12 + 9] = l.size;
var dir = l.look().normalize();
lightsArraySpot[i * 8 ] = dir.x;
lightsArraySpot[i * 8 + 1] = dir.y;
lightsArraySpot[i * 8 + 2] = dir.z;
lightsArraySpot[i * 8 + 3] = l.data.raw.spot_blend;
lightsArraySpot[i * 8 + 3] = l.blend;
// Premultiply scale with z component
var scale = l.transform.scale;
@ -685,22 +705,6 @@ class LightObject extends Object {
return LWVPMatrixArray;
}
public static inline function getMaxLights(): Int {
#if (rp_max_lights == 8)
return 8;
#elseif (rp_max_lights == 16)
return 16;
#elseif (rp_max_lights == 24)
return 24;
#elseif (rp_max_lights == 32)
return 32;
#elseif (rp_max_lights == 64)
return 64;
#else
return 4;
#end
}
public static inline function getMaxLightsCluster(): Int {
#if (rp_max_lights_cluster == 8)
return 8;
@ -718,6 +722,90 @@ class LightObject extends Object {
}
#end // lnx_clusters
public static inline function getMaxLights(): Int {
#if (rp_max_lights == 8)
return 8;
#elseif (rp_max_lights == 16)
return 16;
#elseif (rp_max_lights == 24)
return 24;
#elseif (rp_max_lights == 32)
return 32;
#elseif (rp_max_lights == 64)
return 64;
#else
return 4;
#end
}
#if (rp_shadowmap && lnx_shadowmap_atlas)
public static var sunTileBoundsArray: Float32Array = null;
public static var tileBoundsDirty: Bool = true;
public static function updateSunTileBoundsArray(): Float32Array {
if (!tileBoundsDirty && sunTileBoundsArray != null) return sunTileBoundsArray;
tileBoundsDirty = false;
var maxLights = getMaxLights();
var numTiles = maxLights * cascadeCount;
if (sunTileBoundsArray == null) {
sunTileBoundsArray = new Float32Array(numTiles * 4);
}
for (k in 0...sunTileBoundsArray.length) sunTileBoundsArray[k] = 0;
var i = 0;
for (light in Scene.active.lights) {
if (i >= maxLights) break;
if (!light.visible || light.data.raw.type != "sun") continue;
if (!light.data.raw.cast_shadow || light.data.raw.strength == 0.0) continue;
for (c in 0...cascadeCount) {
var idx = (i * cascadeCount + c) * 4;
sunTileBoundsArray[idx ] = light.tileOffsetX[c];
sunTileBoundsArray[idx + 2] = light.tileOffsetX[c] + light.tileScale[c];
#if (!kha_opengl)
// flip bounds to match
sunTileBoundsArray[idx + 1] = 1.0 - (light.tileOffsetY[c] + light.tileScale[c]);
sunTileBoundsArray[idx + 3] = 1.0 - light.tileOffsetY[c];
#else
sunTileBoundsArray[idx + 1] = light.tileOffsetY[c];
sunTileBoundsArray[idx + 3] = light.tileOffsetY[c] + light.tileScale[c];
#end
}
i++;
}
return sunTileBoundsArray;
}
#end
#if (rp_shadowmap && lnx_shadowmap_atlas)
public static var spotTileBoundsArray: Float32Array = null;
public static function updateSpotTileBoundsArray(): Float32Array {
var maxLights = getMaxLights();
if (spotTileBoundsArray == null) {
spotTileBoundsArray = new Float32Array(maxLights * 4);
}
for (k in 0...spotTileBoundsArray.length) spotTileBoundsArray[k] = 0;
var i = 0;
for (light in Scene.active.lights) {
if (i >= maxLights) break;
if (!light.visible || light.data.raw.type != "spot") continue;
if (!light.data.raw.cast_shadow || light.data.raw.strength == 0.0) continue;
var idx = i * 4;
spotTileBoundsArray[idx ] = light.tileOffsetX[0];
spotTileBoundsArray[idx + 2] = light.tileOffsetX[0] + light.tileScale[0];
#if (!kha_opengl)
// flip bounds
spotTileBoundsArray[idx + 1] = 1.0 - (light.tileOffsetY[0] + light.tileScale[0]);
spotTileBoundsArray[idx + 3] = 1.0 - light.tileOffsetY[0];
#else
spotTileBoundsArray[idx + 1] = light.tileOffsetY[0];
spotTileBoundsArray[idx + 3] = light.tileOffsetY[0] + light.tileScale[0];
#end
i++;
}
return spotTileBoundsArray;
}
#end
public inline function right(): Vec4 {
return new Vec4(V._00, V._10, V._20);
}

View File

@ -28,13 +28,15 @@ class MeshObject extends Object {
public var cameraList: Array<String> = null;
public var screenSize = 0.0;
public var frustumCulling = true;
public var tilesheet: Tilesheet = null;
public var activeTilesheet: Tilesheet = null;
public var tilesheets: Array<Tilesheet> = null;
public var skip_context: String = null; // Do not draw this context
public var force_context: String = null; // Draw only this context
static var lastPipeline: PipelineState = null;
#if lnx_morph_target
public var morphTarget: MorphTarget = null;
#end
public var vertexGroups: Map<String, Array<Vec4>> = null;
#if lnx_veloc
public var prevMatrix = Mat4.identity();
@ -50,6 +52,10 @@ class MeshObject extends Object {
public function setData(data: MeshData) {
this.data = data;
if (this.materials != null && this.materials.length > 0)
data.geom.instanceElements = @:privateAccess this.materials[0].shader.contexts[0].instanceElements;
data.refcount++;
#if (!lnx_batch)
@ -87,8 +93,12 @@ class MeshObject extends Object {
particleSystems = null;
}
#end
if (tilesheet != null) tilesheet.remove();
if (activeTilesheet != null) activeTilesheet.remove();
if (tilesheets != null) { for (ts in tilesheets) { ts.remove(); } tilesheets = null; }
if (Scene.active != null) Scene.active.meshes.remove(this);
#if (rp_renderer == "Deferred")
if (Scene.active != null) Scene.active.markMaterialParamsDirty();
#end
data.refcount--;
super.remove();
}
@ -123,12 +133,34 @@ class MeshObject extends Object {
#end
public function setupTilesheet(tilesheetData: iron.data.SceneFormat.TTilesheetData) {
tilesheet = new Tilesheet(tilesheetData, this);
activeTilesheet = new Tilesheet(tilesheetData, this);
if (tilesheets == null) tilesheets = new Array<Tilesheet>();
tilesheets.push(activeTilesheet);
}
public function setActiveTilesheet(tilesheetData: iron.data.SceneFormat.TTilesheetData, tilesheetActionRef: String = null) {
var set = false;
if (tilesheets != null) {
for (ts in tilesheets) {
if (ts.raw == tilesheetData) {
if (activeTilesheet != null) activeTilesheet.pause();
activeTilesheet = ts;
if (tilesheetActionRef != null) activeTilesheet.play(tilesheetActionRef);
set = true;
break;
}
}
}
if (!set) {
if (activeTilesheet != null) activeTilesheet.pause();
setupTilesheet(tilesheetData);
if (tilesheetActionRef != null) activeTilesheet.play(tilesheetActionRef);
}
}
public function setTilesheetAction(actionRef: String) {
if (tilesheet != null) {
tilesheet.play(actionRef);
if (activeTilesheet != null) {
activeTilesheet.play(actionRef);
}
}
@ -161,6 +193,7 @@ class MeshObject extends Object {
}
function cullMesh(context: String, camera: CameraObject, light: LightObject): Bool {
var isShadow = context == "shadowmap";
if (camera == null) return false;
if (camera.data.raw.frustum_culling && frustumCulling) {
@ -169,11 +202,12 @@ class MeshObject extends Object {
var radiusScale = data.isSkinned ? 2.0 : 1.0;
#if lnx_gpu_particles
// particleSystems for update, particleOwner for render
if (particleSystems != null || particleOwner != null) radiusScale *= 1000;
if (particleSystems != null && particleSystems.length > 0) return setCulled(isShadow, false);
#end
/*
if (context == "voxel") radiusScale *= 100;
if (data.geom.instanced) radiusScale *= 100;
var isShadow = context == "shadowmap";
if (data.geom.instanced) radiusScale *= 100;*/
if (data.geom.instanced) return setCulled(isShadow, false);
var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes;
if (isShadow && light.data.raw.type != "sun") { // Non-sun light bounds intersect camera frustum
@ -188,8 +222,9 @@ class MeshObject extends Object {
}
}
culled = false;
return culled;
//culled = false;
//return culled;
return setCulled(isShadow, false);
}
function skipContext(context: String, mat: MaterialData): Bool {
@ -224,11 +259,6 @@ class MeshObject extends Object {
if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return;
var meshContext = raw != null ? context == "mesh" : false;
// Update tilesheet
if (tilesheet != null && meshContext) {
tilesheet.update();
}
if (cameraList != null && cameraList.indexOf(Scene.active.camera.name) < 0) return;
#if lnx_gpu_particles

View File

@ -61,6 +61,25 @@ class MorphTarget {
public inline function setMorphValueDirect(index: Int, value: Float) {
morphWeights.set(index, value);
}
public function getMorphValue(name: String): Float {
var i = morphMap.get(name);
return (i != null) ? morphWeights.get(i) : 0.0;
}
public function hasMorph(name: String): Bool {
return morphMap.exists(name);
}
public function resetWeights() {
for (i in 0...morphWeights.length) {
morphWeights.set(i, 0.0);
}
}
public function getMorphNames(): Array<String> {
return [for (key in morphMap.keys()) key];
}
}
#end

View File

@ -27,7 +27,6 @@ class Object {
public var culled = false; // Object was culled last frame
public var culledMesh = false;
public var culledShadow = false;
public var vertex_groups: Map<String, Array<Vec4>> = null;
public var properties: Map<String, Dynamic> = null;
var isEmpty = false;
@ -95,6 +94,7 @@ class Object {
Removes the game object from the scene.
**/
public function remove() {
Scene.active.removeFromGroups(this);
if (isEmpty && Scene.active != null) Scene.active.empties.remove(this);
if (animation != null) animation.remove();
while (children.length > 0) children[0].remove();
@ -111,16 +111,12 @@ class Object {
@return Object or null
**/
public function getChild(name: String): Object {
if (this.name == name) return this;
else if (this.filename != "") {
if (this.name == name + "_" + this.filename) return this;
}
for (c in children) {
if (c.name == name) return c;
if (c.filename != "" && c.name == name + "_" + c.filename) return c;
var r = c.getChild(name);
if (r != null) return r;
}
return null;
}
@ -145,12 +141,10 @@ class Object {
}
public function getChildOfType<T: Object>(type: Class<T>): T {
if (Std.isOfType(this, type)) return cast this;
else {
for (c in children) {
var r = c.getChildOfType(type);
if (r != null) return r;
}
for (c in children) {
if (Std.isOfType(c, type)) return cast c;
var r = c.getChildOfType(type);
if (r != null) return r;
}
return null;
}

View File

@ -8,6 +8,7 @@ import iron.data.SceneFormat;
import iron.math.Quat;
import iron.math.Vec3;
import iron.math.Vec4;
import iron.object.CurveObject;
import iron.object.MeshObject;
import iron.object.Object;
import iron.system.Time;
@ -19,6 +20,10 @@ import kha.arrays.Uint32Array;
class ParticleSystemCPU {
public var data: ParticleData;
public var speed: FastFloat = 1.0; // Not used yet. Added to go in hand with `ParticleSystemGPU`
public var curveGuides: Array<CurveObject> = [];
public var curveGuideStrength: FastFloat = 1.0;
public var curveGuideSpeed: FastFloat = 1.0;
var paused: Bool = false;
var r: TParticleData;
// Format
@ -37,6 +42,7 @@ class ParticleSystemCPU {
// Velocity
var velocity: Vec3 = new Vec3(0.0, 0.0, 1.0); // object_align_factor: Float32Array
var velocityRandom: FastFloat = 0.0; // factor_random
var normalFactor: FastFloat = 0.0;
// Rotation
var rotation: Bool = false; // use_rotations
@ -114,11 +120,12 @@ class ParticleSystemCPU {
scale = r.particle_size;
scaleRandom = r.size_random;
velocity = new Vec3(r.object_align_factor[0], r.object_align_factor[1], r.object_align_factor[2]).mult(frameRate / baseFrameRate).mult(1 / scale);
velocity = new Vec3(r.object_align_factor[0], r.object_align_factor[1], r.object_align_factor[2]).mult(frameRate / baseFrameRate);
velocityRandom = r.factor_random * (frameRate / baseFrameRate);
normalFactor = r.normal_factor * (frameRate / baseFrameRate);
if (Scene.active.raw.gravity != null) {
gravity = new Vec3(Scene.active.raw.gravity[0], Scene.active.raw.gravity[1], Scene.active.raw.gravity[2]).mult(frameRate / baseFrameRate).mult(1 / scale);
gravity = new Vec3(Scene.active.raw.gravity[0], Scene.active.raw.gravity[1], Scene.active.raw.gravity[2]).mult(frameRate / baseFrameRate);
}
gravityFactor = r.weight_gravity * (frameRate / baseFrameRate);
textureFactor = r.weight_texture;
@ -139,39 +146,36 @@ class ParticleSystemCPU {
scaleElementsCount = getRampElementsLength();
scaleRampSizeFactor = getRampSizeFactor();
switch (type) {
case 0: // Emission
loopAnim = {
tick: function () {
spawnTime += Time.delta * Time.scale;
var expected: Int = Math.floor(spawnTime / spawnRate);
while (spawnedParticles < expected && spawnedParticles < count) {
spawnParticle();
spawnedParticles++;
}
updateParticles();
},
target: null,
props: null,
duration: loop ? lifetimeSeconds : lifetimeSeconds * 2,
done: function () {
if (loop) start();
}
}
Scene.active.notifyOnInit(function () {
if (autoStart) start();
});
case 1: // Hair
Scene.active.notifyOnInit(function () {
for (i in 0...count) spawnParticle();
});
default:
}
Scene.active.notifyOnInit(function () {
for (i in 0...count) addToPool();
switch (type) {
case 0: // Emission
loopAnim = {
tick: function () {
if (paused) return;
spawnTime += Time.delta;
var expected: Int = Math.floor(spawnTime / spawnRate);
while (spawnedParticles < expected && spawnedParticles < count) {
spawnParticle();
spawnedParticles++;
}
updateParticles();
},
target: null,
props: null,
duration: loop ? lifetimeSeconds : lifetimeSeconds * 2,
done: function () {
if (loop) start();
}
}
if (autoStart) start();
case 1: // Hair
for (i in 0...count) spawnParticle();
default:
}
});
});
}
@ -182,14 +186,12 @@ class ParticleSystemCPU {
Tween.to(loopAnim);
}
// TODO
public function pause() {
paused = true;
}
// TODO
public function resume() {
paused = false;
}
public function stop() {
@ -246,6 +248,8 @@ class ParticleSystemCPU {
var scalePos: FastFloat = owner.data.scalePos;
var scalePosParticle: FastFloat = cast(o, MeshObject).data.scalePos;
var normDir: Vec3 = new Vec3();
// TODO: add all properties from Blender's UI
switch (emitFrom) {
case 0: // Vertices
@ -253,6 +257,8 @@ class ParticleSystemCPU {
var i: Int = Std.int(Math.random() * (pa.values.length / pa.size));
var loc: Vec4 = new Vec4(pa.values[i * pa.size] * normFactor, pa.values[i * pa.size + 1] * normFactor, pa.values[i * pa.size + 2] * normFactor, 1);
if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize();
if (!localCoords) {
loc.applyQuat(objectRot);
loc.add(objectPos);
@ -274,6 +280,9 @@ class ParticleSystemCPU {
var pos: Vec3 = randomPointInTriangle(v0, v1, v2);
var loc: Vec4 = new Vec4(pos.x, pos.y, pos.z, 1).mult(normFactor);
if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize();
if (!localCoords) {
loc.applyQuat(objectRot);
loc.add(objectPos);
@ -285,6 +294,8 @@ class ParticleSystemCPU {
scaleFactorVolume.mult(0.5);
var loc: Vec4 = new Vec4((Math.random() * 2.0 - 1.0) * scaleFactorVolume.x, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z, 1);
if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize();
if (!localCoords) {
loc.applyQuat(objectRot);
loc.add(objectPos);
@ -310,7 +321,9 @@ class ParticleSystemCPU {
var randomZ: FastFloat = (Math.random() * 2 / (scale * particleScale) - 1 / (scale * particleScale)) * velocityRandom;
var g: Vec3 = new Vec3();
var rotatedVelocity: Vec4 = new Vec4(velocity.x + randomX, velocity.y + randomY, velocity.z + randomZ, 1);
if (normalFactor != 0.0) normDir = normDir.mult(normalFactor);
var rotatedVelocity: Vec4 = new Vec4(velocity.x + randomX + normDir.x, velocity.y + randomY + normDir.y, velocity.z + randomZ + normDir.z, 1);
if (!localCoords) rotatedVelocity.applyQuat(objectRot);
if (rotation) {
@ -371,7 +384,7 @@ class ParticleSystemCPU {
function updateParticles() {
for (particle => physics in particlePhysics) {
physics.age += Time.delta * Time.scale;
physics.age += Time.delta;
if (physics.age >= physics.lifetime) {
particlePhysics.remove(particle);
@ -379,14 +392,63 @@ class ParticleSystemCPU {
continue;
}
physics.velocity.x += physics.gravity.x * Time.delta * Time.scale;
physics.velocity.y += physics.gravity.y * Time.delta * Time.scale;
physics.velocity.z += physics.gravity.z * Time.delta * Time.scale;
physics.velocity.x += physics.gravity.x * Time.delta;
physics.velocity.y += physics.gravity.y * Time.delta;
physics.velocity.z += physics.gravity.z * Time.delta;
if (curveGuides != null && curveGuides.length > 0) {
var curveVelX: FastFloat = 0.0;
var curveVelY: FastFloat = 0.0;
var curveVelZ: FastFloat = 0.0;
var validCurves: Int = 0;
for (curve in curveGuides) {
if (curve != null && curve.data != null && curve.data.splines != null && curve.splinesLength > 0) {
var t = physics.age / physics.lifetime;
var tangent = curve.getTangent(t, 0);
tangent.w = 0.0;
tangent.applymat4(curve.transform.world);
tangent.normalize();
var curveLen = curve.getLength(0);
var speed = (curveLen / physics.lifetime) * curveGuideSpeed;
var tgtX = tangent.x * speed;
var tgtY = tangent.y * speed;
var tgtZ = tangent.z * speed;
if (localCoords) {
var targetVel = new Vec4(tgtX, tgtY, tgtZ, 0.0);
var invOwnerRot = new Quat(-owner.transform.rot.x, -owner.transform.rot.y, -owner.transform.rot.z, owner.transform.rot.w);
targetVel.applyQuat(invOwnerRot);
tgtX = targetVel.x;
tgtY = targetVel.y;
tgtZ = targetVel.z;
}
curveVelX += tgtX;
curveVelY += tgtY;
curveVelZ += tgtZ;
validCurves++;
}
}
if (validCurves > 0) {
curveVelX /= validCurves;
curveVelY /= validCurves;
curveVelZ /= validCurves;
physics.velocity.x += (curveVelX - physics.velocity.x) * curveGuideStrength;
physics.velocity.y += (curveVelY - physics.velocity.y) * curveGuideStrength;
physics.velocity.z += (curveVelZ - physics.velocity.z) * curveGuideStrength;
}
}
particle.transform.translate(
physics.velocity.x * Time.delta * Time.scale,
physics.velocity.y * Time.delta * Time.scale,
physics.velocity.z * Time.delta * Time.scale
physics.velocity.x * Time.delta,
physics.velocity.y * Time.delta,
physics.velocity.z * Time.delta
);
if (rotation && dynamicRotation && orientationAxis == 3) setVelocityHair(particle, physics.velocity, randQuat, phaseQuat);

View File

@ -141,10 +141,10 @@ class ParticleSystemGPU {
dimx = object.transform.dim.x;
dimy = object.transform.dim.y;
if (object.tilesheet != null) {
tilesx = object.tilesheet.getTilesX();
tilesy = object.tilesheet.getTilesY();
tilesFramerate = object.tilesheet.action.framerate;
if (object.activeTilesheet != null) {
tilesx = object.activeTilesheet.getTilesX();
tilesy = object.activeTilesheet.getTilesY();
tilesFramerate = object.activeTilesheet.action.framerate;
}
// Animate

View File

@ -1,6 +1,7 @@
package iron.object;
import kha.FastFloat;
import kha.Sound;
import kha.audio1.AudioChannel;
import iron.data.Data;
import iron.data.SceneFormat;
@ -13,9 +14,10 @@ class SpeakerObject extends Object {
public var data: TSpeakerData;
public var paused(default, null) = false;
public var sound(default, null): kha.Sound = null;
public var sound(default, null): Sound = null;
public var channels(default, null): Array<AudioChannel> = [];
public var volume(default, null) : FastFloat;
public var sampleRate(default, null) : Int;
public function new(data: TSpeakerData) {
super();
@ -26,28 +28,32 @@ class SpeakerObject extends Object {
if (data.sound == "") return;
Data.getSound(data.sound, function(sound: kha.Sound) {
this.sound = sound;
Data.getSound(data.sound, function(sound: Sound) {
this.sound = cloneSound(sound);
App.notifyOnInit(init);
});
}
function init() {
sampleRate = sound.sampleRate;
if (data.pitch != 1.0)
sound.sampleRate = Std.int(sampleRate * data.pitch);
if (visible && data.play_on_start) play();
}
public function play() {
if (sound == null || data.muted) return;
public function play(): AudioChannel {
if (sound == null || data.muted) return null;
if (paused) {
for (c in channels) c.play();
paused = false;
return;
return null;
}
var channel = Audio.play(sound, data.loop, data.stream);
if (channel != null) {
channels.push(channel);
if (data.attenuation > 0 && channels.length == 1) App.notifyOnUpdate(update);
}
return channel;
}
public function pause() {
@ -65,14 +71,24 @@ class SpeakerObject extends Object {
data.sound = sound;
Data.getSound(sound, function(sound: kha.Sound) {
this.sound = sound;
Data.getSound(sound, function(sound: Sound) {
this.sound = cloneSound(sound);
});
sampleRate = this.sound.sampleRate;
if (data.pitch != 1.0)
this.sound.sampleRate = Std.int(sampleRate * data.pitch);
}
public function setVolume(volume: FastFloat) {
data.volume = volume;
}
public function setPosition(position: Float) {
for (c in channels)
if (position < c.length) c.position = position;
}
function update() {
if (paused) return;
for (c in channels) if (c.finished) channels.remove(c);
@ -103,6 +119,17 @@ class SpeakerObject extends Object {
super.remove();
}
function cloneSound(sound: Sound): Sound {
if (sound == null) return null;
var s = Type.createEmptyInstance(Sound);
s.compressedData = sound.compressedData;
s.uncompressedData = sound.uncompressedData;
s.sampleRate = sound.sampleRate;
s.length = sound.length;
s.channels = sound.channels;
return s;
}
#end
}

View File

@ -1,11 +1,12 @@
package iron.object;
import iron.App;
import iron.Scene;
import iron.data.SceneFormat;
import iron.system.Time;
import haxe.ds.Map;
@:allow(iron.Scene)
class Tilesheet {
public var tileX: Float = 0.0;
@ -14,7 +15,8 @@ class Tilesheet {
public var flipY: Bool = false;
public var paused: Bool = false;
public var frame: Int = 0;
public var actions: Array<TTilesheetAction>;
public var raw: TTilesheetData = null;
public var actions: Array<TTilesheetAction> = null;
public var action: TTilesheetAction = null;
public var ready: Bool = false;
@ -31,8 +33,11 @@ class Tilesheet {
public function new(tilesheetData: TTilesheetData, ownerObject: MeshObject = null) {
owner = ownerObject;
raw = tilesheetData;
actions = tilesheetData.actions;
Scene.active.tilesheets.push(this);
pendingAction = tilesheetData.start_action;
if ((pendingAction == null || pendingAction == "") && actions.length > 0) {
pendingAction = actions[0].name;
@ -262,8 +267,10 @@ class Tilesheet {
}
public function remove() {
Scene.active.tilesheets.remove(this);
ready = false;
action = null;
raw = null;
actions = null;
owner = null;
currentMesh = null;

View File

@ -106,6 +106,7 @@ class Transform {
Rebuild the matrices, if needed.
**/
public function update() {
if (object.constraints != null && object.constraints.length > 0) dirty = true;
if (dirty) buildMatrix();
}
@ -132,15 +133,11 @@ class Transform {
function composeDelta() {
// Delta transform
var dl = new Vec4().addvecs(loc, dloc);
var ds = new Vec4().setFrom(scale);
ds.x *= dscale.x;
ds.y *= dscale.y;
ds.z *= dscale.z;
var dr = new Quat().fromEuler(_deulerX, _deulerY, _deulerZ);
dr.multquats(dr, rot);
dr.multquats(drot, dr);
local.compose(dl, dr, ds);
dloc.addvecs(loc, dloc);
dscale.addvecs(dscale, scale);
drot.fromEuler(_deulerX, _deulerY, _deulerZ);
drot.multquats(rot, drot);
local.compose(dloc, drot, dscale);
}
/**
@ -154,12 +151,19 @@ class Transform {
if (boneParent != null) local.multmats(boneParent, local);
if (object.parent != null && !localOnly) {
world.multmats3x4(local, object.parent.transform.world);
// Swap multiplication order for linked objects to keep local transform intact
var swapMult: Bool = object.raw != null && object.parent.raw != null && object.parent.raw.group_ref != null && object.parent.raw.group_ref != "";
var a: Mat4 = swapMult ? object.parent.transform.world : local;
var b: Mat4 = swapMult ? local : object.parent.transform.world;
world.multmats3x4(a, b);
}
else {
world.setFrom(local);
}
// Constraints
if (object.constraints != null) for (c in object.constraints) c.apply(this);
worldUnpack.setFrom(world);
if (scaleWorld != 1.0) {
worldUnpack._00 *= scaleWorld;
@ -176,9 +180,6 @@ class Transform {
worldUnpack._23 *= scaleWorld;
}
// Constraints
if (object.constraints != null) for (c in object.constraints) c.apply(this);
computeDim();
// Update children
@ -269,7 +270,7 @@ class Transform {
}
function computeRadius() {
radius = Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z);
radius = 0.5 * Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z);
}
function computeDim() {

View File

@ -428,12 +428,10 @@ class Uniforms {
var v: Vec4 = null;
helpVec.set(0, 0, 0, 0);
switch (c.link) {
#if lnx_debug
case "_input": {
helpVec.set(Input.getMouse().x / iron.App.w(), Input.getMouse().y / iron.App.h(), Input.getMouse().down() ? 1.0 : 0.0, 0.0);
v = helpVec;
}
#end
default:
return false;
}
@ -690,7 +688,7 @@ class Uniforms {
}
case "_hosekSunDirection": {
var w = Scene.active.world;
if (w != null) {
if (w != null && w.raw.sun_direction != null) {
// Clamp Z for night cycle
helpVec.set(w.raw.sun_direction[0],
w.raw.sun_direction[1],
@ -827,9 +825,20 @@ class Uniforms {
}
}
case "_shadowMapSize": {
if (light != null && light.data.raw.cast_shadow) {
var shadowLight = light;
if (shadowLight == null || !shadowLight.data.raw.cast_shadow) {
shadowLight = RenderPath.active.sun;
}
if (shadowLight != null && shadowLight.data.raw.cast_shadow) {
v = helpVec;
v.x = v.y = light.data.raw.shadowmap_size;
v.x = v.y = shadowLight.data.raw.shadowmap_size;
#if lnx_csm
#if (!lnx_shadowmap_atlas)
if (shadowLight.data.raw.type == "sun") {
v.x = shadowLight.data.raw.shadowmap_size * LightObject.cascadeCount;
}
#end
#end
}
}
default:
@ -887,6 +896,11 @@ class Uniforms {
case "_envmapIrradiance": {
fa = Scene.active.world == null ? WorldData.getEmptyIrradiance() : Scene.active.world.probe.irradiance;
}
#if (rp_renderer == "Deferred")
case "_materialParams": {
fa = Scene.active.materialParamsBuffer;
}
#end
#if lnx_clusters
case "_lightsArray": {
fa = LightObject.lightsArray;
@ -903,15 +917,18 @@ class Uniforms {
#end
#end // lnx_clusters
#if lnx_csm
case "_cascadeData": {
for (l in Scene.active.lights) {
if (l.data.raw.type == "sun") {
fa = l.getCascadeData();
break;
}
}
case "_cascadeData": {
var sun = RenderPath.active.sun;
if (sun != null && sun.data.raw.type == "sun") {
fa = sun.getCascadeData();
}
#end
}
#end
#if lnx_shadowmap_atlas
case "_tileBoundsSunArray": {
fa = LightObject.updateSunTileBoundsArray();
}
#end
}
if (fa != null) {
@ -937,6 +954,22 @@ class Uniforms {
}
static function setObjectConstant(g: Graphics, object: Object, location: ConstantLocation, c: TShaderConstant) {
#if lnx_spot
if (c.name == "LWVPSpot") {
var light = getSpot(0);
if (light != null) {
if (object == null) helpMat.setIdentity();
else helpMat.setFrom(object.transform.worldUnpack);
helpMat.multmat(light.VP);
helpMat.multmat(biasMat);
g.setMatrix(location, helpMat.self);
return;
}
}
#end
if (c.link == null) return;
var camera = Scene.active.camera;
@ -1064,32 +1097,30 @@ class Uniforms {
}
}
case "_biasLightWorldViewProjectionMatrixSun": {
for (l in iron.Scene.active.lights) {
if (l.data.raw.type == "sun") {
// object is null for DrawQuad
object == null ? helpMat.setIdentity() : helpMat.setFrom(object.transform.worldUnpack);
helpMat.multmat(l.VP);
helpMat.multmat(biasMat);
#if lnx_shadowmap_atlas
// tile matrix
helpMat2.setIdentity();
// scale [0-1] coords to [0-tilescale]
helpMat2._00 = l.tileScale[0];
helpMat2._11 = l.tileScale[0];
// offset coordinate start from [0, 0] to [tile-start-x, tile-start-y]
helpMat2._30 = l.tileOffsetX[0];
helpMat2._31 = l.tileOffsetY[0];
helpMat.multmat(helpMat2);
#if (!kha_opengl)
helpMat2.setIdentity();
helpMat2._11 = -1.0;
helpMat2._31 = 1.0;
helpMat.multmat(helpMat2);
#end
#end
m = helpMat;
break;
}
var sun = RenderPath.active.sun;
if (sun != null && sun.data.raw.type == "sun") {
// object is null for DrawQuad
object == null ? helpMat.setIdentity() : helpMat.setFrom(object.transform.worldUnpack);
helpMat.multmat(sun.VP);
helpMat.multmat(biasMat);
#if lnx_shadowmap_atlas
// tile matrix
helpMat2.setIdentity();
// scale [0-1] coords to [0-tilescale]
helpMat2._00 = sun.tileScale[0];
helpMat2._11 = sun.tileScale[0];
// offset coordinate start from [0, 0] to [tile-start-x, tile-start-y]
helpMat2._30 = sun.tileOffsetX[0];
helpMat2._31 = sun.tileOffsetY[0];
helpMat.multmat(helpMat2);
#if (!kha_opengl)
helpMat2.setIdentity();
helpMat2._11 = -1.0;
helpMat2._31 = 1.0;
helpMat.multmat(helpMat2);
#end
#end
m = helpMat;
}
}
#if rp_probes
@ -1246,17 +1277,17 @@ class Uniforms {
var vy: Null<kha.FastFloat> = null;
switch (c.link) {
case "_tilesheetOffset": {
var ts = cast(object, MeshObject).tilesheet;
var ts = cast(object, MeshObject).activeTilesheet;
vx = ts.tileX;
vy = ts.tileY;
}
case "_tilesheetFlip": {
var ts = cast(object, MeshObject).tilesheet;
var ts = cast(object, MeshObject).activeTilesheet;
vx = ts.flipX ? 1.0 : 0.0;
vy = ts.flipY ? 1.0 : 0.0;
}
case "_tilesheetTiles": {
var ts = cast(object, MeshObject).tilesheet;
var ts = cast(object, MeshObject).activeTilesheet;
vx = ts.getTilesX();
vy = ts.getTilesY();
}
@ -1373,7 +1404,7 @@ class Uniforms {
if (fa == null) return;
g.setFloats(location, fa);
}
else if (c.type == "int") {
else if (c.type == "int" || c.type == "uint") {
var i: Null<Int> = null;
switch (c.link) {
case "_uid": {

View File

@ -0,0 +1,12 @@
package leenkx.logicnode;
class ActiveSceneObjectNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
return iron.Scene.active.root.getChild(iron.Scene.active.raw.name);
}
}

View File

@ -3,6 +3,8 @@ package leenkx.logicnode;
import iron.data.SceneFormat.TSceneFormat;
import iron.data.Data;
import iron.object.Object;
import iron.object.MeshObject;
import iron.Scene;
class AddParticleToObjectNode extends LogicNode {
@ -22,22 +24,22 @@ class AddParticleToObjectNode extends LogicNode {
if (objFrom == null || objTo == null) return;
var mobjFrom = cast(objFrom, iron.object.MeshObject);
var mobjFrom = cast(objFrom, MeshObject);
var psys = mobjFrom.particleSystems != null ? mobjFrom.particleSystems[slot] :
mobjFrom.particleOwner != null && mobjFrom.particleOwner.particleSystems != null ? mobjFrom.particleOwner.particleSystems[slot] : null;
if (psys == null) return;
var mobjTo = cast(objTo, iron.object.MeshObject);
mobjTo.setupParticleSystem(iron.Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: @:privateAccess psys.r.name});
var mobjTo = cast(objTo, MeshObject);
mobjTo.setupParticleSystem(Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: @:privateAccess psys.r.name});
mobjTo.render_emitter = inputs[4].get();
iron.Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
if (o != null) {
var c: iron.object.MeshObject = cast o;
var c: MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo;
@ -55,7 +57,7 @@ class AddParticleToObjectNode extends LogicNode {
var slot: Int = inputs[3].get();
var mobjTo: Object = inputs[4].get();
var mobjTo = cast(mobjTo, iron.object.MeshObject);
var mobjTo = cast(mobjTo, MeshObject);
#if lnx_json
sceneName += ".json";
@ -65,28 +67,34 @@ class AddParticleToObjectNode extends LogicNode {
Data.getSceneRaw(sceneName, (rawScene: TSceneFormat) -> {
for (obj in rawScene.objects) {
if (obj.name == objectName) {
mobjTo.setupParticleSystem(sceneName, obj.particle_refs[slot]);
mobjTo.render_emitter = inputs[5].get();
for (obj in rawScene.objects) {
if (obj.name == objectName) {
mobjTo.setupParticleSystem(sceneName, obj.particle_refs[slot]);
mobjTo.render_emitter = inputs[5].get();
iron.Scene.active.spawnObject(rawScene.particle_datas[slot].instance_object, null, function(o: Object) {
if (o != null) {
var c: iron.object.MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo;
c.particleIndex = mobjTo.particleChildren.length - 1;
}
}, true, rawScene);
for (i => ps in rawScene.particle_datas)
if (obj.particle_refs[slot].particle == ps.name){
slot = i;
break;
}
var oslot: Int = mobjTo.particleSystems.length-1;
var opsys = mobjTo.particleSystems[oslot];
@:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot]);
Scene.active.spawnObject(rawScene.particle_datas[slot].instance_object, null, function(o: Object) {
if (o != null) {
var c: MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo;
c.particleIndex = mobjTo.particleChildren.length - 1;
}
}, true, rawScene);
break;
var oslot: Int = mobjTo.particleSystems.length-1;
var opsys = mobjTo.particleSystems[oslot];
@:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot]);
break;
}
}
}
});

View File

@ -111,7 +111,9 @@ class AddPhysicsConstraintNode extends LogicNode {
}
}
}
pivotObject.addTrait(con);
con.name = property0;
pivotObject.addTrait(con);
}
#end
runOutput(0);

View File

@ -0,0 +1,46 @@
package leenkx.logicnode;
import iron.object.Object;
#if lnx_bullet
import leenkx.trait.physics.bullet.PhysicsHook;
#end
class AddPhysicsHookNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var obj: Object = inputs[1].get();
var target: Object = inputs[2].get();
var inputVerts: Dynamic = inputs[3].get();
var flattenedVerts: Array<Float> = [];
if (Std.isOfType(inputVerts, Array)) {
var vArray: Array<Dynamic> = cast inputVerts;
for (v in vArray) {
if (v.x != null) {
flattenedVerts.push(v.x);
flattenedVerts.push(v.y);
flattenedVerts.push(v.z);
}
else if (v[0] != null) {
flattenedVerts.push(v[0]);
flattenedVerts.push(v[1]);
flattenedVerts.push(v[2]);
}
}
}
#if lnx_bullet
var hook = obj.getTrait(PhysicsHook);
if (hook == null) {
hook = new PhysicsHook(target.name, flattenedVerts);
obj.addTrait(hook);
}
#end
runOutput(0);
}
}

View File

@ -20,7 +20,6 @@ class AddRigidBodyNode extends LogicNode {
override function run(from: Int) {
object = inputs[1].get();
if (object == null) return;
#if lnx_physics
@ -70,6 +69,7 @@ class AddRigidBodyNode extends LogicNode {
case "Cylinder": shape = Cylinder;
case "Convex Hull": shape = ConvexHull;
case "Mesh": shape = Mesh;
case "Compound Parent": shape = Compound;
}
rb = new RigidBody(shape, mass, friction, bounciness, group, mask);
@ -77,6 +77,48 @@ class AddRigidBodyNode extends LogicNode {
rb.staticObj = !active;
rb.isTriggerObject(trigger);
if (property0 == "Compound Parent") {
var compoundChildren = [];
for (child in object.children) {
var childRb: RigidBody = child.getTrait(RigidBody);
if (childRb != null) {
var childShape = 0;
switch (@:privateAccess childRb.shape) {
case Box: childShape = 0;
case Sphere: childShape = 1;
case ConvexHull: childShape = 2;
case Mesh: childShape = 3;
case Cone: childShape = 4;
case Cylinder: childShape = 5;
case Capsule: childShape = 6;
default: childShape = 0;
}
childRb.remove();
var m = object.transform.world.clone();
m.getInverse(object.transform.world);
m.multmat(child.transform.world);
var loc = new iron.math.Vec4();
var rot = new iron.math.Quat();
var scl = new iron.math.Vec4();
m.decompose(loc, rot, scl);
compoundChildren.push({
shape: childShape,
posX: loc.x,
posY: loc.y,
posZ: loc.z,
rotX: rot.x,
rotY: rot.y,
rotZ: rot.z,
rotW: rot.w,
dimX: child.transform.dim.x,
dimY: child.transform.dim.y,
dimZ: child.transform.dim.z
});
}
}
@:privateAccess rb.compoundChildren = compoundChildren;
}
if (property1) {
rb.linearDamping = linDamp;
rb.angularDamping = angDamp;

View File

@ -0,0 +1,38 @@
package leenkx.logicnode;
import iron.object.Object;
#if lnx_physics_soft
import leenkx.trait.physics.bullet.SoftBody;
#end
class AddSoftBodyNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var obj: Object = inputs[1].get();
if (obj == null) return;
var shape: Int = inputs[2].get(); // 0: Cloth, 1: Volume
var bend: Float = inputs[3].get();
var mass: Float = inputs[4].get();
var margin: Float = inputs[5].get();
var friction: Float = inputs[6].get();
var damping: Float = inputs[7].get();
var pressure: Float = inputs[8].get();
var lStiff: Float = inputs[9].get();
var aStiff: Float = inputs[10].get();
#if lnx_physics_soft
var sb: SoftBody = obj.getTrait(SoftBody);
if (sb == null) {
sb = new SoftBody(shape, bend, mass, margin, friction, damping, lStiff, aStiff, pressure);
obj.addTrait(sb);
}
#end
runOutput(0);
}
}

View File

@ -4,25 +4,38 @@ import iron.object.Object;
class AddTraitNode extends LogicNode {
public var property0: String;
var trait: Dynamic;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var object: Object = inputs[1].get();
var traitName: String = inputs[2].get();
assert(Error, object != null, "Object should not be null");
assert(Error, traitName != null, "Trait name should not be null");
var cname = Type.resolveClass(Main.projectPackage + "." + traitName);
if (cname == null) cname = Type.resolveClass(Main.projectPackage + ".node." + traitName);
assert(Error, cname != null, 'No trait with the name "$traitName" found, make sure that the trait is exported!');
assert(Warning, object.getTrait(cname) == null, 'Object already has the trait "$traitName" applied');
if (property0 == 'TraitName'){
var traitName: String = inputs[2].get();
assert(Error, traitName != null, "Trait name should not be null");
var cname = Type.resolveClass(Main.projectPackage + "." + traitName);
if (cname == null) cname = Type.resolveClass(Main.projectPackage + ".node." + traitName);
assert(Error, cname != null, 'No trait with the name "$traitName" found, make sure that the trait is exported!');
assert(Warning, object.getTrait(cname) == null, 'Object already has the trait "$traitName" applied');
trait = Type.createInstance(cname, []);
} else
trait = inputs[2].get();
var trait = Type.createInstance(cname, []);
object.addTrait(trait);
runOutput(0);
}
override function get(from: Int): Dynamic {
return trait;
}
}

View File

@ -0,0 +1,23 @@
package leenkx.logicnode;
import iron.object.Object;
import iron.object.CurveObject;
class AlongCurveNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var object: Object = inputs[1].get();
var curve: CurveObject = inputs[2].get();
var splineIdx: Int = inputs[3].get();
var forwardAxis: String = inputs[4].get();
var position: Float = inputs[5].get();
curve.follow(object, position, splineIdx, forwardAxis);
runOutput(0);
}
}

View File

@ -0,0 +1,35 @@
package leenkx.logicnode;
import iron.object.Object;
import iron.object.Animation;
import iron.object.ObjectAnimation;
class AnimationNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var object: Object = inputs[0].get();
if (object == null)
return from == 0 ? null : 0;
var animation: Animation = object.animation;
if (animation == null) animation = object.getParentArmature(object.name);
var actions: Array<String> = [];
if (animation.isSkinned)
for(a in animation.armature.actions)
actions.push(a.name);
else
for (a in cast(animation, ObjectAnimation).oactions)
if (a != null)
actions.push(a.objects[0].name);
return from == 0 ? actions : actions.length;
}
}

View File

@ -0,0 +1,29 @@
package leenkx.logicnode;
#if lnx_navigation
import leenkx.trait.navigation.Navigation;
import leenkx.trait.NavMesh;
#end
import iron.object.Object;
import iron.math.Vec4;
class AroundNavigableLocationNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if lnx_navigation
var activeNavMesh: NavMesh = Navigation.active.navMeshes.get(inputs[0].get());
var position: Vec4 = inputs[1].get();
var radius: Float = inputs[2].get();
assert(Error, activeNavMesh != null, "No Navigation Mesh Present");
return activeNavMesh.getRandomPointAround(position, radius);
#end
return null;
}
}

View File

@ -12,7 +12,7 @@ class ArrayAddNode extends LogicNode {
override function run(from: Int) {
ar = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
// "Modify Original" == `false` -> Copy the input array
if (!inputs[2].get()) {

View File

@ -2,7 +2,7 @@ package leenkx.logicnode;
class ArrayGetNextNode extends LogicNode {
var i = 0;
var i = -1;
public function new(tree: LogicTree) {
super(tree);
@ -13,12 +13,12 @@ class ArrayGetNextNode extends LogicNode {
if (ar == null) return null;
var value = ar[i];
if (i < ar.length - 1)
i++;
else
i = 0;
var value = ar[i];
return value;

View File

@ -2,7 +2,7 @@ package leenkx.logicnode;
class ArrayGetPreviousNextNode extends LogicNode {
var i = 0;
var i = -1;
public function new(tree: LogicTree) {
super(tree);

View File

@ -13,7 +13,7 @@ class ArrayInsertNode extends LogicNode {
var index: Int = inputs[2].get();
var value: Dynamic = inputs[3].get();
if (ar == null || value == null) return;
assert(Error, ar != null && value != null, 'Array or Value should not be null');
ar.insert(index, value);

View File

@ -11,7 +11,7 @@ class ArrayLoopNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
index = -1;
for (val in ar) {

View File

@ -10,7 +10,7 @@ class ArrayRemoveNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
var i: Int = inputs[2].get();
if (i < 0) i = ar.length + i;

View File

@ -10,7 +10,7 @@ class ArrayRemoveValueNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
var val: Dynamic = inputs[2].get();

View File

@ -8,7 +8,7 @@ class ArrayResizeNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
var len = inputs[2].get();

View File

@ -8,7 +8,7 @@ class ArraySetNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
var i: Int = inputs[2].get();
var value: Dynamic = inputs[3].get();

View File

@ -10,7 +10,7 @@ class ArraySpliceNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
var i = inputs[2].get();
var len = inputs[3].get();

View File

@ -0,0 +1,62 @@
package leenkx.logicnode;
class AsyncArrayLoopNode extends LogicNode {
var array:Array<Dynamic>;
var index:Int = 0;
var running:Bool = false;
var itemsPerFrame:Int = 1;
public function new(tree:LogicTree) {
super(tree);
}
override function run(from: Int) {
array = inputs[1].get();
itemsPerFrame = inputs[2].get();
index = 0;
if (array == null || array.length == 0) {
runOutput(3);
return;
}
running = true;
tree.notifyOnUpdate(update);
}
function update() {
if (!running) return;
var processed = 0;
while (processed < itemsPerFrame && index < array.length) {
index++;
processed++;
runOutput(0);
if (tree.loopBreak) {
tree.loopBreak = false;
running = false;
tree.removeUpdate(update);
runOutput(2);
return;
}
if (tree.loopContinue) {
tree.loopContinue = false;
continue;
}
}
if (index >= array.length) {
running = false;
tree.removeUpdate(update);
runOutput(3);
}
}
override function get(from: Int): Dynamic {
if (from == 1)
return array[index - 1];
return index - 1;
}
}

View File

@ -0,0 +1,63 @@
package leenkx.logicnode;
class AsyncLoopNode extends LogicNode {
var from:Int;
var to:Int;
var index:Int;
var running:Bool = false;
var itemsPerFrame:Int = 1;
public function new(tree:LogicTree) {
super(tree);
}
override function run(from: Int) {
this.from = inputs[1].get();
this.to = inputs[2].get();
this.itemsPerFrame = inputs[3].get();
index = this.from;
if (this.from >= this.to) {
runOutput(2);
return;
}
running = true;
tree.notifyOnUpdate(update);
}
function update() {
if (!running) return;
var processed = 0;
while (processed < itemsPerFrame && index < to) {
runOutput(0);
index++;
processed++;
if (tree.loopBreak) {
tree.loopBreak = false;
running = false;
tree.removeUpdate(update);
runOutput(2);
return;
}
if (tree.loopContinue) {
tree.loopContinue = false;
continue;
}
}
if (index >= to) {
running = false;
tree.removeUpdate(update);
runOutput(2);
}
}
override function get(from: Int): Dynamic {
return index - 1;
}
}

View File

@ -1,7 +1,5 @@
package leenkx.logicnode;
import iron.object.Object;
class CallFunctionNode extends LogicNode {
var result: Dynamic;
@ -11,8 +9,8 @@ class CallFunctionNode extends LogicNode {
}
override function run(from: Int) {
var object: Dynamic = inputs[1].get();
if (object == null) return;
var trait: Dynamic = inputs[1].get();
if (trait == null){ runOutput(0); return; }
var funName: String = inputs[2].get();
var args: Array<Dynamic> = [];
@ -21,9 +19,9 @@ class CallFunctionNode extends LogicNode {
args.push(inputs[i].get());
}
var func = Reflect.field(object, funName);
var func = Reflect.field(trait, funName);
if (func != null) {
result = Reflect.callMethod(object, func, args);
result = Reflect.callMethod(trait, func, args);
}
runOutput(0);

View File

@ -14,14 +14,16 @@ class CameraGetNode extends LogicNode {
case 3: leenkx.renderpath.Postprocess.camera_uniforms[3];//Camera: Exposure Compensation
case 4: leenkx.renderpath.Postprocess.camera_uniforms[4];//Fisheye Distortion
case 5: leenkx.renderpath.Postprocess.camera_uniforms[5];//DoF AutoFocus §§ If true, it ignores the DoF Distance setting
case 6: leenkx.renderpath.Postprocess.camera_uniforms[6];//DoF Distance
case 7: leenkx.renderpath.Postprocess.camera_uniforms[7];//DoF Focal Length mm
case 8: leenkx.renderpath.Postprocess.camera_uniforms[8];//DoF F-Stop
case 9: leenkx.renderpath.Postprocess.camera_uniforms[9];//Tonemapping Method
case 10: leenkx.renderpath.Postprocess.camera_uniforms[10];//Distort
case 11: leenkx.renderpath.Postprocess.camera_uniforms[11];//Film Grain
case 12: leenkx.renderpath.Postprocess.camera_uniforms[12];//Sharpen
case 13: leenkx.renderpath.Postprocess.camera_uniforms[13];//Vignette
case 6: new iron.math.Vec4(leenkx.renderpath.Postprocess.auto_focus[0], leenkx.renderpath.Postprocess.auto_focus[1], 0, 1); //Auto Focus Value
case 7: leenkx.renderpath.Postprocess.auto_focus[2]; //max blur
case 8: leenkx.renderpath.Postprocess.camera_uniforms[6];//DoF Distance
case 9: leenkx.renderpath.Postprocess.camera_uniforms[7];//DoF Focal Length mm
case 10: leenkx.renderpath.Postprocess.camera_uniforms[8];//DoF F-Stop
case 11: leenkx.renderpath.Postprocess.camera_uniforms[9];//Tonemapping Method
case 12: leenkx.renderpath.Postprocess.camera_uniforms[10];//Distort
case 13: leenkx.renderpath.Postprocess.camera_uniforms[11];//Film Grain
case 14: leenkx.renderpath.Postprocess.camera_uniforms[12];//Sharpen
case 15: leenkx.renderpath.Postprocess.camera_uniforms[13];//Vignette
default: 0.0;
}
}

Some files were not shown because too many files have changed in this diff Show More