@volter/blender-engine 0.1.7 → 0.1.9
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/blender-emscripten-engine.mts +57 -6
- package/browser/blender-engine.mts +2 -0
- package/browser/runtime.ts +5 -5
- package/browser/session.py +200 -16
- package/browser/three/blender-graph-material.ts +6 -2
- package/browser/three/blender-node-graph.ts +15 -6
- package/browser/three/blender-physical-material.ts +11 -4
- package/browser/three/blender-runtime-camera-view.ts +137 -0
- package/browser/three/blender-runtime-cursor.ts +243 -0
- package/browser/three/blender-runtime-extras.ts +772 -0
- package/browser/three/blender-runtime-lighting.ts +19 -4
- package/browser/three/blender-runtime-view.ts +452 -5
- package/browser/three/blender-workbench-material.ts +115 -0
- package/browser/worker.ts +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLENDER'S SOLID SHADING, as Blender computes it: `get_world_lighting` from
|
|
3
|
+
* `draw/engines/workbench/shaders/workbench_world_light_lib.glsl`, transcribed line for line,
|
|
4
|
+
* over the four studio lights of the factory `Default` (`SOLID_LIGHTS`, read back from Blender
|
|
5
|
+
* 5.2's preferences with their specular colours) and no ambient (`light_ambient` is zero).
|
|
6
|
+
*
|
|
7
|
+
* Per light: a wrapped diffuse term, `(N·L + wrap) / (1 + wrap)²`; a normalized Blinn specular
|
|
8
|
+
* whose gloss the wrap softens, blended toward a wrapped "environment" term by `wrap²`; a
|
|
9
|
+
* Fresnel lift of the specular colour (`brdf_approx`); and energy conservation that takes the
|
|
10
|
+
* specular's grey level off the diffuse. The surface is the material's VIEWPORT DISPLAY
|
|
11
|
+
* (`Material.diffuse_color`, `roughness`, `metallic`), which is what Solid's MATERIAL colour type
|
|
12
|
+
* reads — never its node graph.
|
|
13
|
+
*
|
|
14
|
+
* WHY A FUNCTION AND NOT A LIGHT RIG: three's lambert and BRDF cannot express the wrap, the
|
|
15
|
+
* coupled specular or the per-light specular colour, so the four lights plus one fitted gain
|
|
16
|
+
* could match the default view's three faces only by absorbing the difference, and missed a
|
|
17
|
+
* face square to the view by 18 levels (143 against Blender's 161). This function, evaluated
|
|
18
|
+
* offline for that face, gives 161.
|
|
19
|
+
*
|
|
20
|
+
* Built on `MeshLambertMaterial` so skinning, morphs and the view-space normal come with it;
|
|
21
|
+
* only the final colour is replaced. Blender uses a fast reciprocal (`fast_rcp`); this uses the
|
|
22
|
+
* exact one.
|
|
23
|
+
*/
|
|
24
|
+
import * as THREE from 'three';
|
|
25
|
+
import { SOLID_LIGHTS } from './blender-runtime-lighting';
|
|
26
|
+
|
|
27
|
+
/** What Blender's Solid reads off a material. */
|
|
28
|
+
export interface ViewportDisplay {
|
|
29
|
+
readonly color: readonly [number, number, number, number];
|
|
30
|
+
readonly roughness: number;
|
|
31
|
+
readonly metallic: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** For a surface with no material: the factory cube's material's display, read back from Blender
|
|
35
|
+
* 5.2 (grey 0.8, roughness 0.5, metallic 0). Blender's own default surface was not measured. */
|
|
36
|
+
export const DEFAULT_VIEWPORT_DISPLAY: ViewportDisplay = { color: [0.8, 0.8, 0.8, 1], roughness: 0.5, metallic: 0 };
|
|
37
|
+
|
|
38
|
+
const WORKBENCH_GLSL = /* glsl */ `
|
|
39
|
+
uniform vec3 wbDir[4];
|
|
40
|
+
uniform vec3 wbDiffuse[4];
|
|
41
|
+
uniform vec3 wbSpecular[4];
|
|
42
|
+
uniform vec4 wbWrap;
|
|
43
|
+
uniform float wbRoughness;
|
|
44
|
+
uniform float wbMetallic;
|
|
45
|
+
|
|
46
|
+
vec4 wbWrapped(vec4 NL, vec4 w) {
|
|
47
|
+
vec4 w1 = w + 1.0;
|
|
48
|
+
return clamp((NL + w) / (w1 * w1), 0.0, 1.0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
vec3 vgaiWorkbenchLighting(vec3 baseColor, vec3 N, vec3 I) {
|
|
52
|
+
vec3 diffuseColor = mix(baseColor, vec3(0.0), wbMetallic);
|
|
53
|
+
vec3 specularColor = mix(vec3(0.05), baseColor, wbMetallic);
|
|
54
|
+
vec3 R = -reflect(I, N);
|
|
55
|
+
vec4 wrapNL = vec4(dot(wbDir[0], R), dot(wbDir[1], R), dot(wbDir[2], R), dot(wbDir[3], R));
|
|
56
|
+
vec4 specAngle = clamp(vec4(
|
|
57
|
+
dot(normalize(wbDir[0] + I), N), dot(normalize(wbDir[1] + I), N),
|
|
58
|
+
dot(normalize(wbDir[2] + I), N), dot(normalize(wbDir[3] + I), N)), 0.0, 1.0);
|
|
59
|
+
vec4 specNL = clamp(vec4(dot(wbDir[0], N), dot(wbDir[1], N), dot(wbDir[2], N), dot(wbDir[3], N)), 0.0, 1.0);
|
|
60
|
+
vec4 gloss = vec4(1.0 - wbRoughness) * (1.0 - wbWrap);
|
|
61
|
+
vec4 shininess = exp2(10.0 * gloss + 1.0);
|
|
62
|
+
vec4 specLight = pow(specAngle, shininess) * specNL * (shininess * 0.125 + 1.0);
|
|
63
|
+
vec4 envW = mix(wbWrap, vec4(1.0), wbRoughness);
|
|
64
|
+
vec4 specEnv = wbWrapped(wrapNL, envW);
|
|
65
|
+
specLight = mix(specLight, specEnv, wbWrap * wbWrap);
|
|
66
|
+
vec3 specularLight = specLight.x * wbSpecular[0] + specLight.y * wbSpecular[1] +
|
|
67
|
+
specLight.z * wbSpecular[2] + specLight.w * wbSpecular[3];
|
|
68
|
+
float NV = clamp(dot(N, I), 0.0, 1.0);
|
|
69
|
+
float fresnel = exp2(-8.35 * NV) * (1.0 - wbRoughness);
|
|
70
|
+
specularColor = mix(specularColor, vec3(1.0), fresnel);
|
|
71
|
+
specularLight *= specularColor;
|
|
72
|
+
vec4 diffNL = vec4(dot(wbDir[0], N), dot(wbDir[1], N), dot(wbDir[2], N), dot(wbDir[3], N));
|
|
73
|
+
vec4 diffLight = wbWrapped(diffNL, wbWrap);
|
|
74
|
+
vec3 diffuseLight = diffLight.x * wbDiffuse[0] + diffLight.y * wbDiffuse[1] +
|
|
75
|
+
diffLight.z * wbDiffuse[2] + diffLight.w * wbDiffuse[3];
|
|
76
|
+
float specEnergy = dot(specularColor, vec3(0.33333));
|
|
77
|
+
diffuseLight *= diffuseColor * (1.0 - specEnergy);
|
|
78
|
+
return diffuseLight + specularLight;
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
81
|
+
|
|
82
|
+
const vec3s = (pick: (light: (typeof SOLID_LIGHTS)[number]) => readonly number[]) =>
|
|
83
|
+
SOLID_LIGHTS.map((light) => {
|
|
84
|
+
const [x = 0, y = 0, z = 0] = pick(light);
|
|
85
|
+
return new THREE.Vector3(x, y, z);
|
|
86
|
+
});
|
|
87
|
+
const LIGHT_DIRECTIONS = vec3s((light) => light.direction).map((v) => v.normalize());
|
|
88
|
+
const LIGHT_DIFFUSE = vec3s((light) => light.diffuse);
|
|
89
|
+
const LIGHT_SPECULAR = vec3s((light) => light.specular);
|
|
90
|
+
const LIGHT_WRAP = new THREE.Vector4(...SOLID_LIGHTS.map((light) => light.smooth));
|
|
91
|
+
|
|
92
|
+
/** One Solid-shaded surface: Blender's function over this viewport display. */
|
|
93
|
+
export function workbenchMaterial(display: ViewportDisplay, side: THREE.Side): THREE.MeshLambertMaterial {
|
|
94
|
+
const [r, g, b] = display.color;
|
|
95
|
+
const material = new THREE.MeshLambertMaterial({ side });
|
|
96
|
+
material.color.setRGB(r, g, b, THREE.LinearSRGBColorSpace);
|
|
97
|
+
material.name = 'vgai:blender-solid';
|
|
98
|
+
material.onBeforeCompile = (shader) => {
|
|
99
|
+
shader.uniforms['wbDir'] = { value: LIGHT_DIRECTIONS };
|
|
100
|
+
shader.uniforms['wbDiffuse'] = { value: LIGHT_DIFFUSE };
|
|
101
|
+
shader.uniforms['wbSpecular'] = { value: LIGHT_SPECULAR };
|
|
102
|
+
shader.uniforms['wbWrap'] = { value: LIGHT_WRAP };
|
|
103
|
+
shader.uniforms['wbRoughness'] = { value: display.roughness };
|
|
104
|
+
shader.uniforms['wbMetallic'] = { value: display.metallic };
|
|
105
|
+
shader.fragmentShader = shader.fragmentShader
|
|
106
|
+
.replace('void main() {', `${WORKBENCH_GLSL}\nvoid main() {`)
|
|
107
|
+
.replace(
|
|
108
|
+
'#include <opaque_fragment>',
|
|
109
|
+
// The view direction toward the eye, as Blender's `I`: constant in an orthographic view.
|
|
110
|
+
'outgoingLight = vgaiWorkbenchLighting(diffuseColor.rgb, normal, isOrthographic ? vec3(0.0, 0.0, 1.0) : normalize(vViewPosition));\n#include <opaque_fragment>',
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
material.customProgramCacheKey = () => 'vgai-blender-workbench';
|
|
114
|
+
return material;
|
|
115
|
+
}
|
package/browser/worker.ts
CHANGED
|
@@ -458,6 +458,9 @@ async function handle(request: WorkerRequest): Promise<unknown> {
|
|
|
458
458
|
const ask = engine.request.bind(engine);
|
|
459
459
|
switch (request.op) {
|
|
460
460
|
case 'flush-document':
|
|
461
|
+
// A flush lands what is pending; a document nothing changed stays the
|
|
462
|
+
// file it was opened from.
|
|
463
|
+
if (!documentDirty) return { saved: false };
|
|
461
464
|
await saveDocument();
|
|
462
465
|
return { saved: true };
|
|
463
466
|
case 'history-begin':
|