reze-engine 0.61.3 → 0.61.5
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 +74 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +260 -34
- 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/eye.d.ts.map +1 -1
- package/dist/graph/presets/eye.js +19 -0
- package/dist/graph/presets/face.js +1 -1
- package/dist/graph/presets/hair.js +1 -1
- package/dist/graph/presets/metal.js +1 -1
- package/dist/graph/presets/pool_floor.d.ts +3 -0
- package/dist/graph/presets/pool_floor.d.ts.map +1 -0
- package/dist/graph/presets/pool_floor.js +42 -0
- package/dist/graph/presets/water.d.ts +3 -0
- package/dist/graph/presets/water.d.ts.map +1 -0
- package/dist/graph/presets/water.js +31 -0
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +11 -0
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +19 -5
- package/dist/physics/autofit.d.ts +147 -0
- package/dist/physics/autofit.d.ts.map +1 -0
- package/dist/physics/autofit.js +501 -0
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +10 -9
- 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 +12 -0
- package/dist/shaders/passes/field-blit.d.ts +26 -0
- package/dist/shaders/passes/field-blit.d.ts.map +1 -0
- package/dist/shaders/passes/field-blit.js +65 -0
- package/dist/shaders/passes/ground-noise.d.ts +7 -0
- package/dist/shaders/passes/ground-noise.d.ts.map +1 -0
- package/dist/shaders/passes/ground-noise.js +88 -0
- package/dist/shaders/passes/particles.js +6 -6
- package/dist/shaders/passes/scene-contract.js +1 -1
- package/dist/shaders/passes/sim.d.ts +34 -0
- package/dist/shaders/passes/sim.d.ts.map +1 -0
- package/dist/shaders/passes/sim.js +169 -0
- package/dist/shaders/passes/subsurface.d.ts +34 -0
- package/dist/shaders/passes/subsurface.d.ts.map +1 -0
- package/dist/shaders/passes/subsurface.js +134 -0
- package/dist/shaders/scene-light-api.d.ts.map +1 -1
- package/dist/shaders/scene-light-api.js +11 -0
- package/dist/shaders/score-api.d.ts +10 -0
- package/dist/shaders/score-api.d.ts.map +1 -0
- package/dist/shaders/score-api.js +114 -0
- package/dist/shaders/shadow-api.d.ts +29 -0
- package/dist/shaders/shadow-api.d.ts.map +1 -0
- package/dist/shaders/shadow-api.js +112 -0
- package/package.json +1 -1
- package/src/engine.ts +275 -36
- 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/eye.ts +19 -0
- package/src/graph/presets/face.ts +1 -1
- package/src/graph/presets/hair.ts +1 -1
- package/src/graph/presets/metal.ts +1 -1
- package/src/graph/registry.ts +11 -0
- package/src/graph/slots.ts +19 -5
- package/src/shaders/materials/common.ts +10 -9
- package/src/shaders/materials/nodes.ts +12 -0
- package/src/shaders/passes/particles.ts +6 -6
- package/src/shaders/passes/scene-contract.ts +1 -1
- package/src/shaders/passes/subsurface.ts +136 -0
- package/src/shaders/scene-light-api.ts +11 -0
- package/dist/_dbg-loader.mjs +0 -1100
- package/dist/physics-debug.d.ts +0 -30
- package/dist/physics-debug.d.ts.map +0 -1
- package/dist/physics-debug.js +0 -526
- package/dist/shaders/materials/body.d.ts +0 -2
- package/dist/shaders/materials/body.d.ts.map +0 -1
- package/dist/shaders/materials/body.js +0 -95
- package/dist/shaders/materials/cloth_rough.d.ts +0 -2
- package/dist/shaders/materials/cloth_rough.d.ts.map +0 -1
- package/dist/shaders/materials/cloth_rough.js +0 -69
- package/dist/shaders/materials/cloth_smooth.d.ts +0 -2
- package/dist/shaders/materials/cloth_smooth.d.ts.map +0 -1
- package/dist/shaders/materials/cloth_smooth.js +0 -61
- package/dist/shaders/materials/default.d.ts +0 -2
- package/dist/shaders/materials/default.d.ts.map +0 -1
- package/dist/shaders/materials/default.js +0 -43
- package/dist/shaders/materials/eye.d.ts +0 -2
- package/dist/shaders/materials/eye.d.ts.map +0 -1
- package/dist/shaders/materials/eye.js +0 -60
- package/dist/shaders/materials/face.d.ts +0 -2
- package/dist/shaders/materials/face.d.ts.map +0 -1
- package/dist/shaders/materials/face.js +0 -95
- package/dist/shaders/materials/hair.d.ts +0 -2
- package/dist/shaders/materials/hair.d.ts.map +0 -1
- package/dist/shaders/materials/hair.js +0 -90
- package/dist/shaders/materials/metal.d.ts +0 -2
- package/dist/shaders/materials/metal.d.ts.map +0 -1
- package/dist/shaders/materials/metal.js +0 -77
- package/dist/shaders/materials/mmd_classic.d.ts +0 -2
- package/dist/shaders/materials/mmd_classic.d.ts.map +0 -1
- package/dist/shaders/materials/mmd_classic.js +0 -66
- package/dist/shaders/materials/stockings.d.ts +0 -2
- package/dist/shaders/materials/stockings.d.ts.map +0 -1
- package/dist/shaders/materials/stockings.js +0 -122
- package/dist/shaders/passes/physics-debug.d.ts +0 -2
- package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
- package/dist/shaders/passes/physics-debug.js +0 -69
package/src/engine.ts
CHANGED
|
@@ -112,6 +112,7 @@ import {
|
|
|
112
112
|
EFFECT_TRAIL_BASE,
|
|
113
113
|
EFFECT_TRAIL_SAMPLES,
|
|
114
114
|
} from "./shaders/passes/composite"
|
|
115
|
+
import { SUBSURFACE_WGSL, SSS_SPIKE } from "./shaders/passes/subsurface"
|
|
115
116
|
import {
|
|
116
117
|
buildParticleComputeShader,
|
|
117
118
|
buildParticleRenderShader,
|
|
@@ -761,6 +762,12 @@ interface DrawCall {
|
|
|
761
762
|
/** Material draws only: false = excluded from the shadow map (PMX cast-shadow
|
|
762
763
|
* flag off). Sheer texels are cut per fragment by the shadow pass's alpha test. */
|
|
763
764
|
castsShadow?: boolean
|
|
765
|
+
/** The PMX author's double-sided flag (bit 0x01). Off means MMD draws the
|
|
766
|
+
* material's front faces only — an inner lining authored as a flipped copy
|
|
767
|
+
* of the outer layer is invisible from outside, which is what the author
|
|
768
|
+
* built it around. Media planes stay double-sided: they build their own
|
|
769
|
+
* index list and say so there. */
|
|
770
|
+
doubleSided: boolean
|
|
764
771
|
/** Edge-flagged materials: interleaved inverted-hull outline drawn right after
|
|
765
772
|
* this material with the outline pipeline. Shares this call's index range;
|
|
766
773
|
* own bind group (edge uniforms + diffuse texture for the alpha test). */
|
|
@@ -896,8 +903,9 @@ interface ModelInstance {
|
|
|
896
903
|
shadowDrawCalls: DrawCall[]
|
|
897
904
|
shadowBindGroups: GPUBindGroup[]
|
|
898
905
|
mainPerInstanceBindGroup: GPUBindGroup
|
|
899
|
-
/** Its own light — fill and
|
|
900
|
-
* one: a
|
|
906
|
+
/** Its own light — fill, sun and options — when setModelFill, setModelSun or
|
|
907
|
+
* setModelFlatSky gave it one: a 48-byte ModelLight, and the CPU copy it is
|
|
908
|
+
* written from. */
|
|
901
909
|
lightBuffer: GPUBuffer | null
|
|
902
910
|
modelLight: Float32Array | null
|
|
903
911
|
pickPerInstanceBindGroup: GPUBindGroup
|
|
@@ -2081,15 +2089,18 @@ export class Engine {
|
|
|
2081
2089
|
/** Stencil value stamped by eye draws so hair can stencil-test against it and
|
|
2082
2090
|
* alpha-blend a second pass over eye silhouette pixels (see-through-hair effect). */
|
|
2083
2091
|
private static readonly STENCIL_EYE_VALUE = 1
|
|
2084
|
-
/** Aux MRT alongside HDR color.
|
|
2092
|
+
/** Aux MRT alongside HDR color. Three channels:
|
|
2085
2093
|
* .r — bloom mask (1 = model geometry, 0 = ground; sampled by bloom blit to gate prefilter).
|
|
2086
2094
|
* .g — accumulated alpha (the channel that used to live in hdr.a before the HDR format
|
|
2087
2095
|
* switched to rg11b10ufloat, which has no alpha). Sampled by composite/bloom to
|
|
2088
2096
|
* un-premultiply color for tonemap and to produce the canvas-drawable alpha used by
|
|
2089
2097
|
* the premultiplied alphaMode compositor (so the page background still shows through
|
|
2090
2098
|
* cleared / edge-faded regions like before).
|
|
2091
|
-
*
|
|
2092
|
-
|
|
2099
|
+
* .b — subsurface strength, set by a graph's `subsurface` node and 0 everywhere
|
|
2100
|
+
* else; the scattering pass reads it to find skin (passes/subsurface.ts).
|
|
2101
|
+
* rgba8unorm at 4× MSAA is 16 bytes/texel; it never leaves tile memory (storeOp
|
|
2102
|
+
* discard), only its single-sample resolve does. */
|
|
2103
|
+
private static readonly BLOOM_MASK_FORMAT: GPUTextureFormat = "rgba8unorm"
|
|
2093
2104
|
/**
|
|
2094
2105
|
* The master switch for the id attachment. OFF — and off having been proven
|
|
2095
2106
|
* to work, not off because it was never finished.
|
|
@@ -2632,6 +2643,22 @@ export class Engine {
|
|
|
2632
2643
|
// composite adds bloomUp mip 0 × (color × intensity) to HDR before Filmic.
|
|
2633
2644
|
// Matches EEVEE energy: tint/intensity applied at composite, not prefilter.
|
|
2634
2645
|
private bloomSampler!: GPUSampler
|
|
2646
|
+
// Screen-space subsurface scattering (passes/subsurface.ts). The scratch target
|
|
2647
|
+
// is the X pass's output and lives with the canvas size; the bind groups name
|
|
2648
|
+
// it and the HDR resolve, so a resize drops them and the next frame rebuilds.
|
|
2649
|
+
private sssPipelineX: GPURenderPipeline | null = null
|
|
2650
|
+
private sssPipelineY: GPURenderPipeline | null = null
|
|
2651
|
+
private sssScratch: GPUTexture | null = null
|
|
2652
|
+
private sssBindGroupX: GPUBindGroup | null = null
|
|
2653
|
+
private sssBindGroupY: GPUBindGroup | null = null
|
|
2654
|
+
private sssUniformX: GPUBuffer | null = null
|
|
2655
|
+
private sssUniformY: GPUBuffer | null = null
|
|
2656
|
+
private readonly sssUniformData = new Float32Array(8)
|
|
2657
|
+
// Model pipelines' descriptors, and the single-sided twins built from them —
|
|
2658
|
+
// one per pipeline per view (see sidedPipeline).
|
|
2659
|
+
private readonly pipelineDescs = new WeakMap<GPURenderPipeline, GPURenderPipelineDescriptor>()
|
|
2660
|
+
private readonly singleSided = new WeakMap<GPURenderPipeline, GPURenderPipeline>()
|
|
2661
|
+
private readonly singleSidedMirror = new WeakMap<GPURenderPipeline, GPURenderPipeline>()
|
|
2635
2662
|
private bloomBlitUniformBuffer!: GPUBuffer
|
|
2636
2663
|
private bloomUpsampleUniformBuffer!: GPUBuffer
|
|
2637
2664
|
private readonly bloomBlitUniformData = new Float32Array(4)
|
|
@@ -6142,6 +6169,134 @@ export class Engine {
|
|
|
6142
6169
|
/** The user's field mounts, drawn at half resolution for the composite to
|
|
6143
6170
|
* upsample. Runs the whole quad — uniform control flow, so effects may use
|
|
6144
6171
|
* derivatives freely, which the old inline path had to forbid. */
|
|
6172
|
+
/** Whether any installed style group routes through a `subsurface` node —
|
|
6173
|
+
* the scattering pass, and the depth it reads, cost nothing otherwise. */
|
|
6174
|
+
private subsurfaceInUse(): boolean {
|
|
6175
|
+
for (const inst of this.modelInstances.values()) {
|
|
6176
|
+
for (const g of inst.styleGroups.values()) {
|
|
6177
|
+
if (g.group.materials.length && g.group.graph.nodes.some((n) => n.type === "subsurface")) return true
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
return false
|
|
6181
|
+
}
|
|
6182
|
+
|
|
6183
|
+
/**
|
|
6184
|
+
* ray-mmd's skin scattering: a horizontal then a vertical depth-aware blur of
|
|
6185
|
+
* the pixels the scene pass marked as skin (aux .b), the second written back
|
|
6186
|
+
* into the HDR resolve through a blend that keeps ray-mmd's per-channel share
|
|
6187
|
+
* of the unblurred picture. See passes/subsurface.ts.
|
|
6188
|
+
*/
|
|
6189
|
+
private renderSubsurface(encoder: GPUCommandEncoder): void {
|
|
6190
|
+
if (!this.device || !this.subsurfaceInUse()) return
|
|
6191
|
+
if (!this.hdrResolveTexture || !this.maskResolveView || !this.depthReadView || !this.dofUniformBuffer) return
|
|
6192
|
+
const w = this.hdrResolveTexture.width
|
|
6193
|
+
const h = this.hdrResolveTexture.height
|
|
6194
|
+
|
|
6195
|
+
if (!this.sssPipelineX || !this.sssPipelineY) {
|
|
6196
|
+
const module = this.device.createShaderModule({ label: "subsurface scattering", code: SUBSURFACE_WGSL })
|
|
6197
|
+
this.sssPipelineX = this.device.createRenderPipeline({
|
|
6198
|
+
label: "subsurface blur x",
|
|
6199
|
+
layout: "auto",
|
|
6200
|
+
vertex: { module, entryPoint: "vs" },
|
|
6201
|
+
fragment: { module, entryPoint: "fsX", targets: [{ format: this.hdrFormat }] },
|
|
6202
|
+
primitive: { topology: "triangle-list" },
|
|
6203
|
+
})
|
|
6204
|
+
// result = blurred·(1 − spike) + original·spike, per channel: the
|
|
6205
|
+
// destination is the original, and the blend constant holds the spike.
|
|
6206
|
+
this.sssPipelineY = this.device.createRenderPipeline({
|
|
6207
|
+
label: "subsurface blur y",
|
|
6208
|
+
layout: "auto",
|
|
6209
|
+
vertex: { module, entryPoint: "vs" },
|
|
6210
|
+
fragment: {
|
|
6211
|
+
module,
|
|
6212
|
+
entryPoint: "fsY",
|
|
6213
|
+
targets: [
|
|
6214
|
+
{
|
|
6215
|
+
format: this.hdrFormat,
|
|
6216
|
+
blend: {
|
|
6217
|
+
color: { srcFactor: "one-minus-constant", dstFactor: "constant", operation: "add" },
|
|
6218
|
+
alpha: { srcFactor: "zero", dstFactor: "one", operation: "add" },
|
|
6219
|
+
},
|
|
6220
|
+
},
|
|
6221
|
+
],
|
|
6222
|
+
},
|
|
6223
|
+
primitive: { topology: "triangle-list" },
|
|
6224
|
+
})
|
|
6225
|
+
this.sssBindGroupX = null
|
|
6226
|
+
this.sssBindGroupY = null
|
|
6227
|
+
}
|
|
6228
|
+
if (!this.sssUniformX || !this.sssUniformY) {
|
|
6229
|
+
const make = (label: string) =>
|
|
6230
|
+
this.device.createBuffer({ label, size: 32, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST })
|
|
6231
|
+
this.sssUniformX = make("subsurface uniforms x")
|
|
6232
|
+
this.sssUniformY = make("subsurface uniforms y")
|
|
6233
|
+
}
|
|
6234
|
+
if (!this.sssScratch || this.sssScratch.width !== w || this.sssScratch.height !== h) {
|
|
6235
|
+
this.sssScratch?.destroy()
|
|
6236
|
+
this.sssScratch = this.device.createTexture({
|
|
6237
|
+
label: "subsurface scratch",
|
|
6238
|
+
size: [w, h],
|
|
6239
|
+
format: this.hdrFormat,
|
|
6240
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
6241
|
+
})
|
|
6242
|
+
this.sssBindGroupX = null
|
|
6243
|
+
this.sssBindGroupY = null
|
|
6244
|
+
}
|
|
6245
|
+
if (!this.sssBindGroupX || !this.sssBindGroupY) {
|
|
6246
|
+
const mask = this.maskResolveView
|
|
6247
|
+
const depth = this.depthReadView
|
|
6248
|
+
const dof = this.dofUniformBuffer
|
|
6249
|
+
const group = (pipeline: GPURenderPipeline, src: GPUTexture, uniforms: GPUBuffer, label: string) =>
|
|
6250
|
+
this.device.createBindGroup({
|
|
6251
|
+
label,
|
|
6252
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
6253
|
+
entries: [
|
|
6254
|
+
{ binding: 0, resource: src.createView() },
|
|
6255
|
+
{ binding: 1, resource: mask },
|
|
6256
|
+
{ binding: 2, resource: depth },
|
|
6257
|
+
{ binding: 3, resource: { buffer: dof } },
|
|
6258
|
+
{ binding: 4, resource: { buffer: uniforms } },
|
|
6259
|
+
],
|
|
6260
|
+
})
|
|
6261
|
+
this.sssBindGroupX = group(this.sssPipelineX, this.hdrResolveTexture, this.sssUniformX, "subsurface x")
|
|
6262
|
+
this.sssBindGroupY = group(this.sssPipelineY, this.sssScratch, this.sssUniformY, "subsurface y")
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
const tanY = Math.tan(this.camera.fov * 0.5)
|
|
6266
|
+
const u = this.sssUniformData
|
|
6267
|
+
u[2] = 0
|
|
6268
|
+
u[3] = 0
|
|
6269
|
+
u[4] = tanY * this.camera.aspect
|
|
6270
|
+
u[5] = tanY
|
|
6271
|
+
u[6] = w
|
|
6272
|
+
u[7] = h
|
|
6273
|
+
u[0] = 1
|
|
6274
|
+
u[1] = 0
|
|
6275
|
+
this.device.queue.writeBuffer(this.sssUniformX, 0, u)
|
|
6276
|
+
u[0] = 0
|
|
6277
|
+
u[1] = 1
|
|
6278
|
+
this.device.queue.writeBuffer(this.sssUniformY, 0, u)
|
|
6279
|
+
|
|
6280
|
+
const px = encoder.beginRenderPass({
|
|
6281
|
+
label: "subsurface blur x",
|
|
6282
|
+
colorAttachments: [{ view: this.sssScratch.createView(), loadOp: "clear", clearValue: [0, 0, 0, 0], storeOp: "store" }],
|
|
6283
|
+
})
|
|
6284
|
+
px.setPipeline(this.sssPipelineX)
|
|
6285
|
+
px.setBindGroup(0, this.sssBindGroupX)
|
|
6286
|
+
px.draw(3)
|
|
6287
|
+
px.end()
|
|
6288
|
+
|
|
6289
|
+
const py = encoder.beginRenderPass({
|
|
6290
|
+
label: "subsurface blur y",
|
|
6291
|
+
colorAttachments: [{ view: this.hdrResolveTexture.createView(), loadOp: "load", storeOp: "store" }],
|
|
6292
|
+
})
|
|
6293
|
+
py.setPipeline(this.sssPipelineY)
|
|
6294
|
+
py.setBindGroup(0, this.sssBindGroupY)
|
|
6295
|
+
py.setBlendConstant({ r: SSS_SPIKE[0], g: SSS_SPIKE[1], b: SSS_SPIKE[2], a: 0 })
|
|
6296
|
+
py.draw(3)
|
|
6297
|
+
py.end()
|
|
6298
|
+
}
|
|
6299
|
+
|
|
6145
6300
|
private renderFieldPass(encoder: GPUCommandEncoder): void {
|
|
6146
6301
|
// TWO PREDICATES, deliberately, and they are not interchangeable.
|
|
6147
6302
|
//
|
|
@@ -7209,7 +7364,7 @@ export class Engine {
|
|
|
7209
7364
|
multisample?: GPUMultisampleState
|
|
7210
7365
|
}): GPURenderPipeline {
|
|
7211
7366
|
const targets = config.fragmentTargets ?? (config.fragmentTarget ? [config.fragmentTarget] : undefined)
|
|
7212
|
-
return this.
|
|
7367
|
+
return this.createModelPipeline({
|
|
7213
7368
|
label: config.label,
|
|
7214
7369
|
layout: config.layout,
|
|
7215
7370
|
vertex: {
|
|
@@ -7433,7 +7588,7 @@ export class Engine {
|
|
|
7433
7588
|
|
|
7434
7589
|
this.noFillBuffer = this.device.createBuffer({
|
|
7435
7590
|
label: "model light (none)",
|
|
7436
|
-
size:
|
|
7591
|
+
size: 48,
|
|
7437
7592
|
usage: GPUBufferUsage.UNIFORM,
|
|
7438
7593
|
})
|
|
7439
7594
|
|
|
@@ -7652,7 +7807,7 @@ export class Engine {
|
|
|
7652
7807
|
depthCompare: this.depthAhead,
|
|
7653
7808
|
},
|
|
7654
7809
|
}
|
|
7655
|
-
this.depthPrepassPipeline = this.
|
|
7810
|
+
this.depthPrepassPipeline = this.createModelPipeline({
|
|
7656
7811
|
label: "opaque depth prepass",
|
|
7657
7812
|
...prepassDesc,
|
|
7658
7813
|
fragment: {
|
|
@@ -7664,7 +7819,7 @@ export class Engine {
|
|
|
7664
7819
|
// The SOLID prime: same module, cutoff forced to exactly 1.0. Only texels
|
|
7665
7820
|
// whose blend ignores the destination may pre-claim depth in the
|
|
7666
7821
|
// transparent phase — see the override's note in depth-prepass.ts.
|
|
7667
|
-
this.solidPrepassPipeline = this.
|
|
7822
|
+
this.solidPrepassPipeline = this.createModelPipeline({
|
|
7668
7823
|
label: "transparent solid prepass",
|
|
7669
7824
|
...prepassDesc,
|
|
7670
7825
|
fragment: {
|
|
@@ -7680,7 +7835,7 @@ export class Engine {
|
|
|
7680
7835
|
// hair depth from ever claiming the pixels the see-through-hair pass needs
|
|
7681
7836
|
// the eye to survive on. (Bundle draws use the PASS's stencil reference;
|
|
7682
7837
|
// only pipeline/bind/vertex state resets across executeBundles.)
|
|
7683
|
-
this.hairPrimePipeline = this.
|
|
7838
|
+
this.hairPrimePipeline = this.createModelPipeline({
|
|
7684
7839
|
label: "hair depth prime",
|
|
7685
7840
|
...prepassDesc,
|
|
7686
7841
|
depthStencil: {
|
|
@@ -8552,6 +8707,10 @@ export class Engine {
|
|
|
8552
8707
|
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
8553
8708
|
})
|
|
8554
8709
|
this.maskResolveView = this.maskResolveTexture.createView()
|
|
8710
|
+
this.sssScratch?.destroy()
|
|
8711
|
+
this.sssScratch = null
|
|
8712
|
+
this.sssBindGroupX = null
|
|
8713
|
+
this.sssBindGroupY = null
|
|
8555
8714
|
|
|
8556
8715
|
// The floor mirror's targets — FULL resolution, the same attachment
|
|
8557
8716
|
// contract and sample count as the scene pass, which is what lets the
|
|
@@ -10402,6 +10561,8 @@ export class Engine {
|
|
|
10402
10561
|
bindGroup: this.groundShadowBindGroup!,
|
|
10403
10562
|
materialName: "Ground",
|
|
10404
10563
|
groupId: null,
|
|
10564
|
+
// The ground's own pipeline decides its culling.
|
|
10565
|
+
doubleSided: true,
|
|
10405
10566
|
// The ground belongs to no model instance, so it is not in the cull list —
|
|
10406
10567
|
// cullIndex -1 leaves renderGround unconditional. Its box is filled in
|
|
10407
10568
|
// anyway rather than left a lie for whoever reads this next.
|
|
@@ -11020,8 +11181,12 @@ export class Engine {
|
|
|
11020
11181
|
}
|
|
11021
11182
|
|
|
11022
11183
|
/**
|
|
11023
|
-
* Light one model with more than the world gives it: a FILL, linear RGB
|
|
11024
|
-
*
|
|
11184
|
+
* Light one model with more than the world gives it: a FILL, linear RGB times
|
|
11185
|
+
* its surface colour, added after its material graph. Null takes it away again.
|
|
11186
|
+
*
|
|
11187
|
+
* After the graph, not in its ambient: an NPR ramp read an ambient fill as
|
|
11188
|
+
* light, so sliding it moved her shadows and flipped whole regions across a
|
|
11189
|
+
* hard step. Added after, it brightens her evenly and every shadow stays put.
|
|
11025
11190
|
*
|
|
11026
11191
|
* A game lights its stage and its characters apart. Aether Gazer's rooms are
|
|
11027
11192
|
* lit by their lamps and a near-black ambient, while its characters take a
|
|
@@ -11048,15 +11213,31 @@ export class Engine {
|
|
|
11048
11213
|
return this.writeModelLight(name, 4, sun)
|
|
11049
11214
|
}
|
|
11050
11215
|
|
|
11051
|
-
/**
|
|
11052
|
-
*
|
|
11053
|
-
*
|
|
11054
|
-
|
|
11216
|
+
/**
|
|
11217
|
+
* Light one model by the sky's AVERAGE rather than its shape: the world's
|
|
11218
|
+
* colour and brightness from every direction alike. False gives it the
|
|
11219
|
+
* directional sky back.
|
|
11220
|
+
*
|
|
11221
|
+
* For a cast under an anime look. An HDRI's sky is brighter and warmer one
|
|
11222
|
+
* way than another, and taken at each normal it shades a face with soft
|
|
11223
|
+
* realistic gradients that no toon ramp drew — PBR on an anime face. Aether
|
|
11224
|
+
* Gazer gives its characters one flat base light instead. World strength and
|
|
11225
|
+
* colour still reach her; only the shape is gone. A flat World is its own
|
|
11226
|
+
* average, so without an HDRI this changes nothing.
|
|
11227
|
+
*/
|
|
11228
|
+
setModelFlatSky(name: string, flat: boolean): boolean {
|
|
11229
|
+
return this.writeModelLight(name, 8, flat ? new Vec3(1, 0, 0) : null)
|
|
11230
|
+
}
|
|
11231
|
+
|
|
11232
|
+
/** One part of a model's light, at `at` (0 the fill, 4 the sun, 8 its
|
|
11233
|
+
* options) in its ModelLight; w says whether that part is set. All unset
|
|
11234
|
+
* releases the buffer and the model takes the shared zero stand-in again. */
|
|
11235
|
+
private writeModelLight(name: string, at: 0 | 4 | 8, value: Vec3 | null): boolean {
|
|
11055
11236
|
const inst = this.modelInstances.get(name)
|
|
11056
11237
|
if (!inst || !this.device) return false
|
|
11057
|
-
const light = inst.modelLight ?? new Float32Array(
|
|
11238
|
+
const light = inst.modelLight ?? new Float32Array(12)
|
|
11058
11239
|
light.set(value ? [value.x, value.y, value.z, 1] : [0, 0, 0, 0], at)
|
|
11059
|
-
const any = light[3] > 0 || light[7] > 0
|
|
11240
|
+
const any = light[3] > 0 || light[7] > 0 || light[11] > 0
|
|
11060
11241
|
if (!any) {
|
|
11061
11242
|
if (!inst.lightBuffer) return true
|
|
11062
11243
|
const retired = inst.lightBuffer
|
|
@@ -11072,7 +11253,7 @@ export class Engine {
|
|
|
11072
11253
|
if (!inst.lightBuffer) {
|
|
11073
11254
|
inst.lightBuffer = this.device.createBuffer({
|
|
11074
11255
|
label: `${name}: light`,
|
|
11075
|
-
size:
|
|
11256
|
+
size: 48,
|
|
11076
11257
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
11077
11258
|
})
|
|
11078
11259
|
inst.mainPerInstanceBindGroup = this.perInstanceBindGroup(name, inst.skinMatrixBuffer, inst.lightBuffer)
|
|
@@ -14458,6 +14639,7 @@ export class Engine {
|
|
|
14458
14639
|
groupId: null,
|
|
14459
14640
|
baseBindGroupEntries,
|
|
14460
14641
|
castsShadow,
|
|
14642
|
+
doubleSided: inst.isPlane || (mat.edgeFlag & 0x01) !== 0,
|
|
14461
14643
|
outline,
|
|
14462
14644
|
bounds,
|
|
14463
14645
|
cullIndex: -1,
|
|
@@ -16042,7 +16224,10 @@ export class Engine {
|
|
|
16042
16224
|
// The two real readers are both in the composite and both have their own
|
|
16043
16225
|
// flag above: linearDepth() feeds the DoF gather and the depth handed to a
|
|
16044
16226
|
// foreground mount. Nothing else binds depthTex at all.
|
|
16045
|
-
|
|
16227
|
+
// The scattering pass is the third reader: it rejects taps across a depth
|
|
16228
|
+
// step and scales its width by distance, so a scene wearing a subsurface
|
|
16229
|
+
// look stores depth for as long as it does.
|
|
16230
|
+
const depthRead = dofOn || this.effects.some((e) => e.hasForeground) || this.subsurfaceInUse()
|
|
16046
16231
|
this.renderPassDescriptor.depthStencilAttachment!.depthStoreOp = depthRead ? "store" : "discard"
|
|
16047
16232
|
if (depthRead) this.writeDepthOfFieldUniforms()
|
|
16048
16233
|
|
|
@@ -16240,6 +16425,9 @@ export class Engine {
|
|
|
16240
16425
|
// kept ribbons out of bloom.
|
|
16241
16426
|
this.drawTrails(pass, "camera")
|
|
16242
16427
|
pass.end()
|
|
16428
|
+
// Skin scattering, on the resolved scene and before anything reads it:
|
|
16429
|
+
// bloom should bleed the scattered skin, not the raw one.
|
|
16430
|
+
this.renderSubsurface(encoder)
|
|
16243
16431
|
// The field mounts, likewise: after the scene so foregrounds can read its
|
|
16244
16432
|
// depth, before the composite that samples both layers.
|
|
16245
16433
|
this.renderFieldPass(encoder)
|
|
@@ -17060,13 +17248,55 @@ export class Engine {
|
|
|
17060
17248
|
stencilWriteMask: 0xff,
|
|
17061
17249
|
}
|
|
17062
17250
|
}
|
|
17063
|
-
|
|
17251
|
+
const desc: GPURenderPipelineDescriptor = {
|
|
17064
17252
|
...base,
|
|
17065
17253
|
fragment: { module, constants, targets: blend === "additive" ? this.sceneTargetsAdditive : this.sceneTargets },
|
|
17066
17254
|
depthStencil,
|
|
17255
|
+
}
|
|
17256
|
+
return this.device.createRenderPipelineAsync(desc).then((pipeline) => {
|
|
17257
|
+
this.pipelineDescs.set(pipeline, desc)
|
|
17258
|
+
return pipeline
|
|
17067
17259
|
})
|
|
17068
17260
|
}
|
|
17069
17261
|
|
|
17262
|
+
/** A pipeline whose descriptor is kept, so a single-sided twin can be built
|
|
17263
|
+
* from it on demand (see sidedPipeline). */
|
|
17264
|
+
private createModelPipeline(desc: GPURenderPipelineDescriptor): GPURenderPipeline {
|
|
17265
|
+
const pipeline = this.device.createRenderPipeline(desc)
|
|
17266
|
+
this.pipelineDescs.set(pipeline, desc)
|
|
17267
|
+
return pipeline
|
|
17268
|
+
}
|
|
17269
|
+
|
|
17270
|
+
/**
|
|
17271
|
+
* The pipeline a draw should use once its double-sided flag is honoured.
|
|
17272
|
+
*
|
|
17273
|
+
* A single-sided material culls its back faces, as MMD does. PMX winds a front
|
|
17274
|
+
* face clockwise, which this engine sees as a BACK face — the eye's front cull
|
|
17275
|
+
* and the outline hull's back cull are the same fact — so the cull is "front"
|
|
17276
|
+
* for the camera and "back" in the mirror, whose reflection flips winding.
|
|
17277
|
+
*
|
|
17278
|
+
* Every pipeline that draws a material's surface goes through here, depth
|
|
17279
|
+
* primes included: a prime that kept a culled face would write depth for a
|
|
17280
|
+
* surface nobody sees and hide what is behind it. A pipeline that already
|
|
17281
|
+
* culls (the eye) is returned as it is.
|
|
17282
|
+
*/
|
|
17283
|
+
private sidedPipeline(pipeline: GPURenderPipeline, draw: DrawCall, mirrored: boolean): GPURenderPipeline {
|
|
17284
|
+
if (draw.doubleSided) return pipeline
|
|
17285
|
+
const desc = this.pipelineDescs.get(pipeline)
|
|
17286
|
+
if (!desc || (desc.primitive?.cullMode ?? "none") !== "none") return pipeline
|
|
17287
|
+
const cache = mirrored ? this.singleSidedMirror : this.singleSided
|
|
17288
|
+
let twin = cache.get(pipeline)
|
|
17289
|
+
if (!twin) {
|
|
17290
|
+
twin = this.device.createRenderPipeline({
|
|
17291
|
+
...desc,
|
|
17292
|
+
label: `${desc.label ?? "pipeline"} (single-sided${mirrored ? ", mirror" : ""})`,
|
|
17293
|
+
primitive: { ...desc.primitive, cullMode: mirrored ? "back" : "front" },
|
|
17294
|
+
})
|
|
17295
|
+
cache.set(pipeline, twin)
|
|
17296
|
+
}
|
|
17297
|
+
return twin
|
|
17298
|
+
}
|
|
17299
|
+
|
|
17070
17300
|
// Pipeline for a material draw call: its group's compiled pipeline when grouped, else
|
|
17071
17301
|
// the neutral base (ungrouped materials render the default graph). Transparent-bucket
|
|
17072
17302
|
// draws on the CAST use the SAME depth-write-on pipeline — babylon-mmd's
|
|
@@ -17124,7 +17354,8 @@ export class Engine {
|
|
|
17124
17354
|
pass.setBindGroup(1, inst.mainPerInstanceBindGroup)
|
|
17125
17355
|
bound = true
|
|
17126
17356
|
}
|
|
17127
|
-
const
|
|
17357
|
+
const mirrored = view.args === "mirror"
|
|
17358
|
+
const pipeline = this.sidedPipeline(this.pipelineForDrawCall(inst, draw, mirrored), draw, mirrored)
|
|
17128
17359
|
if (pipeline !== currentPipeline) {
|
|
17129
17360
|
pass.setPipeline(pipeline)
|
|
17130
17361
|
currentPipeline = pipeline
|
|
@@ -17247,14 +17478,17 @@ export class Engine {
|
|
|
17247
17478
|
inst: ModelInstance,
|
|
17248
17479
|
view: { perFrame: GPUBindGroup; args: "camera" | "mirror"; outlines: boolean },
|
|
17249
17480
|
): void {
|
|
17250
|
-
let bound =
|
|
17481
|
+
let bound: GPURenderPipeline | null = null
|
|
17251
17482
|
for (const draw of inst.drawCalls) {
|
|
17252
17483
|
if (draw.type !== "opaque" || !this.isHairDraw(inst, draw)) continue
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
pass.
|
|
17256
|
-
|
|
17257
|
-
|
|
17484
|
+
const pipeline = this.sidedPipeline(this.hairPrimePipeline, draw, view.args === "mirror")
|
|
17485
|
+
if (bound !== pipeline) {
|
|
17486
|
+
pass.setPipeline(pipeline)
|
|
17487
|
+
if (!bound) {
|
|
17488
|
+
pass.setBindGroup(0, view.perFrame)
|
|
17489
|
+
pass.setBindGroup(1, inst.mainPerInstanceBindGroup)
|
|
17490
|
+
}
|
|
17491
|
+
bound = pipeline
|
|
17258
17492
|
}
|
|
17259
17493
|
pass.setBindGroup(2, draw.bindGroup)
|
|
17260
17494
|
this.issueDraw(pass, draw, view.args)
|
|
@@ -17317,6 +17551,7 @@ export class Engine {
|
|
|
17317
17551
|
if (install?.alphaMode === "hashed") pipeline = this.solidPrepassPipeline
|
|
17318
17552
|
else if (install && install.alphaMode !== "opaque") continue
|
|
17319
17553
|
}
|
|
17554
|
+
pipeline = this.sidedPipeline(pipeline, draw, view.args === "mirror")
|
|
17320
17555
|
if (bound !== pipeline) {
|
|
17321
17556
|
pass.setPipeline(pipeline)
|
|
17322
17557
|
// One layout for both prepass pipelines, so groups 0 and 1 carry over.
|
|
@@ -17354,14 +17589,17 @@ export class Engine {
|
|
|
17354
17589
|
view: { perFrame: GPUBindGroup; args: "camera" | "mirror"; outlines: boolean },
|
|
17355
17590
|
): void {
|
|
17356
17591
|
if (inst.isStage) return
|
|
17357
|
-
let bound =
|
|
17592
|
+
let bound: GPURenderPipeline | null = null
|
|
17358
17593
|
for (const draw of inst.drawCalls) {
|
|
17359
17594
|
if (draw.type !== "transparent") continue
|
|
17360
|
-
|
|
17361
|
-
|
|
17362
|
-
pass.
|
|
17363
|
-
|
|
17364
|
-
|
|
17595
|
+
const pipeline = this.sidedPipeline(this.solidPrepassPipeline, draw, view.args === "mirror")
|
|
17596
|
+
if (bound !== pipeline) {
|
|
17597
|
+
pass.setPipeline(pipeline)
|
|
17598
|
+
if (!bound) {
|
|
17599
|
+
pass.setBindGroup(0, view.perFrame)
|
|
17600
|
+
pass.setBindGroup(1, inst.mainPerInstanceBindGroup)
|
|
17601
|
+
}
|
|
17602
|
+
bound = pipeline
|
|
17365
17603
|
}
|
|
17366
17604
|
pass.setBindGroup(2, draw.bindGroup)
|
|
17367
17605
|
this.issueDraw(pass, draw, view.args)
|
|
@@ -17421,8 +17659,9 @@ export class Engine {
|
|
|
17421
17659
|
let currentPipeline: GPURenderPipeline | null = null
|
|
17422
17660
|
for (const draw of inst.drawCalls) {
|
|
17423
17661
|
if (draw.type !== "opaque") continue
|
|
17424
|
-
const
|
|
17425
|
-
if (!
|
|
17662
|
+
const hairOverEyes = this.overEyesPipelineFor(inst, draw)
|
|
17663
|
+
if (!hairOverEyes) continue
|
|
17664
|
+
const overEyes = this.sidedPipeline(hairOverEyes, draw, view.args === "mirror")
|
|
17426
17665
|
if (!bound) {
|
|
17427
17666
|
pass.setBindGroup(0, view.perFrame)
|
|
17428
17667
|
pass.setBindGroup(1, inst.mainPerInstanceBindGroup)
|
|
@@ -19,7 +19,7 @@ export const BODY_GRAPH: ShaderGraph = {
|
|
|
19
19
|
{ id: "tex_base", type: "mix/multiply", inputs: { fac: 1.0 } },
|
|
20
20
|
{ id: "geo", type: "geometry" },
|
|
21
21
|
{ id: "str", type: "shader_to_rgb_diffuse" },
|
|
22
|
-
{ id: "toon", type: "
|
|
22
|
+
{ id: "toon", type: "ramp_cardinal", inputs: { pos0: 0.2466, pos1: 0.3466 } },
|
|
23
23
|
{ id: "shadow_tint", type: "hue_sat", inputs: { hue: 0.5, saturation: 2.0, value: 0.3499999940395355, fac: 1.0 } },
|
|
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" },
|
|
@@ -19,7 +19,7 @@ export const CLOTH_ROUGH_GRAPH: ShaderGraph = {
|
|
|
19
19
|
{ id: "tex_base", type: "mix/multiply", inputs: { fac: 1.0 } },
|
|
20
20
|
{ id: "geo", type: "geometry" },
|
|
21
21
|
{ id: "str", type: "shader_to_rgb_diffuse" },
|
|
22
|
-
{ id: "ramp_008", type: "
|
|
22
|
+
{ id: "ramp_008", type: "ramp_cardinal", inputs: { pos0: 0.2466, pos1: 0.3466 } },
|
|
23
23
|
{ id: "mix04_fac", type: "math/multiply", inputs: { b: 0.5 } },
|
|
24
24
|
{ id: "dark_tex", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 0.19999998807907104, fac: 1.0 } },
|
|
25
25
|
{ id: "mix_004", type: "mix/blend" },
|
|
@@ -19,7 +19,7 @@ export const CLOTH_SMOOTH_GRAPH: ShaderGraph = {
|
|
|
19
19
|
{ id: "tex_base", type: "mix/multiply", inputs: { fac: 1.0 } },
|
|
20
20
|
{ id: "geo", type: "geometry" },
|
|
21
21
|
{ id: "str", type: "shader_to_rgb_diffuse" },
|
|
22
|
-
{ id: "ramp_008", type: "
|
|
22
|
+
{ id: "ramp_008", type: "ramp_cardinal", inputs: { pos0: 0.2466, pos1: 0.3466 } },
|
|
23
23
|
{ id: "mix04_fac", type: "math/multiply", inputs: { b: 0.5 } },
|
|
24
24
|
{ id: "dark_tex", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 0.19999998807907104, fac: 1.0 } },
|
|
25
25
|
{ id: "mix_004", type: "mix/blend" },
|
package/src/graph/presets/eye.ts
CHANGED
|
@@ -28,6 +28,17 @@ export const EYE_GRAPH: ShaderGraph = {
|
|
|
28
28
|
},
|
|
29
29
|
{ id: "emission", type: "emission", inputs: { strength: 1.5 } },
|
|
30
30
|
{ id: "add", type: "add_shader" },
|
|
31
|
+
// The iris glow follows the key round her head: full (1.5) lit from the
|
|
32
|
+
// front, easing to 0.2 of it lit from behind - a fixed 1.5 left the eyes
|
|
33
|
+
// bright white in a backlit face. head_basis.forward · L, half-Lambert,
|
|
34
|
+
// times the sun's shadow at the eye, so an eye in a shadowed face dims too.
|
|
35
|
+
{ id: "eye_hb", type: "head_basis" },
|
|
36
|
+
{ id: "eye_lt", type: "light" },
|
|
37
|
+
{ id: "eye_fl", type: "vector_math/dot" },
|
|
38
|
+
{ id: "eye_half", type: "math/multiply_add", inputs: { b: 0.5, c: 0.5 } },
|
|
39
|
+
{ id: "eye_lvl", type: "map_range", inputs: { from_min: 0.0, from_max: 1.0, to_min: 0.2, to_max: 1.0 } },
|
|
40
|
+
{ id: "eye_sh", type: "math/multiply" },
|
|
41
|
+
{ id: "eye_str", type: "math/multiply", inputs: { b: 1.5 } },
|
|
31
42
|
],
|
|
32
43
|
links: [
|
|
33
44
|
{ from: { node: "tex", socket: "color" }, to: { node: "tex_base", socket: "a" } },
|
|
@@ -36,6 +47,14 @@ export const EYE_GRAPH: ShaderGraph = {
|
|
|
36
47
|
{ from: { node: "tex_base", socket: "color" }, to: { node: "emission", socket: "color" } },
|
|
37
48
|
{ from: { node: "principled", socket: "color" }, to: { node: "add", socket: "a" } },
|
|
38
49
|
{ from: { node: "emission", socket: "color" }, to: { node: "add", socket: "b" } },
|
|
50
|
+
{ from: { node: "eye_hb", socket: "forward" }, to: { node: "eye_fl", socket: "a" } },
|
|
51
|
+
{ from: { node: "eye_lt", socket: "direction" }, to: { node: "eye_fl", socket: "b" } },
|
|
52
|
+
{ from: { node: "eye_fl", socket: "value" }, to: { node: "eye_half", socket: "a" } },
|
|
53
|
+
{ from: { node: "eye_half", socket: "value" }, to: { node: "eye_sh", socket: "a" } },
|
|
54
|
+
{ from: { node: "eye_lt", socket: "shadow" }, to: { node: "eye_sh", socket: "b" } },
|
|
55
|
+
{ from: { node: "eye_sh", socket: "value" }, to: { node: "eye_lvl", socket: "value" } },
|
|
56
|
+
{ from: { node: "eye_lvl", socket: "value" }, to: { node: "eye_str", socket: "a" } },
|
|
57
|
+
{ from: { node: "eye_str", socket: "value" }, to: { node: "emission", socket: "strength" } },
|
|
39
58
|
],
|
|
40
59
|
output: { node: "add", socket: "color" },
|
|
41
60
|
}
|
|
@@ -24,7 +24,7 @@ export const FACE_GRAPH: ShaderGraph = {
|
|
|
24
24
|
|
|
25
25
|
// ── toon shading ──
|
|
26
26
|
{ id: "str", type: "shader_to_rgb_diffuse" },
|
|
27
|
-
{ id: "toon", type: "
|
|
27
|
+
{ id: "toon", type: "ramp_cardinal", inputs: { pos0: 0.2466, pos1: 0.3466 } },
|
|
28
28
|
{ id: "shadow_tint", type: "hue_sat", inputs: { hue: 0.46000000834465027, saturation: 2.0, value: 0.3499999940395355, fac: 1.0 } },
|
|
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" },
|
|
@@ -29,7 +29,7 @@ export const HAIR_GRAPH: ShaderGraph = {
|
|
|
29
29
|
{ id: "hs_002", type: "hue_sat", inputs: { hue: 0.48, saturation: 1.2, value: 0.7, fac: 1.0 } },
|
|
30
30
|
{ id: "hs_001", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.5, value: 1.0, fac: 1.0 } },
|
|
31
31
|
{ id: "str", type: "shader_to_rgb_diffuse" },
|
|
32
|
-
{ id: "ramp_008", type: "
|
|
32
|
+
{ id: "ramp_008", type: "ramp_cardinal", inputs: { pos0: 0.2466, color0: [0, 0, 0, 1], pos1: 0.3466, color1: [1, 1, 1, 1] } },
|
|
33
33
|
{ id: "mix_004", type: "mix/blend" },
|
|
34
34
|
{ id: "bc", type: "bright_contrast", inputs: { bright: 0.1, contrast: 0.2 } },
|
|
35
35
|
{ id: "sep_n", type: "separate_xyz" },
|
|
@@ -21,7 +21,7 @@ export const METAL_GRAPH: ShaderGraph = {
|
|
|
21
21
|
{ id: "geo", type: "geometry" },
|
|
22
22
|
{ id: "tex_tint", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 0.800000011920929, fac: 1.0 } },
|
|
23
23
|
{ id: "str", type: "shader_to_rgb_diffuse" },
|
|
24
|
-
{ id: "ramp_008", type: "
|
|
24
|
+
{ id: "ramp_008", type: "ramp_cardinal", inputs: { pos0: 0.2466, pos1: 0.3466 } },
|
|
25
25
|
{ id: "mix04_fac", type: "math/multiply", inputs: { b: 0.5 } },
|
|
26
26
|
{ id: "dark_tex", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 0.19999998807907104, fac: 1.0 } },
|
|
27
27
|
{ id: "mix_004", type: "mix/blend" },
|
package/src/graph/registry.ts
CHANGED
|
@@ -724,6 +724,17 @@ export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
|
724
724
|
outputs: { value: "float" },
|
|
725
725
|
emit: () => `shader_to_rgb_diffuse(n, l, sun, amb, shadow)`,
|
|
726
726
|
},
|
|
727
|
+
/**
|
|
728
|
+
* Hands this surface to the screen-space scattering pass: the colour passes
|
|
729
|
+
* through, and the pixel is blurred afterwards the way ray-mmd blurs skin —
|
|
730
|
+
* red furthest, never across a depth step. Strength scales the width; 1 is
|
|
731
|
+
* ray-mmd's skin, up to 4. It has to lie on the path to the output to take effect.
|
|
732
|
+
*/
|
|
733
|
+
subsurface: {
|
|
734
|
+
inputs: { color: C([1, 1, 1], true), strength: F(1) },
|
|
735
|
+
outputs: { color: "color" },
|
|
736
|
+
emit: (a) => `rz_subsurface(${a.strength}, ${a.color})`,
|
|
737
|
+
},
|
|
727
738
|
|
|
728
739
|
/**
|
|
729
740
|
* The same closure as a COLOUR, which is what Blender's Shader to RGB
|