@absolutejs/rendering 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,9 +137,24 @@ scene-dependent; the qualification uses software rendering, not a hardware FPS c
137
137
  `createThinFoliageMaterial({ leafTransmission: 0.35 })` creates a standard
138
138
  material with approximate thin-leaf backlighting. Transmission is a bounded
139
139
  fraction (0–1) of direct diffuse; zero retains standard lighting. The shader
140
- uses Three's shadowed incident light, preserving PBR specular, indirect lighting,
141
- fog and tone mapping. Area lights retain standard lighting. This is a lightweight
140
+ uses Three's shadowed incident light, preserving PBR specular,
141
+ fog and tone mapping. Area lights retain standard lighting; hemisphere lights and PMREM diffuse illumination transmit from the reverse side. This is a lightweight
142
142
  leaf approximation, not volumetric subsurface scattering or a complete grass
143
143
  renderer. Distribution, blade shape, wind and terrain integration remain the
144
144
  caller's responsibility. Compose custom shader hooks after the factory hook;
145
145
  use `cloneRenderMaterial` to preserve them when cloning.
146
+
147
+
148
+ `withLeafLighting(fragmentShader, transmission)` and
149
+ `withLeafTransmissionField(fragmentShader, identifier)` apply the same canonical
150
+ thin-sheet model to an existing standard-material shader. The field form expects
151
+ a declared scalar clamped to [0,1]. These helpers preserve the existing Three
152
+ specular paths, including environment multiple-scattering compensation. Compose
153
+ vertex deformation independently and include transmission policy in cache keys.
154
+ An environment-lit leaf uses an additional reverse-normal irradiance lookup;
155
+ this is not a free shader operation. No extra passes or geometry are added.
156
+
157
+ Version 0.6 replaces the factory's view-dependent direct-scattering factor with
158
+ the same thin-sheet diffuse weighting used by the lower-level hooks. Existing
159
+ material colour/transmission settings remain valid, but direct backlighting can
160
+ change. This release also adds sky transmission missing from 0.5.
package/dist/index.d.ts CHANGED
@@ -14,5 +14,6 @@ export declare function cloneRenderMaterial<T extends Material>(source: T): T;
14
14
  export { renderTargetScope, readTargetPixelsScoped, readFloatTargetScoped, } from "./renderTargetScope.js";
15
15
  export { createObjectTriplanarMaterial } from "./objectTriplanar.js";
16
16
  export type { ObjectTriplanarProjection, ObjectTriplanarMaterialParameters, } from "./objectTriplanar.js";
17
- export { createThinFoliageMaterial } from './thinFoliage.js';
18
- export type { ThinFoliageMaterialParameters } from './thinFoliage.js';
17
+ export { createThinFoliageMaterial } from "./thinFoliage.js";
18
+ export type { ThinFoliageMaterialParameters } from "./thinFoliage.js";
19
+ export { withLeafLighting, withLeafTransmissionField, leafLightingGLSL, } from "./leafLighting.js";
package/dist/index.js CHANGED
@@ -77,4 +77,5 @@ export function cloneRenderMaterial(source) {
77
77
  }
78
78
  export { renderTargetScope, readTargetPixelsScoped, readFloatTargetScoped, } from "./renderTargetScope.js";
79
79
  export { createObjectTriplanarMaterial } from "./objectTriplanar.js";
