Skip to content

Commit e31b001

Browse files
dakerfinetjul
authored andcommitted
feat(WebGPU): add polydata backface material and local culling
Add backface material support to the WebGPU polydata mapper.
1 parent 3f6ede9 commit e31b001

File tree

4 files changed

+188
-17
lines changed

4 files changed

+188
-17
lines changed

Sources/Rendering/WebGPU/CellArrayMapper/index.js

Lines changed: 115 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,31 @@ fn main(
322322
// Temporary ambient, diffuse, and opacity
323323
var ambientColor: vec4<f32> = mapperUBO.AmbientColor;
324324
var diffuseColor: vec4<f32> = mapperUBO.DiffuseColor;
325+
var ambientIntensity: f32 = mapperUBO.AmbientIntensity;
326+
var diffuseIntensity: f32 = mapperUBO.DiffuseIntensity;
327+
var specularColor: vec4<f32> = mapperUBO.SpecularColor;
328+
var specularIntensity: f32 = mapperUBO.SpecularIntensity;
325329
var opacity: f32 = mapperUBO.Opacity;
326330
var ior: f32 = mapperUBO.BaseIOR;
331+
var normalStrengthUniform: f32 = mapperUBO.NormalStrength;
332+
var roughnessUniform: f32 = mapperUBO.Roughness;
333+
var metallicUniform: f32 = mapperUBO.Metallic;
334+
var emissionUniform: f32 = mapperUBO.Emission;
335+
336+
if (!input.frontFacing) {
337+
ambientColor = mapperUBO.AmbientColorBF;
338+
diffuseColor = mapperUBO.DiffuseColorBF;
339+
ambientIntensity = mapperUBO.AmbientIntensityBF;
340+
diffuseIntensity = mapperUBO.DiffuseIntensityBF;
341+
specularColor = mapperUBO.SpecularColorBF;
342+
specularIntensity = mapperUBO.SpecularIntensityBF;
343+
opacity = mapperUBO.OpacityBF;
344+
ior = mapperUBO.BaseIORBF;
345+
normalStrengthUniform = mapperUBO.NormalStrengthBF;
346+
roughnessUniform = mapperUBO.RoughnessBF;
347+
metallicUniform = mapperUBO.MetallicBF;
348+
emissionUniform = mapperUBO.EmissionBF;
349+
}
327350
328351
// This should be declared somewhere else
329352
var _diffuseMap: vec4<f32> = vec4<f32>(1.0);
@@ -346,7 +369,7 @@ fn main(
346369
//VTK::Select::Impl
347370
348371
// Use texture alpha for transparency
349-
computedColor.a = mapperUBO.Opacity * _diffuseMap.a;
372+
computedColor.a = opacity * _diffuseMap.a;
350373
if (computedColor.a == 0.0) { discard; };
351374
352375
//VTK::Position::Impl
@@ -409,10 +432,12 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
409432
publicAPI.updateUBO = () => {
410433
const actor = model.WebGPUActor.getRenderable();
411434
const ppty = actor.getProperty();
435+
const backfaceProperty = actor.getBackfaceProperty?.() ?? ppty;
412436
const utime = model.UBO.getSendTime();
413437
if (
414438
publicAPI.getMTime() <= utime &&
415439
ppty.getMTime() <= utime &&
440+
backfaceProperty.getMTime() <= utime &&
416441
model.renderable.getMTime() <= utime
417442
) {
418443
return;
@@ -434,9 +459,28 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
434459
);
435460
const aColor = ppty.getColorByReference();
436461
model.UBO.setValue('AmbientIntensity', 1.0);
462+
model.UBO.setArray('AmbientColor', [...aColor, 1.0]);
437463
model.UBO.setArray('DiffuseColor', [...aColor, 1.0]);
438464
model.UBO.setValue('DiffuseIntensity', 0.0);
465+
model.UBO.setArray('SpecularColor', [1.0, 1.0, 1.0, 1.0]);
439466
model.UBO.setValue('SpecularIntensity', 0.0);
467+
model.UBO.setValue('Roughness', 1.0);
468+
model.UBO.setValue('BaseIOR', 1.45);
469+
model.UBO.setValue('Metallic', 0.0);
470+
model.UBO.setValue('Emission', 1.0);
471+
model.UBO.setValue('NormalStrength', 1.0);
472+
model.UBO.setValue('AmbientIntensityBF', 1.0);
473+
model.UBO.setArray('AmbientColorBF', [...aColor, 1.0]);
474+
model.UBO.setValue('DiffuseIntensityBF', 0.0);
475+
model.UBO.setArray('DiffuseColorBF', [...aColor, 1.0]);
476+
model.UBO.setArray('SpecularColorBF', [1.0, 1.0, 1.0, 1.0]);
477+
model.UBO.setValue('SpecularIntensityBF', 0.0);
478+
model.UBO.setValue('RoughnessBF', 1.0);
479+
model.UBO.setValue('BaseIORBF', 1.45);
480+
model.UBO.setValue('MetallicBF', 0.0);
481+
model.UBO.setValue('EmissionBF', 1.0);
482+
model.UBO.setValue('NormalStrengthBF', 1.0);
483+
model.UBO.setValue('OpacityBF', ppty.getOpacity());
440484
} else {
441485
// Base Colors
442486
model.UBO.setValue('AmbientIntensity', ppty.getAmbient());
@@ -460,12 +504,35 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
460504
model.UBO.setValue('Emission', ppty.getEmission());
461505
// Specular
462506
model.UBO.setValue('SpecularIntensity', ppty.getSpecular());
463-
if (ppty.getSpecularColorByReference()) {
464-
model.UBO.setArray('SpecularColor', [
465-
...ppty.getSpecularColorByReference(),
466-
1.0,
467-
]);
468-
}
507+
model.UBO.setArray('SpecularColor', [
508+
...ppty.getSpecularColorByReference(),
509+
1.0,
510+
]);
511+
512+
model.UBO.setValue('AmbientIntensityBF', backfaceProperty.getAmbient());
513+
model.UBO.setArray('AmbientColorBF', [
514+
...backfaceProperty.getAmbientColorByReference(),
515+
1.0,
516+
]);
517+
model.UBO.setValue('DiffuseIntensityBF', backfaceProperty.getDiffuse());
518+
model.UBO.setArray('DiffuseColorBF', [
519+
...backfaceProperty.getDiffuseColorByReference(),
520+
1.0,
521+
]);
522+
model.UBO.setValue('RoughnessBF', backfaceProperty.getRoughness());
523+
model.UBO.setValue('BaseIORBF', backfaceProperty.getBaseIOR());
524+
model.UBO.setValue('MetallicBF', backfaceProperty.getMetallic());
525+
model.UBO.setValue('EmissionBF', backfaceProperty.getEmission());
526+
model.UBO.setValue(
527+
'NormalStrengthBF',
528+
backfaceProperty.getNormalStrength()
529+
);
530+
model.UBO.setValue('SpecularIntensityBF', backfaceProperty.getSpecular());
531+
model.UBO.setArray('SpecularColorBF', [
532+
...backfaceProperty.getSpecularColorByReference(),
533+
1.0,
534+
]);
535+
model.UBO.setValue('OpacityBF', backfaceProperty.getOpacity());
469536
}
470537

471538
// --- Edge and Misc ---
@@ -497,6 +564,24 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
497564
return true;
498565
};
499566

567+
publicAPI.getCullMode = () => {
568+
const actor = model.WebGPUActor.getRenderable();
569+
const property = actor.getProperty();
570+
if (property.getFrontfaceCulling()) {
571+
return 'front';
572+
}
573+
if (property.getBackfaceCulling()) {
574+
return 'back';
575+
}
576+
return 'none';
577+
};
578+
579+
publicAPI.getPipelineSettings = () => ({
580+
primitive: {
581+
cullMode: publicAPI.getCullMode(),
582+
},
583+
});
584+
500585
publicAPI.replaceShaderPosition = (hash, pipeline, vertexInput) => {
501586
const vDesc = pipeline.getShaderDescription('vertex');
502587
vDesc.addBuiltinOutput('vec4<f32>', '@builtin(position) Position');
@@ -615,7 +700,7 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
615700
' tangent.z, bitangent.z, normal.z,',
616701
' );',
617702
' var mappedNormal: vec3<f32> = TCVCMatrix * (_normalMap.xyz * 2 - 1);',
618-
' normal = mix(normal, mappedNormal, mapperUBO.NormalStrength);',
703+
' normal = mix(normal, mappedNormal, normalStrengthUniform);',
619704
' normal = normalize(normal);',
620705
]).result;
621706
} else {
@@ -659,14 +744,14 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
659744
' let V = mix(normalize(-fragPos), vec3<f32>(0, 0, 1), f32(rendererUBO.cameraParallel)); // View Vector',
660745
// Values needed for light calculations
661746
' let baseColor = _diffuseMap.rgb * diffuseColor.rgb;',
662-
' let roughness = max(0.000001, mapperUBO.Roughness * _roughnessMap.r);', // Need to have a different way of sampling greyscale values aside from .r
663-
' let metallic = mapperUBO.Metallic * _metallicMap.r;',
747+
' let roughness = max(0.000001, roughnessUniform * _roughnessMap.r);', // Need to have a different way of sampling greyscale values aside from .r
748+
' let metallic = metallicUniform * _metallicMap.r;',
664749
' let alpha = roughness * roughness;',
665750
' let k = alpha * alpha / 2.0;',
666751
// Split diffuse and specular components
667752
' var diffuse = vec3<f32>(0.);',
668753
' var specular = vec3<f32>(0.);',
669-
' let emission = _emissionMap.rgb * mapperUBO.Emission;',
754+
' let emission = _emissionMap.rgb * emissionUniform;',
670755
'',
671756
' // Material struct',
672757
' let mat = Material(ior, roughness, metallic, baseColor);',
@@ -717,8 +802,9 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
717802
' let fresnelMetallic = schlickFresnelRGB(V, normal, baseColor); // Fresnel for metal, takes color into account',
718803
' let kS = min(vec3<f32>(1.0), mix(vec3<f32>(fresnel), fresnelMetallic, metallic));',
719804
' let kD = (1.0 - kS) * (1.0 - metallic);',
720-
' let PBR = mapperUBO.DiffuseIntensity * kD * diffuse + kS * specular;',
721-
' computedColor = vec4<f32>(PBR + emission, mapperUBO.Opacity);',
805+
' let specularMaterial = specularColor.rgb * specularColor.a;',
806+
' let PBR = diffuseIntensity * kD * diffuse + kS * specularIntensity * specular * specularMaterial;',
807+
' computedColor = vec4<f32>(PBR + emission, opacity);',
722808
];
723809
if (renderer.getEnvironmentTexture()?.getImageLoaded()) {
724810
lightingCode.push(
@@ -750,8 +836,8 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
750836
} else {
751837
code = vtkWebGPUShaderCache.substitute(code, '//VTK::Light::Impl', [
752838
' let diffuse = diffuseColor.rgb;',
753-
' let specular = mapperUBO.SpecularColor.rgb * mapperUBO.SpecularColor.a;',
754-
' computedColor = vec4<f32>(diffuse * _diffuseMap.rgb, mapperUBO.Opacity);',
839+
' let specular = specularColor.rgb * specularColor.a;',
840+
' computedColor = vec4<f32>(diffuse * _diffuseMap.rgb, opacity);',
755841
]).result;
756842
fDesc.setCode(code);
757843
}
@@ -797,7 +883,7 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
797883
code = vtkWebGPUShaderCache.substitute(code, '//VTK::Color::Impl', [
798884
'ambientColor = input.color;',
799885
'diffuseColor = input.color;',
800-
'opacity = mapperUBO.Opacity * input.color.a;',
886+
'opacity = opacity * input.color.a;',
801887
]).result;
802888
fDesc.setCode(code);
803889
};
@@ -1301,6 +1387,7 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
13011387

