@forgeax/engine-vfx-render 0.1.7 → 0.1.20
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/.tsbuildinfo +1 -1
- package/dist/feature/gpu-particle-feature.d.ts.map +1 -1
- package/dist/feature/particle-resources.d.ts +11 -0
- package/dist/feature/particle-resources.d.ts.map +1 -1
- package/dist/index.mjs +16 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -8
- package/src/__tests__/fog-producer.integration.test.ts +7 -18
- package/src/__tests__/gpu-host.integration.test.ts +20 -2
- package/src/__tests__/particle-resources.unit.test.ts +9 -0
- package/src/__tests__/stage-plan.integration.test.ts +7 -1
- package/src/feature/gpu-particle-feature.ts +17 -3
- package/src/feature/particle-resources.ts +18 -0
- package/src/shaders/beam.wgsl +1 -31
- package/src/shaders/billboard.wgsl +5 -29
- package/src/shaders/mesh.wgsl +2 -31
- package/src/shaders/ribbon.wgsl +1 -31
- package/src/shaders/trail.wgsl +1 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-vfx-render",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@forgeax/engine-assets-runtime": "0.1.
|
|
26
|
-
"@forgeax/engine-ecs": "0.1.
|
|
27
|
-
"@forgeax/engine-math": "0.1.
|
|
28
|
-
"@forgeax/engine-render": "0.1.
|
|
29
|
-
"@forgeax/engine-scene": "0.1.
|
|
30
|
-
"@forgeax/engine-types": "0.1.
|
|
31
|
-
"@forgeax/engine-vfx": "0.1.
|
|
25
|
+
"@forgeax/engine-assets-runtime": "0.1.20",
|
|
26
|
+
"@forgeax/engine-ecs": "0.1.20",
|
|
27
|
+
"@forgeax/engine-math": "0.1.20",
|
|
28
|
+
"@forgeax/engine-render": "0.1.20",
|
|
29
|
+
"@forgeax/engine-scene": "0.1.20",
|
|
30
|
+
"@forgeax/engine-types": "0.1.20",
|
|
31
|
+
"@forgeax/engine-vfx": "0.1.20"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {},
|
|
34
34
|
"forgeax": {
|
|
@@ -8,23 +8,11 @@ function shaderSource(name: (typeof shaderNames)[number]): string {
|
|
|
8
8
|
return readFileSync(fileURLToPath(new URL(`../shaders/${name}.wgsl`, import.meta.url)), 'utf8');
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe('VFX Fog producer contract', () => {
|
|
16
|
-
it('requires one shared Fog call with a world-space ray for every topology', () => {
|
|
11
|
+
describe('VFX volumetric-fog boundary contract', () => {
|
|
12
|
+
it('keeps analytic fog out of every particle topology', () => {
|
|
17
13
|
for (const name of shaderNames) {
|
|
18
14
|
const source = shaderSource(name);
|
|
19
|
-
expect(source, name).toMatch(
|
|
20
|
-
/#import\s+forgeax_view::common::\{[^}]*\bFogViewParams\b[^}]*\bFogRay\b[^}]*\}/,
|
|
21
|
-
);
|
|
22
|
-
expect(source, name).toMatch(/#import\s+forgeax_view::fog::\{[^}]*\bapply_fog\b[^}]*\}/);
|
|
23
|
-
expect(count(source, /\bapply_fog\s*\(/g), name).toBe(1);
|
|
24
|
-
expect(count(source, /\bFogRay\s*\(/g), name).toBe(1);
|
|
25
|
-
expect(source, name).toMatch(/world[_A-Za-z]*position|worldPosition/);
|
|
26
|
-
expect(source, name).toMatch(/ray[_A-Za-z]*distance|distance/);
|
|
27
|
-
expect(source, name).toMatch(/\.a\b|alpha/);
|
|
15
|
+
expect(source, name).not.toMatch(/\bFogViewParams\b|\bFogRay\b|\bapply_fog\s*\(/);
|
|
28
16
|
expect(source, name).not.toMatch(/texture_3d|raymarch/i);
|
|
29
17
|
}
|
|
30
18
|
});
|
|
@@ -46,15 +34,16 @@ describe('VFX Fog producer contract', () => {
|
|
|
46
34
|
expect(camera).toContain('viewProjection');
|
|
47
35
|
});
|
|
48
36
|
|
|
49
|
-
it('keeps billboard scene-depth soft-particle alpha
|
|
37
|
+
it('keeps billboard scene-depth soft-particle alpha in the final color', () => {
|
|
50
38
|
const billboard = shaderSource('billboard');
|
|
51
39
|
const depthLoad = billboard.indexOf('textureLoad(scene_depth');
|
|
52
40
|
const softParticleCall = billboard.indexOf('softParticle(input.position');
|
|
53
|
-
const
|
|
41
|
+
const finalColor = billboard.indexOf('return vec4<f32>(rgb * alpha, alpha);');
|
|
54
42
|
|
|
55
43
|
expect(depthLoad).toBeGreaterThanOrEqual(0);
|
|
56
44
|
expect(softParticleCall).toBeGreaterThan(depthLoad);
|
|
57
|
-
expect(
|
|
45
|
+
expect(finalColor).toBeGreaterThan(softParticleCall);
|
|
58
46
|
expect(billboard).toContain('input.color.a * edge, input.fade_distance');
|
|
47
|
+
expect(billboard).not.toContain('apply_fog(');
|
|
59
48
|
});
|
|
60
49
|
});
|
|
@@ -50,7 +50,10 @@ describe('GPU VFX public host', () => {
|
|
|
50
50
|
const world = new World();
|
|
51
51
|
const lookups: string[] = [];
|
|
52
52
|
class StatefulAssets {
|
|
53
|
-
readonly material: MaterialAsset = {
|
|
53
|
+
readonly material: MaterialAsset = {
|
|
54
|
+
kind: 'material',
|
|
55
|
+
passes: [{ name: 'particle-billboard', program: { module: 'custom-depth' } }],
|
|
56
|
+
};
|
|
54
57
|
readonly loaders = { registerPackLoader: () => {} };
|
|
55
58
|
|
|
56
59
|
lookup<T extends Asset = Asset>(guid: string): T | undefined {
|
|
@@ -118,14 +121,29 @@ describe('GPU VFX public host', () => {
|
|
|
118
121
|
frameNumber: 1,
|
|
119
122
|
} as never,
|
|
120
123
|
{
|
|
121
|
-
targets: [
|
|
124
|
+
targets: [
|
|
125
|
+
{ name: 'scene-color', kind: 'color', format: 'rgba8unorm-srgb', sampleCount: 1 },
|
|
126
|
+
{ name: 'scene-depth', kind: 'depth', format: 'depth24plus', sampleCount: 1 },
|
|
127
|
+
],
|
|
122
128
|
caps: {},
|
|
123
129
|
frame: { frameNumber: 1 },
|
|
124
130
|
generation: 1,
|
|
131
|
+
materialShaderBindingContract: () => 'group-0-resource',
|
|
125
132
|
} as never,
|
|
126
133
|
);
|
|
127
134
|
expect(planned.ok).toBe(true);
|
|
128
135
|
expect(lookups).toEqual(['material-guid']);
|
|
136
|
+
if (planned.ok) {
|
|
137
|
+
expect(planned.value.resources).toEqual(
|
|
138
|
+
expect.arrayContaining([
|
|
139
|
+
expect.objectContaining({
|
|
140
|
+
kind: 'graphics-bindings',
|
|
141
|
+
values: expect.objectContaining({ sceneDepthBinding: 0 }),
|
|
142
|
+
logicalTargets: { sceneDepth: 'scene-depth' },
|
|
143
|
+
}),
|
|
144
|
+
]),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
129
147
|
await host.detachWorld({ world });
|
|
130
148
|
});
|
|
131
149
|
|
|
@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
3
3
|
import {
|
|
4
4
|
canonicalMeshVertices,
|
|
5
5
|
particleMaterialPass,
|
|
6
|
+
particleMaterialSceneDepthBinding,
|
|
6
7
|
particleMaterialUsesBindings,
|
|
7
8
|
} from '../feature/particle-resources.js';
|
|
8
9
|
|
|
@@ -53,6 +54,14 @@ describe('particle material pass', () => {
|
|
|
53
54
|
});
|
|
54
55
|
});
|
|
55
56
|
|
|
57
|
+
it('maps renderer shader contracts to their scene-depth binding slots', () => {
|
|
58
|
+
expect(particleMaterialSceneDepthBinding('group-0-resource')).toBe(0);
|
|
59
|
+
expect(particleMaterialSceneDepthBinding('view-and-scene-depth')).toBe(1);
|
|
60
|
+
expect(particleMaterialSceneDepthBinding('group-0')).toBeUndefined();
|
|
61
|
+
expect(particleMaterialSceneDepthBinding('view-only')).toBeUndefined();
|
|
62
|
+
expect(particleMaterialSceneDepthBinding('render-material')).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
56
65
|
it('does not mistake an ordinary Forward pass for a particle shader', () => {
|
|
57
66
|
const material: MaterialAsset = {
|
|
58
67
|
kind: 'material',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import type
|
|
3
|
+
import { createSceneDataCatalog, type RenderFeaturePlan } from '@forgeax/engine-render';
|
|
4
4
|
import type { VfxGpuTickIntent } from '@forgeax/engine-vfx';
|
|
5
5
|
import { describe, expect, it } from 'vitest';
|
|
6
6
|
import { freezeRenderFeaturePlan } from '../../../render/src/features/plan';
|
|
@@ -108,6 +108,12 @@ describe('VFX managed stage execution seam', () => {
|
|
|
108
108
|
{ name: 'scene-color', kind: 'color', format: 'rgba16float', sampleCount: 1 },
|
|
109
109
|
{ name: 'scene-depth', kind: 'depth', format: 'depth24plus', sampleCount: 1 },
|
|
110
110
|
],
|
|
111
|
+
sceneData: createSceneDataCatalog({
|
|
112
|
+
featureIdentity: feature.identity,
|
|
113
|
+
generation: 1,
|
|
114
|
+
planIdentity: `${feature.identity}:1`,
|
|
115
|
+
rgba16floatRenderable: true,
|
|
116
|
+
}),
|
|
111
117
|
},
|
|
112
118
|
);
|
|
113
119
|
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
createTopologyResourcePlan,
|
|
26
26
|
PARTICLE_SHADER_IDENTIFIERS,
|
|
27
27
|
particleMaterialPass,
|
|
28
|
+
particleMaterialSceneDepthBinding,
|
|
28
29
|
particleMaterialUsesBindings,
|
|
29
30
|
} from './particle-resources.js';
|
|
30
31
|
import type { VfxStagePlanObservation, VfxValidatedStagePlan } from './stage-plan.js';
|
|
@@ -346,6 +347,11 @@ export function gpuParticleRenderFeature(
|
|
|
346
347
|
return {
|
|
347
348
|
identity: IDENTITY,
|
|
348
349
|
requiredCapabilities: ['compute', 'indirectDrawing'],
|
|
350
|
+
// Generated emitter programs are first-use assets. They must hand a
|
|
351
|
+
// WebGPU module to the prepared feature pipeline without waiting for the
|
|
352
|
+
// diagnostic-only getCompilationInfo() round trip; pipeline creation still
|
|
353
|
+
// validates the module before the pass is submitted.
|
|
354
|
+
shaderModuleMode: 'immediate',
|
|
349
355
|
requiredMaterialShaders: Object.values(PARTICLE_SHADER_IDENTIFIERS),
|
|
350
356
|
extract: (context) => {
|
|
351
357
|
const extracted: ExtractedWorld[] = [];
|
|
@@ -552,6 +558,14 @@ export function gpuParticleRenderFeature(
|
|
|
552
558
|
const submesh =
|
|
553
559
|
renderer.kind === 'mesh' ? mesh?.submeshes[renderer.submesh ?? 0] : undefined;
|
|
554
560
|
const indexFormat = mesh?.indices instanceof Uint32Array ? 'uint32' : 'uint16';
|
|
561
|
+
const sceneDepthBinding = isBillboard
|
|
562
|
+
? particleMaterialSceneDepthBinding(
|
|
563
|
+
context.materialShaderBindingContract?.(materialPass.shader) ??
|
|
564
|
+
(materialPass.shader === PARTICLE_SHADER_IDENTIFIERS.billboard
|
|
565
|
+
? 'view-and-scene-depth'
|
|
566
|
+
: undefined),
|
|
567
|
+
)
|
|
568
|
+
: undefined;
|
|
555
569
|
const instances = `${rendererPrefix}.instances`;
|
|
556
570
|
const history = `${rendererPrefix}.history`;
|
|
557
571
|
const projectionRuntime = `${rendererPrefix}.runtime`;
|
|
@@ -690,9 +704,9 @@ export function gpuParticleRenderFeature(
|
|
|
690
704
|
group: 0,
|
|
691
705
|
runtime: projectionRuntime,
|
|
692
706
|
instances,
|
|
693
|
-
...(
|
|
707
|
+
...(sceneDepthBinding === undefined ? {} : { sceneDepthBinding }),
|
|
694
708
|
},
|
|
695
|
-
...(
|
|
709
|
+
...(sceneDepthBinding !== undefined && depthTarget !== undefined
|
|
696
710
|
? { logicalTargets: { sceneDepth: depthTarget.name } }
|
|
697
711
|
: {}),
|
|
698
712
|
},
|
|
@@ -784,7 +798,7 @@ export function gpuParticleRenderFeature(
|
|
|
784
798
|
depthStoreOp: 'store',
|
|
785
799
|
},
|
|
786
800
|
}),
|
|
787
|
-
...(
|
|
801
|
+
...(sceneDepthBinding !== undefined && depthTarget !== undefined
|
|
788
802
|
? { sampledTargets: [depthTarget.name] }
|
|
789
803
|
: {}),
|
|
790
804
|
draws: [
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RenderFeatureMaterialShaderBindingContract } from '@forgeax/engine-render';
|
|
1
2
|
import {
|
|
2
3
|
err,
|
|
3
4
|
type MaterialAsset,
|
|
@@ -161,6 +162,23 @@ export function particleMaterialUsesBindings(material: MaterialAsset | undefined
|
|
|
161
162
|
return (material?.parameters?.length ?? 0) > 0;
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Return the scene-depth binding slot required by a particle material shader.
|
|
167
|
+
*
|
|
168
|
+
* Custom particle shaders historically bind only the sampled depth resource at
|
|
169
|
+
* group(0)/binding(0), while the renderer-owned billboard shader also consumes
|
|
170
|
+
* the view UBO at binding(0) and therefore uses depth at binding(1). Keep this
|
|
171
|
+
* choice derived from the renderer contract instead of assuming every
|
|
172
|
+
* billboard has the built-in layout.
|
|
173
|
+
*/
|
|
174
|
+
export function particleMaterialSceneDepthBinding(
|
|
175
|
+
contract: RenderFeatureMaterialShaderBindingContract | undefined,
|
|
176
|
+
): 0 | 1 | undefined {
|
|
177
|
+
if (contract === 'group-0-resource') return 0;
|
|
178
|
+
if (contract === 'view-and-scene-depth') return 1;
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
164
182
|
function floatAttribute(value: ArrayBuffer | Float32Array | Uint16Array | undefined): Float32Array {
|
|
165
183
|
if (value instanceof Float32Array) return value;
|
|
166
184
|
if (value instanceof Uint16Array) return Float32Array.from(value);
|
package/src/shaders/beam.wgsl
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
#define_import_path forgeax::vfx-render.particles.beam
|
|
2
|
-
#import forgeax_view::common::{View, FogViewParams, FogRay, view}
|
|
3
|
-
#import forgeax_view::fog::{apply_fog}
|
|
4
2
|
|
|
5
3
|
struct BeamInput {
|
|
6
4
|
@location(0) start: vec3<f32>,
|
|
@@ -15,29 +13,6 @@ struct VertexOutput {
|
|
|
15
13
|
@location(1) clip_position: vec3<f32>,
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
fn fogWorldPoint(ndc: vec3<f32>) -> vec3<f32> {
|
|
19
|
-
let homogeneous = view.inverseViewProj * vec4<f32>(ndc, 1.0);
|
|
20
|
-
let divisor = select(1.0, homogeneous.w, abs(homogeneous.w) > 0.000001);
|
|
21
|
-
return homogeneous.xyz / divisor;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
fn fogRayFromNdc(ndc: vec3<f32>) -> FogRay {
|
|
25
|
-
let worldPosition = fogWorldPoint(ndc);
|
|
26
|
-
let nearPosition = fogWorldPoint(vec3<f32>(ndc.xy, 0.0));
|
|
27
|
-
let farPosition = fogWorldPoint(vec3<f32>(ndc.xy, 1.0));
|
|
28
|
-
let perspective = view.temporalProjection.z < 0.5;
|
|
29
|
-
let perspectiveVector = worldPosition - view.cameraPos;
|
|
30
|
-
let orthographicVector = farPosition - nearPosition;
|
|
31
|
-
let direction = normalize(select(orthographicVector, perspectiveVector, perspective));
|
|
32
|
-
let origin = select(nearPosition, view.cameraPos, perspective);
|
|
33
|
-
let ray_distance = select(
|
|
34
|
-
max(dot(worldPosition - nearPosition, direction), 0.0),
|
|
35
|
-
length(perspectiveVector),
|
|
36
|
-
perspective,
|
|
37
|
-
);
|
|
38
|
-
return FogRay(origin, direction, ray_distance);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
16
|
@vertex
|
|
42
17
|
fn vs_main(input: BeamInput, @builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
43
18
|
let corners = array<vec2<f32>, 6>(
|
|
@@ -60,10 +35,5 @@ fn vs_main(input: BeamInput, @builtin(vertex_index) vertexIndex: u32) -> VertexO
|
|
|
60
35
|
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
61
36
|
let base = vec4<f32>(0.7, 0.2, 1.0, 1.0);
|
|
62
37
|
let alpha = base.a * input.color.a;
|
|
63
|
-
|
|
64
|
-
view.fog,
|
|
65
|
-
fogRayFromNdc(input.clip_position),
|
|
66
|
-
vec4<f32>(base.rgb * input.color.rgb, alpha),
|
|
67
|
-
);
|
|
68
|
-
return vec4<f32>(fogged.rgb * fogged.a, fogged.a);
|
|
38
|
+
return vec4<f32>(base.rgb * input.color.rgb * alpha, alpha);
|
|
69
39
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#define_import_path forgeax::vfx-render.particles.billboard
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
@group(0) @binding(
|
|
2
|
+
// The analytic fog producer was removed from this shader. Billboard
|
|
3
|
+
// soft-particle depth is therefore the sole group-0 resource again, matching
|
|
4
|
+
// the prepared group-0-resource layout and its binding resolver.
|
|
5
|
+
@group(0) @binding(0) var scene_depth: texture_depth_2d;
|
|
6
6
|
|
|
7
7
|
struct VertexOutput {
|
|
8
8
|
@builtin(position) position: vec4<f32>,
|
|
@@ -51,29 +51,6 @@ fn softParticle(position: vec4<f32>, alpha: f32, fadeDistance: f32) -> f32 {
|
|
|
51
51
|
return alpha * softParticleFactor(position.z, sceneDepth, fadeDistance);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
fn fogWorldPoint(ndc: vec3<f32>) -> vec3<f32> {
|
|
55
|
-
let homogeneous = view.inverseViewProj * vec4<f32>(ndc, 1.0);
|
|
56
|
-
let divisor = select(1.0, homogeneous.w, abs(homogeneous.w) > 0.000001);
|
|
57
|
-
return homogeneous.xyz / divisor;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
fn fogRayFromNdc(ndc: vec3<f32>) -> FogRay {
|
|
61
|
-
let worldPosition = fogWorldPoint(ndc);
|
|
62
|
-
let nearPosition = fogWorldPoint(vec3<f32>(ndc.xy, 0.0));
|
|
63
|
-
let farPosition = fogWorldPoint(vec3<f32>(ndc.xy, 1.0));
|
|
64
|
-
let perspective = view.temporalProjection.z < 0.5;
|
|
65
|
-
let perspectiveVector = worldPosition - view.cameraPos;
|
|
66
|
-
let orthographicVector = farPosition - nearPosition;
|
|
67
|
-
let direction = normalize(select(orthographicVector, perspectiveVector, perspective));
|
|
68
|
-
let origin = select(nearPosition, view.cameraPos, perspective);
|
|
69
|
-
let ray_distance = select(
|
|
70
|
-
max(dot(worldPosition - nearPosition, direction), 0.0),
|
|
71
|
-
length(perspectiveVector),
|
|
72
|
-
perspective,
|
|
73
|
-
);
|
|
74
|
-
return FogRay(origin, direction, ray_distance);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
54
|
struct VertexInput {
|
|
78
55
|
@location(0) position: vec3<f32>,
|
|
79
56
|
@location(1) right: vec2<f32>,
|
|
@@ -127,6 +104,5 @@ fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
|
127
104
|
let alpha = softParticle(input.position, input.color.a * edge, input.fade_distance);
|
|
128
105
|
let sheetPulse = 0.82 + 0.18 * fract(input.sheet_frame * 0.618 + input.sheet_uv.x + input.sheet_uv.y);
|
|
129
106
|
let rgb = (input.color.rgb + emissive * (0.35 + core * 0.65) + vec3<f32>(highlight)) * sheetPulse;
|
|
130
|
-
|
|
131
|
-
return vec4<f32>(fogged.rgb * fogged.a, fogged.a);
|
|
107
|
+
return vec4<f32>(rgb * alpha, alpha);
|
|
132
108
|
}
|
package/src/shaders/mesh.wgsl
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
#define_import_path forgeax::vfx-render.particles.mesh
|
|
2
|
-
#import forgeax_view::common::{View, FogViewParams, FogRay, view}
|
|
3
|
-
#import forgeax_view::fog::{apply_fog}
|
|
4
2
|
|
|
5
3
|
struct VertexOutput {
|
|
6
4
|
@builtin(position) position: vec4<f32>,
|
|
@@ -26,29 +24,6 @@ struct VertexInput {
|
|
|
26
24
|
@location(11) surface: vec4<f32>,
|
|
27
25
|
};
|
|
28
26
|
|
|
29
|
-
fn fogWorldPoint(ndc: vec3<f32>) -> vec3<f32> {
|
|
30
|
-
let homogeneous = view.inverseViewProj * vec4<f32>(ndc, 1.0);
|
|
31
|
-
let divisor = select(1.0, homogeneous.w, abs(homogeneous.w) > 0.000001);
|
|
32
|
-
return homogeneous.xyz / divisor;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
fn fogRayFromNdc(ndc: vec3<f32>) -> FogRay {
|
|
36
|
-
let worldPosition = fogWorldPoint(ndc);
|
|
37
|
-
let nearPosition = fogWorldPoint(vec3<f32>(ndc.xy, 0.0));
|
|
38
|
-
let farPosition = fogWorldPoint(vec3<f32>(ndc.xy, 1.0));
|
|
39
|
-
let perspective = view.temporalProjection.z < 0.5;
|
|
40
|
-
let perspectiveVector = worldPosition - view.cameraPos;
|
|
41
|
-
let orthographicVector = farPosition - nearPosition;
|
|
42
|
-
let direction = normalize(select(orthographicVector, perspectiveVector, perspective));
|
|
43
|
-
let origin = select(nearPosition, view.cameraPos, perspective);
|
|
44
|
-
let ray_distance = select(
|
|
45
|
-
max(dot(worldPosition - nearPosition, direction), 0.0),
|
|
46
|
-
length(perspectiveVector),
|
|
47
|
-
perspective,
|
|
48
|
-
);
|
|
49
|
-
return FogRay(origin, direction, ray_distance);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
27
|
@vertex
|
|
53
28
|
fn vs_main(input: VertexInput) -> VertexOutput {
|
|
54
29
|
var output: VertexOutput;
|
|
@@ -89,10 +64,6 @@ fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
|
89
64
|
let specular_color = mix(dielectric, input.color.rgb, metallic);
|
|
90
65
|
let lit = input.color.rgb * diffuse * (1.0 - metallic * 0.55);
|
|
91
66
|
let emissive = input.emissive_intensity.rgb * input.emissive_intensity.a;
|
|
92
|
-
let
|
|
93
|
-
|
|
94
|
-
fogRayFromNdc(input.clip_position),
|
|
95
|
-
vec4<f32>(lit + specular_color * specular + vec3<f32>(coat) + emissive, input.color.a),
|
|
96
|
-
);
|
|
97
|
-
return vec4<f32>(fogged.rgb * fogged.a, fogged.a);
|
|
67
|
+
let rgb = lit + specular_color * specular + vec3<f32>(coat) + emissive;
|
|
68
|
+
return vec4<f32>(rgb * input.color.a, input.color.a);
|
|
98
69
|
}
|
package/src/shaders/ribbon.wgsl
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
#define_import_path forgeax::vfx-render.particles.ribbon
|
|
2
|
-
#import forgeax_view::common::{View, FogViewParams, FogRay, view}
|
|
3
|
-
#import forgeax_view::fog::{apply_fog}
|
|
4
2
|
|
|
5
3
|
struct SegmentInput {
|
|
6
4
|
@location(0) start: vec3<f32>,
|
|
@@ -15,29 +13,6 @@ struct VertexOutput {
|
|
|
15
13
|
@location(1) clip_position: vec3<f32>,
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
fn fogWorldPoint(ndc: vec3<f32>) -> vec3<f32> {
|
|
19
|
-
let homogeneous = view.inverseViewProj * vec4<f32>(ndc, 1.0);
|
|
20
|
-
let divisor = select(1.0, homogeneous.w, abs(homogeneous.w) > 0.000001);
|
|
21
|
-
return homogeneous.xyz / divisor;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
fn fogRayFromNdc(ndc: vec3<f32>) -> FogRay {
|
|
25
|
-
let worldPosition = fogWorldPoint(ndc);
|
|
26
|
-
let nearPosition = fogWorldPoint(vec3<f32>(ndc.xy, 0.0));
|
|
27
|
-
let farPosition = fogWorldPoint(vec3<f32>(ndc.xy, 1.0));
|
|
28
|
-
let perspective = view.temporalProjection.z < 0.5;
|
|
29
|
-
let perspectiveVector = worldPosition - view.cameraPos;
|
|
30
|
-
let orthographicVector = farPosition - nearPosition;
|
|
31
|
-
let direction = normalize(select(orthographicVector, perspectiveVector, perspective));
|
|
32
|
-
let origin = select(nearPosition, view.cameraPos, perspective);
|
|
33
|
-
let ray_distance = select(
|
|
34
|
-
max(dot(worldPosition - nearPosition, direction), 0.0),
|
|
35
|
-
length(perspectiveVector),
|
|
36
|
-
perspective,
|
|
37
|
-
);
|
|
38
|
-
return FogRay(origin, direction, ray_distance);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
16
|
@vertex
|
|
42
17
|
fn vs_main(input: SegmentInput, @builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
43
18
|
let corners = array<vec2<f32>, 6>(
|
|
@@ -60,10 +35,5 @@ fn vs_main(input: SegmentInput, @builtin(vertex_index) vertexIndex: u32) -> Vert
|
|
|
60
35
|
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
61
36
|
let base = vec4<f32>(0.2, 0.7, 1.0, 0.9);
|
|
62
37
|
let alpha = base.a * input.color.a;
|
|
63
|
-
|
|
64
|
-
view.fog,
|
|
65
|
-
fogRayFromNdc(input.clip_position),
|
|
66
|
-
vec4<f32>(base.rgb * input.color.rgb, alpha),
|
|
67
|
-
);
|
|
68
|
-
return fogged;
|
|
38
|
+
return vec4<f32>(base.rgb * input.color.rgb * alpha, alpha);
|
|
69
39
|
}
|
package/src/shaders/trail.wgsl
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
#define_import_path forgeax::vfx-render.particles.trail
|
|
2
|
-
#import forgeax_view::common::{View, FogViewParams, FogRay, view}
|
|
3
|
-
#import forgeax_view::fog::{apply_fog}
|
|
4
2
|
|
|
5
3
|
struct SegmentInput {
|
|
6
4
|
@location(0) start: vec3<f32>,
|
|
@@ -15,29 +13,6 @@ struct VertexOutput {
|
|
|
15
13
|
@location(1) clip_position: vec3<f32>,
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
fn fogWorldPoint(ndc: vec3<f32>) -> vec3<f32> {
|
|
19
|
-
let homogeneous = view.inverseViewProj * vec4<f32>(ndc, 1.0);
|
|
20
|
-
let divisor = select(1.0, homogeneous.w, abs(homogeneous.w) > 0.000001);
|
|
21
|
-
return homogeneous.xyz / divisor;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
fn fogRayFromNdc(ndc: vec3<f32>) -> FogRay {
|
|
25
|
-
let worldPosition = fogWorldPoint(ndc);
|
|
26
|
-
let nearPosition = fogWorldPoint(vec3<f32>(ndc.xy, 0.0));
|
|
27
|
-
let farPosition = fogWorldPoint(vec3<f32>(ndc.xy, 1.0));
|
|
28
|
-
let perspective = view.temporalProjection.z < 0.5;
|
|
29
|
-
let perspectiveVector = worldPosition - view.cameraPos;
|
|
30
|
-
let orthographicVector = farPosition - nearPosition;
|
|
31
|
-
let direction = normalize(select(orthographicVector, perspectiveVector, perspective));
|
|
32
|
-
let origin = select(nearPosition, view.cameraPos, perspective);
|
|
33
|
-
let ray_distance = select(
|
|
34
|
-
max(dot(worldPosition - nearPosition, direction), 0.0),
|
|
35
|
-
length(perspectiveVector),
|
|
36
|
-
perspective,
|
|
37
|
-
);
|
|
38
|
-
return FogRay(origin, direction, ray_distance);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
16
|
@vertex
|
|
42
17
|
fn vs_main(input: SegmentInput, @builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
43
18
|
let corners = array<vec2<f32>, 6>(
|
|
@@ -68,10 +43,5 @@ fn vs_main(input: SegmentInput, @builtin(vertex_index) vertexIndex: u32) -> Vert
|
|
|
68
43
|
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
69
44
|
let base = vec4<f32>(0.8, 0.35, 0.1, 1.0);
|
|
70
45
|
let alpha = base.a * input.color.a;
|
|
71
|
-
|
|
72
|
-
view.fog,
|
|
73
|
-
fogRayFromNdc(input.clip_position),
|
|
74
|
-
vec4<f32>(base.rgb * input.color.rgb, alpha),
|
|
75
|
-
);
|
|
76
|
-
return vec4<f32>(fogged.rgb * fogged.a, fogged.a);
|
|
46
|
+
return vec4<f32>(base.rgb * input.color.rgb * alpha, alpha);
|
|
77
47
|
}
|