@volter/blender-engine 0.1.8 → 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.
@@ -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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@volter/blender-engine",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "GPL-3.0-or-later",
5
- "version": "0.1.8",
5
+ "version": "0.1.9",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },