reze-engine 0.66.5 → 0.67.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/dist/engine.d.ts +20 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +83 -17
- package/dist/graph/presets/body.js +1 -1
- package/dist/graph/presets/cloth_rough.js +1 -1
- package/dist/graph/presets/cloth_smooth.js +1 -1
- package/dist/graph/presets/face.js +1 -1
- package/dist/graph/presets/metal.js +1 -1
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +84 -6
- package/dist/graph/schema.d.ts +14 -0
- package/dist/graph/schema.d.ts.map +1 -1
- 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 +176 -7
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +4 -2
- package/dist/shaders/passes/outline.d.ts +7 -0
- package/dist/shaders/passes/outline.d.ts.map +1 -1
- package/dist/shaders/passes/outline.js +67 -16
- package/package.json +1 -1
- package/src/engine.ts +101 -19
- package/src/graph/presets/body.ts +1 -1
- package/src/graph/presets/cloth_rough.ts +1 -1
- package/src/graph/presets/cloth_smooth.ts +1 -1
- package/src/graph/presets/face.ts +1 -1
- package/src/graph/presets/metal.ts +1 -1
- package/src/graph/registry.ts +85 -6
- package/src/graph/schema.ts +14 -0
- package/src/shaders/materials/nodes.ts +176 -7
- package/src/shaders/passes/composite.ts +4 -2
- package/src/shaders/passes/outline.ts +67 -16
package/src/engine.ts
CHANGED
|
@@ -79,7 +79,14 @@ import {
|
|
|
79
79
|
} from "./shaders/lights"
|
|
80
80
|
import { buildLightGrid } from "./light-grid"
|
|
81
81
|
import { groundShaderWgsl, GROUND_NOISE_BAKE_WGSL, GROUND_NOISE_SIZE } from "./shaders/passes/ground"
|
|
82
|
-
import {
|
|
82
|
+
import {
|
|
83
|
+
outlineShaderWgsl,
|
|
84
|
+
RZ_OUTLINE_COLOR_OFFSET,
|
|
85
|
+
RZ_OUTLINE_DISSOLVE_OFFSET,
|
|
86
|
+
RZ_OUTLINE_SHADOW_OFFSET,
|
|
87
|
+
RZ_OUTLINE_TEXTURED_OFFSET,
|
|
88
|
+
RZ_OUTLINE_WIDTH_OFFSET,
|
|
89
|
+
} from "./shaders/passes/outline"
|
|
83
90
|
import { transparentDepthPrepassWgsl } from "./shaders/passes/depth-prepass"
|
|
84
91
|
import { SELECTION_MASK_SHADER_WGSL, SELECTION_EDGE_SHADER_WGSL } from "./shaders/passes/selection"
|
|
85
92
|
import { GIZMO_SHADER_WGSL } from "./shaders/passes/gizmo"
|
|
@@ -238,7 +245,7 @@ const PRESET_NAME_HINTS: Array<[MaterialPreset, string[]]> = [
|
|
|
238
245
|
"hair", "ahoge", "bang",
|
|
239
246
|
],
|
|
240
247
|
],
|
|
241
|
-
["body", ["肌", "皮肤", "skin"]],
|
|
248
|
+
["body", ["肌", "皮肤", "skin", "指甲", "nail"]],
|
|
242
249
|
["metal", ["金属", "メタル", "metal", "earring", "耳环", "耳環"]],
|
|
243
250
|
[
|
|
244
251
|
"cloth_smooth",
|
|
@@ -284,6 +291,11 @@ const PRESET_NAME_HINTS: Array<[MaterialPreset, string[]]> = [
|
|
|
284
291
|
],
|
|
285
292
|
]
|
|
286
293
|
|
|
294
|
+
// Whole names that are body parts — skin by another name. Whole, never
|
|
295
|
+
// substrings: 手 alone is a hand, inside 手套 (gloves) or 手镯 (a bracelet) it is
|
|
296
|
+
// what the hand wears; 足 is a leg, 足袋 a sock.
|
|
297
|
+
const BODY_PART_NAMES = new Set(["手", "手部", "腕", "足", "脚", "腿", "爪", "hand", "hands", "arm", "arms", "leg", "legs", "body"])
|
|
298
|
+
|
|
287
299
|
// Resolve a material name to a style category (override map first, then name hints), or
|
|
288
300
|
// null if nothing matches — a null-resolving material stays ungrouped (neutral default).
|
|
289
301
|
function resolvePreset(materialName: string, map: MaterialPresetMap | undefined): MaterialPreset | null {
|
|
@@ -293,6 +305,7 @@ function resolvePreset(materialName: string, map: MaterialPresetMap | undefined)
|
|
|
293
305
|
}
|
|
294
306
|
}
|
|
295
307
|
const lower = materialName.toLowerCase()
|
|
308
|
+
if (BODY_PART_NAMES.has(lower.replace(/[\s_.\-0-9]+$/, ""))) return "body"
|
|
296
309
|
for (const [preset, hints] of PRESET_NAME_HINTS) {
|
|
297
310
|
for (const hint of hints) {
|
|
298
311
|
if (lower.includes(hint)) return preset
|
|
@@ -641,10 +654,16 @@ export type ViewTransformOptions = {
|
|
|
641
654
|
exposure: number
|
|
642
655
|
/** After the transform, display gamma (`pow(rgb, 1/gamma)`). */
|
|
643
656
|
gamma: number
|
|
657
|
+
/**
|
|
658
|
+
* The "soft" curve's power, (1 - e^(-2.5x))^contrast. Aether Gazer sets it per
|
|
659
|
+
* scene (SceneSetting._contrast): 1.4 is its common value, the character page
|
|
660
|
+
* runs 1.7. A stage brings its own; the other transforms ignore it.
|
|
661
|
+
*/
|
|
662
|
+
contrast: number
|
|
644
663
|
/**
|
|
645
664
|
* Which display transform the frame is formed with (composite.ts, viewTransform).
|
|
646
665
|
*
|
|
647
|
-
* "soft" — Aether Gazer's own final curve, (1 - e^(-2.5x))^
|
|
666
|
+
* "soft" — Aether Gazer's own final curve, (1 - e^(-2.5x))^contrast. The default.
|
|
648
667
|
* "neutral" — Unity URP's Neutral, the Khronos PBR Neutral operator: colour
|
|
649
668
|
* passes through until the top fifth, which rolls off.
|
|
650
669
|
* "aces" — Unity URP's ACES (RRT + ODT approximation from the Core RP).
|
|
@@ -660,6 +679,7 @@ export type ViewTransformName = "soft" | "neutral" | "aces" | "none"
|
|
|
660
679
|
export const DEFAULT_VIEW_TRANSFORM: ViewTransformOptions = {
|
|
661
680
|
exposure: 0,
|
|
662
681
|
gamma: 1.0,
|
|
682
|
+
contrast: 1.4,
|
|
663
683
|
transform: "soft",
|
|
664
684
|
}
|
|
665
685
|
|
|
@@ -823,7 +843,9 @@ interface DrawCall {
|
|
|
823
843
|
/** Edge-flagged materials: interleaved inverted-hull outline drawn right after
|
|
824
844
|
* this material with the outline pipeline. Shares this call's index range;
|
|
825
845
|
* own bind group (edge uniforms + diffuse texture for the alpha test). */
|
|
826
|
-
|
|
846
|
+
/** The inverted hull: `pmx` when the PMX asked for an edge, `drawn` when it
|
|
847
|
+
* is drawn — the PMX's, or its style group's look's outline. */
|
|
848
|
+
outline?: { bindGroup: GPUBindGroup; pmx: boolean; drawn: boolean }
|
|
827
849
|
/** MODEL-SPACE AABB over this material's index range, computed at load:
|
|
828
850
|
* [minX, minY, minZ, maxX, maxY, maxZ]. Usable for culling only while the
|
|
829
851
|
* owning model is rigid (see ModelInstance.rigid) — animation moves vertices
|
|
@@ -930,6 +952,9 @@ interface ModelInstance {
|
|
|
930
952
|
* material buffers never reached it and a dissolved character kept her
|
|
931
953
|
* outline. Kept here so that write has somewhere to go. */
|
|
932
954
|
outlineUniformBuffers: GPUBuffer[]
|
|
955
|
+
/** Each outlined material's hull block and its PMX edge (colour, size), for
|
|
956
|
+
* a style graph's outline to take over and give back. */
|
|
957
|
+
outlineByMaterial: Map<string, { buffer: GPUBuffer; edge: Float32Array<ArrayBuffer>; picture: GPUTextureView }>
|
|
933
958
|
model: Model
|
|
934
959
|
/**
|
|
935
960
|
* Keep simulating this model's cloth while it is HIDDEN.
|
|
@@ -2632,7 +2657,7 @@ export class Engine {
|
|
|
2632
2657
|
// 15 × vec4f — see the viewU comment in composite.ts. The last one is the
|
|
2633
2658
|
// camera's world position, which is what lets a foreground effect turn the
|
|
2634
2659
|
// depth it is handed into a PLACE (bgWorldPos) rather than a distance.
|
|
2635
|
-
private readonly compositeUniformData = new Float32Array(
|
|
2660
|
+
private readonly compositeUniformData = new Float32Array(64)
|
|
2636
2661
|
/** Composite background (display-space sRGB 0–1) — null = transparent canvas. */
|
|
2637
2662
|
private backgroundColor: Vec3 | null = null
|
|
2638
2663
|
// 360 backdrop (equirectangular skybox, sampled by view ray in composite).
|
|
@@ -3054,6 +3079,7 @@ export class Engine {
|
|
|
3054
3079
|
return {
|
|
3055
3080
|
exposure: partial?.exposure ?? d.exposure,
|
|
3056
3081
|
gamma: partial?.gamma ?? d.gamma,
|
|
3082
|
+
contrast: partial?.contrast ?? d.contrast,
|
|
3057
3083
|
transform: partial?.transform ?? d.transform,
|
|
3058
3084
|
}
|
|
3059
3085
|
}
|
|
@@ -3072,7 +3098,7 @@ export class Engine {
|
|
|
3072
3098
|
|
|
3073
3099
|
getViewTransformOptions(): ViewTransformOptions {
|
|
3074
3100
|
const v = this.viewTransform
|
|
3075
|
-
return { exposure: v.exposure, gamma: v.gamma, transform: v.transform }
|
|
3101
|
+
return { exposure: v.exposure, gamma: v.gamma, contrast: v.contrast, transform: v.transform }
|
|
3076
3102
|
}
|
|
3077
3103
|
|
|
3078
3104
|
private colorGrading: ColorGradingOptions = {
|
|
@@ -3188,6 +3214,7 @@ export class Engine {
|
|
|
3188
3214
|
const v = this.viewTransform
|
|
3189
3215
|
if (patch.exposure !== undefined) v.exposure = patch.exposure
|
|
3190
3216
|
if (patch.gamma !== undefined) v.gamma = patch.gamma
|
|
3217
|
+
if (patch.contrast !== undefined) v.contrast = patch.contrast
|
|
3191
3218
|
if (patch.transform !== undefined) v.transform = patch.transform
|
|
3192
3219
|
if (this.device && this.compositeUniformBuffer) {
|
|
3193
3220
|
this.writeCompositeViewUniforms()
|
|
@@ -3266,6 +3293,9 @@ export class Engine {
|
|
|
3266
3293
|
u[25] = Engine.VIEW_TRANSFORM_ID[v.transform] ?? 0
|
|
3267
3294
|
u[26] = this.canvas.width
|
|
3268
3295
|
u[27] = this.canvas.height
|
|
3296
|
+
// The soft curve's power (viewU[15].x), the scene's own (SceneSetting._contrast).
|
|
3297
|
+
// Its own vec4: viewU[11..14] are the cast's positions whenever an effect runs.
|
|
3298
|
+
u[60] = v.contrast
|
|
3269
3299
|
// ── Grade (viewU[7..9]) ── The UI's three tonal COLORS map to ASC CDL here,
|
|
3270
3300
|
// on the CPU, so the shader only ever sees slope/offset/power. Mid-gray is
|
|
3271
3301
|
// neutral in all three; the signed distance from it is the amount.
|
|
@@ -8468,6 +8498,8 @@ export class Engine {
|
|
|
8468
8498
|
entries: [
|
|
8469
8499
|
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
8470
8500
|
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
8501
|
+
// the key light, for a style graph's outline colour (see outline.ts)
|
|
8502
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
8471
8503
|
],
|
|
8472
8504
|
})
|
|
8473
8505
|
// Outline per-instance reuses mainPerInstanceBindGroupLayout (same skinMats binding)
|
|
@@ -8476,6 +8508,7 @@ export class Engine {
|
|
|
8476
8508
|
entries: [
|
|
8477
8509
|
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
8478
8510
|
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
8511
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
8479
8512
|
],
|
|
8480
8513
|
})
|
|
8481
8514
|
|
|
@@ -8494,6 +8527,7 @@ export class Engine {
|
|
|
8494
8527
|
entries: [
|
|
8495
8528
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
8496
8529
|
{ binding: 1, resource: this.materialSampler },
|
|
8530
|
+
{ binding: 2, resource: { buffer: this.lightUniformBuffer } },
|
|
8497
8531
|
],
|
|
8498
8532
|
})
|
|
8499
8533
|
|
|
@@ -8507,6 +8541,7 @@ export class Engine {
|
|
|
8507
8541
|
entries: [
|
|
8508
8542
|
{ binding: 0, resource: { buffer: this.mirrorCameraBuffer } },
|
|
8509
8543
|
{ binding: 1, resource: this.materialSampler },
|
|
8544
|
+
{ binding: 2, resource: { buffer: this.lightUniformBuffer } },
|
|
8510
8545
|
],
|
|
8511
8546
|
})
|
|
8512
8547
|
|
|
@@ -8694,13 +8729,14 @@ export class Engine {
|
|
|
8694
8729
|
// strength belong to the combine step.
|
|
8695
8730
|
this.compositeUniformBuffer = this.device.createBuffer({
|
|
8696
8731
|
label: "composite view uniforms",
|
|
8697
|
-
//
|
|
8732
|
+
// 16 × vec4f: (exposure, invGamma, _, _) · (bloom tint, intensity) ·
|
|
8698
8733
|
// (bg rgb, mode) · camera right/up/forward basis for the 360 skybox ray ·
|
|
8699
8734
|
// (time, _, canvas width, canvas height) for user effects · three grade
|
|
8700
8735
|
// vectors (CDL offset+contrast, power+saturation, slope+flag) · camera
|
|
8701
8736
|
// world position, for an effect placing itself in the scene · four
|
|
8702
|
-
// character positions, for one that wants to respond to the cast
|
|
8703
|
-
|
|
8737
|
+
// character positions, for one that wants to respond to the cast · the
|
|
8738
|
+
// soft curve's contrast. Shaders that stop at the cast declare 15.
|
|
8739
|
+
size: 256,
|
|
8704
8740
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
8705
8741
|
})
|
|
8706
8742
|
this.dofUniformBuffer = this.device.createBuffer({
|
|
@@ -14894,6 +14930,7 @@ export class Engine {
|
|
|
14894
14930
|
dissolve: 1,
|
|
14895
14931
|
materialUniformBuffers: [],
|
|
14896
14932
|
outlineUniformBuffers: [],
|
|
14933
|
+
outlineByMaterial: new Map(),
|
|
14897
14934
|
cullModelIndex: 0,
|
|
14898
14935
|
// Seeded false: the first skin-matrix upload decides it, and until then the
|
|
14899
14936
|
// sphere path is the safe answer (it never culls something it should not).
|
|
@@ -15535,8 +15572,12 @@ export class Engine {
|
|
|
15535
15572
|
// every material — on the heaviest mesh in the scene that doubles the
|
|
15536
15573
|
// geometry submitted per frame to draw cartoon outlines around
|
|
15537
15574
|
// architecture, which is not the look anyone is after.
|
|
15575
|
+
// Every character material gets its hull: the PMX edge flag draws it as
|
|
15576
|
+
// MMD does, and a style group whose look carries an outline draws it on
|
|
15577
|
+
// any material (assignDrawCallGroups sets `drawn`).
|
|
15538
15578
|
let outline: DrawCall["outline"]
|
|
15539
|
-
|
|
15579
|
+
const pmxEdge = (mat.edgeFlag & 0x10) !== 0 && mat.edgeSize > 0
|
|
15580
|
+
if (!inst.isStage) {
|
|
15540
15581
|
const materialUniformData = new Float32Array([
|
|
15541
15582
|
mat.edgeColor[0],
|
|
15542
15583
|
mat.edgeColor[1],
|
|
@@ -15551,25 +15592,29 @@ export class Engine {
|
|
|
15551
15592
|
1,
|
|
15552
15593
|
// The scene's width multiplier, at RZ_OUTLINE_WIDTH_OFFSET.
|
|
15553
15594
|
this.outlineWidth,
|
|
15595
|
+
// A style graph's outline, at RZ_OUTLINE_TEXTURED_OFFSET: none yet.
|
|
15554
15596
|
0,
|
|
15555
15597
|
// The scene's colour override, at RZ_OUTLINE_COLOR_OFFSET.
|
|
15556
15598
|
this.outlineColor[0],
|
|
15557
15599
|
this.outlineColor[1],
|
|
15558
15600
|
this.outlineColor[2],
|
|
15559
15601
|
this.outlineColor[3],
|
|
15602
|
+
// The graph outline's shadow colour, at RZ_OUTLINE_SHADOW_OFFSET.
|
|
15603
|
+
0,
|
|
15604
|
+
0,
|
|
15605
|
+
0,
|
|
15606
|
+
0,
|
|
15560
15607
|
])
|
|
15561
15608
|
const outlineUniformBuffer = this.createUniformBuffer(`${prefix}outline: ${mat.name}`, materialUniformData)
|
|
15562
15609
|
inst.gpuBuffers.push(outlineUniformBuffer)
|
|
15563
15610
|
inst.outlineUniformBuffers.push(outlineUniformBuffer)
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
{ binding: 0, resource: { buffer: outlineUniformBuffer } },
|
|
15569
|
-
{ binding: 1, resource: textureView },
|
|
15570
|
-
],
|
|
15611
|
+
inst.outlineByMaterial.set(mat.name, {
|
|
15612
|
+
buffer: outlineUniformBuffer,
|
|
15613
|
+
edge: materialUniformData.slice(0, 5),
|
|
15614
|
+
picture: textureView,
|
|
15571
15615
|
})
|
|
15572
|
-
|
|
15616
|
+
const outlineBindGroup = this.outlineBindGroup(`${prefix}outline: ${mat.name}`, outlineUniformBuffer, textureView, null)
|
|
15617
|
+
outline = { bindGroup: outlineBindGroup, pmx: pmxEdge, drawn: pmxEdge }
|
|
15573
15618
|
if (!inst.outlineVertexBuffer) {
|
|
15574
15619
|
const data = inst.model.getOutlineVertices()
|
|
15575
15620
|
const buf = this.device.createBuffer({
|
|
@@ -18109,8 +18154,45 @@ export class Engine {
|
|
|
18109
18154
|
|
|
18110
18155
|
// Rebind each material draw call to its (successfully-installed) group's uniform buffer,
|
|
18111
18156
|
// or the zero buffer when ungrouped, then re-sort by render-class draw order.
|
|
18157
|
+
/** A hull's per-material group: its block, the picture, the look's tint. */
|
|
18158
|
+
private outlineBindGroup(label: string, buffer: GPUBuffer, picture: GPUTextureView, tint: GPUTexture | null): GPUBindGroup {
|
|
18159
|
+
return this.device.createBindGroup({
|
|
18160
|
+
label,
|
|
18161
|
+
layout: this.outlinePerMaterialBindGroupLayout,
|
|
18162
|
+
entries: [
|
|
18163
|
+
{ binding: 0, resource: { buffer } },
|
|
18164
|
+
{ binding: 1, resource: picture },
|
|
18165
|
+
{ binding: 2, resource: (tint ?? this.fallbackMaterialTexture).createView() },
|
|
18166
|
+
],
|
|
18167
|
+
})
|
|
18168
|
+
}
|
|
18169
|
+
|
|
18112
18170
|
private assignDrawCallGroups(inst: ModelInstance, claimed: Map<string, string>): void {
|
|
18113
18171
|
inst.materialToGroup.clear()
|
|
18172
|
+
// The hulls: a group whose look carries an outline (ShaderGraph.outline)
|
|
18173
|
+
// draws it in place of the PMX edge; the rest keep, or get back, their own.
|
|
18174
|
+
for (const [name, hull] of inst.outlineByMaterial) {
|
|
18175
|
+
const id = claimed.get(name)
|
|
18176
|
+
const install = id ? inst.styleGroups.get(id) : undefined
|
|
18177
|
+
const o = install?.group.graph.outline
|
|
18178
|
+
const block = o
|
|
18179
|
+
? new Float32Array([o.color[0], o.color[1], o.color[2], 1, o.width])
|
|
18180
|
+
: hull.edge
|
|
18181
|
+
this.device.queue.writeBuffer(hull.buffer, 0, block)
|
|
18182
|
+
this.device.queue.writeBuffer(hull.buffer, RZ_OUTLINE_TEXTURED_OFFSET, new Float32Array([o ? 1 : 0]))
|
|
18183
|
+
if (o) this.device.queue.writeBuffer(hull.buffer, RZ_OUTLINE_SHADOW_OFFSET, new Float32Array([...o.shadowColor, 1]))
|
|
18184
|
+
const tint = o?.tint != null ? (install?.imagesByMaterial?.[name] ?? install?.images)?.[o.tint] ?? null : null
|
|
18185
|
+
for (const dc of inst.drawCalls) {
|
|
18186
|
+
if (dc.materialName !== name || !dc.outline) continue
|
|
18187
|
+
dc.outline.bindGroup = this.outlineBindGroup(`outline: ${name}`, hull.buffer, hull.picture, tint)
|
|
18188
|
+
this.bundlesDirty = true
|
|
18189
|
+
const drawn = o ? o.width > 0 : dc.outline.pmx
|
|
18190
|
+
if (dc.outline.drawn !== drawn) {
|
|
18191
|
+
dc.outline.drawn = drawn
|
|
18192
|
+
this.bundlesDirty = true
|
|
18193
|
+
}
|
|
18194
|
+
}
|
|
18195
|
+
}
|
|
18114
18196
|
for (const dc of inst.drawCalls) {
|
|
18115
18197
|
if (!dc.baseBindGroupEntries) continue // outlines/ground are never grouped
|
|
18116
18198
|
const wantId = claimed.get(dc.materialName)
|
|
@@ -18609,7 +18691,7 @@ export class Engine {
|
|
|
18609
18691
|
}
|
|
18610
18692
|
pass.setBindGroup(2, draw.bindGroup)
|
|
18611
18693
|
this.issueDraw(pass, draw, view.args)
|
|
18612
|
-
if (draw.outline && this.outlineEnabled && view.outlines) outlined.push(draw)
|
|
18694
|
+
if (draw.outline?.drawn && this.outlineEnabled && view.outlines) outlined.push(draw)
|
|
18613
18695
|
}
|
|
18614
18696
|
// THE HULLS AFTER THE SURFACES, as MMD draws a model's edges. A hull held
|
|
18615
18697
|
// at the minimum width is faded, and it still writes depth: drawn between
|
|
@@ -24,7 +24,7 @@ export const BODY_GRAPH: ShaderGraph = {
|
|
|
24
24
|
{ id: "lit_tint", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.5, value: 1.0, fac: 1.0 } },
|
|
25
25
|
{ id: "toon_color", type: "mix/blend" },
|
|
26
26
|
{ id: "bc", type: "bright_contrast", inputs: { bright: 0.1, contrast: 0.2 } },
|
|
27
|
-
{ id: "emission3", type: "vector_math/scale", inputs: { scale:
|
|
27
|
+
{ id: "emission3", type: "vector_math/scale", inputs: { scale: 3.2 } },
|
|
28
28
|
{ id: "warm_add", type: "math/add", inputs: { b: 0.5 } },
|
|
29
29
|
{ id: "warm_clamp", type: "math/clamp01" },
|
|
30
30
|
{
|
|
@@ -30,7 +30,7 @@ export const CLOTH_ROUGH_GRAPH: ShaderGraph = {
|
|
|
30
30
|
{ id: "mix_003", type: "mix/blend" },
|
|
31
31
|
{ id: "hue_004", type: "hue_sat", inputs: { hue: 0.5, saturation: 0.800000011920929, value: 2.0, fac: 1.0 } },
|
|
32
32
|
{ id: "npr_overlay", type: "mix/overlay", inputs: { fac: 1.0 } },
|
|
33
|
-
{ id: "npr_emit", type: "vector_math/scale", inputs: { scale:
|
|
33
|
+
{ id: "npr_emit", type: "vector_math/scale", inputs: { scale: 14.56 } },
|
|
34
34
|
{ id: "noise", type: "tex_noise", inputs: { scale: 17.7 } },
|
|
35
35
|
{ id: "noise_ramp", type: "ramp_linear", inputs: { pos0: 0.0, pos1: 1.0 } },
|
|
36
36
|
{ id: "bump", type: "bump", inputs: { strength: 1.0 } },
|
|
@@ -28,7 +28,7 @@ export const CLOTH_SMOOTH_GRAPH: ShaderGraph = {
|
|
|
28
28
|
{ id: "mix_003", type: "mix/blend" },
|
|
29
29
|
{ id: "hue_004", type: "hue_sat", inputs: { hue: 0.5, saturation: 0.800000011920929, value: 2.0, fac: 1.0 } },
|
|
30
30
|
{ id: "npr_overlay", type: "mix/overlay", inputs: { fac: 1.0 } },
|
|
31
|
-
{ id: "npr_emit", type: "vector_math/scale", inputs: { scale:
|
|
31
|
+
{ id: "npr_emit", type: "vector_math/scale", inputs: { scale: 14.56 } },
|
|
32
32
|
{ id: "principled_base", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 0.800000011920929, fac: 1.0 } },
|
|
33
33
|
{
|
|
34
34
|
id: "principled",
|
|
@@ -29,7 +29,7 @@ export const FACE_GRAPH: ShaderGraph = {
|
|
|
29
29
|
{ id: "lit_tint", type: "hue_sat", inputs: { hue: 0.46000000834465027, saturation: 1.600000023841858, value: 1.5, fac: 1.0 } },
|
|
30
30
|
{ id: "toon_color", type: "mix/blend" },
|
|
31
31
|
{ id: "bc", type: "bright_contrast", inputs: { bright: 0.1, contrast: 0.2 } },
|
|
32
|
-
{ id: "emission3", type: "vector_math/scale", inputs: { scale: 2.
|
|
32
|
+
{ id: "emission3", type: "vector_math/scale", inputs: { scale: 2.0 } },
|
|
33
33
|
|
|
34
34
|
// ── warm rim (toon*0.5+0.5 → cardinal ramp) ──
|
|
35
35
|
{ id: "warm_mul", type: "math/multiply", inputs: { b: 0.5 } },
|
|
@@ -27,7 +27,7 @@ export const METAL_GRAPH: ShaderGraph = {
|
|
|
27
27
|
{ id: "mix_004", type: "mix/blend" },
|
|
28
28
|
{ id: "hue_004", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 2.0, fac: 1.0 } },
|
|
29
29
|
{ id: "npr_overlay", type: "mix/overlay", inputs: { fac: 1.0 } },
|
|
30
|
-
{ id: "npr_emit", type: "vector_math/scale", inputs: { scale:
|
|
30
|
+
{ id: "npr_emit", type: "vector_math/scale", inputs: { scale: 6.48 } },
|
|
31
31
|
{ id: "voro_cross", type: "vect_cross", inputs: { b: [0, 1, 0] } },
|
|
32
32
|
{ id: "voro", type: "tex_voronoi/color", inputs: { scale: 4.3 } },
|
|
33
33
|
{ id: "voro_ramp", type: "ramp_linear", inputs: { pos0: 0.0, pos1: 1.0 } },
|
package/src/graph/registry.ts
CHANGED
|
@@ -123,7 +123,11 @@ const uberNode = (slot: number): NodeSpec => ({
|
|
|
123
123
|
roughness: F(0.6),
|
|
124
124
|
occlusion: F(1),
|
|
125
125
|
normal: { type: "vector", contextDefault: "n" },
|
|
126
|
-
|
|
126
|
+
// The key light: this draw's, by default — the sun, or the light on the
|
|
127
|
+
// character's own layer — its way and its colour (the game's _LocalLightDir,
|
|
128
|
+
// _LocalLightColor). Linked, a look supplies its own (ag_key_light).
|
|
129
|
+
direction: { type: "vector", contextDefault: "l" },
|
|
130
|
+
light_color: { type: "color", contextDefault: "sun" },
|
|
127
131
|
// Unity's v into the ramp: the property map's alpha, which picks a
|
|
128
132
|
// surface's band (skin 0.702, hair and pale cloth 0.506).
|
|
129
133
|
row: F(0.702),
|
|
@@ -137,24 +141,45 @@ const uberNode = (slot: number): NodeSpec => ({
|
|
|
137
141
|
rim_in_light: F(1),
|
|
138
142
|
emission: C([0, 0, 0]),
|
|
139
143
|
reflection: F(1),
|
|
144
|
+
// THE STUDIO AMBIENT (_GlobalIlluminationOverride): the game blends a
|
|
145
|
+
// character's ambient from its own reflection cube's smallest mip — near
|
|
146
|
+
// neutral, 0.4–0.9 across the newer skins — over the scene's light, by the
|
|
147
|
+
// material's blend (bodies 0.8, hair 1, faces 0.5). It is what keeps a
|
|
148
|
+
// character clean on a coloured stage.
|
|
149
|
+
studio: C([0.6, 0.6, 0.6]),
|
|
150
|
+
studio_blend: F(0),
|
|
140
151
|
// A face's SDF shade (ag_face_sdf); left at -1, the material reads N·L.
|
|
141
152
|
shade: F(-1),
|
|
142
153
|
// 1: the game's FACE_MODE — no GGX, the environment at a flat 0.0157.
|
|
143
154
|
face: F(0),
|
|
155
|
+
// A face's SDF highlight (ag_face_spec), in place of GGX.
|
|
156
|
+
face_spec: F(0),
|
|
157
|
+
// 1: hair (_ANISOTROPIC_SPECULAR) — its ring (ag_hair_ring) in place of GGX,
|
|
158
|
+
// the environment at a flat 0.0157.
|
|
159
|
+
hair: F(0),
|
|
160
|
+
hair_spec: C([0, 0, 0]),
|
|
144
161
|
},
|
|
145
162
|
outputs: { color: "color" },
|
|
146
163
|
emit: (a) =>
|
|
147
|
-
`ag_uber(${slot}u, ${a.base}, ${a.metallic}, ${a.roughness}, ${a.occlusion}, ${a.normal}, ${a.direction}, ${a.row}, ${a.receive_shadow}, ` +
|
|
148
|
-
`${a.rim_mask}, ${a.rim_mid}, ${a.rim_width}, ${a.rim_tint}, ${a.rim_intensity}, ${a.rim_albedo}, ${a.rim_in_light}, ${a.emission}, ${a.reflection}, ${a.shade}, ${a.face}, input.worldPos, v)`,
|
|
164
|
+
`ag_uber(${slot}u, ${a.base}, ${a.metallic}, ${a.roughness}, ${a.occlusion}, ${a.normal}, ${a.direction}, ${a.light_color}, ${a.row}, ${a.receive_shadow}, ` +
|
|
165
|
+
`${a.rim_mask}, ${a.rim_mid}, ${a.rim_width}, ${a.rim_tint}, ${a.rim_intensity}, ${a.rim_albedo}, ${a.rim_in_light}, ${a.emission}, ${a.reflection}, ${a.studio}, ${a.studio_blend}, ${a.shade}, ${a.face}, ${a.face_spec}, ${a.hair}, ${a.hair_spec}, input.worldPos, v)`,
|
|
149
166
|
takesLight: true,
|
|
150
167
|
})
|
|
151
168
|
const UBER_NODES: Record<string, NodeSpec> = Object.fromEntries([0, 1, 2, 3].map((s) => [`ag_uber/${s}`, uberNode(s)]))
|
|
152
|
-
//
|
|
153
|
-
//
|
|
169
|
+
// How much a colour reads as skin (ag_skin): what picks a surface's ramp row in
|
|
170
|
+
// a general look, where the game would read its property mask.
|
|
171
|
+
const AG_SKIN: NodeSpec = {
|
|
172
|
+
inputs: { color: C([1, 1, 1], true) },
|
|
173
|
+
outputs: { value: "float" },
|
|
174
|
+
emit: (a) => `ag_skin(${a.color})`,
|
|
175
|
+
}
|
|
176
|
+
// The face's SDF shade, the image on the slot the type names. An empty slot
|
|
177
|
+
// reads white: the whole face takes one shade from where the key light stands
|
|
178
|
+
// to the head, the game's flat face for a model without its SDF.
|
|
154
179
|
const faceSdfNode = (slot: number): NodeSpec => ({
|
|
155
180
|
inputs: {
|
|
156
181
|
uv: { type: "vector", contextDefault: "vec3f(input.uv, 0.0)" },
|
|
157
|
-
direction: { type: "vector",
|
|
182
|
+
direction: { type: "vector", contextDefault: "l" },
|
|
158
183
|
normal: { type: "vector", contextDefault: "n" },
|
|
159
184
|
smoothness: F(0.1),
|
|
160
185
|
invert: F(0),
|
|
@@ -163,6 +188,55 @@ const faceSdfNode = (slot: number): NodeSpec => ({
|
|
|
163
188
|
emit: (a) => `ag_face_sdf(${slot}u, ${a.uv}.xy, ${a.direction}, ${a.normal}, ${a.smoothness}, ${a.invert})`,
|
|
164
189
|
})
|
|
165
190
|
const FACE_SDF_NODES: Record<string, NodeSpec> = Object.fromEntries([0, 1, 2, 3].map((s) => [`ag_face_sdf/${s}`, faceSdfNode(s)]))
|
|
191
|
+
// The newer SDF (_SDFType 1): a painted face normal, not a threshold field.
|
|
192
|
+
const faceSdfNewNode = (slot: number): NodeSpec => ({
|
|
193
|
+
...faceSdfNode(slot),
|
|
194
|
+
emit: (a) => `ag_face_sdf_new(${slot}u, ${a.uv}.xy, ${a.direction}, ${a.normal}, ${a.smoothness}, ${a.invert})`,
|
|
195
|
+
})
|
|
196
|
+
const FACE_SDF_NEW_NODES: Record<string, NodeSpec> = Object.fromEntries([0, 1, 2, 3].map((s) => [`ag_face_sdf_new/${s}`, faceSdfNewNode(s)]))
|
|
197
|
+
// The face's highlight from the newer SDF's green and blue.
|
|
198
|
+
const faceSpecNode = (slot: number): NodeSpec => ({
|
|
199
|
+
inputs: {
|
|
200
|
+
uv: { type: "vector", contextDefault: "vec3f(input.uv, 0.0)" },
|
|
201
|
+
direction: { type: "vector", contextDefault: "l" },
|
|
202
|
+
normal: { type: "vector", contextDefault: "n" },
|
|
203
|
+
anisotropy: F(1),
|
|
204
|
+
shift: F(0),
|
|
205
|
+
},
|
|
206
|
+
outputs: { value: "float" },
|
|
207
|
+
emit: (a) => `ag_face_spec(${slot}u, ${a.uv}.xy, ${a.direction}, ${a.normal}, v, ${a.anisotropy}, ${a.shift})`,
|
|
208
|
+
})
|
|
209
|
+
// The hair's angel ring, the band on the slot's image.
|
|
210
|
+
const hairRingNode = (slot: number): NodeSpec => ({
|
|
211
|
+
inputs: {
|
|
212
|
+
uv: { type: "vector", contextDefault: "vec3f(input.uv, 0.0)" },
|
|
213
|
+
normal: { type: "vector", contextDefault: "n" },
|
|
214
|
+
color: C([1, 1, 1]),
|
|
215
|
+
anisotropy: F(1),
|
|
216
|
+
shift: F(0),
|
|
217
|
+
},
|
|
218
|
+
outputs: { color: "color" },
|
|
219
|
+
emit: (a) => `ag_hair_ring(${slot}u, ${a.uv}.xy, ${a.normal}, v, ${a.color}, ${a.anisotropy}, ${a.shift})`,
|
|
220
|
+
})
|
|
221
|
+
const HAIR_RING_NODES: Record<string, NodeSpec> = Object.fromEntries([0, 1, 2, 3].map((s) => [`ag_hair_ring/${s}`, hairRingNode(s)]))
|
|
222
|
+
// The game's pupil (SimPipeline/Character/Eye): unlit relief, matcap and
|
|
223
|
+
// glints — slots: 0 depth, 1 normal, 2 matcap, 3 glint mask.
|
|
224
|
+
const AG_EYE: NodeSpec = {
|
|
225
|
+
inputs: {
|
|
226
|
+
height_scale: F(0.2), min_layer: F(8), max_layer: F(32), intensity: F(0.9),
|
|
227
|
+
main_color: C([1, 1, 1]), matcap_color: C([1, 1, 1]), matcap_pow: F(1),
|
|
228
|
+
mask_scale: F(0.15), mask_soft: F(0.014),
|
|
229
|
+
mask2: F(0), mask2_color: C([1, 1, 1]), mask3: F(0), mask3_color: C([1, 1, 1]),
|
|
230
|
+
blink: F(0), blink_scale: F(10), scale_speed: F(0.1), rotate_speed: F(0.6), blink_angle: F(5),
|
|
231
|
+
level: F(1),
|
|
232
|
+
},
|
|
233
|
+
outputs: { color: "color" },
|
|
234
|
+
emit: (a) =>
|
|
235
|
+
`ag_eye(input.uv, n, v, input.worldPos, ${a.height_scale}, ${a.min_layer}, ${a.max_layer}, ${a.intensity}, ${a.main_color}, ` +
|
|
236
|
+
`${a.matcap_color}, ${a.matcap_pow}, ${a.mask_scale}, ${a.mask_soft}, ${a.mask2}, ${a.mask2_color}, ${a.mask3}, ${a.mask3_color}, ` +
|
|
237
|
+
`${a.blink}, ${a.blink_scale}, ${a.scale_speed}, ${a.rotate_speed}, ${a.blink_angle}, ${a.level})`,
|
|
238
|
+
}
|
|
239
|
+
const FACE_SPEC_NODES: Record<string, NodeSpec> = Object.fromEntries([0, 1, 2, 3].map((s) => [`ag_face_spec/${s}`, faceSpecNode(s)]))
|
|
166
240
|
|
|
167
241
|
// A packed property map on the slot the type names — the game's material
|
|
168
242
|
// contract: R metal, G perceptual roughness (answered as smoothness, Unity's
|
|
@@ -630,7 +704,12 @@ export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
|
630
704
|
// Principled is one: the ramp image on the group slot named by the type.
|
|
631
705
|
// Defaults are 104903's skin.
|
|
632
706
|
...UBER_NODES,
|
|
707
|
+
ag_skin: AG_SKIN,
|
|
633
708
|
...FACE_SDF_NODES,
|
|
709
|
+
...FACE_SDF_NEW_NODES,
|
|
710
|
+
...FACE_SPEC_NODES,
|
|
711
|
+
ag_eye: AG_EYE,
|
|
712
|
+
...HAIR_RING_NODES,
|
|
634
713
|
...PROPERTY_MAP_NODES,
|
|
635
714
|
|
|
636
715
|
/**
|
package/src/graph/schema.ts
CHANGED
|
@@ -75,6 +75,20 @@ export type ShaderGraph = {
|
|
|
75
75
|
* images keeps those.
|
|
76
76
|
*/
|
|
77
77
|
images?: ({ url: string; srgb?: boolean } | null)[]
|
|
78
|
+
/**
|
|
79
|
+
* The look's outline, the anime game's in place of MMD's flat edge: the
|
|
80
|
+
* material's picture × colour, toward shadowColor where the key light leaves
|
|
81
|
+
* it, `width` world units wide (held 1.2–3 px of a 1920-wide frame) — an Aether
|
|
82
|
+
* Gazer _OutlineWidth w is w·1.3/1920·8 here. Unset, the PMX edge stands.
|
|
83
|
+
*/
|
|
84
|
+
outline?: {
|
|
85
|
+
color: [number, number, number]
|
|
86
|
+
shadowColor: [number, number, number]
|
|
87
|
+
width: number
|
|
88
|
+
/** One of `images` (its index) whose colour multiplies the outline at the
|
|
89
|
+
* mesh's uvs — the game's per-texel outline mask. */
|
|
90
|
+
tint?: number
|
|
91
|
+
}
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
export type Diagnostic = {
|