@absolutejs/rendering 0.3.0 → 0.5.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 +56 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/objectTriplanar.d.ts +18 -0
- package/dist/objectTriplanar.js +149 -0
- package/dist/thinFoliage.d.ts +11 -0
- package/dist/thinFoliage.js +34 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -87,3 +87,59 @@ React Spring peers are optional for the core material utilities, but required
|
|
|
87
87
|
when using this subpath. `@react-spring/three` supplies host types only; it is not
|
|
88
88
|
runtime-imported by this entry. The tested versions are React 19.3, Fiber 9.8.2,
|
|
89
89
|
Three 0.186.1 and React Spring 10.1.2.
|
|
90
|
+
|
|
91
|
+
## Rigid-object triplanar materials
|
|
92
|
+
|
|
93
|
+
`createObjectTriplanarMaterial` keeps color, normal detail and roughness aligned
|
|
94
|
+
without depending on mesh UVs. Coordinates remain local to the geometry, so a
|
|
95
|
+
moving rigid object does not slide through a world-space texture field.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { createObjectTriplanarMaterial } from '@absolutejs/rendering';
|
|
99
|
+
import { RepeatWrapping, SRGBColorSpace, Vector2 } from 'three';
|
|
100
|
+
|
|
101
|
+
for (const texture of [albedo, normal, roughness]) {
|
|
102
|
+
texture.wrapS = texture.wrapT = RepeatWrapping;
|
|
103
|
+
}
|
|
104
|
+
albedo.colorSpace = SRGBColorSpace; // normal and roughness remain non-color data
|
|
105
|
+
const material = createObjectTriplanarMaterial({
|
|
106
|
+
map: albedo,
|
|
107
|
+
normalMap: normal,
|
|
108
|
+
roughnessMap: roughness,
|
|
109
|
+
normalScale: new Vector2(0.65, 0.65),
|
|
110
|
+
projection: { scale: 1, offset: [0, 0, 0], sharpness: 4 },
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Projection scale is repeats per geometry-local unit; UV texture matrices/repeat
|
|
115
|
+
values are replaced by these controls. Offset and scale are copied at creation.
|
|
116
|
+
Sharpness ranges from 1 to 16; scale must be finite, positive and at most 10,000.
|
|
117
|
+
Color uses the texture's color space; roughness uses its green channel. Tangent
|
|
118
|
+
normal maps, including packed RG and negative normal-Y scale, blend as surface
|
|
119
|
+
slopes before perturbing the local normal. Neutral maps preserve base normals.
|
|
120
|
+
|
|
121
|
+
Scope is opaque, rigid geometry. Mesh and non-sheared instanced transforms follow
|
|
122
|
+
Three's normal-matrix conventions. Skinning, active morphs, displacement, bump maps,
|
|
123
|
+
object-space normal maps and video albedo are rejected. Transparent, alpha-test,
|
|
124
|
+
alpha-hash and alpha-to-coverage surfaces are rejected because they need matching
|
|
125
|
+
shadow coverage shaders. Other StandardMaterial channels retain their normal Three
|
|
126
|
+
behavior. The helper is for WebGLRenderer; it is not a WebGPU/TSL implementation.
|
|
127
|
+
|
|
128
|
+
The material borrows textures and does not alter or dispose them. Use
|
|
129
|
+
`cloneRenderMaterial` to retain shader hooks when cloning. Dispose material and
|
|
130
|
+
owned textures separately. Standard lighting, shadows and geometry stay intact.
|
|
131
|
+
With color, normal and roughness present, the shader uses nine texture samples
|
|
132
|
+
instead of three UV samples, with no extra draw or render pass. Hardware cost is
|
|
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, indirect lighting,
|
|
141
|
+
fog and tone mapping. Area lights retain standard lighting. 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.
|
package/dist/index.d.ts
CHANGED
|
@@ -12,3 +12,7 @@ export declare function bindAlphaToCoverageDestination(material: Material, enabl
|
|
|
12
12
|
* cannot alter another. No source material is mutated. */
|
|
13
13
|
export declare function cloneRenderMaterial<T extends Material>(source: T): T;
|
|
14
14
|
export { renderTargetScope, readTargetPixelsScoped, readFloatTargetScoped, } from "./renderTargetScope.js";
|
|
15
|
+
export { createObjectTriplanarMaterial } from "./objectTriplanar.js";
|
|
16
|
+
export type { ObjectTriplanarProjection, ObjectTriplanarMaterialParameters, } from "./objectTriplanar.js";
|
|
17
|
+
export { createThinFoliageMaterial } from './thinFoliage.js';
|
|
18
|
+
export type { ThinFoliageMaterialParameters } from './thinFoliage.js';
|
package/dist/index.js
CHANGED
|
@@ -76,3 +76,5 @@ export function cloneRenderMaterial(source) {
|
|
|
76
76
|
return copy;
|
|
77
77
|
}
|
|
78
78
|
export { renderTargetScope, readTargetPixelsScoped, readFloatTargetScoped, } from "./renderTargetScope.js";
|
|
79
|
+
export { createObjectTriplanarMaterial } from "./objectTriplanar.js";
|
|
80
|
+
export { createThinFoliageMaterial } from './thinFoliage.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MeshStandardMaterial, type MeshStandardMaterialParameters } from "three";
|
|
2
|
+
export type ObjectTriplanarProjection = {
|
|
3
|
+
/** Repeats per geometry-local unit; does not swim when an object moves. */
|
|
4
|
+
scale?: number;
|
|
5
|
+
/** Stable geometry-local phase, shared by color, normal and roughness. */
|
|
6
|
+
offset?: readonly [number, number, number];
|
|
7
|
+
/** Axis blending exponent. One is broad; larger values favor the dominant axis. */
|
|
8
|
+
sharpness?: number;
|
|
9
|
+
};
|
|
10
|
+
export type ObjectTriplanarMaterialParameters = MeshStandardMaterialParameters & {
|
|
11
|
+
projection?: ObjectTriplanarProjection;
|
|
12
|
+
};
|
|
13
|
+
/** UV-independent color, tangent-normal and roughness projection for rigid meshes.
|
|
14
|
+
* Borrows textures. Uses MeshStandardMaterial lighting/shadows and one draw.
|
|
15
|
+
* Instancing follows Three's non-sheared instance normal transform. Skinning,
|
|
16
|
+
* active morphs and displacement are outside this material's rigid-object scope.
|
|
17
|
+
* Use cloneRenderMaterial when cloning so the authored shader hooks are retained. */
|
|
18
|
+
export declare function createObjectTriplanarMaterial(options?: ObjectTriplanarMaterialParameters): MeshStandardMaterial;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { MeshStandardMaterial, TangentSpaceNormalMap, Vector3, } from "three";
|
|
2
|
+
const revision = "absolute-object-triplanar-1";
|
|
3
|
+
function replaceOnce(source, anchor, replacement) {
|
|
4
|
+
if (source.split(anchor).length !== 2)
|
|
5
|
+
throw Error(`Unsupported Three shader anchor: ${anchor}`);
|
|
6
|
+
return source.replace(anchor, replacement);
|
|
7
|
+
}
|
|
8
|
+
const varying = `
|
|
9
|
+
varying vec3 absTriPosition;
|
|
10
|
+
varying vec3 absTriObjectNormal;
|
|
11
|
+
varying mat3 absTriNormalToView;
|
|
12
|
+
`;
|
|
13
|
+
const functions = `
|
|
14
|
+
${varying}
|
|
15
|
+
uniform float absTriScale;
|
|
16
|
+
uniform vec3 absTriOffset;
|
|
17
|
+
uniform float absTriSharpness;
|
|
18
|
+
vec4 absTriSample(sampler2D tex, vec3 p, vec3 dx, vec3 dy, vec3 w) {
|
|
19
|
+
return textureGrad(tex,p.yz,dx.yz,dy.yz)*w.x
|
|
20
|
+
+textureGrad(tex,p.xz,dx.xz,dy.xz)*w.y
|
|
21
|
+
+textureGrad(tex,p.xy,dx.xy,dy.xy)*w.z;
|
|
22
|
+
}
|
|
23
|
+
#ifdef USE_NORMALMAP_TANGENTSPACE
|
|
24
|
+
vec2 absTriSlope(vec3 encoded) {
|
|
25
|
+
vec3 n=encoded*2.-1.;
|
|
26
|
+
#ifdef USE_PACKED_NORMALMAP
|
|
27
|
+
n.z=sqrt(max(0.,1.-dot(n.xy,n.xy)));
|
|
28
|
+
#endif
|
|
29
|
+
return -n.xy*normalScale/max(n.z,.1);
|
|
30
|
+
}
|
|
31
|
+
vec3 absTriGradient(vec3 p,vec3 dx,vec3 dy,vec3 w) {
|
|
32
|
+
vec2 x=absTriSlope(textureGrad(normalMap,p.yz,dx.yz,dy.yz).xyz);
|
|
33
|
+
vec2 y=absTriSlope(textureGrad(normalMap,p.xz,dx.xz,dy.xz).xyz);
|
|
34
|
+
vec2 z=absTriSlope(textureGrad(normalMap,p.xy,dx.xy,dy.xy).xyz);
|
|
35
|
+
return vec3(0.,x.x,x.y)*w.x+vec3(y.x,0.,y.y)*w.y+vec3(z.x,z.y,0.)*w.z;
|
|
36
|
+
}
|
|
37
|
+
#endif
|
|
38
|
+
`;
|
|
39
|
+
const prepare = `
|
|
40
|
+
vec3 absTriN=normalize(absTriObjectNormal);
|
|
41
|
+
#ifdef FLAT_SHADED
|
|
42
|
+
vec3 absTriArea=cross(dFdx(absTriPosition),dFdy(absTriPosition));
|
|
43
|
+
if(dot(absTriArea,absTriArea)>1e-20) {
|
|
44
|
+
absTriArea=normalize(absTriArea);
|
|
45
|
+
absTriN=dot(absTriArea,absTriN)<0.?-absTriArea:absTriArea;
|
|
46
|
+
}
|
|
47
|
+
#endif
|
|
48
|
+
vec3 absTriW=pow(abs(absTriN),vec3(absTriSharpness));
|
|
49
|
+
absTriW/=max(dot(absTriW,vec3(1.)),1e-20);
|
|
50
|
+
vec3 absTriP=absTriPosition*absTriScale+absTriOffset;
|
|
51
|
+
vec3 absTriDx=dFdx(absTriP),absTriDy=dFdy(absTriP);
|
|
52
|
+
`;
|
|
53
|
+
/** UV-independent color, tangent-normal and roughness projection for rigid meshes.
|
|
54
|
+
* Borrows textures. Uses MeshStandardMaterial lighting/shadows and one draw.
|
|
55
|
+
* Instancing follows Three's non-sheared instance normal transform. Skinning,
|
|
56
|
+
* active morphs and displacement are outside this material's rigid-object scope.
|
|
57
|
+
* Use cloneRenderMaterial when cloning so the authored shader hooks are retained. */
|
|
58
|
+
export function createObjectTriplanarMaterial(options = {}) {
|
|
59
|
+
const { projection = {}, ...parameters } = options;
|
|
60
|
+
const scale = projection.scale ?? 1;
|
|
61
|
+
const sharpness = projection.sharpness ?? 4;
|
|
62
|
+
const offset = [...(projection.offset ?? [0, 0, 0])];
|
|
63
|
+
if (!Number.isFinite(scale) || scale <= 0 || scale > 10000)
|
|
64
|
+
throw Error("Invalid triplanar scale");
|
|
65
|
+
if (!Number.isFinite(sharpness) || sharpness < 1 || sharpness > 16)
|
|
66
|
+
throw Error("Invalid triplanar sharpness");
|
|
67
|
+
if (offset.length !== 3 ||
|
|
68
|
+
offset.some((v) => !Number.isFinite(v) || Math.abs(v) > 1e6))
|
|
69
|
+
throw Error("Invalid triplanar offset");
|
|
70
|
+
if (parameters.bumpMap ||
|
|
71
|
+
parameters.displacementMap ||
|
|
72
|
+
(parameters.normalMapType !== undefined &&
|
|
73
|
+
parameters.normalMapType !== TangentSpaceNormalMap) ||
|
|
74
|
+
parameters.map?.isVideoTexture)
|
|
75
|
+
throw Error("Unsupported object-triplanar map type");
|
|
76
|
+
if (parameters.transparent ||
|
|
77
|
+
parameters.alphaHash ||
|
|
78
|
+
parameters.alphaToCoverage ||
|
|
79
|
+
(parameters.alphaTest ?? 0) > 0)
|
|
80
|
+
throw Error("Object triplanar mapping requires opaque coverage");
|
|
81
|
+
const material = new MeshStandardMaterial(parameters);
|
|
82
|
+
material.customProgramCacheKey = () => revision;
|
|
83
|
+
material.onBeforeRender = function (_renderer, _scene, _camera, _geometry, object) {
|
|
84
|
+
if (this.transparent ||
|
|
85
|
+
this.alphaHash ||
|
|
86
|
+
this.alphaToCoverage ||
|
|
87
|
+
this.alphaTest > 0)
|
|
88
|
+
throw Error("Object triplanar mapping requires opaque coverage");
|
|
89
|
+
if (this.bumpMap ||
|
|
90
|
+
this.displacementMap ||
|
|
91
|
+
this.normalMapType !== TangentSpaceNormalMap ||
|
|
92
|
+
this.map?.isVideoTexture)
|
|
93
|
+
throw Error("Unsupported object-triplanar map type");
|
|
94
|
+
const mesh = object;
|
|
95
|
+
if (mesh.isSkinnedMesh ||
|
|
96
|
+
mesh.morphTargetInfluences?.some((value) => value !== 0))
|
|
97
|
+
throw Error("Object triplanar mapping requires rigid geometry");
|
|
98
|
+
};
|
|
99
|
+
material.onBeforeCompile = (shader) => {
|
|
100
|
+
shader.uniforms.absTriScale = { value: scale };
|
|
101
|
+
shader.uniforms.absTriSharpness = { value: sharpness };
|
|
102
|
+
shader.uniforms.absTriOffset = {
|
|
103
|
+
value: new Vector3(offset[0], offset[1], offset[2]),
|
|
104
|
+
};
|
|
105
|
+
shader.vertexShader = replaceOnce(shader.vertexShader, "#include <common>", `#include <common>\n${varying}`);
|
|
106
|
+
shader.vertexShader = replaceOnce(shader.vertexShader, "#include <defaultnormal_vertex>", `#include <defaultnormal_vertex>
|
|
107
|
+
mat3 absTriFrame=mat3(1.);
|
|
108
|
+
#ifdef USE_BATCHING
|
|
109
|
+
absTriFrame=mat3(bm[0]/dot(bm[0],bm[0]),bm[1]/dot(bm[1],bm[1]),bm[2]/dot(bm[2],bm[2]))*absTriFrame;
|
|
110
|
+
#endif
|
|
111
|
+
#ifdef USE_INSTANCING
|
|
112
|
+
absTriFrame=mat3(im[0]/dot(im[0],im[0]),im[1]/dot(im[1],im[1]),im[2]/dot(im[2],im[2]))*absTriFrame;
|
|
113
|
+
#endif
|
|
114
|
+
absTriNormalToView=normalMatrix*absTriFrame;
|
|
115
|
+
absTriObjectNormal=objectNormal;
|
|
116
|
+
`);
|
|
117
|
+
shader.vertexShader = replaceOnce(shader.vertexShader, "#include <begin_vertex>", "#include <begin_vertex>\nabsTriPosition=transformed;");
|
|
118
|
+
// Normal-map declarations precede helper functions that use their uniforms.
|
|
119
|
+
shader.fragmentShader = replaceOnce(shader.fragmentShader, "#include <normalmap_pars_fragment>", `#include <normalmap_pars_fragment>\n${functions}`);
|
|
120
|
+
shader.fragmentShader = replaceOnce(shader.fragmentShader, "#include <map_fragment>", `${prepare}
|
|
121
|
+
#ifdef USE_MAP
|
|
122
|
+
diffuseColor*=absTriSample(map,absTriP,absTriDx,absTriDy,absTriW);
|
|
123
|
+
#endif
|
|
124
|
+
`);
|
|
125
|
+
shader.fragmentShader = replaceOnce(shader.fragmentShader, "#include <roughnessmap_fragment>", `
|
|
126
|
+
float roughnessFactor=roughness;
|
|
127
|
+
#ifdef USE_ROUGHNESSMAP
|
|
128
|
+
roughnessFactor*=absTriSample(roughnessMap,absTriP,absTriDx,absTriDy,absTriW).g;
|
|
129
|
+
#endif
|
|
130
|
+
`);
|
|
131
|
+
shader.fragmentShader = replaceOnce(shader.fragmentShader, "#include <normal_fragment_maps>", `
|
|
132
|
+
#ifdef USE_NORMALMAP_TANGENTSPACE
|
|
133
|
+
vec3 absTriG=absTriGradient(absTriP,absTriDx,absTriDy,absTriW);
|
|
134
|
+
vec3 absTriSurfaceG=absTriG-absTriN*dot(absTriG,absTriN);
|
|
135
|
+
if(dot(absTriSurfaceG,absTriSurfaceG)>1e-20) {
|
|
136
|
+
vec3 absTriBumped=normalize(absTriNormalToView*(absTriN-absTriSurfaceG));
|
|
137
|
+
#ifdef FLIP_SIDED
|
|
138
|
+
absTriBumped=-absTriBumped;
|
|
139
|
+
#endif
|
|
140
|
+
#ifdef DOUBLE_SIDED
|
|
141
|
+
absTriBumped*=faceDirection;
|
|
142
|
+
#endif
|
|
143
|
+
normal=absTriBumped;
|
|
144
|
+
}
|
|
145
|
+
#endif
|
|
146
|
+
`);
|
|
147
|
+
};
|
|
148
|
+
return material;
|
|
149
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MeshStandardMaterial, type MeshStandardMaterialParameters } from 'three';
|
|
2
|
+
export type ThinFoliageMaterialParameters = MeshStandardMaterialParameters & {
|
|
3
|
+
/** Fraction of direct diffuse allocated to approximate leaf transmission, 0..1. */
|
|
4
|
+
leafTransmission?: number;
|
|
5
|
+
};
|
|
6
|
+
/** Approximate thin-leaf backlighting using Three's already shadowed direct light.
|
|
7
|
+
* Not volumetric transmission. No extra render pass, texture, or animation loop.
|
|
8
|
+
* Area-light and environment paths retain standard shading. Use
|
|
9
|
+
* cloneRenderMaterial to preserve shader hooks when cloning.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createThinFoliageMaterial(options?: ThinFoliageMaterialParameters): MeshStandardMaterial;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DoubleSide, MeshStandardMaterial } from 'three';
|
|
2
|
+
/** Approximate thin-leaf backlighting using Three's already shadowed direct light.
|
|
3
|
+
* Not volumetric transmission. No extra render pass, texture, or animation loop.
|
|
4
|
+
* Area-light and environment paths retain standard shading. Use
|
|
5
|
+
* cloneRenderMaterial to preserve shader hooks when cloning.
|
|
6
|
+
*/
|
|
7
|
+
export function createThinFoliageMaterial(options = {}) {
|
|
8
|
+
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');
|
|
16
|
+
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
|
+
`);
|
|
31
|
+
};
|
|
32
|
+
material.customProgramCacheKey = () => `absolute-thin-foliage-1:${leafTransmission}`;
|
|
33
|
+
return material;
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/rendering",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
"test": "bun test ./tests",
|
|
38
38
|
"check:package": "bun run build && bun run test",
|
|
39
39
|
"test:browser": "bun scripts/verify-browser.cjs",
|
|
40
|
-
"test:spring:browser": "bun scripts/verify-spring.cjs"
|
|
40
|
+
"test:spring:browser": "bun scripts/verify-spring.cjs",
|
|
41
|
+
"test:triplanar:browser": "bun scripts/verify-triplanar.cjs",
|
|
42
|
+
"test:foliage:browser": "bun scripts/verify-foliage.cjs"
|
|
41
43
|
},
|
|
42
44
|
"peerDependencies": {
|
|
43
45
|
"three": ">=0.186.0 <0.187.0",
|