13021388
const uhash = publicAPI.getHashFromUsage(model.usage);
13031389
pipelineHash += uhash;
1390+
pipelineHash += `cm${publicAPI.getCullMode()}`;
13041391
pipelineHash += model.renderEncoder.getPipelineHash();
13051392

13061393
model.pipelineHash = pipelineHash;
@@ -1372,21 +1459,33 @@ export function extend(publicAPI, model, initiaLalues = {}) {
13721459
model.UBO.addEntry('BCSCMatrix', 'mat4x4<f32>');
13731460
model.UBO.addEntry('MCWCNormals', 'mat4x4<f32>');
13741461
model.UBO.addEntry('AmbientColor', 'vec4<f32>');
1462+
model.UBO.addEntry('AmbientColorBF', 'vec4<f32>');
13751463
model.UBO.addEntry('DiffuseColor', 'vec4<f32>');
1464+
model.UBO.addEntry('DiffuseColorBF', 'vec4<f32>');
13761465
model.UBO.addEntry('EdgeColor', 'vec4<f32>');
13771466
model.UBO.addEntry('SpecularColor', 'vec4<f32>');
1467+
model.UBO.addEntry('SpecularColorBF', 'vec4<f32>');
13781468
model.UBO.addEntry('AmbientIntensity', 'f32');
1469+
model.UBO.addEntry('AmbientIntensityBF', 'f32');
13791470
model.UBO.addEntry('DiffuseIntensity', 'f32');
1471+
model.UBO.addEntry('DiffuseIntensityBF', 'f32');
13801472
model.UBO.addEntry('Roughness', 'f32');
1473+
model.UBO.addEntry('RoughnessBF', 'f32');
13811474
model.UBO.addEntry('Metallic', 'f32');
1475+
model.UBO.addEntry('MetallicBF', 'f32');
13821476
model.UBO.addEntry('Ambient', 'f32');
13831477
model.UBO.addEntry('Normal', 'f32');
13841478
model.UBO.addEntry('Emission', 'f32');
1479+
model.UBO.addEntry('EmissionBF', 'f32');
13851480
model.UBO.addEntry('NormalStrength', 'f32');
1481+
model.UBO.addEntry('NormalStrengthBF', 'f32');
13861482
model.UBO.addEntry('BaseIOR', 'f32');
1483+
model.UBO.addEntry('BaseIORBF', 'f32');
13871484
model.UBO.addEntry('SpecularIntensity', 'f32');
1485+
model.UBO.addEntry('SpecularIntensityBF', 'f32');
13881486
model.UBO.addEntry('LineWidth', 'f32');
13891487
model.UBO.addEntry('Opacity', 'f32');
1488+
model.UBO.addEntry('OpacityBF', 'f32');
13901489
model.UBO.addEntry('ZValue', 'f32');
13911490
model.UBO.addEntry('PropID', 'u32');
13921491
model.UBO.addEntry('ClipNear', 'f32');

Sources/Rendering/WebGPU/Pipeline/index.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,37 @@ function vtkWebGPUPipeline(publicAPI, model) {
99

1010
publicAPI.getShaderDescriptions = () => model.shaderDescriptions;
1111

12+
publicAPI.applyPipelineSettings = (baseSettings, extraSettings) => {
13+
if (!extraSettings) {
14+
return baseSettings;
15+
}
16+
17+
const result = {
18+
...baseSettings,
19+
...extraSettings,
20+
primitive: {
21+
...(baseSettings.primitive || {}),
22+
...(extraSettings.primitive || {}),
23+
},
24+
depthStencil: {
25+
...(baseSettings.depthStencil || {}),
26+
...(extraSettings.depthStencil || {}),
27+
},
28+
fragment: {
29+
...(baseSettings.fragment || {}),
30+
...(extraSettings.fragment || {}),
31+
},
32+
};
33+
34+
return result;
35+
};
36+
1237
publicAPI.initialize = (device, hash) => {
1338
// start with the renderencoder settings
14-
model.pipelineDescription = model.renderEncoder.getPipelineSettings();
39+
model.pipelineDescription = publicAPI.applyPipelineSettings(
40+
model.renderEncoder.getPipelineSettings(),
41+
model.extraPipelineSettings
42+
);
1543

1644
model.pipelineDescription.primitive.topology = model.topology;
1745

@@ -85,6 +113,7 @@ function vtkWebGPUPipeline(publicAPI, model) {
85113
// Object factory
86114
// ----------------------------------------------------------------------------
87115
const DEFAULT_VALUES = {
116+
extraPipelineSettings: null,
88117
handle: null,
89118
layouts: null,
90119
renderEncoder: null,
@@ -107,6 +136,7 @@ export function extend(publicAPI, model, initialValues = {}) {
107136
macro.get(publicAPI, model, ['handle', 'pipelineDescription']);
108137
macro.setGet(publicAPI, model, [
109138
'device',
139+
'extraPipelineSettings',
110140
'renderEncoder',
111141
'topology',
112142
'vertexState',

Sources/Rendering/WebGPU/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,45 @@ updatePipeline()
167167

168168
We set the render encoder on the pipeline because the renderEncoder used may add shader code to the fragment shader to direct the computed fragment data to specific outputs/textureViews.
169169

170+
### extraPipelineSettings
171+
172+
`vtkWebGPUPipeline` can take `extraPipelineSettings` to override parts of the
173+
base pipeline settings coming from the active render encoder. In normal mapper
174+
code this is typically provided indirectly from `vtkWebGPUSimpleMapper` via
175+
`publicAPI.getPipelineSettings()`.
176+
177+
The merge is:
178+
179+
- top-level keys are shallow-merged onto the encoder pipeline settings
180+
- `primitive`, `depthStencil`, and `fragment` are also shallow-merged one level deeper
181+
- other nested objects are replaced rather than recursively merged
182+
183+
The flow looks like:
184+
185+
```
186+
renderEncoder.getPipelineSettings()
187+
-> pipeline.applyPipelineSettings(baseSettings, extraPipelineSettings)
188+
-> device.createRenderPipeline(...)
189+
```
190+
191+
This is useful when a mapper wants to keep the renderer/pass defaults but
192+
override a few pipeline fields such as `primitive.cullMode`.
193+
194+
Example:
195+
196+
```js
197+
publicAPI.getPipelineSettings = () => ({
198+
primitive: {
199+
cullMode: 'back',
200+
},
201+
});
202+
```
203+
204+
That example keeps the encoder's other settings intact while changing only the
205+
primitive culling mode for that mapper's pipeline. For backface-property style
206+
handling, the mapper can switch between values such as `'none'`, `'back'`, or
207+
`'front'` based on actor/property state before returning the settings object.
208+
170209
The simple mapper is a viewnode subclass so it can handle render passes. The FullScreenQuad is a small subclass of SimpleMapper designed to render a quad. CellArrayMapper is a large subclass of SimpleMapper designed to render a CellArray from a PolyData suchs as verts, lines, polys, strips. PolyDataMapper is a simple class that instantiates CellArrayMappers as needed to do the actual work.
171210

172211
## IndexBuffers

Sources/Rendering/WebGPU/SimpleMapper/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ function vtkWebGPUSimpleMapper(publicAPI, model) {
269269

270270
publicAPI.computePipelineHash = () => {};
271271

272+
publicAPI.getPipelineSettings = () => null;
273+
272274
publicAPI.registerDrawCallback = (encoder) => {
273275
encoder.registerDrawCallback(model.pipeline, publicAPI.draw);
274276
};
@@ -350,6 +352,7 @@ function vtkWebGPUSimpleMapper(publicAPI, model) {
350352
model.pipeline,
351353
model.vertexInput
352354
);
355+
model.pipeline.setExtraPipelineSettings(publicAPI.getPipelineSettings());
353356
model.pipeline.setTopology(model.topology);
354357
model.pipeline.setRenderEncoder(model.renderEncoder);
355358
model.pipeline.setVertexState(

0 commit comments

Comments
 (0)