@forgeax/engine-vfx-render 0.1.7 → 0.1.19
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 +5 -3
- 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 -24
- package/src/shaders/billboard.wgsl +1 -24
- package/src/shaders/mesh.wgsl +1 -24
- package/src/shaders/ribbon.wgsl +1 -24
- package/src/shaders/trail.wgsl +1 -24
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.19",
|
|
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.19",
|
|
26
|
+
"@forgeax/engine-ecs": "0.1.19",
|
|
27
|
+
"@forgeax/engine-math": "0.1.19",
|
|
28
|
+
"@forgeax/engine-render": "0.1.19",
|
|
29
|
+
"@forgeax/engine-scene": "0.1.19",
|
|
30
|
+
"@forgeax/engine-types": "0.1.19",
|
|
31
|
+
"@forgeax/engine-vfx": "0.1.19"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {},
|
|
34
34
|
"forgeax": {
|
|
@@ -21,9 +21,11 @@ describe('VFX Fog producer contract', () => {
|
|
|
21
21
|
);
|
|
22
22
|
expect(source, name).toMatch(/#import\s+forgeax_view::fog::\{[^}]*\bapply_fog\b[^}]*\}/);
|
|
23
23
|
expect(count(source, /\bapply_fog\s*\(/g), name).toBe(1);
|
|
24
|
-
expect(count(source, /\bFogRay\s*\(/g), name).toBe(
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
expect(count(source, /\bFogRay\s*\(/g) + count(source, /\bfogRayFromNdc\s*\(/g), name).toBe(
|
|
25
|
+
1,
|
|
26
|
+
);
|
|
27
|
+
expect(source, name).toMatch(/world[_A-Za-z]*position|worldPosition|fogRayFromNdc/);
|
|
28
|
+
expect(source, name).toMatch(/ray[_A-Za-z]*distance|distance|fogRayFromNdc/);
|
|
27
29
|
expect(source, name).toMatch(/\.a\b|alpha/);
|
|
28
30
|
expect(source, name).not.toMatch(/texture_3d|raymarch/i);
|
|
29
31
|
}
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
#define_import_path forgeax::vfx-render.particles.beam
|
|
2
|
-
#import forgeax_view::common::{View, FogViewParams, FogRay, view}
|
|
2
|
+
#import forgeax_view::common::{View, FogViewParams, FogRay, view, fogRayFromNdc}
|
|
3
3
|
#import forgeax_view::fog::{apply_fog}
|
|
4
4
|
|
|
5
5
|
struct BeamInput {
|
|
@@ -15,29 +15,6 @@ struct VertexOutput {
|
|
|
15
15
|
@location(1) clip_position: vec3<f32>,
|
|
16
16
|
}
|
|
17
17
|
|
|
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
18
|
@vertex
|
|
42
19
|
fn vs_main(input: BeamInput, @builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
43
20
|
let corners = array<vec2<f32>, 6>(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#define_import_path forgeax::vfx-render.particles.billboard
|
|
2
|
-
#import forgeax_view::common::{View, FogViewParams, FogRay, view}
|
|
2
|
+
#import forgeax_view::common::{View, FogViewParams, FogRay, view, fogRayFromNdc}
|
|
3
3
|
#import forgeax_view::fog::{apply_fog}
|
|
4
4
|
|
|
5
5
|
@group(0) @binding(1) var scene_depth: texture_depth_2d;
|
|
@@ -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>,
|
package/src/shaders/mesh.wgsl
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#define_import_path forgeax::vfx-render.particles.mesh
|
|
2
|
-
#import forgeax_view::common::{View, FogViewParams, FogRay, view}
|
|
2
|
+
#import forgeax_view::common::{View, FogViewParams, FogRay, view, fogRayFromNdc}
|
|
3
3
|
#import forgeax_view::fog::{apply_fog}
|
|
4
4
|
|
|
5
5
|
struct VertexOutput {
|
|
@@ -26,29 +26,6 @@ struct VertexInput {
|
|
|
26
26
|
@location(11) surface: vec4<f32>,
|
|
27
27
|
};
|
|
28
28
|
|
|
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
29
|
@vertex
|
|
53
30
|
fn vs_main(input: VertexInput) -> VertexOutput {
|
|
54
31
|
var output: VertexOutput;
|
package/src/shaders/ribbon.wgsl
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#define_import_path forgeax::vfx-render.particles.ribbon
|
|
2
|
-
#import forgeax_view::common::{View, FogViewParams, FogRay, view}
|
|
2
|
+
#import forgeax_view::common::{View, FogViewParams, FogRay, view, fogRayFromNdc}
|
|
3
3
|
#import forgeax_view::fog::{apply_fog}
|
|
4
4
|
|
|
5
5
|
struct SegmentInput {
|
|
@@ -15,29 +15,6 @@ struct VertexOutput {
|
|
|
15
15
|
@location(1) clip_position: vec3<f32>,
|
|
16
16
|
}
|
|
17
17
|
|
|
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
18
|
@vertex
|
|
42
19
|
fn vs_main(input: SegmentInput, @builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
43
20
|
let corners = array<vec2<f32>, 6>(
|
package/src/shaders/trail.wgsl
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#define_import_path forgeax::vfx-render.particles.trail
|
|
2
|
-
#import forgeax_view::common::{View, FogViewParams, FogRay, view}
|
|
2
|
+
#import forgeax_view::common::{View, FogViewParams, FogRay, view, fogRayFromNdc}
|
|
3
3
|
#import forgeax_view::fog::{apply_fog}
|
|
4
4
|
|
|
5
5
|
struct SegmentInput {
|
|
@@ -15,29 +15,6 @@ struct VertexOutput {
|
|
|
15
15
|
@location(1) clip_position: vec3<f32>,
|
|
16
16
|
}
|
|
17
17
|
|
|
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
18
|
@vertex
|
|
42
19
|
fn vs_main(input: SegmentInput, @builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
43
20
|
let corners = array<vec2<f32>, 6>(
|