@volter/blender-engine 0.1.5 → 0.1.7
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/browser/runtime.ts +31 -2
- package/browser/session.py +563 -11
- package/browser/three/blender-frame-columns.ts +6 -0
- package/browser/three/blender-graph-material.ts +402 -0
- package/browser/three/blender-node-glsl.generated.ts +401 -0
- package/browser/three/blender-node-graph.ts +1026 -0
- package/browser/three/blender-physical-material.ts +162 -0
- package/browser/three/blender-presenter-change.ts +24 -0
- package/browser/three/blender-runtime-geometry.ts +112 -9
- package/browser/three/blender-runtime-lighting.ts +11 -0
- package/browser/three/blender-runtime-view.ts +237 -49
- package/browser/three/blender-texture-samplers.ts +79 -0
- package/browser/three/blender-world-volume.ts +235 -0
- package/browser/three/presenter.ts +6 -1
- package/package.json +1 -1
- package/wasm/BUNDLE.json +13 -25
- package/wasm/blender_browser.data.br +0 -0
- package/wasm/blender_browser.wasm.br +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/** Homogeneous World transport, in scene-linear radiance. Surface depth bounds
|
|
2
|
+
* the camera segment; a World with no hit is unbounded, not a camera-far fog box.
|
|
3
|
+
* Absorption/emission are analytic. Local-light single scattering is numerical;
|
|
4
|
+
* multiple scattering and heterogeneous fields are not implemented here. */
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import type {WorldData, WorldExpression} from './blender-runtime-lighting';
|
|
7
|
+
import {worldField} from './world-field-sampler';
|
|
8
|
+
|
|
9
|
+
export interface WorldMedium {
|
|
10
|
+
extinction: THREE.Vector3;
|
|
11
|
+
emission: THREE.Vector3;
|
|
12
|
+
lobes: {coefficient: THREE.Vector3; g: number; alpha: number}[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function constant(expression: WorldExpression): number | THREE.Vector3 {
|
|
16
|
+
const visit = (value: unknown): void => {
|
|
17
|
+
if (!value || typeof value !== 'object') return;
|
|
18
|
+
if ('kind' in value && ['direction', 'window', 'sky'].includes(String(value.kind)))
|
|
19
|
+
throw new Error(`World volume spatial input ${value.kind} is not implemented`);
|
|
20
|
+
for (const child of Object.values(value)) visit(child);
|
|
21
|
+
};
|
|
22
|
+
visit(expression);
|
|
23
|
+
const value = worldField(expression)(new THREE.Vector3());
|
|
24
|
+
const components = typeof value === 'number' ? [value] : value.toArray();
|
|
25
|
+
if (!components.every(Number.isFinite)) throw new Error('World volume input is not finite');
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function scalar(expression: WorldExpression): number {
|
|
29
|
+
const value = constant(expression);
|
|
30
|
+
if (typeof value !== 'number') throw new Error('World volume scalar input resolved to a color');
|
|
31
|
+
return Math.max(0, value);
|
|
32
|
+
}
|
|
33
|
+
function color(expression: WorldExpression): THREE.Vector3 {
|
|
34
|
+
const value = constant(expression);
|
|
35
|
+
return (typeof value === 'number' ? new THREE.Vector3().setScalar(value) : value.clone()).max(new THREE.Vector3());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Blender IMB_colormanagement_blackbody_temperature_to_rgb, default linear Rec.709.
|
|
39
|
+
// Coefficients from source/blender/imbuf/intern/colormanagement.cc (GPL-2.0-or-later).
|
|
40
|
+
export function blackbody(t: number): THREE.Vector3 {
|
|
41
|
+
if (t < 800) return new THREE.Vector3(5.413294490189271, 0, 0);
|
|
42
|
+
if (t >= 12000) return new THREE.Vector3(.8262954810464208, .9945080501520986, 1.566307710274283);
|
|
43
|
+
const i = [965,1167,1449,1902,3315,6365].filter(limit => t >= limit).length;
|
|
44
|
+
const r = [[1619.19106,-.00205010916,5.02995757],[2488.45471,-.00111330907,3.22621544],
|
|
45
|
+
[3341.43193,-.000486551192,1.76486769],[4094.61742,-.000127446582,.725731635],
|
|
46
|
+
[4670.28036,.0000291258199,.126703442],[4595.09185,.0000287495649,.150345020],
|
|
47
|
+
[3787.17450,.00000935907826,.399075871]][i]!;
|
|
48
|
+
const g = [[-488.999748,.000604330754,-.0755807526],[-755.994277,.000316730098,.478306139],
|
|
49
|
+
[-1023.63977,.000120223470,.936662319],[-1265.71316,.00000487340896,1.27054498],
|
|
50
|
+
[-1425.29332,-.0000401150431,1.43972784],[-1175.54822,-.0000216378048,1.30408023],
|
|
51
|
+
[-500.799571,-.00000459832026,1.09098763]][i]!;
|
|
52
|
+
const b = [[5.96945309e-11,-4.85742887e-8,-9.70622247e-5,-.00407936148],
|
|
53
|
+
[2.40430366e-11,5.55021075e-8,-1.98503712e-4,.0289312858],
|
|
54
|
+
[-1.40949732e-11,1.89878968e-7,-3.56632824e-4,.0910767778],
|
|
55
|
+
[-3.61460868e-11,2.84822009e-7,-4.93211319e-4,.156723440],
|
|
56
|
+
[-1.97075738e-11,1.75359352e-7,-2.50542825e-4,-.0222783266],
|
|
57
|
+
[-1.61997957e-13,-1.64216008e-8,3.86216271e-4,-.738077418],
|
|
58
|
+
[6.72650283e-13,-2.73078809e-8,4.24098264e-4,-.752335691]][i]!;
|
|
59
|
+
return new THREE.Vector3(r[0]!/t+r[1]!*t+r[2]!, g[0]!/t+g[1]!*t+g[2]!,
|
|
60
|
+
((b[0]!*t+b[1]!)*t+b[2]!)*t+b[3]!).max(new THREE.Vector3());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function worldMedium(world: WorldData | null | undefined): WorldMedium | null {
|
|
64
|
+
if (!world?.volume?.length) return null;
|
|
65
|
+
const medium: WorldMedium = {extinction: new THREE.Vector3(), emission: new THREE.Vector3(), lobes: []};
|
|
66
|
+
for (const node of world.volume) {
|
|
67
|
+
const weight = scalar(node.weight);
|
|
68
|
+
if (weight === 0) continue;
|
|
69
|
+
const c = color(node.color), density = scalar(node.density);
|
|
70
|
+
const absorption = new THREE.Vector3(), scatter = new THREE.Vector3();
|
|
71
|
+
if (node.kind === 'absorption') absorption.set(1-c.x,1-c.y,1-c.z).max(new THREE.Vector3()).multiplyScalar(density);
|
|
72
|
+
if (node.kind === 'scatter' || node.kind === 'principled') {
|
|
73
|
+
scatter.copy(c).multiplyScalar(density);
|
|
74
|
+
const phase = node.kind === 'principled' ? 'HENYEY_GREENSTEIN' : node.phase;
|
|
75
|
+
if (!['HENYEY_GREENSTEIN','DRAINE','RAYLEIGH'].includes(phase))
|
|
76
|
+
throw new Error(`World volume ${phase} phase is not implemented`);
|
|
77
|
+
const anisotropy = constant(node.anisotropy);
|
|
78
|
+
if (typeof anisotropy !== 'number') throw new Error('World volume anisotropy must be scalar');
|
|
79
|
+
medium.lobes.push({coefficient: scatter.clone().multiplyScalar(weight),
|
|
80
|
+
g: phase === 'RAYLEIGH' ? 0 : THREE.MathUtils.clamp(anisotropy, -.999, .999),
|
|
81
|
+
alpha: phase === 'RAYLEIGH' ? 1 : phase === 'DRAINE' ? scalar(node.alpha) : 0});
|
|
82
|
+
}
|
|
83
|
+
if (node.kind === 'principled') {
|
|
84
|
+
const a = color(node.absorption_color);
|
|
85
|
+
absorption.set(Math.max(1-c.x,0)*Math.max(1-Math.sqrt(a.x),0),
|
|
86
|
+
Math.max(1-c.y,0)*Math.max(1-Math.sqrt(a.y),0), Math.max(1-c.z,0)*Math.max(1-Math.sqrt(a.z),0)).multiplyScalar(density);
|
|
87
|
+
const bb = scalar(node.blackbody_intensity);
|
|
88
|
+
if (bb > .001) {
|
|
89
|
+
const t = scalar(node.temperature);
|
|
90
|
+
const power = 5.670373e-14 / Math.PI * (1 + bb * (t**4 - 1));
|
|
91
|
+
medium.emission.add(blackbody(t).multiply(color(node.blackbody_tint)).multiplyScalar(power * weight));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (node.kind === 'principled' || node.kind === 'emission')
|
|
95
|
+
medium.emission.add((node.kind === 'emission' ? c : color(node.emission_color)).multiplyScalar(scalar(node.emission_strength)*weight));
|
|
96
|
+
medium.extinction.add(absorption.add(scatter).multiplyScalar(weight));
|
|
97
|
+
}
|
|
98
|
+
for (const axis of ['x','y','z'] as const)
|
|
99
|
+
if (medium.extinction[axis] === 0 && medium.emission[axis] > 0)
|
|
100
|
+
throw new Error('Unbounded emissive World volume with zero extinction has divergent radiance');
|
|
101
|
+
return medium;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Integral of exp(-sigma*t) on a finite or infinite segment. */
|
|
105
|
+
export function mediumIntegral(sigma: number, distance: number): number {
|
|
106
|
+
return sigma > 0 ? -Math.expm1(-sigma*distance)/sigma : distance;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function phaseDraine(cosine: number, g: number, alpha: number): number {
|
|
110
|
+
return (1-g*g)*(1+alpha*cosine*cosine) /
|
|
111
|
+
(4*Math.PI*(1+alpha*(1+2*g*g)/3)*Math.pow(1+g*g-2*g*cosine,1.5));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Caller owns the returned target until dispose. Construct per revision-owned
|
|
115
|
+
* capture, so a concurrent model edit cannot change its medium or resources. */
|
|
116
|
+
export class WorldVolumePass {
|
|
117
|
+
private readonly target = new THREE.WebGLRenderTarget(1,1,{type: THREE.HalfFloatType, depthBuffer: false});
|
|
118
|
+
private readonly geometry = new THREE.PlaneGeometry(2,2);
|
|
119
|
+
private readonly scene = new THREE.Scene();
|
|
120
|
+
private readonly camera = new THREE.Camera();
|
|
121
|
+
private material: THREE.ShaderMaterial | undefined;
|
|
122
|
+
constructor(readonly medium: WorldMedium) {}
|
|
123
|
+
|
|
124
|
+
render(renderer: THREE.WebGLRenderer, input: THREE.WebGLRenderTarget, scene: THREE.Scene, camera: THREE.Camera): THREE.WebGLRenderTarget {
|
|
125
|
+
if (!input.depthTexture) throw new Error('World volume render requires surface depth');
|
|
126
|
+
const uniforms: Record<string, THREE.IUniform> = {
|
|
127
|
+
surface: {value: input.texture}, depth: {value: input.depthTexture},
|
|
128
|
+
inverseProjection: {value: camera.projectionMatrixInverse}, cameraWorld: {value: camera.matrixWorld},
|
|
129
|
+
orthographic: {value: (camera as THREE.OrthographicCamera).isOrthographicCamera === true},
|
|
130
|
+
sigma: {value: this.medium.extinction}, emission: {value: this.medium.emission},
|
|
131
|
+
};
|
|
132
|
+
let declarations = '', lighting = '';
|
|
133
|
+
let lightIndex = 0, samplers = 2;
|
|
134
|
+
scene.traverseVisible(object => {
|
|
135
|
+
if (!(object instanceof THREE.PointLight || object instanceof THREE.SpotLight || object instanceof THREE.RectAreaLight)) return;
|
|
136
|
+
const light = object, id = lightIndex++;
|
|
137
|
+
const position = light.getWorldPosition(new THREE.Vector3());
|
|
138
|
+
uniforms[`lp${id}`] = {value: position};
|
|
139
|
+
uniforms[`lc${id}`] = {value: light.color.clone().multiplyScalar(light.intensity)};
|
|
140
|
+
declarations += `uniform vec3 lp${id}, lc${id};\n`;
|
|
141
|
+
let visibility = '1.0', spot = '1.0';
|
|
142
|
+
if (light instanceof THREE.SpotLight) {
|
|
143
|
+
uniforms[`ld${id}`] = {value: light.target.getWorldPosition(new THREE.Vector3()).sub(position).normalize()};
|
|
144
|
+
uniforms[`cone${id}`] = {value: new THREE.Vector2(Math.cos(light.angle), Math.cos(light.angle*(1-light.penumbra)))};
|
|
145
|
+
declarations += `uniform vec3 ld${id}; uniform vec2 cone${id};\n`;
|
|
146
|
+
spot = `smoothstep(cone${id}.x, max(cone${id}.x+1e-6,cone${id}.y),dot(-L,ld${id}))`;
|
|
147
|
+
}
|
|
148
|
+
if (!(light instanceof THREE.RectAreaLight) && light.castShadow && light.shadow.map) {
|
|
149
|
+
if (++samplers > renderer.capabilities.maxTextures) throw new Error('World volume shadow samplers exceed this GPU texture-unit limit');
|
|
150
|
+
uniforms[`sm${id}`] = {value: light.shadow.map.texture};
|
|
151
|
+
uniforms[`sx${id}`] = {value: light.shadow.matrix};
|
|
152
|
+
uniforms[`ss${id}`] = {value: light.shadow.mapSize};
|
|
153
|
+
uniforms[`sb${id}`] = {value: light.shadow.bias};
|
|
154
|
+
uniforms[`sn${id}`] = {value: light.shadow.camera.near};
|
|
155
|
+
uniforms[`sf${id}`] = {value: light.shadow.camera.far};
|
|
156
|
+
declarations += `uniform sampler2D sm${id}; uniform mat4 sx${id}; uniform vec2 ss${id}; uniform float sb${id},sn${id},sf${id};\n`;
|
|
157
|
+
visibility = light instanceof THREE.PointLight
|
|
158
|
+
? `getPointShadow(sm${id},ss${id},1.0,sb${id},1.0,sx${id}*vec4(p,1.0),sn${id},sf${id})`
|
|
159
|
+
: `getShadow(sm${id},ss${id},1.0,sb${id},1.0,sx${id}*vec4(p,1.0))`;
|
|
160
|
+
}
|
|
161
|
+
if (light instanceof THREE.RectAreaLight) {
|
|
162
|
+
const rotation = new THREE.Matrix4().extractRotation(light.matrixWorld);
|
|
163
|
+
uniforms[`lx${id}`] = {value: new THREE.Vector3(light.width,0,0).applyMatrix4(rotation)};
|
|
164
|
+
uniforms[`ly${id}`] = {value: new THREE.Vector3(0,light.height,0).applyMatrix4(rotation)};
|
|
165
|
+
uniforms[`ln${id}`] = {value: new THREE.Vector3(0,0,-1).transformDirection(rotation)};
|
|
166
|
+
declarations += `uniform vec3 lx${id},ly${id},ln${id};\n`;
|
|
167
|
+
lighting += `for(int ax=0;ax<4;ax++) for(int ay=0;ay<4;ay++) {
|
|
168
|
+
vec3 delta=lp${id}+lx${id}*((float(ax)+.5)/4.-.5)+ly${id}*((float(ay)+.5)/4.-.5)-p;
|
|
169
|
+
float d=length(delta); vec3 L=delta/max(d,1e-6);
|
|
170
|
+
sum+=lc${id}*exp(-sigma*d)*phase(dot(L,ray))*max(dot(-L,ln${id}),0.)*
|
|
171
|
+
length(lx${id})*length(ly${id})/(16.*max(d*d,1e-4)); }\n`;
|
|
172
|
+
} else {
|
|
173
|
+
uniforms[`cut${id}`] = {value: light.distance};
|
|
174
|
+
uniforms[`decay${id}`] = {value: light.decay};
|
|
175
|
+
declarations += `uniform float cut${id},decay${id};\n`;
|
|
176
|
+
lighting += `{ vec3 delta=lp${id}-p; float d=length(delta); vec3 L=delta/max(d,1e-6);
|
|
177
|
+
float cutoff=cut${id}>0.?pow(clamp(1.-pow(d/cut${id},4.),0.,1.),2.):1.;
|
|
178
|
+
sum+=lc${id}*exp(-sigma*d)*phase(dot(L,ray))*${spot}*${visibility}*cutoff/max(pow(d,decay${id}),.01); }\n`;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
let phase = 'vec3 result=vec3(0.);';
|
|
182
|
+
this.medium.lobes.forEach((lobe,i) => {
|
|
183
|
+
uniforms[`scatter${i}`] = {value: lobe.coefficient};
|
|
184
|
+
uniforms[`g${i}`] = {value: lobe.g}; uniforms[`a${i}`] = {value: lobe.alpha};
|
|
185
|
+
declarations += `uniform vec3 scatter${i}; uniform float g${i},a${i};\n`;
|
|
186
|
+
phase += `result+=scatter${i}*(1.-g${i}*g${i})*(1.+a${i}*c*c)/(12.56637061436*(1.+a${i}*(1.+2.*g${i}*g${i})/3.)*pow(1.+g${i}*g${i}-2.*g${i}*c,1.5));`;
|
|
187
|
+
});
|
|
188
|
+
// The finite quadrature tail is bounded at 20 optical depths (2e-9).
|
|
189
|
+
const positive = this.medium.extinction.toArray().filter(v => v > 0);
|
|
190
|
+
uniforms['range'] = {value: positive.length ? 20/Math.min(...positive) : 1};
|
|
191
|
+
this.material?.dispose();
|
|
192
|
+
this.material = new THREE.ShaderMaterial({uniforms, depthTest: false, depthWrite: false, toneMapped: false,
|
|
193
|
+
defines: {USE_SHADOWMAP: '', NUM_DIR_LIGHT_SHADOWS: 0, NUM_SPOT_LIGHT_SHADOWS: 0, NUM_POINT_LIGHT_SHADOWS: 0, NUM_SPOT_LIGHT_COORDS: 0},
|
|
194
|
+
vertexShader: 'varying vec2 vUv; void main(){vUv=uv;gl_Position=vec4(position.xy,0.,1.);}',
|
|
195
|
+
fragmentShader: `
|
|
196
|
+
#include <common>
|
|
197
|
+
#include <packing>
|
|
198
|
+
#include <shadowmap_pars_fragment>
|
|
199
|
+
varying vec2 vUv; uniform sampler2D surface,depth;
|
|
200
|
+
uniform mat4 inverseProjection,cameraWorld; uniform bool orthographic;
|
|
201
|
+
uniform vec3 sigma,emission; uniform float range;
|
|
202
|
+
${declarations}
|
|
203
|
+
vec3 phase(float c){${phase} return result;}
|
|
204
|
+
vec3 incident(vec3 p,vec3 ray){vec3 sum=vec3(0.);${lighting}return sum;}
|
|
205
|
+
float integral(float s,float d){float x=s*d;return x<.001 ? d*(1.-x*.5+x*x/6.) : (1.-exp(-x))/s;}
|
|
206
|
+
void main(){
|
|
207
|
+
float z=texture2D(depth,vUv).r; vec4 base=texture2D(surface,vUv);
|
|
208
|
+
vec4 v=inverseProjection*vec4(vUv*2.-1.,z*2.-1.,1.);v/=v.w;
|
|
209
|
+
vec3 hit=(cameraWorld*v).xyz;
|
|
210
|
+
vec3 origin=cameraWorld[3].xyz;
|
|
211
|
+
if(orthographic) origin=(cameraWorld*vec4(v.xy,0.,1.)).xyz;
|
|
212
|
+
vec3 ray=normalize(hit-origin); float d=length(hit-origin); bool background=z>=1.;
|
|
213
|
+
vec3 tr=exp(-sigma*d);
|
|
214
|
+
vec3 integrals=vec3(integral(sigma.x,d),integral(sigma.y,d),integral(sigma.z,d));
|
|
215
|
+
if(background){tr=vec3(sigma.x>0.?0.:1.,sigma.y>0.?0.:1.,sigma.z>0.?0.:1.);
|
|
216
|
+
integrals=vec3(sigma.x>0.?1./sigma.x:0.,sigma.y>0.?1./sigma.y:0.,sigma.z>0.?1./sigma.z:0.);}
|
|
217
|
+
vec3 radiance=base.rgb*tr+emission*integrals;
|
|
218
|
+
float end=background?range:min(range,d);
|
|
219
|
+
for(int i=0;i<256;i++){
|
|
220
|
+
float lo=end*pow(float(i)/256.,3.); float hi=end*pow(float(i+1)/256.,3.);
|
|
221
|
+
float t=(lo+hi)*.5;
|
|
222
|
+
radiance+=incident(origin+ray*t,ray)*exp(-sigma*t)*(hi-lo);
|
|
223
|
+
}
|
|
224
|
+
gl_FragColor=vec4(radiance,base.a+(1.-base.a)*(1.-dot(tr,vec3(1./3.))));
|
|
225
|
+
}`});
|
|
226
|
+
this.scene.clear();
|
|
227
|
+
this.scene.add(new THREE.Mesh(this.geometry,this.material));
|
|
228
|
+
this.target.setSize(input.width,input.height);
|
|
229
|
+
const previous = renderer.getRenderTarget();
|
|
230
|
+
try { renderer.setRenderTarget(this.target); renderer.render(this.scene,this.camera); }
|
|
231
|
+
finally { renderer.setRenderTarget(previous); }
|
|
232
|
+
return this.target;
|
|
233
|
+
}
|
|
234
|
+
dispose(): void {this.material?.dispose();this.geometry.dispose();this.target.dispose();this.scene.clear();}
|
|
235
|
+
}
|
|
@@ -191,7 +191,10 @@ export function createPresenter(options: PresenterOptions = {}): Presenter {
|
|
|
191
191
|
renderer.toneMapping = mappings[render.toneMapping] ?? THREE.AgXToneMapping;
|
|
192
192
|
renderer.toneMappingExposure = render.exposure;
|
|
193
193
|
const sceneTarget = new THREE.WebGLRenderTarget(width, height, { type: THREE.HalfFloatType });
|
|
194
|
+
let effect: ReturnType<BlenderRuntimeView['createWorldVolumePass']>;
|
|
194
195
|
try {
|
|
196
|
+
effect = render.linearInput ? undefined : view.createWorldVolumePass();
|
|
197
|
+
if (effect) sceneTarget.depthTexture = new THREE.DepthTexture(width, height);
|
|
195
198
|
await view.setRendered(true, camera);
|
|
196
199
|
let pixels: Uint16Array;
|
|
197
200
|
if (render.linearInput) {
|
|
@@ -210,7 +213,8 @@ export function createPresenter(options: PresenterOptions = {}): Presenter {
|
|
|
210
213
|
scene.background = background;
|
|
211
214
|
}
|
|
212
215
|
pixels = new Uint16Array(width * height * 4);
|
|
213
|
-
renderer
|
|
216
|
+
const resolved = effect?.render(renderer, sceneTarget, scene, camera) ?? sceneTarget;
|
|
217
|
+
renderer.readRenderTargetPixels(resolved, 0, 0, width, height, pixels);
|
|
214
218
|
renderer.setRenderTarget(null);
|
|
215
219
|
}
|
|
216
220
|
const count = width * height;
|
|
@@ -239,6 +243,7 @@ export function createPresenter(options: PresenterOptions = {}): Presenter {
|
|
|
239
243
|
}
|
|
240
244
|
return answer(photograph);
|
|
241
245
|
} finally {
|
|
246
|
+
effect?.dispose();
|
|
242
247
|
sceneTarget.dispose();
|
|
243
248
|
renderer.toneMapping = previousMapping;
|
|
244
249
|
renderer.toneMappingExposure = previousExposure;
|
package/package.json
CHANGED
package/wasm/BUNDLE.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"blender": "5.2.0 LTS",
|
|
3
|
-
"source": "
|
|
4
|
-
"reviewedSource": "
|
|
3
|
+
"source": "071e080a21c09e07f2dac8593dca2f9a566e591e",
|
|
4
|
+
"reviewedSource": "071e080a21c09e07f2dac8593dca2f9a566e591e",
|
|
5
5
|
"upstream": "fbe6228777e7d9afefcd61a413844e790ae75db7",
|
|
6
6
|
"sourceRepository": "https://github.com/volter-ai/blender",
|
|
7
|
-
"sourceRecipe": "https://github.com/volter-ai/blender/blob/
|
|
8
|
-
"sourceRelease": "https://github.com/volter-ai/blender/releases/tag/blender-5.2.0-wasm.
|
|
9
|
-
"verification": "https://github.com/volter-ai/blender/
|
|
7
|
+
"sourceRecipe": "https://github.com/volter-ai/blender/blob/071e080a21c09e07f2dac8593dca2f9a566e591e/build_files/emscripten/RELEASE.md",
|
|
8
|
+
"sourceRelease": "https://github.com/volter-ai/blender/releases/tag/blender-5.2.0-wasm.3",
|
|
9
|
+
"verification": "https://github.com/volter-ai/blender/releases/download/blender-5.2.0-wasm.3/VERIFICATION.json",
|
|
10
10
|
"factoryEngine": "BLENDER_EEVEE",
|
|
11
11
|
"files": {
|
|
12
12
|
"blender_browser.js": "raw",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"sha256": "2a5d4dbcc90cab571eb16eef9672ef8583e1c0a3638d0b85165d1944d191d9a7"
|
|
20
20
|
},
|
|
21
21
|
"blender_browser.wasm": {
|
|
22
|
-
"bytes":
|
|
23
|
-
"sha256": "
|
|
22
|
+
"bytes": 86305783,
|
|
23
|
+
"sha256": "a9927fc4c71b41b9d464c34e8700b234bd736f75425e10a8b4d9986770d0954e"
|
|
24
24
|
},
|
|
25
25
|
"blender_browser.data": {
|
|
26
26
|
"bytes": 52698568,
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"sha256": "2a5d4dbcc90cab571eb16eef9672ef8583e1c0a3638d0b85165d1944d191d9a7"
|
|
34
34
|
},
|
|
35
35
|
"blender_browser.wasm.br": {
|
|
36
|
-
"bytes":
|
|
37
|
-
"sha256": "
|
|
36
|
+
"bytes": 18601875,
|
|
37
|
+
"sha256": "63098ef8988e1040aa9e97c9aa7c01f06c5f835582732e057daf2ccf69040440"
|
|
38
38
|
},
|
|
39
39
|
"blender_browser.data.br": {
|
|
40
|
-
"bytes":
|
|
41
|
-
"sha256": "
|
|
40
|
+
"bytes": 8056563,
|
|
41
|
+
"sha256": "1d63c1250e2ab9538df36d7d07338396b271577c49227508ae7530f7bde2ee4a"
|
|
42
42
|
},
|
|
43
43
|
"DEPENDENCY-LICENSES.txt": {
|
|
44
44
|
"bytes": 241352,
|
|
@@ -48,18 +48,6 @@
|
|
|
48
48
|
"note": "Official Blender v5.2.0 plus our headless wasm changes and our existing export/session integration. Dependencies are rebuilt from pinned official sources. No third-party Blender port commits or build scripts are inputs. PROXY_TO_PTHREAD, WasmFS, 512 MiB initial / 4 GiB maximum memory, twelve-worker pool. Build information is disabled. The source release includes the exact source tree, materialized startup.blend, dependency archives, pure Python wheels, and notices.",
|
|
49
49
|
"callTrampoline": "CPython 3.13.13 is compiled with PY_CALL_TRAMPOLINE for Blender CPython slot arity casts. This applies to CPython dispatch, not arbitrary indirect calls.",
|
|
50
50
|
"engineIds": "The existing session registers Python render engines under CYCLES, BLENDER_EEVEE and BLENDER_WORKBENCH; the headless build permits built-in IDs to be shadowed.",
|
|
51
|
-
"exportDoor": "
|
|
52
|
-
"preloadedPayload": "releasePreloadedFileData releases the JS preload buffer after session ready. The measured working payload exclusions are retained."
|
|
53
|
-
"verificationSummary": {
|
|
54
|
-
"completedCalls": 362,
|
|
55
|
-
"expectedCalls": 362,
|
|
56
|
-
"freshEnginePerModel": true,
|
|
57
|
-
"harnessFailures": 0,
|
|
58
|
-
"baselineLimitations": [
|
|
59
|
-
"The original working bundle also has the recorded geometry/image differences; no oracle or comparison threshold was changed.",
|
|
60
|
-
"On the current editor host both bundles produce the same courier render getX errors (27 total recorded error positions); the historical host recorded four.",
|
|
61
|
-
"Both bundles report geometry-less curve profile nodes in bridge/tram presentation.",
|
|
62
|
-
"The current shared-engine board stalled on the frozen baseline; the historical fresh-engine-per-model mode was used for this comparison."
|
|
63
|
-
]
|
|
64
|
-
}
|
|
51
|
+
"exportDoor": "The source-owned bpy_web_export.cc exports Principled physical inputs, normal images, eight named UV layers and evaluated corner normals, and takes graph_materials/graph_images/graph_generated: materials the session draws from their node graph are reduced quietly (a shader mix to the one Principled BSDF it reaches), the images their graphs sample ship at the door revisions (a UDIM image with every tile), and a deformed mesh whose graph reads Generated coordinates ships its orco. Datablock revisions are keyed by session_uid. blender_web_session.cc is unchanged.",
|
|
52
|
+
"preloadedPayload": "releasePreloadedFileData releases the JS preload buffer after session ready. The measured working payload exclusions are retained."
|
|
65
53
|
}
|
|
Binary file
|
|
Binary file
|