80
- export { createThinFoliageMaterial } from './thinFoliage.js';
80
+ export { createThinFoliageMaterial } from "./thinFoliage.js";
81
+ export { withLeafLighting, withLeafTransmissionField, leafLightingGLSL, } from "./leafLighting.js";
@@ -0,0 +1,7 @@
1
+ /** Bounded thin-sheet diffuse approximation. Thickness values are art calibration,
2
+ * not measured transmittance; direct-light color already includes Three's shadows. */
3
+ export declare const leafLightingGLSL = "\nvec2 leafDiffuseWeights(float cosine, float transmission) {\n float t=clamp(transmission,0.,1.);\n return vec2(max(0.,cosine)*(1.-t),max(0.,-cosine)*t);\n}\nvoid RE_Direct_Leaf(const in IncidentLight light, const in vec3 position,\n const in vec3 normal, const in vec3 viewDirection, const in vec3 clearcoatNormal,\n const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n vec3 previous=reflectedLight.directDiffuse;\n RE_Direct_Physical(light,position,normal,viewDirection,clearcoatNormal,material,reflectedLight);\n vec2 weights=leafDiffuseWeights(dot(normal,light.direction),LEAF_TRANSMISSION);\n reflectedLight.directDiffuse=previous+(reflectedLight.directDiffuse-previous)*(1.-LEAF_TRANSMISSION)\n +light.color*material.diffuseColor*(RECIPROCAL_PI*weights.y);\n}\n#if NUM_HEMI_LIGHTS > 0\nvec3 getLeafHemisphereLightIrradiance(const in HemisphereLight light, const in vec3 normal) {\n return mix(getHemisphereLightIrradiance(light,normal),getHemisphereLightIrradiance(light,-normal),LEAF_TRANSMISSION);\n}\n#define getHemisphereLightIrradiance getLeafHemisphereLightIrradiance\n#endif\n#undef RE_Direct\n#define RE_Direct RE_Direct_Leaf\n";
4
+ export declare function withLeafLighting(shader: string, transmission: number): string;
5
+ /** Per-fragment transmission must already be clamped to [0,1] by its material.
6
+ * Accept a single declared identifier, never arbitrary injected shader text. */
7
+ export declare function withLeafTransmissionField(shader: string, identifier: string): string;
@@ -0,0 +1,64 @@
1
+ import { ShaderChunk } from "three";
2
+ /** Bounded thin-sheet diffuse approximation. Thickness values are art calibration,
3
+ * not measured transmittance; direct-light color already includes Three's shadows. */
4
+ export const leafLightingGLSL = /* glsl */ `
5
+ vec2 leafDiffuseWeights(float cosine, float transmission) {
6
+ float t=clamp(transmission,0.,1.);
7
+ return vec2(max(0.,cosine)*(1.-t),max(0.,-cosine)*t);
8
+ }
9
+ void RE_Direct_Leaf(const in IncidentLight light, const in vec3 position,
10
+ const in vec3 normal, const in vec3 viewDirection, const in vec3 clearcoatNormal,
11
+ const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
12
+ vec3 previous=reflectedLight.directDiffuse;
13
+ RE_Direct_Physical(light,position,normal,viewDirection,clearcoatNormal,material,reflectedLight);
14
+ vec2 weights=leafDiffuseWeights(dot(normal,light.direction),LEAF_TRANSMISSION);
15
+ reflectedLight.directDiffuse=previous+(reflectedLight.directDiffuse-previous)*(1.-LEAF_TRANSMISSION)
16
+ +light.color*material.diffuseColor*(RECIPROCAL_PI*weights.y);
17
+ }
18
+ #if NUM_HEMI_LIGHTS > 0
19
+ vec3 getLeafHemisphereLightIrradiance(const in HemisphereLight light, const in vec3 normal) {
20
+ return mix(getHemisphereLightIrradiance(light,normal),getHemisphereLightIrradiance(light,-normal),LEAF_TRANSMISSION);
21
+ }
22
+ #define getHemisphereLightIrradiance getLeafHemisphereLightIrradiance
23
+ #endif
24
+ #undef RE_Direct
25
+ #define RE_Direct RE_Direct_Leaf
26
+ `;
27
+ export function withLeafLighting(shader, transmission) {
28
+ if (!Number.isFinite(transmission) || transmission < 0 || transmission > 1)
29
+ throw Error("Leaf transmission must be in [0,1]");
30
+ return withLeafExpression(shader, transmission.toFixed(4));
31
+ }
32
+ /** Per-fragment transmission must already be clamped to [0,1] by its material.
33
+ * Accept a single declared identifier, never arbitrary injected shader text. */
34
+ export function withLeafTransmissionField(shader, identifier) {
35
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier) ||
36
+ identifier.startsWith("gl_"))
37
+ throw Error("Invalid leaf transmission field identifier");
38
+ return withLeafExpression(shader, identifier);
39
+ }
40
+ function withLeafExpression(shader, expression) {
41
+ const marker = "#include <lights_physical_pars_fragment>";
42
+ if (shader.split(marker).length !== 2)
43
+ throw Error("Three physical lighting hook missing");
44
+ // Three r186 shares irradiance with multiple-scattering specular compensation.
45
+ // Patch only its diffuse accumulation; changing getIBLIrradiance globally
46
+ // would also transmit a portion of the specular response through the leaf.
47
+ const diffuse = "vec3 indirectDiffuse = diffuse * cosineWeightedIrradiance;";
48
+ const physical = ShaderChunk.lights_physical_pars_fragment;
49
+ if (physical.split(diffuse).length !== 2)
50
+ throw Error("Three indirect diffuse hook missing");
51
+ return shader.replace(marker, "\n#define LEAF_TRANSMISSION " +
52
+ expression +
53
+ "\n" +
54
+ physical.replace(diffuse, `
55
+ #ifdef USE_ENVMAP
56
+ vec3 leafIrradiance = mix(irradiance, getIBLIrradiance(-geometryNormal), LEAF_TRANSMISSION);
57
+ vec3 indirectDiffuse = diffuse * leafIrradiance * RECIPROCAL_PI;
58
+ #else
59
+ ${diffuse}
60
+ #endif
61
+ `) +
62
+ "\n" +
63
+ leafLightingGLSL);
64
+ }
@@ -1,11 +1,11 @@
1
- import { MeshStandardMaterial, type MeshStandardMaterialParameters } from 'three';
1
+ import { MeshStandardMaterial, type MeshStandardMaterialParameters } from "three";
2
2
  export type ThinFoliageMaterialParameters = MeshStandardMaterialParameters & {
3
- /** Fraction of direct diffuse allocated to approximate leaf transmission, 0..1. */
3
+ /** Fraction of diffuse illumination allocated to approximate leaf transmission, 0..1. */
4
4
  leafTransmission?: number;
5
5
  };
