import { mat3, mat4 } from 'gl-matrix';
import * as macro from 'vtk.js/Sources/macros'; import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper'; import vtkProp from 'vtk.js/Sources/Rendering/Core/Prop'; import vtkProperty from 'vtk.js/Sources/Rendering/Core/Property'; import vtkProperty2D from 'vtk.js/Sources/Rendering/Core/Property2D'; import vtkTexture from 'vtk.js/Sources/Rendering/Core/Texture'; import vtkWebGPUBufferManager from 'vtk.js/Sources/Rendering/WebGPU/BufferManager'; import vtkWebGPUShaderCache from 'vtk.js/Sources/Rendering/WebGPU/ShaderCache'; import vtkWebGPUUniformBuffer from 'vtk.js/Sources/Rendering/WebGPU/UniformBuffer'; import vtkWebGPUSimpleMapper from 'vtk.js/Sources/Rendering/WebGPU/SimpleMapper'; import vtkWebGPUTypes from 'vtk.js/Sources/Rendering/WebGPU/Types';
const { BufferUsage, PrimitiveTypes } = vtkWebGPUBufferManager; const { Representation } = vtkProperty; const { ScalarMode } = vtkMapper; const { CoordinateSystem } = vtkProp; const { DisplayLocation } = vtkProperty2D;
const vtkWebGPUPolyDataVS = ` //VTK::Renderer::Dec
//VTK::Color::Dec
//VTK::Normal::Dec
//VTK::TCoord::Dec
//VTK::Select::Dec
//VTK::Mapper::Dec
//VTK::IOStructs::Dec
@vertex fn main( //VTK::IOStructs::Input ) //VTK::IOStructs::Output { var output : vertexOutput;
var vertex: vec4<f32> = vertexBC;
//VTK::Color::Impl
//VTK::Normal::Impl
//VTK::TCoord::Impl
//VTK::Select::Impl
//VTK::Position::Impl
return output; } `;
const vtkWebGPUPolyDataFS = ` struct PBRData { diffuse: vec3<f32>, specular: vec3<f32>, }
// Dot product with the max already in it fn mdot(a: vec3<f32>, b: vec3<f32>) -> f32 { return max(0.0, dot(a, b)); } // Dot product with a max in it that does not allow for negative values // Physically based rendering is accurate as long as normals are accurate, // however this is pretty often not the case. In order to prevent negative // values from ruining light calculations and creating zones of zero light, // this remapping is used, which smoothly clamps the dot product between // zero and one while still maintaining a good amount of accuracy. fn cdot(a: vec3<f32>, b: vec3<f32>) -> f32 { var d: f32 = max(0.0, dot(a, b)); d = pow((d + 1.0) / 2.0, 2.6); return d; }
// Lambertian diffuse model fn lambertDiffuse(base: vec3<f32>, N: vec3<f32>, L: vec3<f32>) -> vec3<f32> { var pi: f32 = 3.14159265359; var NdotL: f32 = mdot(N, L); NdotL = pow(NdotL, 1.5); return (base/pi)*NdotL; }
// Yasuhiro Fujii improvement on the Oren-Nayar model // https://mimosa-pudica.net/improved-oren-nayar.html // p is surface color, o is roughness fn fujiiOrenNayar(p: vec3<f32>, o: f32, N: vec3<f32>, L: vec3<f32>, V: vec3<f32>) -> vec3<f32> { var invpi: f32 = 0.31830988618; // 1/pi
var o2 = o*o; var NdotL: f32 = mdot(N, L); NdotL = pow(NdotL, 1.5); // Less physically accurate, but hides the "seams" between lights better
var NdotV: f32 = mdot(N, V); var LdotV: f32 = mdot(L, V);
var s: f32 = LdotV - NdotL*NdotV; var t: f32 = mix(1.0, max(NdotL, NdotV), step(0.0, s)); // Mix with step is the equivalent of an if statement var A: vec3<f32> = 0.5*(o2 / (o2 + 0.33)) + 0.17*p*(o2 / (o2 + 0.13)); A = invpi*(1 - A); var B: f32 = 0.45*(o2 / (o2 + 0.09)); B = invpi*B;
return p*NdotL*(A + B*(s/t)); }
// Fresnel portion of BRDF (IOR only, simplified) fn schlickFresnelIOR(V: vec3<f32>, N: vec3<f32>, ior: f32, k: f32) -> f32 { var NdotV: f32 = mdot(V, N); var F0: f32 = (pow((ior - 1.0), 2.0) + k*k) / (pow((ior + 1.0), 2.0) + k*k); // This takes into account the roughness, which the other one does not return F0 + (1.0 - F0) * pow((1.0-NdotV), 5.0); }
// Fresnel portion of BRDF (Color ior, better) fn schlickFresnelRGB(V: vec3<f32>, N: vec3<f32>, F0: vec3<f32>) -> vec3<f32> { var NdotV: f32 = mdot(V, N); return F0 + (1.0 - F0) * pow((1-NdotV), 5.0); }
// Normal portion of BRDF // https://learnopengl.com/PBR/Theory // Trowbridge-Reitz GGX functions: normal, halfway, roughness^2 fn trGGX(N: vec3<f32>, H: vec3<f32>, a: f32) -> f32 { var pi: f32 = 3.14159265359;
var a2: f32 = a*a; var NdotH = mdot(N, H); var NdotH2 = NdotH*NdotH; var denom: f32 = NdotH2 * (a2 - 1.0) + 1.0;
return a2 / max((pi*denom*denom), 0.000001); }
// A VERY bad approximation of anisotropy. Real anisotropic calculations require tangent and bitangent fn anisotrophicTrGGX(N: vec3<f32>, H: vec3<f32>, O: vec3<f32>, s: f32, a: f32) -> f32 { var Op: vec3<f32> = (rendererUBO.WCVCNormals * vec4<f32>(normalize(O) * s, 0.)).xyz;
var ggx1: f32 = trGGX(N + Op*s, H, a); var ggx2: f32 = trGGX(N - Op*s, H, a); return (0.5 * ggx1 + 0.5 * ggx2); }
// Geometry portion of BRDF fn schlickGGX(N: vec3<f32>, X: vec3<f32>, k: f32) -> f32 { var NdotX = cdot(N, X); return NdotX / max(0.000001, (NdotX*(1.0-k) + k)); }
fn smithSurfaceRoughness(N: vec3<f32>, V: vec3<f32>, L: vec3<f32>, k: f32) -> f32 { var ggx1: f32 = min(1.0, schlickGGX(N, V, k)); var ggx2: f32 = min(1.0, schlickGGX(N, L, k)); return ggx1*ggx2; }
// BRDF Combination fn cookTorrance(D: f32, F: f32, G: f32, N: vec3<f32>, V: vec3<f32>, L: vec3<f32>) -> f32 { var num: f32 = D*F*G; var denom: f32 = 4*cdot(V, N)*cdot(L, N);
return num / max(denom, 0.000001); }
// Different lighting calculations for different light sources fn calcDirectionalLight(N: vec3<f32>, V: vec3<f32>, ior: f32, roughness: f32, metallic: f32, direction: vec3<f32>, color: vec3<f32>, base: vec3<f32>) -> PBRData { var L: vec3<f32> = normalize(direction); // Light Vector var H: vec3<f32> = normalize(L + V); // Halfway Vector
var alpha = roughness*roughness; var k: f32 = alpha*alpha / 2;
var D: f32 = trGGX(N, H, alpha); // Distribution // var F: f32 = schlickFresnelIOR(V, N, ior, k); // Fresnel var G: f32 = smithSurfaceRoughness(N, V, L, k); // Geometry
var brdf: f32 = cookTorrance(D, 1.0, G, N, V, L); // Fresnel term is replaced with 1 because it is added later var incoming: vec3<f32> = color; var angle: f32 = mdot(L, N); angle = pow(angle, 1.5);
var specular: vec3<f32> = brdf*incoming*angle; // Oren-Nayar gives a clay-like effect when fully rough which some people may not want, so it might be better to give a separate // control property for the diffuse vs specular roughness var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V); // Stores the specular and diffuse separately to allow for finer post processing var out = PBRData(diffuse, specular); return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas) }
// TODO: find some way to reduce the number of arguments going in here fn calcPointLight(N: vec3<f32>, V: vec3<f32>, fragPos: vec3<f32>, ior: f32, roughness: f32, metallic: f32, position: vec3<f32>, color: vec3<f32>, base: vec3<f32>) -> PBRData { var L: vec3<f32> = normalize(position - fragPos); // Light Vector var H: vec3<f32> = normalize(L + V); // Halfway Vector var dist = distance(position, fragPos);
var alpha = roughness*roughness; var k: f32 = alpha*alpha / 2.0; // could also be pow(alpha + 1.0, 2) / 8
var D: f32 = trGGX(N, H, alpha); // Distribution // var F: f32 = schlickFresnelIOR(V, N, ior, k); // Fresnel var G: f32 = smithSurfaceRoughness(N, V, L, k); // Geometry
var brdf: f32 = cookTorrance(D, 1.0, G, N, V, L); var incoming: vec3<f32> = color * (1.0 / (dist*dist)); var angle: f32 = mdot(L, N); angle = pow(angle, 1.5); // Smoothing factor makes it less accurate, but reduces ugly "seams" bewteen light sources
var specular: vec3<f32> = brdf*incoming*angle; var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V);
// Stores the specular and diffuse separately to allow for finer post processing // Could also be done (propably more properly) with a struct var out = PBRData(diffuse, specular); return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas) }
// For a reason unknown to me, spheres dont seem to behave propperly with head-on spot lights fn calcSpotLight(N: vec3<f32>, V: vec3<f32>, fragPos: vec3<f32>, ior: f32, roughness: f32, metallic: f32, position: vec3<f32>, direction: vec3<f32>, cones: vec2<f32>, color: vec3<f32>, base: vec3<f32>) -> PBRData { var L: vec3<f32> = normalize(position - fragPos); var H: vec3<f32> = normalize(L + V); // Halfway Vector var dist = distance(position, fragPos);
var alpha = roughness*roughness; var k: f32 = alpha*alpha / 2.0; // could also be pow(alpha + 1.0, 2) / 8
var D: f32 = trGGX(N, H, alpha); // Distribution // var F: f32 = schlickFresnelIOR(V, N, ior, k); // Fresnel var G: f32 = smithSurfaceRoughness(N, V, L, k); // Geometry
var brdf: f32 = cookTorrance(D, 1.0, G, N, V, L); // Cones.x is the inner phi and cones.y is the outer phi var theta: f32 = mdot(normalize(direction), L); var epsilon: f32 = cones.x - cones.y; var intensity: f32 = (theta - cones.y) / epsilon; intensity = clamp(intensity, 0.0, 1.0); intensity /= dist*dist;
var incoming: vec3<f32> = color * intensity;
var angle: f32 = mdot(L, N); angle = pow(angle, 1.5); // Smoothing factor makes it less accurate, but reduces ugly "seams" bewteen light sources
var specular: vec3<f32> = brdf*incoming*angle; var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V);
// Stores the specular and diffuse separately to allow for finer post processing // Could also be done (propably more properly) with a struct var out = PBRData(diffuse, specular); return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas) }
// Environment mapping stuff // Takes in a vector and converts it to an equivalent coordinate in a rectilinear texture. Should be replaced with cubemaps at some point fn vecToRectCoord(dir: vec3<f32>) -> vec2<f32> { var tau: f32 = 6.28318530718; var pi: f32 = 3.14159265359; var out: vec2<f32> = vec2<f32>(0.0);
out.x = atan2(dir.z, dir.x) / tau; out.x += 0.5;
var phix: f32 = length(vec2(dir.x, dir.z)); out.y = atan2(dir.y, phix) / pi + 0.5;
return out; }
//VTK::Renderer::Dec
//VTK::Color::Dec
//VTK::TCoord::Dec
// optional surface normal declaration //VTK::Normal::Dec
//VTK::Select::Dec
//VTK::RenderEncoder::Dec
//VTK::Mapper::Dec
//VTK::IOStructs::Dec
@fragment fn main( //VTK::IOStructs::Input ) //VTK::IOStructs::Output { var output : fragmentOutput;
// Temporary ambient, diffuse, and opacity var ambientColor: vec4<f32> = mapperUBO.AmbientColor; var diffuseColor: vec4<f32> = mapperUBO.DiffuseColor; var opacity: f32 = mapperUBO.Opacity;
// This should be declared somewhere else var _diffuseMap: vec4<f32> = vec4<f32>(1.0); var _roughnessMap: vec4<f32> = vec4<f32>(1.0); var _metallicMap: vec4<f32> = vec4<f32>(1.0); var _normalMap: vec4<f32> = vec4<f32>(0.0, 0.0, 1.0, 0.0); // normal map was setting off the normal vector detection in fragment var _ambientOcclusionMap: vec4<f32> = vec4<f32>(1.); var _emissionMap: vec4<f32> = vec4<f32>(0.);
//VTK::Color::Impl
//VTK::TCoord::Impl
//VTK::Normal::Impl
var computedColor: vec4<f32> = vec4<f32>(diffuseColor.rgb, 1.0);
//VTK::Light::Impl
//VTK::Select::Impl
if (computedColor.a == 0.0) { discard; };
//VTK::Position::Impl
//VTK::RenderEncoder::Impl
return output; } `;
function isEdges(hash) { return hash.indexOf('edge') >= 0; }
function vtkWebGPUCellArrayMapper(publicAPI, model) { model.classHierarchy.push('vtkWebGPUCellArrayMapper');
publicAPI.buildPass = (prepass) => { if (prepass) { if (model.is2D) { model.WebGPUActor = publicAPI.getFirstAncestorOfType('vtkWebGPUActor2D'); model.forceZValue = true; } else { model.WebGPUActor = publicAPI.getFirstAncestorOfType('vtkWebGPUActor'); model.forceZValue = false; } model.coordinateSystem = model.WebGPUActor.getRenderable().getCoordinateSystem(); model.useRendererMatrix = model.coordinateSystem !== CoordinateSystem.DISPLAY; model.WebGPURenderer = model.WebGPUActor.getFirstAncestorOfType('vtkWebGPURenderer'); model.WebGPURenderWindow = model.WebGPURenderer.getParent(); model.device = model.WebGPURenderWindow.getDevice(); } };
publicAPI.translucentPass = (prepass) => { if (prepass) { publicAPI.prepareToDraw(model.WebGPURenderer.getRenderEncoder()); model.renderEncoder.registerDrawCallback(model.pipeline, publicAPI.draw); } };
publicAPI.opaquePass = (prepass) => { if (prepass) { publicAPI.prepareToDraw(model.WebGPURenderer.getRenderEncoder()); model.renderEncoder.registerDrawCallback(model.pipeline, publicAPI.draw); } };
publicAPI.updateUBO = () => { const actor = model.WebGPUActor.getRenderable(); const ppty = actor.getProperty(); const utime = model.UBO.getSendTime();
if ( publicAPI.getMTime() > utime || ppty.getMTime() > utime || model.renderable.getMTime() > utime ) { const keyMats = model.WebGPUActor.getKeyMatrices(model.WebGPURenderer); model.UBO.setArray('BCWCMatrix', keyMats.bcwc); model.UBO.setArray('BCSCMatrix', keyMats.bcsc); model.UBO.setArray('MCWCNormals', keyMats.normalMatrix);
if (model.is2D) { model.UBO.setValue( 'ZValue', model.WebGPUActor.getRenderable() .getProperty() .getDisplayLocation() === DisplayLocation.FOREGROUND ? 1.0 : 0.0 );
const aColor = ppty.getColorByReference(); model.UBO.setValue('AmbientIntensity', 1.0); model.UBO.setArray('DiffuseColor', [ aColor[0], aColor[1], aColor[2], 1.0, ]); model.UBO.setValue('DiffuseIntensity', 0.0); model.UBO.setValue('SpecularIntensity', 0.0); } else { let aColor = ppty.getAmbientColorByReference(); model.UBO.setValue('AmbientIntensity', ppty.getAmbient()); model.UBO.setArray('AmbientColor', [ aColor[0], aColor[1], aColor[2], 1.0, ]); model.UBO.setValue('DiffuseIntensity', ppty.getDiffuse()); aColor = ppty.getDiffuseColorByReference(); model.UBO.setArray('DiffuseColor', [ aColor[0], aColor[1], aColor[2], 1.0, ]); model.UBO.setValue('Roughness', ppty.getRoughness()); model.UBO.setValue('BaseIOR', ppty.getBaseIOR()); model.UBO.setValue('Metallic', ppty.getMetallic()); model.UBO.setValue('NormalStrength', ppty.getNormalStrength()); model.UBO.setValue('Emission', ppty.getEmission()); model.UBO.setValue('SpecularIntensity', ppty.getSpecular()); aColor = ppty.getSpecularColorByReference(); model.UBO.setArray('SpecularColor', [ aColor[0], aColor[1], aColor[2], 1.0, ]); } const aColor = ppty.getEdgeColorByReference?.(); if (aColor) { model.UBO.setArray('EdgeColor', [aColor[0], aColor[1], aColor[2], 1.0]); } model.UBO.setValue('LineWidth', ppty.getLineWidth()); model.UBO.setValue('Opacity', ppty.getOpacity()); model.UBO.setValue('PropID', model.WebGPUActor.getPropID()); const device = model.WebGPURenderWindow.getDevice(); model.UBO.sendIfNeeded(device); } };
publicAPI.haveWideLines = () => { const actor = model.WebGPUActor.getRenderable(); const representation = actor.getProperty().getRepresentation(); if (actor.getProperty().getLineWidth() <= 1.0) { return false; } if (model.primitiveType === PrimitiveTypes.Verts) { return false; } if ( model.primitiveType === PrimitiveTypes.Triangles || model.primitiveType === PrimitiveTypes.TriangleStrips ) { return representation === Representation.WIREFRAME; } return true; };
publicAPI.replaceShaderPosition = (hash, pipeline, vertexInput) => { const vDesc = pipeline.getShaderDescription('vertex'); vDesc.addBuiltinOutput('vec4<f32>', '@builtin(position) Position'); if (!vDesc.hasOutput('vertexVC')) vDesc.addOutput('vec4<f32>', 'vertexVC'); let code = vDesc.getCode(); if (model.useRendererMatrix) { code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', [ ' var pCoord: vec4<f32> = rendererUBO.SCPCMatrix*mapperUBO.BCSCMatrix*vertexBC;', ' output.vertexVC = rendererUBO.SCVCMatrix * mapperUBO.BCSCMatrix * vec4<f32>(vertexBC.xyz, 1.0);', '//VTK::Position::Impl', ]).result; if (model.forceZValue) { code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', [ 'pCoord = vec4<f32>(pCoord.xyz/pCoord.w, 1.0);', 'pCoord.z = mapperUBO.ZValue;', '//VTK::Position::Impl', ]).result; } } else { code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', [ ' var pCoord: vec4<f32> = mapperUBO.BCSCMatrix*vertexBC;', ' pCoord.x = 2.0* pCoord.x / rendererUBO.viewportSize.x - 1.0;', ' pCoord.y = 2.0* pCoord.y / rendererUBO.viewportSize.y - 1.0;', ' pCoord.z = 0.5 - 0.5 * pCoord.z;', '//VTK::Position::Impl', ]).result; if (model.forceZValue) { code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', [ ' pCoord.z = mapperUBO.ZValue;', '//VTK::Position::Impl', ]).result; } } if (publicAPI.haveWideLines()) { vDesc.addBuiltinInput('u32', '@builtin(instance_index) instanceIndex'); code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', [ ' var tmpPos: vec4<f32> = pCoord;', ' var numSteps: f32 = ceil(mapperUBO.LineWidth - 1.0);', ' var offset: f32 = (mapperUBO.LineWidth - 1.0) * (f32(input.instanceIndex / 2u) - numSteps/2.0) / numSteps;', ' var tmpPos2: vec3<f32> = tmpPos.xyz / tmpPos.w;', ' tmpPos2.x = tmpPos2.x + 2.0 * (f32(input.instanceIndex) % 2.0) * offset / rendererUBO.viewportSize.x;', ' tmpPos2.y = tmpPos2.y + 2.0 * (f32(input.instanceIndex + 1u) % 2.0) * offset / rendererUBO.viewportSize.y;', ' tmpPos2.z = min(1.0, tmpPos2.z + 0.00001);', ' pCoord = vec4<f32>(tmpPos2.xyz * tmpPos.w, tmpPos.w);', '//VTK::Position::Impl', ]).result; } code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', [ ' output.Position = pCoord;', ]).result;
vDesc.setCode(code); }; model.shaderReplacements.set( 'replaceShaderPosition', publicAPI.replaceShaderPosition );
publicAPI.replaceShaderNormal = (hash, pipeline, vertexInput) => { const normalBuffer = vertexInput.getBuffer('normalMC'); const actor = model.WebGPUActor.getRenderable();
if (normalBuffer) { const vDesc = pipeline.getShaderDescription('vertex');
if (!vDesc.hasOutput('normalVC')) { vDesc.addOutput( 'vec3<f32>', 'normalVC', normalBuffer.getArrayInformation()[0].interpolation ); } if (!vDesc.hasOutput('tangentVC')) { vDesc.addOutput( 'vec3<f32>', 'tangentVC', normalBuffer.getArrayInformation()[0].interpolation ); } if (!vDesc.hasOutput('bitangentVC')) { vDesc.addOutput( 'vec3<f32>', 'bitangentVC', normalBuffer.getArrayInformation()[0].interpolation ); }
let code = vDesc.getCode(); code = vtkWebGPUShaderCache.substitute(code, '//VTK::Normal::Impl', [ ' output.normalVC = normalize((rendererUBO.WCVCNormals * mapperUBO.MCWCNormals * normalMC).xyz);', ' var c1: vec3<f32> = cross(output.normalVC, vec3<f32>(0, 0, 1));', ' var c2: vec3<f32> = cross(output.normalVC, vec3<f32>(0, 1, 0));', ' var tangent: vec3<f32> = mix(c1, c2, distance(c1, c2));', ' output.tangentVC = normalize(tangent);', ' output.bitangentVC = normalize(cross(output.normalVC, tangent));', ]).result;
vDesc.setCode(code);
const fDesc = pipeline.getShaderDescription('fragment'); code = fDesc.getCode();
if (actor.getProperty().getNormalTexture()) { code = vtkWebGPUShaderCache.substitute(code, '//VTK::Normal::Impl', [ ' var normal: vec3<f32> = input.normalVC;', ' if (!input.frontFacing) { normal = -normal; }', ' var tangent: vec3<f32> = input.tangentVC;', ' var bitangent: vec3<f32> = input.bitangentVC;', ' var TCVCMatrix: mat3x3<f32> = mat3x3<f32>(', ' tangent.x, bitangent.x, normal.x,', ' tangent.y, bitangent.y, normal.y,', ' tangent.z, bitangent.z, normal.z,', ' );', ' var mappedNormal: vec3<f32> = TCVCMatrix * (_normalMap.xyz * 2 - 1);', ' normal = mix(normal, mappedNormal, mapperUBO.NormalStrength);', ' normal = normalize(normal);', ]).result; } else { code = vtkWebGPUShaderCache.substitute(code, '//VTK::Normal::Impl', [ ' var normal: vec3<f32> = input.normalVC;', ' if (!input.frontFacing) { normal = -normal; }', ' normal = normalize(normal);', ]).result; } fDesc.setCode(code); } }; model.shaderReplacements.set( 'replaceShaderNormal', publicAPI.replaceShaderNormal );
publicAPI.replaceShaderLight = (hash, pipeline, vertexInput) => { if (hash.includes('sel')) return; const vDesc = pipeline.getShaderDescription('vertex'); if (!vDesc.hasOutput('vertexVC')) vDesc.addOutput('vec4<f32>', 'vertexVC');
const renderer = model.WebGPURenderer.getRenderable();
const fDesc = pipeline.getShaderDescription('fragment'); let code = fDesc.getCode();
if ( code.includes('var normal:') && model.useRendererMatrix && !isEdges(hash) && !model.is2D && !hash.includes('sel') ) { const lightingCode = [ ' var pi: f32 = 3.14159265359;', ' var fragPos: vec3<f32> = vec3<f32>(input.vertexVC.xyz);', ' var V: vec3<f32> = mix(normalize(-fragPos), vec3<f32>(0, 0, 1), f32(rendererUBO.cameraParallel)); // View Vector', ' var baseColor: vec3<f32> = _diffuseMap.rgb * diffuseColor.rgb;', ' var roughness: f32 = max(0.000001, mapperUBO.Roughness * _roughnessMap.r);', ' var metallic: f32 = mapperUBO.Metallic * _metallicMap.r;', ' var alpha: f32 = roughness*roughness;', ' var ior: f32 = mapperUBO.BaseIOR;', ' var k: f32 = alpha*alpha / 2;', ' var diffuse: vec3<f32> = vec3<f32>(0.);', ' var specular: vec3<f32> = vec3<f32>(0.);', ' var emission: vec3<f32> = _emissionMap.rgb * mapperUBO.Emission;', ' {', ' var i: i32 = 0;', ' loop {', ' if !(i < rendererUBO.LightCount) { break; }', ' switch (i32(rendererLightSSBO.values[i].LightData.x)) {', ' // Point Light', ' case 0 {', ' var color: vec3<f32> = rendererLightSSBO.values[i].LightColor.rgb * rendererLightSSBO.values[i].LightColor.w;', ' var pos: vec3<f32> = (rendererLightSSBO.values[i].LightPos).xyz;', ' var calculated: PBRData = calcPointLight(normal, V, fragPos, ior, roughness, metallic, pos, color, baseColor);', ' diffuse += max(vec3<f32>(0), calculated.diffuse);', ' specular += max(vec3<f32>(0), calculated.specular);', ' }', ' // Directional light', ' case 1 {', ' var dir: vec3<f32> = (rendererUBO.WCVCNormals * vec4<f32>(normalize(rendererLightSSBO.values[i].LightDir.xyz), 0.)).xyz;', ' dir = normalize(dir);', ' var color: vec3<f32> = rendererLightSSBO.values[i].LightColor.rgb * rendererLightSSBO.values[i].LightColor.w;', ' var calculated: PBRData = calcDirectionalLight(normal, V, ior, roughness, metallic, dir, color, baseColor); // diffuseColor.rgb needs to be fixed with a more dynamic diffuse color', ' diffuse += max(vec3<f32>(0), calculated.diffuse);', ' specular += max(vec3<f32>(0), calculated.specular);', ' }', ' // Spot Light', ' case 2 {', ' var color: vec3<f32> = rendererLightSSBO.values[i].LightColor.rgb * rendererLightSSBO.values[i].LightColor.w;', ' var pos: vec3<f32> = (rendererLightSSBO.values[i].LightPos).xyz;', ' var dir: vec3<f32> = (rendererUBO.WCVCNormals * vec4<f32>(normalize(rendererLightSSBO.values[i].LightDir.xyz), 0.)).xyz;', ' dir = normalize(dir);', ' var cones: vec2<f32> = vec2<f32>(rendererLightSSBO.values[i].LightData.y, rendererLightSSBO.values[i].LightData.z);', ' var calculated: PBRData = calcSpotLight(normal, V, fragPos, ior, roughness, metallic, pos, dir, cones, color, baseColor);', ' diffuse += max(vec3<f32>(0), calculated.diffuse);', ' specular += max(vec3<f32>(0), calculated.specular);', ' }', ' default { continue; }', ' }', ' continuing { i++; }', ' }', ' }', ' var fresnel: f32 = schlickFresnelIOR(V, normal, ior, k); // Fresnel', ' fresnel = min(1.0, fresnel);', ' // This could be controlled with its own variable (that isnt base color) for better artistic control', ' var fresnelMetallic: vec3<f32> = schlickFresnelRGB(V, normal, baseColor); // Fresnel for metal, takes color into account', ' var kS: vec3<f32> = mix(vec3<f32>(fresnel), fresnelMetallic, metallic);', ' kS = min(vec3<f32>(1.0), kS);', ' var kD: vec3<f32> = (1.0 - kS) * (1.0 - metallic);', ' var PBR: vec3<f32> = mapperUBO.DiffuseIntensity*kD*diffuse + kS*specular;', ' PBR += emission;', ' computedColor = vec4<f32>(PBR, mapperUBO.Opacity);', ]; if (renderer.getEnvironmentTexture()?.getImageLoaded()) { lightingCode.push( ' // To get diffuse IBL, the texture is sampled with normals in worldspace', ' var diffuseIBLCoords: vec3<f32> = (transpose(rendererUBO.WCVCNormals) * vec4<f32>(normal, 1.)).xyz;', ' var diffuseCoords: vec2<f32> = vecToRectCoord(diffuseIBLCoords);', ' // To get specular IBL, the texture is sampled as the worldspace reflection between the normal and view vectors', ' // Reflections are first calculated in viewspace, then converted to worldspace to sample the environment', ' var VreflN: vec3<f32> = normalize(reflect(-V, normal));', ' var reflectionIBLCoords = (transpose(rendererUBO.WCVCNormals) * vec4<f32>(VreflN, 1.)).xyz;', ' var specularCoords: vec2<f32> = vecToRectCoord(reflectionIBLCoords);', ' var diffuseIBL = textureSampleLevel(EnvironmentTexture, EnvironmentTextureSampler, diffuseCoords, rendererUBO.MaxEnvironmentMipLevel);', ' var level = roughness * rendererUBO.MaxEnvironmentMipLevel;', ' var specularIBL = textureSampleLevel(EnvironmentTexture, EnvironmentTextureSampler, specularCoords, level);', ' var specularIBLContribution: vec3<f32> = specularIBL.rgb*rendererUBO.BackgroundSpecularStrength;', ' computedColor += vec4<f32>(specularIBLContribution*kS, 0);', ' var diffuseIBLContribution: vec3<f32> = diffuseIBL.rgb*rendererUBO.BackgroundDiffuseStrength;', ' diffuseIBLContribution *= baseColor * _ambientOcclusionMap.rgb;', ' computedColor += vec4<f32>(diffuseIBLContribution*kD, 0);' ); } code = vtkWebGPUShaderCache.substitute( code, '//VTK::Light::Impl', lightingCode ).result; fDesc.setCode(code); } else { code = vtkWebGPUShaderCache.substitute(code, '//VTK::Light::Impl', [ ' var diffuse: vec3<f32> = diffuseColor.rgb;', ' var specular: vec3<f32> = mapperUBO.SpecularColor.rgb * mapperUBO.SpecularColor.a;', ' computedColor = vec4<f32>(diffuse * _diffuseMap.rgb, mapperUBO.Opacity);', ]).result; fDesc.setCode(code); } }; model.shaderReplacements.set( 'replaceShaderLight', publicAPI.replaceShaderLight );
publicAPI.replaceShaderColor = (hash, pipeline, vertexInput) => { if (isEdges(hash)) { const fDesc = pipeline.getShaderDescription('fragment'); let code = fDesc.getCode(); code = vtkWebGPUShaderCache.substitute(code, '//VTK::Color::Impl', [ 'ambientColor = mapperUBO.EdgeColor;', 'diffuseColor = mapperUBO.EdgeColor;', ]).result; fDesc.setCode(code); return; }
const colorBuffer = vertexInput.getBuffer('colorVI'); if (!colorBuffer) return;
const vDesc = pipeline.getShaderDescription('vertex'); vDesc.addOutput( 'vec4<f32>', 'color', colorBuffer.getArrayInformation()[0].interpolation ); let code = vDesc.getCode(); code = vtkWebGPUShaderCache.substitute(code, '//VTK::Color::Impl', [ ' output.color = colorVI;', ]).result; vDesc.setCode(code);
const fDesc = pipeline.getShaderDescription('fragment'); code = fDesc.getCode(); code = vtkWebGPUShaderCache.substitute(code, '//VTK::Color::Impl', [ 'ambientColor = input.color;', 'diffuseColor = input.color;', 'opacity = mapperUBO.Opacity * input.color.a;', ]).result; fDesc.setCode(code); }; model.shaderReplacements.set( 'replaceShaderColor', publicAPI.replaceShaderColor );
publicAPI.replaceShaderTCoord = (hash, pipeline, vertexInput) => { if (!vertexInput.hasAttribute('tcoord')) return;
const vDesc = pipeline.getShaderDescription('vertex'); const tcoords = vertexInput.getBuffer('tcoord'); const numComp = vtkWebGPUTypes.getNumberOfComponentsFromBufferFormat( tcoords.getArrayInformation()[0].format ); let code = vDesc.getCode(); vDesc.addOutput(`vec${numComp}<f32>`, 'tcoordVS'); code = vtkWebGPUShaderCache.substitute(code, '//VTK::TCoord::Impl', [ ' output.tcoordVS = tcoord;', ]).result; vDesc.setCode(code);
const fDesc = pipeline.getShaderDescription('fragment'); code = fDesc.getCode();
const actor = model.WebGPUActor.getRenderable();
const checkDims = (texture) => { if (!texture) return false; const dims = texture.getDimensionality(); return dims === numComp; };
const usedTextures = [];
if ( actor.getProperty().getDiffuseTexture?.()?.getImageLoaded() || actor.getTextures()[0] || model.colorTexture ) { if ( checkDims(actor.getProperty().getDiffuseTexture?.()) || checkDims(actor.getTextures()[0]) || checkDims(model.colorTexture) ) { usedTextures.push( '_diffuseMap = textureSample(DiffuseTexture, DiffuseTextureSampler, input.tcoordVS);' ); } } if (actor.getProperty().getRoughnessTexture?.()?.getImageLoaded()) { if (checkDims(actor.getProperty().getRoughnessTexture())) { usedTextures.push( '_roughnessMap = textureSample(RoughnessTexture, RoughnessTextureSampler, input.tcoordVS);' ); } } if (actor.getProperty().getMetallicTexture?.()?.getImageLoaded()) { if (checkDims(actor.getProperty().getMetallicTexture())) { usedTextures.push( '_metallicMap = textureSample(MetallicTexture, MetallicTextureSampler, input.tcoordVS);' ); } } if (actor.getProperty().getNormalTexture?.()?.getImageLoaded()) { if (checkDims(actor.getProperty().getNormalTexture())) { usedTextures.push( '_normalMap = textureSample(NormalTexture, NormalTextureSampler, input.tcoordVS);' ); } } if (actor.getProperty().getAmbientOcclusionTexture?.()?.getImageLoaded()) { if (checkDims(actor.getProperty().getAmbientOcclusionTexture())) { usedTextures.push( '_ambientOcclusionMap = textureSample(AmbientOcclusionTexture, AmbientOcclusionTextureSampler, input.tcoordVS);' ); } } if (actor.getProperty().getEmissionTexture?.()?.getImageLoaded()) { if (checkDims(actor.getProperty().getEmissionTexture())) { usedTextures.push( '_emissionMap = textureSample(EmissionTexture, EmissionTextureSampler, input.tcoordVS);' ); } }
code = vtkWebGPUShaderCache.substitute( code, '//VTK::TCoord::Impl', usedTextures ).result; fDesc.setCode(code); }; model.shaderReplacements.set( 'replaceShaderTCoord', publicAPI.replaceShaderTCoord );
publicAPI.replaceShaderSelect = (hash, pipeline, vertexInput) => { if (hash.includes('sel')) { const fDesc = pipeline.getShaderDescription('fragment'); let code = fDesc.getCode(); code = vtkWebGPUShaderCache.substitute(code, '//VTK::Select::Impl', [ ' var compositeID: u32 = 0u;', ]).result; fDesc.setCode(code); } }; model.shaderReplacements.set( 'replaceShaderSelect', publicAPI.replaceShaderSelect );
publicAPI.getUsage = (rep, i) => { if (rep === Representation.POINTS || i === PrimitiveTypes.Points) { return BufferUsage.Verts; }
if (i === PrimitiveTypes.Lines) { return BufferUsage.Lines; }
if (rep === Representation.WIREFRAME) { if (i === PrimitiveTypes.Triangles) { return BufferUsage.LinesFromTriangles; } return BufferUsage.LinesFromStrips; }
if (i === PrimitiveTypes.Triangles) { return BufferUsage.Triangles; }
if (i === PrimitiveTypes.TriangleStrips) { return BufferUsage.Strips; }
if (i === PrimitiveTypes.TriangleEdges) { return BufferUsage.LinesFromTriangles; }
return BufferUsage.LinesFromStrips; };
publicAPI.getHashFromUsage = (usage) => `pt${usage}`;
publicAPI.getTopologyFromUsage = (usage) => { switch (usage) { case BufferUsage.Triangles: return 'triangle-list'; case BufferUsage.Verts: return 'point-list'; case BufferUsage.Lines: default: return 'line-list'; } };
publicAPI.buildVertexInput = () => { const pd = model.currentInput; const cells = model.cellArray; const primType = model.primitiveType;
const actor = model.WebGPUActor.getRenderable(); let representation = actor.getProperty().getRepresentation(); const device = model.WebGPURenderWindow.getDevice(); let edges = false; if (primType === PrimitiveTypes.TriangleEdges) { edges = true; representation = Representation.WIREFRAME; }
const vertexInput = model.vertexInput; const points = pd.getPoints(); let indexBuffer;
if (cells) { const buffRequest = { hash: `R${representation}P${primType}${cells.getMTime()}`, usage: BufferUsage.Index, cells, numberOfPoints: points.getNumberOfPoints(), primitiveType: primType, representation, }; indexBuffer = device.getBufferManager().getBuffer(buffRequest); vertexInput.setIndexBuffer(indexBuffer); } else { vertexInput.setIndexBuffer(null); }
if (points) { const shift = model.WebGPUActor.getBufferShift(model.WebGPURenderer); const buffRequest = { hash: `${points.getMTime()}I${indexBuffer.getMTime()}${shift.join()}float32x4`, usage: BufferUsage.PointArray, format: 'float32x4', dataArray: points, indexBuffer, shift, packExtra: true, }; const buff = device.getBufferManager().getBuffer(buffRequest); vertexInput.addBuffer(buff, ['vertexBC']); } else { vertexInput.removeBufferIfPresent('vertexBC'); }
const usage = publicAPI.getUsage(representation, primType); model._usesCellNormals = false; if ( !model.is2D && (usage === BufferUsage.Triangles || usage === BufferUsage.Strips) ) { const normals = pd.getPointData().getNormals(); const buffRequest = { format: 'snorm8x4', indexBuffer, packExtra: true, shift: 0, scale: 127, }; if (normals) { buffRequest.hash = `${normals.getMTime()}I${indexBuffer.getMTime()}snorm8x4`; buffRequest.dataArray = normals; buffRequest.usage = BufferUsage.PointArray; const buff = device.getBufferManager().getBuffer(buffRequest); vertexInput.addBuffer(buff, ['normalMC']); } else if (primType === PrimitiveTypes.Triangles) { model._usesCellNormals = true; buffRequest.hash = `PFN${points.getMTime()}I${indexBuffer.getMTime()}snorm8x4`; buffRequest.dataArray = points; buffRequest.cells = cells; buffRequest.usage = BufferUsage.NormalsFromPoints; const buff = device.getBufferManager().getBuffer(buffRequest); vertexInput.addBuffer(buff, ['normalMC']); } else { vertexInput.removeBufferIfPresent('normalMC'); } } else { vertexInput.removeBufferIfPresent('normalMC'); }
let haveColors = false; if (model.renderable.getScalarVisibility()) { const c = model.renderable.getColorMapColors(); if (c && !edges) { const scalarMode = model.renderable.getScalarMode(); let haveCellScalars = false; if ( (scalarMode === ScalarMode.USE_CELL_DATA || scalarMode === ScalarMode.USE_CELL_FIELD_DATA || scalarMode === ScalarMode.USE_FIELD_DATA || !pd.getPointData().getScalars()) && scalarMode !== ScalarMode.USE_POINT_FIELD_DATA && c ) { haveCellScalars = true; } const buffRequest = { usage: BufferUsage.PointArray, format: 'unorm8x4', hash: `${haveCellScalars}${c.getMTime()}I${indexBuffer.getMTime()}unorm8x4`, dataArray: c, indexBuffer, cellData: haveCellScalars, cellOffset: 0, }; const buff = device.getBufferManager().getBuffer(buffRequest); vertexInput.addBuffer(buff, ['colorVI']); haveColors = true; } } if (!haveColors) { vertexInput.removeBufferIfPresent('colorVI'); }
let tcoords = null; if ( model.renderable.getInterpolateScalarsBeforeMapping?.() && model.renderable.getColorCoordinates() ) { tcoords = model.renderable.getColorCoordinates(); } else { tcoords = pd.getPointData().getTCoords(); } if (tcoords && !edges) { const buff = device .getBufferManager() .getBufferForPointArray(tcoords, vertexInput.getIndexBuffer()); vertexInput.addBuffer(buff, ['tcoord']); } else { vertexInput.removeBufferIfPresent('tcoord'); } };
publicAPI.updateTextures = () => { const usedTextures = []; const newTextures = [];
const idata = model.renderable.getColorTextureMap?.(); if (idata) { if (!model.colorTexture) { model.colorTexture = vtkTexture.newInstance({ label: 'polyDataColor' }); } model.colorTexture.setInputData(idata); newTextures.push(['Diffuse', model.colorTexture]); }
const actor = model.WebGPUActor.getRenderable(); const renderer = model.WebGPURenderer.getRenderable();
const textures = [];
if (actor.getProperty().getDiffuseTexture?.()) { const pair = ['Diffuse', actor.getProperty().getDiffuseTexture()]; textures.push(pair); } if (actor.getTextures()[0]) { const pair = ['Diffuse', actor.getTextures()[0]]; textures.push(pair); } if (model.colorTexture) { const pair = ['Diffuse', model.colorTexture]; textures.push(pair); } if (actor.getProperty().getRoughnessTexture?.()) { const pair = ['Roughness', actor.getProperty().getRoughnessTexture()]; textures.push(pair); } if (actor.getProperty().getMetallicTexture?.()) { const pair = ['Metallic', actor.getProperty().getMetallicTexture()]; textures.push(pair); } if (actor.getProperty().getNormalTexture?.()) { const pair = ['Normal', actor.getProperty().getNormalTexture()]; textures.push(pair); } if (actor.getProperty().getAmbientOcclusionTexture?.()) { const pair = [ 'AmbientOcclusion', actor.getProperty().getAmbientOcclusionTexture(), ]; textures.push(pair); } if (actor.getProperty().getEmissionTexture?.()) { const pair = ['Emission', actor.getProperty().getEmissionTexture()]; textures.push(pair); } if (renderer.getEnvironmentTexture?.()) { const pair = ['Environment', renderer.getEnvironmentTexture()]; textures.push(pair); }
for (let i = 0; i < textures.length; i++) { if ( textures[i][1].getInputData() || textures[i][1].getJsImageData() || textures[i][1].getCanvas() ) { newTextures.push(textures[i]); } if (textures[i][1].getImage() && textures[i][1].getImageLoaded()) { newTextures.push(textures[i]); } }
for (let i = 0; i < newTextures.length; i++) { const srcTexture = newTextures[i][1]; const textureName = newTextures[i][0]; const newTex = model.device .getTextureManager() .getTextureForVTKTexture(srcTexture); if (newTex.getReady()) { let found = false; for (let t = 0; t < model.textures.length; t++) { if (model.textures[t] === newTex) { found = true; usedTextures[t] = true; } } if (!found) { usedTextures[model.textures.length] = true; const tview = newTex.createView(`${textureName}Texture`); model.textures.push(newTex); model.textureViews.push(tview); const interpolate = srcTexture.getInterpolate() ? 'linear' : 'nearest'; let addressMode = null; if ( !addressMode && srcTexture.getEdgeClamp() && srcTexture.getRepeat() ) addressMode = 'mirror-repeat'; if (!addressMode && srcTexture.getEdgeClamp()) addressMode = 'clamp-to-edge'; if (!addressMode && srcTexture.getRepeat()) addressMode = 'repeat';
if (textureName !== 'Environment') { tview.addSampler(model.device, { addressModeU: addressMode, addressModeV: addressMode, addressModeW: addressMode, minFilter: interpolate, magFilter: interpolate, }); } else { tview.addSampler(model.device, { addressModeU: 'repeat', addressModeV: 'clamp-to-edge', addressModeW: 'repeat', minFilter: interpolate, magFilter: interpolate, mipmapFilter: 'linear', }); } } } }
for (let i = model.textures.length - 1; i >= 0; i--) { if (!usedTextures[i]) { model.textures.splice(i, 1); model.textureViews.splice(i, 1); } } };
publicAPI.computePipelineHash = () => { let pipelineHash = `pd${model.useRendererMatrix ? 'r' : ''}${ model.forceZValue ? 'z' : '' }`;
if ( model.primitiveType === PrimitiveTypes.TriangleEdges || model.primitiveType === PrimitiveTypes.TriangleStripEdges ) { pipelineHash += 'edge'; } else { if (model.vertexInput.hasAttribute(`normalMC`)) { pipelineHash += `n`; } if (model.vertexInput.hasAttribute(`colorVI`)) { pipelineHash += `c`; } if (model.vertexInput.hasAttribute(`tcoord`)) { const tcoords = model.vertexInput.getBuffer('tcoord'); const numComp = vtkWebGPUTypes.getNumberOfComponentsFromBufferFormat( tcoords.getArrayInformation()[0].format ); pipelineHash += `t${numComp}`; } if (model.textures.length) { pipelineHash += `tx${model.textures.length}`; } }
if (model._usesCellNormals) { pipelineHash += `cn`; }
if (model.SSBO) { pipelineHash += `ssbo`; }
const uhash = publicAPI.getHashFromUsage(model.usage); pipelineHash += uhash; pipelineHash += model.renderEncoder.getPipelineHash();
model.pipelineHash = pipelineHash; };
publicAPI.updateBuffers = () => { if ( model.primitiveType !== PrimitiveTypes.TriangleEdges && model.primitiveType !== PrimitiveTypes.TriangleStripEdges ) { publicAPI.updateTextures(); }
const actor = model.WebGPUActor.getRenderable(); const rep = actor.getProperty().getRepresentation();
model.usage = publicAPI.getUsage(rep, model.primitiveType); publicAPI.buildVertexInput();
const vbo = model.vertexInput.getBuffer('vertexBC'); publicAPI.setNumberOfVertices( vbo.getSizeInBytes() / vbo.getStrideInBytes() ); publicAPI.setTopology(publicAPI.getTopologyFromUsage(model.usage)); publicAPI.updateUBO(); if (publicAPI.haveWideLines()) { const ppty = actor.getProperty(); publicAPI.setNumberOfInstances(Math.ceil(ppty.getLineWidth() * 2.0)); } else { publicAPI.setNumberOfInstances(1); } }; }
const DEFAULT_VALUES = { is2D: false, cellArray: null, currentInput: null, cellOffset: 0, primitiveType: 0, colorTexture: null, renderEncoder: null, textures: null, };
export function extend(publicAPI, model, initiaLalues = {}) { Object.assign(model, DEFAULT_VALUES, initiaLalues);
vtkWebGPUSimpleMapper.extend(publicAPI, model, initiaLalues);
model.fragmentShaderTemplate = vtkWebGPUPolyDataFS; model.vertexShaderTemplate = vtkWebGPUPolyDataVS;
model._tmpMat3 = mat3.identity(new Float64Array(9)); model._tmpMat4 = mat4.identity(new Float64Array(16));
model.UBO = vtkWebGPUUniformBuffer.newInstance({ label: 'mapperUBO' }); model.UBO.addEntry('BCWCMatrix', 'mat4x4<f32>'); model.UBO.addEntry('BCSCMatrix', 'mat4x4<f32>'); model.UBO.addEntry('MCWCNormals', 'mat4x4<f32>'); model.UBO.addEntry('AmbientColor', 'vec4<f32>'); model.UBO.addEntry('DiffuseColor', 'vec4<f32>'); model.UBO.addEntry('EdgeColor', 'vec4<f32>'); model.UBO.addEntry('SpecularColor', 'vec4<f32>'); model.UBO.addEntry('AmbientIntensity', 'f32'); model.UBO.addEntry('DiffuseIntensity', 'f32'); model.UBO.addEntry('Roughness', 'f32'); model.UBO.addEntry('Metallic', 'f32'); model.UBO.addEntry('Ambient', 'f32'); model.UBO.addEntry('Normal', 'f32'); model.UBO.addEntry('Emission', 'f32'); model.UBO.addEntry('NormalStrength', 'f32'); model.UBO.addEntry('BaseIOR', 'f32'); model.UBO.addEntry('SpecularIntensity', 'f32'); model.UBO.addEntry('LineWidth', 'f32'); model.UBO.addEntry('Opacity', 'f32'); model.UBO.addEntry('ZValue', 'f32'); model.UBO.addEntry('PropID', 'u32'); model.UBO.addEntry('ClipNear', 'f32'); model.UBO.addEntry('ClipFar', 'f32'); model.UBO.addEntry('Time', 'u32');
macro.setGet(publicAPI, model, [ 'cellArray', 'currentInput', 'cellOffset', 'is2D', 'primitiveType', 'renderEncoder', ]);
model.textures = [];
vtkWebGPUCellArrayMapper(publicAPI, model); }
export const newInstance = macro.newInstance( extend, 'vtkWebGPUCellArrayMapper' );
export default { newInstance, extend };
|