@absolutejs/rendering 0.4.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
@@ -131,3 +131,30 @@ owned textures separately. Standard lighting, shadows and geometry stay intact.
131
131
  With color, normal and roughness present, the shader uses nine texture samples
132
132
  instead of three UV samples, with no extra draw or render pass. Hardware cost is
133
133
  scene-dependent; the qualification uses software rendering, not a hardware FPS claim.
134
+
135
+ ### Thin foliage
136
+
137
+ `createThinFoliageMaterial({ leafTransmission: 0.35 })` creates a standard
138
+ material with approximate thin-leaf backlighting. Transmission is a bounded
139
+ fraction (0–1) of direct diffuse; zero retains standard lighting. The shader
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
+ leaf approximation, not volumetric subsurface scattering or a complete grass
143
+ renderer. Distribution, blade shape, wind and terrain integration remain the
144
+ caller's responsibility. Compose custom shader hooks after the factory hook;
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,3 +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";
19
+ export { withLeafLighting, withLeafTransmissionField, leafLightingGLSL, } from "./leafLighting.js";
package/dist/index.js CHANGED
@@ -77,3 +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";
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
+ }
@@ -0,0 +1,11 @@
1
+ import { MeshStandardMaterial, type MeshStandardMaterialParameters } from "three";
2
+ export type ThinFoliageMaterialParameters = MeshStandardMaterialParameters & {
3
+ /** Fraction of diffuse illumination allocated to approximate leaf transmission, 0..1. */
4
+ leafTransmission?: number;
5
+ };
6
+ /** Approximate thin-leaf backlighting using Three's shadowed direct and directional sky light.
7
+ * Not volumetric transmission. No extra render pass, texture, or animation loop.
8
+ * Sky diffuse is transmitted; specular and area lights retain standard shading. Use
9
+ * cloneRenderMaterial to preserve shader hooks when cloning.
10
+ */
11
+ export declare function createThinFoliageMaterial(options?: ThinFoliageMaterialParameters): MeshStandardMaterial;
@@ -0,0 +1,27 @@
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.
4
+ * Not volumetric transmission. No extra render pass, texture, or animation loop.
5
+ * Sky diffuse is transmitted; specular and area lights retain standard shading. Use
6
+ * cloneRenderMaterial to preserve shader hooks when cloning.
7
+ */
8
+ export function createThinFoliageMaterial(options = {}) {
9
+ const { leafTransmission = 0.35, ...parameters } = options;
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) => {
20
+ shader.uniforms.absLeafTransmission = { value: leafTransmission };
21
+ shader.fragmentShader =
22
+ "uniform float absLeafTransmission;\n" +
23
+ withLeafTransmissionField(shader.fragmentShader, "absLeafTransmission");
24
+ };
25
+ material.customProgramCacheKey = () => `absolute-thin-foliage-2:${leafTransmission}`;
26
+ return material;
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/rendering",
3
- "version": "0.4.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",
@@ -38,7 +38,9 @@
38
38
  "check:package": "bun run build && bun run test",
39
39
  "test:browser": "bun scripts/verify-browser.cjs",
40
40
  "test:spring:browser": "bun scripts/verify-spring.cjs",
41
- "test:triplanar:browser": "bun scripts/verify-triplanar.cjs"
41
+ "test:triplanar:browser": "bun scripts/verify-triplanar.cjs",
42
+ "test:foliage:browser": "bun scripts/verify-foliage.cjs",
43
+ "test:foliage:environment": "bun scripts/verify-foliage-environment.cjs"
42
44
  },
43
45
  "peerDependencies": {
44
46
  "three": ">=0.186.0 <0.187.0",