reze-engine 0.41.0 → 0.41.2
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 +4 -2
- package/dist/engine.d.ts +31 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +190 -18
- package/dist/graph/presets/body.js +2 -2
- package/dist/graph/presets/cloth_rough.js +2 -2
- package/dist/graph/presets/cloth_smooth.js +2 -2
- package/dist/graph/presets/default.js +2 -2
- package/dist/graph/presets/eye.js +2 -2
- package/dist/graph/presets/face.js +2 -2
- package/dist/graph/presets/hair.js +2 -2
- package/dist/graph/presets/metal.js +2 -2
- package/dist/graph/presets/stockings.js +3 -3
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +426 -10
- package/dist/graph/style-group.d.ts +46 -0
- package/dist/graph/style-group.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/shaders/agx-lut.d.ts +11 -0
- package/dist/shaders/agx-lut.d.ts.map +1 -0
- package/dist/shaders/agx-lut.js +29 -0
- package/dist/shaders/materials/common.d.ts +1 -1
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +10 -0
- package/dist/shaders/materials/nodes.d.ts +1 -1
- package/dist/shaders/materials/nodes.d.ts.map +1 -1
- package/dist/shaders/materials/nodes.js +957 -576
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +76 -4
- package/package.json +1 -1
- package/src/engine.ts +211 -17
- package/src/graph/presets/body.ts +2 -2
- package/src/graph/presets/cloth_rough.ts +2 -2
- package/src/graph/presets/cloth_smooth.ts +2 -2
- package/src/graph/presets/default.ts +2 -2
- package/src/graph/presets/eye.ts +2 -2
- package/src/graph/presets/face.ts +2 -2
- package/src/graph/presets/hair.ts +2 -2
- package/src/graph/presets/metal.ts +2 -2
- package/src/graph/presets/stockings.ts +3 -3
- package/src/graph/registry.ts +439 -10
- package/src/graph/style-group.ts +44 -0
- package/src/index.ts +2 -0
- package/src/shaders/agx-lut.ts +30 -0
- package/src/shaders/materials/common.ts +10 -0
- package/src/shaders/materials/nodes.ts +962 -581
- package/src/shaders/passes/composite.ts +76 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;;;;;+BAwB+B;AAC/B,MAAM,MAAM,qBAAqB,GAAG;IAClC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,aAAa,EAAE,OAAO,CAAA;IACtB,oEAAoE;IACpE,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA;
|
|
1
|
+
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;;;;;+BAwB+B;AAC/B,MAAM,MAAM,qBAAqB,GAAG;IAClC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,aAAa,EAAE,OAAO,CAAA;IACtB,oEAAoE;IACpE,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA;AA4XD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,GAAG,MAAM,CAclF;AAED,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,QAA6B,CAAA"}
|
|
@@ -23,7 +23,7 @@ override APPLY_GAMMA: bool = true;
|
|
|
23
23
|
@group(0) @binding(0) var hdrTex: texture_2d<f32>;
|
|
24
24
|
@group(0) @binding(1) var bloomTex: texture_2d<f32>; // bloomUpTexture mip 0 (full pyramid top)
|
|
25
25
|
@group(0) @binding(2) var bloomSamp: sampler;
|
|
26
|
-
@group(0) @binding(3) var<uniform> viewU: array<vec4<f32>,
|
|
26
|
+
@group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 15>;
|
|
27
27
|
// Aux mask/alpha texture. .r = bloom mask (unused here; bloom blit uses it).
|
|
28
28
|
// .g = accumulated canvas alpha (what hdr.a carried before the HDR format
|
|
29
29
|
// became rg11b10ufloat). We unpremultiply HDR by this alpha for tonemap, then
|
|
@@ -47,7 +47,7 @@ override APPLY_GAMMA: bool = true;
|
|
|
47
47
|
// the compiled variant IS the flag.
|
|
48
48
|
// viewU[3] = (camera right, tanHalfFov·aspect); viewU[4] = (camera up, tanHalfFov);
|
|
49
49
|
// viewU[5] = (camera forward, _) — refreshed per frame while skybox/effect active.
|
|
50
|
-
// viewU[6] = (time seconds,
|
|
50
|
+
// viewU[6] = (time seconds, view transform id, canvas width, canvas height).
|
|
51
51
|
// viewU[7] = (grade offset.rgb, contrast); viewU[8] = (grade power.rgb, saturation);
|
|
52
52
|
// viewU[9] = (grade slope.rgb, grade on/off) — see grade() below.
|
|
53
53
|
// viewU[10] = (camera world position, _) — refreshed with the basis above.
|
|
@@ -67,6 +67,9 @@ override APPLY_GAMMA: bool = true;
|
|
|
67
67
|
// track the camera radius. Cleared depth (1.0) inverts to the far
|
|
68
68
|
// plane, so empty sky reads as maximally defocused background.
|
|
69
69
|
@group(0) @binding(9) var<uniform> dofU: array<vec4<f32>, 3>;
|
|
70
|
+
// Blender's AgX, as the 57³ lookup it ships as rather than a reconstruction of
|
|
71
|
+
// it. Sampled in the log-encoded E-Gamut space the cube expects — see agxTransform.
|
|
72
|
+
@group(0) @binding(10) var agxLut: texture_3d<f32>;
|
|
70
73
|
|
|
71
74
|
// Must match FILMIC_LUT_WIDTH in engine.ts (bakeFilmicLut).
|
|
72
75
|
const FILMIC_LUT_W: f32 = 256.0;
|
|
@@ -110,12 +113,82 @@ fn filmic(x: f32) -> f32 {
|
|
|
110
113
|
return textureSampleLevel(filmicLut, bloomSamp, vec2f(u, 0.5), 0.0).r;
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
/** The sRGB display encoding — Blender's "Standard" view transform, which is what
|
|
117
|
+
* NPR work uses: no film curve at all, so the colours a graph computes are the
|
|
118
|
+
* colours that land. Not a 2.2 power law; sRGB has a linear toe. */
|
|
119
|
+
fn srgb_encode(x: f32) -> f32 {
|
|
120
|
+
let c = max(x, 0.0);
|
|
121
|
+
return select(1.055 * pow(c, 1.0 / 2.4) - 0.055, c * 12.92, c <= 0.0031308);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* AgX, the chain config.ocio states for "AgX Base Rec.1886" on an sRGB display:
|
|
126
|
+
*
|
|
127
|
+
* scene-linear Rec.709 → 3x3 into FilmLight E-Gamut
|
|
128
|
+
* → log2 across 25 stops, [-12.47393, +12.5261] → 0..1
|
|
129
|
+
* → the 57³ cube
|
|
130
|
+
* → Rec.1886 (pure γ2.4) decoded, then re-encoded as sRGB for the swapchain
|
|
131
|
+
*
|
|
132
|
+
* The last step is not pedantry: Rec.1886 is a pure power law and sRGB has a
|
|
133
|
+
* linear toe, so skipping it lifts the darkest few code values.
|
|
134
|
+
*
|
|
135
|
+
* Blender interpolates the cube tetrahedrally and a GPU sampler is trilinear.
|
|
136
|
+
* The difference is small and confined to saturated gradients; if it ever shows,
|
|
137
|
+
* tetrahedral is a dozen lines here.
|
|
138
|
+
*/
|
|
139
|
+
fn agxTransform(c: vec3f) -> vec3f {
|
|
140
|
+
let m = mat3x3<f32>(
|
|
141
|
+
vec3f(0.55937113, 0.07622071, 0.06552670),
|
|
142
|
+
vec3f(0.30478326, 0.78797183, 0.16454675),
|
|
143
|
+
vec3f(0.13584556, 0.13580748, 0.76992653),
|
|
144
|
+
);
|
|
145
|
+
let e = m * max(c, vec3f(0.0));
|
|
146
|
+
// The cube's domain: an unlit pixel would take log2 to -inf, so floor it at
|
|
147
|
+
// the bottom stop rather than sampling outside the LUT.
|
|
148
|
+
let t = clamp((log2(max(e, vec3f(1e-10))) + vec3f(12.47393)) / 25.0, vec3f(0.0), vec3f(1.0));
|
|
149
|
+
// Half-texel inset, so the end stops are the cube's own values and not a blend
|
|
150
|
+
// with the clamp-to-edge border.
|
|
151
|
+
let n = 57.0;
|
|
152
|
+
let uvw = t * ((n - 1.0) / n) + (0.5 / n);
|
|
153
|
+
let formed = textureSampleLevel(agxLut, bloomSamp, uvw, 0.0).rgb;
|
|
154
|
+
let linear = pow(max(formed, vec3f(0.0)), vec3f(2.4));
|
|
155
|
+
return vec3f(srgb_encode(linear.r), srgb_encode(linear.g), srgb_encode(linear.b));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Which display transform, chosen per frame at viewU[6].y (0 filmic, 1 standard, 2 agx).
|
|
159
|
+
* A uniform branch rather than a pipeline variant: switching is rare, and both
|
|
160
|
+
* arms are cheap enough that specialising the pipeline would buy nothing. */
|
|
161
|
+
fn viewTransform(c: vec3f) -> vec3f {
|
|
162
|
+
let mode = viewU[6].y;
|
|
163
|
+
if (mode > 1.5) { return agxTransform(c); }
|
|
164
|
+
if (mode > 0.5) { return vec3f(srgb_encode(c.r), srgb_encode(c.g), srgb_encode(c.b)); }
|
|
165
|
+
return vec3f(filmic(c.r), filmic(c.g), filmic(c.b));
|
|
166
|
+
}
|
|
167
|
+
|
|
113
168
|
/** Canvas size in pixels — for user effects (aspect correction). */
|
|
114
169
|
fn bgResolution() -> vec2f { return viewU[6].zw; }
|
|
115
170
|
|
|
116
171
|
/** The camera's world position. */
|
|
117
172
|
fn bgCameraPos() -> vec3f { return viewU[10].xyz; }
|
|
118
173
|
|
|
174
|
+
/** How many characters are in the scene, up to four. */
|
|
175
|
+
fn bgSubjectCount() -> i32 { return i32(viewU[10].w); }
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Where a character is standing, in world space.
|
|
179
|
+
*
|
|
180
|
+
* An effect that wants to RESPOND to the cast — ripples under the feet, a glow
|
|
181
|
+
* that follows someone, dust kicked up where they are — needs to know where
|
|
182
|
+
* they are, and the ray and the depth cannot tell it: they describe the pixel,
|
|
183
|
+
* not the scene. This is the model's root, which for a PMX is between the feet
|
|
184
|
+
* on the floor, so it is already the contact point a ripple wants.
|
|
185
|
+
*
|
|
186
|
+
* Clamped rather than bounds-checked: an effect looping past the count reads the
|
|
187
|
+
* last subject instead of sampling whatever follows the array, which is a wrong
|
|
188
|
+
* ripple rather than an undefined one.
|
|
189
|
+
*/
|
|
190
|
+
fn bgSubjectPos(i: i32) -> vec3f { return viewU[11 + clamp(i, 0, 3)].xyz; }
|
|
191
|
+
|
|
119
192
|
/** Where in the WORLD the scene drew this pixel — the depth handed to
|
|
120
193
|
* foreground() turned into a place. Without it an effect can only think in
|
|
121
194
|
* distances from the lens, which is no use to anything that belongs somewhere:
|
|
@@ -222,8 +295,7 @@ const COMPOSITE_BODY = /* wgsl */ `
|
|
|
222
295
|
let sceneStraight = scenePm.rgb / max(sceneAlpha, 1e-6);
|
|
223
296
|
|
|
224
297
|
let exposed = sceneStraight * exp2(viewU[0].x);
|
|
225
|
-
|
|
226
|
-
var disp = max(tm, vec3f(0.0));
|
|
298
|
+
var disp = max(viewTransform(exposed), vec3f(0.0));
|
|
227
299
|
// Grade the SCENE only, before the display gamma. Deliberately not applied to
|
|
228
300
|
// the background: it keeps a picked background color exactly as picked, and —
|
|
229
301
|
// load-bearing — leaves green-screen mode's key color unshifted so chroma
|
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
BLOOM_DOWNSAMPLE_SHADER_WGSL,
|
|
32
32
|
BLOOM_UPSAMPLE_SHADER_WGSL,
|
|
33
33
|
} from "./shaders/passes/bloom"
|
|
34
|
+
import { AGX_LUT_GZ, AGX_LUT_SIZE } from "./shaders/agx-lut"
|
|
34
35
|
import { buildCompositeShader } from "./shaders/passes/composite"
|
|
35
36
|
import { PICK_SHADER_WGSL } from "./shaders/passes/pick"
|
|
36
37
|
import { MIPMAP_BLIT_SHADER_WGSL } from "./shaders/passes/mipmap"
|
|
@@ -84,7 +85,17 @@ const PRESET_NAME_HINTS: Array<[MaterialPreset, string[]]> = [
|
|
|
84
85
|
"face",
|
|
85
86
|
["顔", "颜", "顏", "脸", "臉", "かお", "face", "舌", "tongue", "牙", "牙齿", "齿", "歯", "teeth", "tooth", "口腔", "口内", "mouth", "嘴", "唇", "歯茎", "gums"],
|
|
86
87
|
],
|
|
87
|
-
|
|
88
|
+
// Simplified 发 is listed as compounds, never bare: it also writes 发光 (glow),
|
|
89
|
+
// and hair carries a renderClass, so a chance hit puts an emissive panel in the
|
|
90
|
+
// hair pass. Same reasoning as bare 口 being omitted from face above.
|
|
91
|
+
[
|
|
92
|
+
"hair",
|
|
93
|
+
[
|
|
94
|
+
"前髪", "後髪", "髪", "髮", "頭髪", "もみあげ", "アホ毛", "ヘア",
|
|
95
|
+
"头发", "前发", "后发", "长发", "短发", "发丝", "刘海", "辫", "马尾",
|
|
96
|
+
"hair", "ahoge", "bang",
|
|
97
|
+
],
|
|
98
|
+
],
|
|
88
99
|
["body", ["肌", "皮肤", "skin"]],
|
|
89
100
|
["metal", ["金属", "メタル", "metal", "earring", "耳环", "耳環"]],
|
|
90
101
|
[
|
|
@@ -187,6 +198,9 @@ type GroupInstall = {
|
|
|
187
198
|
/** hair render-class only: the stencil-matched IS_OVER_EYES=true variant. */
|
|
188
199
|
overEyesPipeline?: GPURenderPipeline
|
|
189
200
|
uniformBuffer: GPUBuffer
|
|
201
|
+
/** The group's own image maps, uploaded once per apply and owned here — the
|
|
202
|
+
* install destroys them when it is replaced, so a re-apply cannot leak. */
|
|
203
|
+
images?: (GPUTexture | null)[]
|
|
190
204
|
slotMap: StyleSlot[]
|
|
191
205
|
/** Serialized (graph + renderClass + alphaMode) — lets applyStyleGroups skip recompiling
|
|
192
206
|
* an unchanged group. */
|
|
@@ -218,6 +232,14 @@ export type WorldOptions = {
|
|
|
218
232
|
|
|
219
233
|
/** A model's scene placement — root offset baked into skinning + visibility. Serializable
|
|
220
234
|
* into a scene descriptor via getModelTransform. */
|
|
235
|
+
/** How many character positions an effect can read (viewU[11..14]). */
|
|
236
|
+
const MAX_EFFECT_SUBJECTS = 4
|
|
237
|
+
/** Where a character IS, for an effect that follows them. センター carries a
|
|
238
|
+
* motion's root movement — walking, jumping — where the model transform only
|
|
239
|
+
* carries where the model was placed; 全ての親 is the fallback for a model that
|
|
240
|
+
* animates the true root instead. */
|
|
241
|
+
const SUBJECT_BONES = ["センター", "全ての親"]
|
|
242
|
+
|
|
221
243
|
export type ModelTransform = {
|
|
222
244
|
position: Vec3
|
|
223
245
|
rotation: Quat
|
|
@@ -320,7 +342,17 @@ export type ViewTransformOptions = {
|
|
|
320
342
|
exposure: number
|
|
321
343
|
/** After Filmic, display gamma (`pow(rgb, 1/gamma)`). */
|
|
322
344
|
gamma: number
|
|
323
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Which display transform the frame is formed with.
|
|
347
|
+
*
|
|
348
|
+
* "standard" is Blender's Standard: the sRGB encoding and nothing else, which
|
|
349
|
+
* is what NPR and anime work uses — the colours the graph computes are the
|
|
350
|
+
* colours that land, with no film curve reinterpreting them. Both of the
|
|
351
|
+
* reference Wuthering Waves projects render this way.
|
|
352
|
+
*
|
|
353
|
+
* "filmic" is Blender 3.6's Filmic, Medium High Contrast, baked as a LUT.
|
|
354
|
+
*/
|
|
355
|
+
transform: "agx" | "filmic" | "standard"
|
|
324
356
|
}
|
|
325
357
|
|
|
326
358
|
// Matches the reference Blender project: Filmic view, Medium High Contrast look,
|
|
@@ -328,7 +360,7 @@ export type ViewTransformOptions = {
|
|
|
328
360
|
export const DEFAULT_VIEW_TRANSFORM: ViewTransformOptions = {
|
|
329
361
|
exposure: 0.6,
|
|
330
362
|
gamma: 1.0,
|
|
331
|
-
|
|
363
|
+
transform: "filmic",
|
|
332
364
|
}
|
|
333
365
|
|
|
334
366
|
/** Color grading applied to the tonemapped scene (ASC CDL — see grade() in
|
|
@@ -778,7 +810,7 @@ export class Engine {
|
|
|
778
810
|
// 11 × vec4f — see the viewU comment in composite.ts. The last one is the
|
|
779
811
|
// camera's world position, which is what lets a foreground effect turn the
|
|
780
812
|
// depth it is handed into a PLACE (bgWorldPos) rather than a distance.
|
|
781
|
-
private readonly compositeUniformData = new Float32Array(
|
|
813
|
+
private readonly compositeUniformData = new Float32Array(60)
|
|
782
814
|
/** Composite background (display-space sRGB 0–1) — null = transparent canvas. */
|
|
783
815
|
private backgroundColor: Vec3 | null = null
|
|
784
816
|
// 360 backdrop (equirectangular skybox, sampled by view ray in composite).
|
|
@@ -802,6 +834,8 @@ export class Engine {
|
|
|
802
834
|
* STORE its depth, which it otherwise discards into tile memory. */
|
|
803
835
|
hasForeground: boolean
|
|
804
836
|
} | null = null
|
|
837
|
+
private agxLutTexture: GPUTexture | null = null
|
|
838
|
+
private agxFallbackTexture!: GPUTexture
|
|
805
839
|
/** Bound at composite binding 7 when no effect (or a param-less one) is set. */
|
|
806
840
|
private bgParamsDummyBuffer!: GPUBuffer
|
|
807
841
|
private compositePipelineLayout!: GPUPipelineLayout
|
|
@@ -981,7 +1015,7 @@ export class Engine {
|
|
|
981
1015
|
return {
|
|
982
1016
|
exposure: partial?.exposure ?? d.exposure,
|
|
983
1017
|
gamma: partial?.gamma ?? d.gamma,
|
|
984
|
-
|
|
1018
|
+
transform: partial?.transform ?? d.transform,
|
|
985
1019
|
}
|
|
986
1020
|
}
|
|
987
1021
|
|
|
@@ -1001,7 +1035,7 @@ export class Engine {
|
|
|
1001
1035
|
|
|
1002
1036
|
getViewTransformOptions(): ViewTransformOptions {
|
|
1003
1037
|
const v = this.viewTransform
|
|
1004
|
-
return { exposure: v.exposure, gamma: v.gamma,
|
|
1038
|
+
return { exposure: v.exposure, gamma: v.gamma, transform: v.transform }
|
|
1005
1039
|
}
|
|
1006
1040
|
|
|
1007
1041
|
private colorGrading: ColorGradingOptions = {
|
|
@@ -1044,7 +1078,7 @@ export class Engine {
|
|
|
1044
1078
|
const v = this.viewTransform
|
|
1045
1079
|
if (patch.exposure !== undefined) v.exposure = patch.exposure
|
|
1046
1080
|
if (patch.gamma !== undefined) v.gamma = patch.gamma
|
|
1047
|
-
if (patch.
|
|
1081
|
+
if (patch.transform !== undefined) v.transform = patch.transform
|
|
1048
1082
|
if (this.device && this.compositeUniformBuffer) {
|
|
1049
1083
|
this.writeCompositeViewUniforms()
|
|
1050
1084
|
}
|
|
@@ -1076,8 +1110,10 @@ export class Engine {
|
|
|
1076
1110
|
u[10] = bg?.z ?? 0
|
|
1077
1111
|
// Base-layer mode only. A user effect is a separate LAYER over whichever
|
|
1078
1112
|
// base is active, and needs no flag of its own: the composite pipeline is
|
|
1079
|
-
// rebuilt per effect, so the compiled variant IS the flag.
|
|
1113
|
+
// rebuilt per effect, so the compiled variant IS the flag.
|
|
1080
1114
|
u[11] = this.backdropEquirectView ? 2 : bg ? 1 : 0
|
|
1115
|
+
// Which display transform forms the frame (see viewTransform in composite.ts).
|
|
1116
|
+
u[25] = v.transform === "agx" ? 2 : v.transform === "standard" ? 1 : 0
|
|
1081
1117
|
u[26] = this.canvas.width
|
|
1082
1118
|
u[27] = this.canvas.height
|
|
1083
1119
|
// ── Grade (viewU[7..9]) ── The UI's three tonal COLORS map to ASC CDL here,
|
|
@@ -1148,6 +1184,7 @@ export class Engine {
|
|
|
1148
1184
|
{ binding: 7, resource: { buffer: this.effect?.paramsBuffer ?? this.bgParamsDummyBuffer } },
|
|
1149
1185
|
{ binding: 8, resource: this.depthReadView },
|
|
1150
1186
|
{ binding: 9, resource: { buffer: this.dofUniformBuffer } },
|
|
1187
|
+
{ binding: 10, resource: (this.agxLutTexture ?? this.agxFallbackTexture).createView({ dimension: "3d" }) },
|
|
1151
1188
|
],
|
|
1152
1189
|
})
|
|
1153
1190
|
}
|
|
@@ -1638,6 +1675,37 @@ export class Engine {
|
|
|
1638
1675
|
// smooth gradients) while still passing through every anchor (look preserved) and staying
|
|
1639
1676
|
// monotone (no tonemap overshoot/ringing). Domain is uniform in log2 space: anchor k sits
|
|
1640
1677
|
// at t=k, k=0..13 (t = log2(linear)+10). See composite.ts::filmic for the sampling map.
|
|
1678
|
+
/**
|
|
1679
|
+
* Decompress and upload Blender's AgX cube.
|
|
1680
|
+
*
|
|
1681
|
+
* Deliberately off the critical path: it is 723 KB once inflated, and a frame
|
|
1682
|
+
* rendered before it lands should show the scene under whatever transform is
|
|
1683
|
+
* already there rather than wait. Until then binding 10 holds a 1×1×1 stand-in,
|
|
1684
|
+
* which is only ever sampled if someone selects AgX in that window.
|
|
1685
|
+
*/
|
|
1686
|
+
private async loadAgxLut(): Promise<void> {
|
|
1687
|
+
try {
|
|
1688
|
+
const packed = Uint8Array.from(atob(AGX_LUT_GZ), (ch) => ch.charCodeAt(0))
|
|
1689
|
+
const stream = new Blob([packed]).stream().pipeThrough(new DecompressionStream("gzip"))
|
|
1690
|
+
const bytes = new Uint8Array(await new Response(stream).arrayBuffer())
|
|
1691
|
+
const n = AGX_LUT_SIZE
|
|
1692
|
+
if (bytes.byteLength !== n * n * n * 4) throw new Error(`AgX LUT is ${bytes.byteLength} bytes, expected ${n ** 3 * 4}`)
|
|
1693
|
+
const tex = this.device.createTexture({
|
|
1694
|
+
label: "AgX 57³ LUT",
|
|
1695
|
+
size: [n, n, n],
|
|
1696
|
+
dimension: "3d",
|
|
1697
|
+
format: "rgb10a2unorm",
|
|
1698
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
1699
|
+
})
|
|
1700
|
+
// .cube order is red fastest, which is exactly a 3D texture's own layout.
|
|
1701
|
+
this.device.queue.writeTexture({ texture: tex }, bytes, { bytesPerRow: n * 4, rowsPerImage: n }, [n, n, n])
|
|
1702
|
+
this.agxLutTexture = tex
|
|
1703
|
+
this.rebuildCompositeBindGroup()
|
|
1704
|
+
} catch {
|
|
1705
|
+
// A missing LUT costs AgX, not the renderer — the other transforms stand.
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1641
1709
|
private bakeFilmicLut() {
|
|
1642
1710
|
const anchors = [
|
|
1643
1711
|
0.0028, 0.0068, 0.0151, 0.0313, 0.061, 0.112, 0.192, 0.306, 0.459, 0.631, 0.82, 0.907, 0.962, 0.989,
|
|
@@ -1911,6 +1979,13 @@ export class Engine {
|
|
|
1911
1979
|
// StyleUniforms for compiled graph shaders (adjust-tier sliders). Hand-written
|
|
1912
1980
|
// presets simply don't declare it — a layout may carry bindings a shader ignores.
|
|
1913
1981
|
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
1982
|
+
// Style-group image maps. A PMX material carries one image; a
|
|
1983
|
+
// Blender-authored look needs a lightmap or ramp beside it, and those
|
|
1984
|
+
// belong to the GROUP rather than to the model's own material data.
|
|
1985
|
+
{ binding: 5, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
1986
|
+
{ binding: 6, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
1987
|
+
{ binding: 7, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
1988
|
+
{ binding: 8, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
1914
1989
|
],
|
|
1915
1990
|
})
|
|
1916
1991
|
|
|
@@ -2043,6 +2118,14 @@ export class Engine {
|
|
|
2043
2118
|
|
|
2044
2119
|
// One-shot bake of Blender EEVEE's combined BRDF LUT (DFG + LTC packed rgba8unorm).
|
|
2045
2120
|
this.bakeBrdfLut()
|
|
2121
|
+
this.agxFallbackTexture = this.device.createTexture({
|
|
2122
|
+
label: "AgX LUT fallback",
|
|
2123
|
+
size: [1, 1, 1],
|
|
2124
|
+
dimension: "3d",
|
|
2125
|
+
format: "rgb10a2unorm",
|
|
2126
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
2127
|
+
})
|
|
2128
|
+
void this.loadAgxLut()
|
|
2046
2129
|
this.bakeFilmicLut()
|
|
2047
2130
|
|
|
2048
2131
|
// Now that shadow resources exist, create the main per-frame bind group
|
|
@@ -2337,8 +2420,9 @@ export class Engine {
|
|
|
2337
2420
|
// (bg rgb, mode) · camera right/up/forward basis for the 360 skybox ray ·
|
|
2338
2421
|
// (time, _, canvas width, canvas height) for user effects · three grade
|
|
2339
2422
|
// vectors (CDL offset+contrast, power+saturation, slope+flag) · camera
|
|
2340
|
-
// world position, for an effect placing itself in the scene
|
|
2341
|
-
|
|
2423
|
+
// world position, for an effect placing itself in the scene · four
|
|
2424
|
+
// character positions, for one that wants to respond to the cast.
|
|
2425
|
+
size: 240,
|
|
2342
2426
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
2343
2427
|
})
|
|
2344
2428
|
this.dofUniformBuffer = this.device.createBuffer({
|
|
@@ -2378,6 +2462,9 @@ export class Engine {
|
|
|
2378
2462
|
texture: { sampleType: "depth", viewDimension: "2d", multisampled: true },
|
|
2379
2463
|
},
|
|
2380
2464
|
{ binding: 9, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
2465
|
+
// AgX's 57³ cube. Decompressed and uploaded off the critical path, so a
|
|
2466
|
+
// 1×1×1 stand-in keeps the bind group valid until it arrives.
|
|
2467
|
+
{ binding: 10, visibility: GPUShaderStage.FRAGMENT, texture: { viewDimension: "3d" } },
|
|
2381
2468
|
],
|
|
2382
2469
|
})
|
|
2383
2470
|
this.fallbackEquirectTexture = this.device.createTexture({
|
|
@@ -3293,7 +3380,7 @@ export class Engine {
|
|
|
3293
3380
|
// explicit destroy). Per-model group buffers are torn down in removeModel; the shared
|
|
3294
3381
|
// zero buffer is engine-owned.
|
|
3295
3382
|
this.forEachInstance((inst) => {
|
|
3296
|
-
for (const install of inst.styleGroups.values())
|
|
3383
|
+
for (const install of inst.styleGroups.values()) this.destroyInstall(install)
|
|
3297
3384
|
inst.styleGroups.clear()
|
|
3298
3385
|
})
|
|
3299
3386
|
this.zeroStyleBuffer?.destroy()
|
|
@@ -3442,7 +3529,7 @@ export class Engine {
|
|
|
3442
3529
|
buf.destroy()
|
|
3443
3530
|
}
|
|
3444
3531
|
// Per-group StyleUniforms buffers aren't in gpuBuffers (allocated post-load).
|
|
3445
|
-
for (const install of inst.styleGroups.values())
|
|
3532
|
+
for (const install of inst.styleGroups.values()) this.destroyInstall(install)
|
|
3446
3533
|
this.modelInstances.delete(name)
|
|
3447
3534
|
}
|
|
3448
3535
|
|
|
@@ -3524,11 +3611,58 @@ export class Engine {
|
|
|
3524
3611
|
|
|
3525
3612
|
// Build a material's bind group with binding(4) pointing at a given StyleUniforms buffer
|
|
3526
3613
|
// (the group's buffer when grouped, or the shared zero buffer when ungrouped).
|
|
3527
|
-
|
|
3614
|
+
/** A group's uniform buffer and its maps have the same lifetime — freeing one
|
|
3615
|
+
* without the other is how a re-apply leaks GPU memory a frame at a time. */
|
|
3616
|
+
private destroyInstall(install: GroupInstall): void {
|
|
3617
|
+
install.uniformBuffer.destroy()
|
|
3618
|
+
for (const tex of install.images ?? []) tex?.destroy()
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
/** Upload a group's image maps. Sources are decoded images the host already
|
|
3622
|
+
* holds; the engine never fetches, matching how models and motions arrive. */
|
|
3623
|
+
private uploadGroupImages(group: StyleGroup): (GPUTexture | null)[] | undefined {
|
|
3624
|
+
if (!group.images?.length) return undefined
|
|
3625
|
+
return group.images.slice(0, 4).map((entry) => {
|
|
3626
|
+
if (!entry) return null
|
|
3627
|
+
const wrapped = "source" in entry
|
|
3628
|
+
const src = wrapped ? entry.source : entry
|
|
3629
|
+
const width = Math.max(1, "naturalWidth" in src ? src.naturalWidth : src.width)
|
|
3630
|
+
const height = Math.max(1, "naturalHeight" in src ? src.naturalHeight : src.height)
|
|
3631
|
+
const tex = this.device.createTexture({
|
|
3632
|
+
label: `group map: ${group.id}`,
|
|
3633
|
+
size: [width, height],
|
|
3634
|
+
// Colour maps decode to linear on sample, the way material textures do;
|
|
3635
|
+
// data maps must not, or every threshold packed in their channels moves.
|
|
3636
|
+
format: wrapped && entry.srgb ? "rgba8unorm-srgb" : "rgba8unorm",
|
|
3637
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
3638
|
+
})
|
|
3639
|
+
this.device.queue.copyExternalImageToTexture(
|
|
3640
|
+
{ source: src },
|
|
3641
|
+
{ texture: tex, premultipliedAlpha: wrapped && entry.premultiplied === true },
|
|
3642
|
+
[width, height],
|
|
3643
|
+
)
|
|
3644
|
+
return tex
|
|
3645
|
+
})
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
private createMaterialBindGroup(
|
|
3649
|
+
label: string,
|
|
3650
|
+
baseEntries: GPUBindGroupEntry[],
|
|
3651
|
+
styleBuffer: GPUBuffer,
|
|
3652
|
+
groupImages?: (GPUTexture | null)[],
|
|
3653
|
+
): GPUBindGroup {
|
|
3654
|
+
// Every material bind group in the engine is built here, which is why the
|
|
3655
|
+
// group's maps are threaded through this one function rather than patched in
|
|
3656
|
+
// at each call site — an unset slot reads white, never stale.
|
|
3657
|
+
const slots: GPUBindGroupEntry[] = []
|
|
3658
|
+
for (let i = 0; i < 4; i++) {
|
|
3659
|
+
const tex = groupImages?.[i] ?? this.fallbackMaterialTexture
|
|
3660
|
+
slots.push({ binding: 5 + i, resource: tex.createView() })
|
|
3661
|
+
}
|
|
3528
3662
|
return this.device.createBindGroup({
|
|
3529
3663
|
label,
|
|
3530
3664
|
layout: this.mainPerMaterialBindGroupLayout,
|
|
3531
|
-
entries: [...baseEntries, { binding: 4, resource: { buffer: styleBuffer } }],
|
|
3665
|
+
entries: [...baseEntries, { binding: 4, resource: { buffer: styleBuffer } }, ...slots],
|
|
3532
3666
|
})
|
|
3533
3667
|
}
|
|
3534
3668
|
|
|
@@ -5500,7 +5634,7 @@ export class Engine {
|
|
|
5500
5634
|
for (const [id, install] of inst.styleGroups) {
|
|
5501
5635
|
if (!nextIds.has(id)) {
|
|
5502
5636
|
inst.styleGroupGen.set(id, (inst.styleGroupGen.get(id) ?? 0) + 1)
|
|
5503
|
-
|
|
5637
|
+
this.destroyInstall(install)
|
|
5504
5638
|
inst.styleGroups.delete(id)
|
|
5505
5639
|
}
|
|
5506
5640
|
}
|
|
@@ -5537,7 +5671,7 @@ export class Engine {
|
|
|
5537
5671
|
const install = inst?.styleGroups.get(groupId)
|
|
5538
5672
|
if (!inst || !install) return
|
|
5539
5673
|
inst.styleGroupGen.set(groupId, (inst.styleGroupGen.get(groupId) ?? 0) + 1) // discard in-flight compile
|
|
5540
|
-
|
|
5674
|
+
this.destroyInstall(install)
|
|
5541
5675
|
inst.styleGroups.delete(groupId)
|
|
5542
5676
|
this.assignDrawCallGroups(inst, this.currentClaims(inst))
|
|
5543
5677
|
}
|
|
@@ -5548,7 +5682,7 @@ export class Engine {
|
|
|
5548
5682
|
if (!inst) return
|
|
5549
5683
|
for (const [id, install] of inst.styleGroups) {
|
|
5550
5684
|
inst.styleGroupGen.set(id, (inst.styleGroupGen.get(id) ?? 0) + 1)
|
|
5551
|
-
|
|
5685
|
+
this.destroyInstall(install)
|
|
5552
5686
|
}
|
|
5553
5687
|
inst.styleGroups.clear()
|
|
5554
5688
|
this.assignDrawCallGroups(inst, new Map())
|
|
@@ -5648,6 +5782,9 @@ export class Engine {
|
|
|
5648
5782
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
5649
5783
|
})
|
|
5650
5784
|
|
|
5785
|
+
// The outgoing install's maps go with it — a re-apply that changes images
|
|
5786
|
+
// would otherwise strand the old textures for the life of the model.
|
|
5787
|
+
const previousImages = inst.styleGroups.get(group.id)?.images
|
|
5651
5788
|
inst.styleGroups.set(group.id, {
|
|
5652
5789
|
group,
|
|
5653
5790
|
renderClass,
|
|
@@ -5656,9 +5793,31 @@ export class Engine {
|
|
|
5656
5793
|
pipelineNoDepthWrite,
|
|
5657
5794
|
overEyesPipeline,
|
|
5658
5795
|
uniformBuffer,
|
|
5796
|
+
images: this.uploadGroupImages(group),
|
|
5659
5797
|
slotMap: result.slotMap,
|
|
5660
5798
|
signature,
|
|
5661
5799
|
})
|
|
5800
|
+
// Rebind this group's draw calls before the old textures go.
|
|
5801
|
+
//
|
|
5802
|
+
// assignDrawCallGroups only rebuilds a bind group when a material CHANGES
|
|
5803
|
+
// group, which is the wrong test here: swapping the graph on a group a
|
|
5804
|
+
// material already belongs to leaves its id alone while replacing the maps
|
|
5805
|
+
// underneath it. The draw call then kept a bind group holding the outgoing
|
|
5806
|
+
// textures — destroyed on the next line — so a graph swap either sampled the
|
|
5807
|
+
// old maps, or the fallback white where the previous graph had none (which
|
|
5808
|
+
// reads as a blown-out white material through any screen or add), or tripped
|
|
5809
|
+
// a validation error on a destroyed texture. Only a reload cleared it.
|
|
5810
|
+
const install = inst.styleGroups.get(group.id)
|
|
5811
|
+
for (const dc of inst.drawCalls) {
|
|
5812
|
+
if (!dc.baseBindGroupEntries || dc.groupId !== group.id) continue
|
|
5813
|
+
dc.bindGroup = this.createMaterialBindGroup(
|
|
5814
|
+
`material: ${dc.materialName}`,
|
|
5815
|
+
dc.baseBindGroupEntries,
|
|
5816
|
+
uniformBuffer,
|
|
5817
|
+
install?.images,
|
|
5818
|
+
)
|
|
5819
|
+
}
|
|
5820
|
+
for (const tex of previousImages ?? []) tex?.destroy()
|
|
5662
5821
|
this.writeGroupDefaults(uniformBuffer, group, result.slotMap)
|
|
5663
5822
|
return { ok: true, diagnostics, slotMap: result.slotMap }
|
|
5664
5823
|
}
|
|
@@ -5679,6 +5838,7 @@ export class Engine {
|
|
|
5679
5838
|
`material: ${dc.materialName}`,
|
|
5680
5839
|
dc.baseBindGroupEntries,
|
|
5681
5840
|
install ? install.uniformBuffer : this.zeroStyleBuffer,
|
|
5841
|
+
install?.images,
|
|
5682
5842
|
)
|
|
5683
5843
|
}
|
|
5684
5844
|
this.sortDrawCalls(inst)
|
|
@@ -5971,6 +6131,40 @@ export class Engine {
|
|
|
5971
6131
|
u[40] = cameraPos.x
|
|
5972
6132
|
u[41] = cameraPos.y
|
|
5973
6133
|
u[42] = cameraPos.z
|
|
6134
|
+
// Character positions (viewU[11..14]), count in viewU[10].w. Stages are
|
|
6135
|
+
// excluded: an effect asking where the cast is means the characters, and a
|
|
6136
|
+
// stage's origin is wherever its author put it, which is not a place
|
|
6137
|
+
// anything is standing. Four is the cap because the uniform is small and a
|
|
6138
|
+
// scene with five characters is not the case this serves.
|
|
6139
|
+
let n = 0
|
|
6140
|
+
this.forEachInstance((inst) => {
|
|
6141
|
+
if (n >= MAX_EFFECT_SUBJECTS || inst.isStage) return
|
|
6142
|
+
const m = inst.model
|
|
6143
|
+
// The model transform is only where the model was PLACED. A motion moves
|
|
6144
|
+
// the character by animating bones, so an effect anchored to the
|
|
6145
|
+
// transform never follows anyone anywhere — it sits at the spawn point
|
|
6146
|
+
// while they walk out of it. Composed exactly as the follow camera
|
|
6147
|
+
// composes it, for the same reason: bone matrices are model-space.
|
|
6148
|
+
let px = m.position.x
|
|
6149
|
+
let py = m.position.y
|
|
6150
|
+
let pz = m.position.z
|
|
6151
|
+
for (const bone of SUBJECT_BONES) {
|
|
6152
|
+
const pos = m.getBoneWorldPosition(bone)
|
|
6153
|
+
if (!pos) continue
|
|
6154
|
+
const sc = m.scale
|
|
6155
|
+
pos.setXYZ(pos.x * sc, pos.y * sc, pos.z * sc)
|
|
6156
|
+
Quat.rotateVecInto(m.rotation, pos, pos)
|
|
6157
|
+
px += pos.x
|
|
6158
|
+
py += pos.y
|
|
6159
|
+
pz += pos.z
|
|
6160
|
+
break
|
|
6161
|
+
}
|
|
6162
|
+
u[44 + n * 4] = px
|
|
6163
|
+
u[45 + n * 4] = py
|
|
6164
|
+
u[46 + n * 4] = pz
|
|
6165
|
+
n++
|
|
6166
|
+
})
|
|
6167
|
+
u[43] = n
|
|
5974
6168
|
this.device.queue.writeBuffer(this.compositeUniformBuffer, 0, u)
|
|
5975
6169
|
}
|
|
5976
6170
|
}
|
|
@@ -55,7 +55,7 @@ export const BODY_GRAPH: ShaderGraph = {
|
|
|
55
55
|
{
|
|
56
56
|
id: "principled",
|
|
57
57
|
type: "principled",
|
|
58
|
-
inputs: { metallic: 0.0,
|
|
58
|
+
inputs: { metallic: 0.0, specular_ior_level: 0.5, roughness: 0.3, spec_clamp: 10.0, sheen_weight: 0.0, sheen_tint: 0.0 },
|
|
59
59
|
},
|
|
60
60
|
{ id: "p_sum", type: "add_shader" },
|
|
61
61
|
{ id: "mix_shader_001", type: "mix_shader", inputs: { fac: 0.5 } },
|
|
@@ -92,7 +92,7 @@ export const BODY_GRAPH: ShaderGraph = {
|
|
|
92
92
|
{ from: { node: "noise_ramp", socket: "fac_out" }, to: { node: "principled_base", socket: "fac" } },
|
|
93
93
|
{ from: { node: "bc", socket: "color" }, to: { node: "principled_base", socket: "a" } },
|
|
94
94
|
{ from: { node: "bc", socket: "color" }, to: { node: "p_emit", socket: "color" } },
|
|
95
|
-
{ from: { node: "principled_base", socket: "color" }, to: { node: "principled", socket: "
|
|
95
|
+
{ from: { node: "principled_base", socket: "color" }, to: { node: "principled", socket: "base_color" } },
|
|
96
96
|
{ from: { node: "bump", socket: "vector" }, to: { node: "principled", socket: "normal" } },
|
|
97
97
|
{ from: { node: "principled", socket: "color" }, to: { node: "p_sum", socket: "a" } },
|
|
98
98
|
{ from: { node: "p_emit", socket: "color" }, to: { node: "p_sum", socket: "b" } },
|
|
@@ -30,7 +30,7 @@ export const CLOTH_ROUGH_GRAPH: ShaderGraph = {
|
|
|
30
30
|
{
|
|
31
31
|
id: "principled",
|
|
32
32
|
type: "principled",
|
|
33
|
-
inputs: { metallic: 0.0,
|
|
33
|
+
inputs: { metallic: 0.0, specular_ior_level: 0.8, roughness: 0.8187, spec_clamp: 10.0, sheen_weight: 0.0, sheen_tint: 0.0 },
|
|
34
34
|
},
|
|
35
35
|
{ id: "mix_shader_001", type: "mix_shader", inputs: { fac: 0.8999999761581421 } },
|
|
36
36
|
],
|
|
@@ -55,7 +55,7 @@ export const CLOTH_ROUGH_GRAPH: ShaderGraph = {
|
|
|
55
55
|
{ from: { node: "noise_ramp", socket: "fac_out" }, to: { node: "bump", socket: "height" } },
|
|
56
56
|
{ from: { node: "geo", socket: "normal" }, to: { node: "bump", socket: "normal" } },
|
|
57
57
|
{ from: { node: "tex", socket: "color" }, to: { node: "principled_base", socket: "color" } },
|
|
58
|
-
{ from: { node: "principled_base", socket: "color" }, to: { node: "principled", socket: "
|
|
58
|
+
{ from: { node: "principled_base", socket: "color" }, to: { node: "principled", socket: "base_color" } },
|
|
59
59
|
{ from: { node: "bump", socket: "vector" }, to: { node: "principled", socket: "normal" } },
|
|
60
60
|
{ from: { node: "npr_emit", socket: "color" }, to: { node: "mix_shader_001", socket: "a" } },
|
|
61
61
|
{ from: { node: "principled", socket: "color" }, to: { node: "mix_shader_001", socket: "b" } },
|
|
@@ -27,7 +27,7 @@ export const CLOTH_SMOOTH_GRAPH: ShaderGraph = {
|
|
|
27
27
|
{
|
|
28
28
|
id: "principled",
|
|
29
29
|
type: "principled",
|
|
30
|
-
inputs: { metallic: 0.0,
|
|
30
|
+
inputs: { metallic: 0.0, specular_ior_level: 0.8, roughness: 0.5, spec_clamp: 10.0, sheen_weight: 0.0, sheen_tint: 0.0 },
|
|
31
31
|
},
|
|
32
32
|
{ id: "mix_shader_001", type: "mix_shader", inputs: { fac: 0.8999999761581421 } },
|
|
33
33
|
],
|
|
@@ -48,7 +48,7 @@ export const CLOTH_SMOOTH_GRAPH: ShaderGraph = {
|
|
|
48
48
|
{ from: { node: "hue_004", socket: "color" }, to: { node: "npr_overlay", socket: "b" } },
|
|
49
49
|
{ from: { node: "npr_overlay", socket: "color" }, to: { node: "npr_emit", socket: "color" } },
|
|
50
50
|
{ from: { node: "tex", socket: "color" }, to: { node: "principled_base", socket: "color" } },
|
|
51
|
-
{ from: { node: "principled_base", socket: "color" }, to: { node: "principled", socket: "
|
|
51
|
+
{ from: { node: "principled_base", socket: "color" }, to: { node: "principled", socket: "base_color" } },
|
|
52
52
|
{ from: { node: "npr_emit", socket: "color" }, to: { node: "mix_shader_001", socket: "a" } },
|
|
53
53
|
{ from: { node: "principled", socket: "color" }, to: { node: "mix_shader_001", socket: "b" } },
|
|
54
54
|
],
|
|
@@ -18,13 +18,13 @@ export const DEFAULT_GRAPH: ShaderGraph = {
|
|
|
18
18
|
{
|
|
19
19
|
id: "principled",
|
|
20
20
|
type: "principled",
|
|
21
|
-
inputs: { metallic: 0.0,
|
|
21
|
+
inputs: { metallic: 0.0, specular_ior_level: 0.5, roughness: 0.5, spec_clamp: 10.0, sheen_weight: 0.0, sheen_tint: 0.0 },
|
|
22
22
|
},
|
|
23
23
|
],
|
|
24
24
|
links: [
|
|
25
25
|
{ from: { node: "tex", socket: "color" }, to: { node: "base", socket: "a" } },
|
|
26
26
|
{ from: { node: "mat", socket: "color" }, to: { node: "base", socket: "b" } },
|
|
27
|
-
{ from: { node: "base", socket: "color" }, to: { node: "principled", socket: "
|
|
27
|
+
{ from: { node: "base", socket: "color" }, to: { node: "principled", socket: "base_color" } },
|
|
28
28
|
],
|
|
29
29
|
output: { node: "principled", socket: "color" },
|
|
30
30
|
}
|
package/src/graph/presets/eye.ts
CHANGED
|
@@ -18,13 +18,13 @@ export const EYE_GRAPH: ShaderGraph = {
|
|
|
18
18
|
{
|
|
19
19
|
id: "principled",
|
|
20
20
|
type: "principled",
|
|
21
|
-
inputs: { metallic: 0.0,
|
|
21
|
+
inputs: { metallic: 0.0, specular_ior_level: 0.5, roughness: 0.5, spec_clamp: 1e30, sheen_weight: 0.0, sheen_tint: 0.0 },
|
|
22
22
|
},
|
|
23
23
|
{ id: "emission", type: "emission", inputs: { strength: 1.5 } },
|
|
24
24
|
{ id: "add", type: "add_shader" },
|
|
25
25
|
],
|
|
26
26
|
links: [
|
|
27
|
-
{ from: { node: "tex", socket: "color" }, to: { node: "principled", socket: "
|
|
27
|
+
{ from: { node: "tex", socket: "color" }, to: { node: "principled", socket: "base_color" } },
|
|
28
28
|
{ from: { node: "tex", socket: "color" }, to: { node: "emission", socket: "color" } },
|
|
29
29
|
{ from: { node: "principled", socket: "color" }, to: { node: "add", socket: "a" } },
|
|
30
30
|
{ from: { node: "emission", socket: "color" }, to: { node: "add", socket: "b" } },
|