6
- /** Approximate thin-leaf backlighting using Three's already shadowed direct light.
6
+ /** Approximate thin-leaf backlighting using Three's shadowed direct and directional sky light.
7
7
  * Not volumetric transmission. No extra render pass, texture, or animation loop.
8
- * Area-light and environment paths retain standard shading. Use
8
+ * Sky diffuse is transmitted; specular and area lights retain standard shading. Use
9
9
  * cloneRenderMaterial to preserve shader hooks when cloning.
10
10
  */
11
11
  export declare function createThinFoliageMaterial(options?: ThinFoliageMaterialParameters): MeshStandardMaterial;
@@ -1,34 +1,27 @@
1
- import { DoubleSide, MeshStandardMaterial } from 'three';
2
- /** Approximate thin-leaf backlighting using Three's already shadowed direct light.
1
+ import { withLeafTransmissionField } from "./leafLighting.js";
2
+ import { DoubleSide, MeshStandardMaterial, } from "three";
3
+ /** Approximate thin-leaf backlighting using Three's shadowed direct and directional sky light.
3
4
  * Not volumetric transmission. No extra render pass, texture, or animation loop.
4
- * Area-light and environment paths retain standard shading. Use
5
+ * Sky diffuse is transmitted; specular and area lights retain standard shading. Use
5
6
  * cloneRenderMaterial to preserve shader hooks when cloning.
6
7
  */
7
8
  export function createThinFoliageMaterial(options = {}) {
8
9
  const { leafTransmission = 0.35, ...parameters } = options;
9
- if (!Number.isFinite(leafTransmission) || leafTransmission < 0 || leafTransmission > 1)
10
- throw Error('Invalid leaf transmission');
11
- const material = new MeshStandardMaterial({ side: DoubleSide, roughness: 0.85, ...parameters });
12
- material.onBeforeCompile = shader => {
13
- const anchor = '#include <lights_physical_pars_fragment>';
14
- if (shader.fragmentShader.split(anchor).length !== 2)
15
- throw Error('Unsupported thin-foliage shader');
10
+ if (!Number.isFinite(leafTransmission) ||
11
+ leafTransmission < 0 ||
12
+ leafTransmission > 1)
13
+ throw Error("Invalid leaf transmission");
14
+ const material = new MeshStandardMaterial({
15
+ side: DoubleSide,
16
+ roughness: 0.85,
17
+ ...parameters,
18
+ });
19
+ material.onBeforeCompile = (shader) => {
16
20
  shader.uniforms.absLeafTransmission = { value: leafTransmission };
17
- shader.fragmentShader = shader.fragmentShader.replace(anchor, `${anchor}
18
- uniform float absLeafTransmission;
19
- void absLeafDirect(const in IncidentLight light, const in vec3 p, const in vec3 n,
20
- const in vec3 v, const in vec3 coatN, const in PhysicalMaterial m, inout ReflectedLight result) {
21
- vec3 previousDiffuse=result.directDiffuse;
22
- RE_Direct_Physical(light,p,n,v,coatN,m,result);
23
- float through=max(0.,dot(-n,light.direction));
24
- float forward=max(0.,dot(v,-light.direction));
25
- vec3 transmitted=light.color*BRDF_Lambert(m.diffuseColor)*through*(.5+.5*forward*forward);
26
- result.directDiffuse=previousDiffuse+mix(result.directDiffuse-previousDiffuse,transmitted,absLeafTransmission);
27
- }
28
- #undef RE_Direct
29
- #define RE_Direct absLeafDirect
30
- `);
21
+ shader.fragmentShader =
22
+ "uniform float absLeafTransmission;\n" +
23
+ withLeafTransmissionField(shader.fragmentShader, "absLeafTransmission");
31
24
  };
32
- material.customProgramCacheKey = () => `absolute-thin-foliage-1:${leafTransmission}`;
25
+ material.customProgramCacheKey = () => `absolute-thin-foliage-2:${leafTransmission}`;
33
26
  return material;
34
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/rendering",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Three.js material ownership, destination coverage, rigid-object triplanar mapping and isolated React Spring hosts.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,7 +39,8 @@
39
39
  "test:browser": "bun scripts/verify-browser.cjs",
40
40
  "test:spring:browser": "bun scripts/verify-spring.cjs",
41
41
  "test:triplanar:browser": "bun scripts/verify-triplanar.cjs",
42
- "test:foliage:browser": "bun scripts/verify-foliage.cjs"
42
+ "test:foliage:browser": "bun scripts/verify-foliage.cjs",
43
+ "test:foliage:environment": "bun scripts/verify-foliage-environment.cjs"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "three": ">=0.186.0 <0.187.0",