|
| 1 | +import { mat4 } from 'gl-matrix'; |
| 2 | + |
| 3 | +import macro from 'vtk.js/Sources/macros'; |
| 4 | +import { areEquals } from 'vtk.js/Sources/Common/Core/Math'; |
| 5 | +import vtkWebGPUFullScreenQuad from 'vtk.js/Sources/Rendering/WebGPU/FullScreenQuad'; |
| 6 | +import vtkWebGPUUniformBuffer from 'vtk.js/Sources/Rendering/WebGPU/UniformBuffer'; |
| 7 | + |
| 8 | +const solidFragTemplate = ` |
| 9 | +//VTK::Renderer::Dec |
| 10 | +
|
| 11 | +//VTK::Mapper::Dec |
| 12 | +
|
| 13 | +//VTK::TCoord::Dec |
| 14 | +
|
| 15 | +//VTK::RenderEncoder::Dec |
| 16 | +
|
| 17 | +//VTK::IOStructs::Dec |
| 18 | +
|
| 19 | +@fragment |
| 20 | +fn main( |
| 21 | +//VTK::IOStructs::Input |
| 22 | +) |
| 23 | +//VTK::IOStructs::Output |
| 24 | +{ |
| 25 | + var output: fragmentOutput; |
| 26 | +
|
| 27 | + var computedColor: vec4<f32> = mapperUBO.BackgroundColor; |
| 28 | +
|
| 29 | + //VTK::RenderEncoder::Impl |
| 30 | + return output; |
| 31 | +} |
| 32 | +`; |
| 33 | + |
| 34 | +const gradientFragTemplate = ` |
| 35 | +//VTK::Renderer::Dec |
| 36 | +
|
| 37 | +//VTK::Mapper::Dec |
| 38 | +
|
| 39 | +//VTK::TCoord::Dec |
| 40 | +
|
| 41 | +//VTK::RenderEncoder::Dec |
| 42 | +
|
| 43 | +//VTK::IOStructs::Dec |
| 44 | +
|
| 45 | +@fragment |
| 46 | +fn main( |
| 47 | +//VTK::IOStructs::Input |
| 48 | +) |
| 49 | +//VTK::IOStructs::Output |
| 50 | +{ |
| 51 | + var output: fragmentOutput; |
| 52 | +
|
| 53 | + let t = clamp(input.tcoordVS.y, 0.0, 1.0); |
| 54 | + var computedColor: vec4<f32> = mix(mapperUBO.BackgroundColor2, mapperUBO.BackgroundColor, t); |
| 55 | +
|
| 56 | + //VTK::RenderEncoder::Impl |
| 57 | + return output; |
| 58 | +} |
| 59 | +`; |
| 60 | + |
| 61 | +const textureFragTemplate = ` |
| 62 | +//VTK::Renderer::Dec |
| 63 | +
|
| 64 | +//VTK::Mapper::Dec |
| 65 | +
|
| 66 | +//VTK::TCoord::Dec |
| 67 | +
|
| 68 | +//VTK::RenderEncoder::Dec |
| 69 | +
|
| 70 | +//VTK::IOStructs::Dec |
| 71 | +
|
| 72 | +@fragment |
| 73 | +fn main( |
| 74 | +//VTK::IOStructs::Input |
| 75 | +) |
| 76 | +//VTK::IOStructs::Output |
| 77 | +{ |
| 78 | + var output: fragmentOutput; |
| 79 | +
|
| 80 | + var computedColor: vec4<f32> = textureSampleLevel(BackgroundTexture, BackgroundTextureSampler, input.tcoordVS, 0.0); |
| 81 | +
|
| 82 | + //VTK::RenderEncoder::Impl |
| 83 | + return output; |
| 84 | +} |
| 85 | +`; |
| 86 | + |
| 87 | +const environmentFragTemplate = ` |
| 88 | +fn vecToRectCoord(dir: vec3<f32>) -> vec2<f32> { |
| 89 | + var tau: f32 = 6.28318530718; |
| 90 | + var pi: f32 = 3.14159265359; |
| 91 | + var out: vec2<f32> = vec2<f32>(0.0); |
| 92 | +
|
| 93 | + out.x = atan2(dir.z, dir.x) / tau; |
| 94 | + out.x += 0.5; |
| 95 | +
|
| 96 | + var phix: f32 = length(vec2(dir.x, dir.z)); |
| 97 | + out.y = atan2(dir.y, phix) / pi + 0.5; |
| 98 | +
|
| 99 | + return out; |
| 100 | +} |
| 101 | +
|
| 102 | +//VTK::Renderer::Dec |
| 103 | +
|
| 104 | +//VTK::Mapper::Dec |
| 105 | +
|
| 106 | +//VTK::TCoord::Dec |
| 107 | +
|
| 108 | +//VTK::RenderEncoder::Dec |
| 109 | +
|
| 110 | +//VTK::IOStructs::Dec |
| 111 | +
|
| 112 | +@fragment |
| 113 | +fn main( |
| 114 | +//VTK::IOStructs::Input |
| 115 | +) |
| 116 | +//VTK::IOStructs::Output |
| 117 | +{ |
| 118 | + var output: fragmentOutput; |
| 119 | +
|
| 120 | + var tcoord: vec4<f32> = vec4<f32>(input.vertexVC.xy, -1, 1); |
| 121 | + var V: vec4<f32> = normalize(mapperUBO.FSQMatrix * tcoord); |
| 122 | + var background = textureSampleLevel(EnvironmentTexture, EnvironmentTextureSampler, vecToRectCoord(V.xyz), 0.0); |
| 123 | + var computedColor: vec4<f32> = vec4<f32>(background.rgb, 1.0); |
| 124 | +
|
| 125 | + //VTK::RenderEncoder::Impl |
| 126 | + return output; |
| 127 | +} |
| 128 | +`; |
| 129 | + |
| 130 | +const _fsqMat4 = new Float64Array(16); |
| 131 | +const _tNormalMat4 = new Float64Array(16); |
| 132 | + |
| 133 | +function vtkWebGPUBackground(publicAPI, model) { |
| 134 | + model.classHierarchy.push('vtkWebGPUBackground'); |
| 135 | + |
| 136 | + publicAPI.getMode = (renderer) => { |
| 137 | + if ( |
| 138 | + renderer.getTexturedBackground?.() && |
| 139 | + renderer.getBackgroundTexture?.().getImageLoaded?.() |
| 140 | + ) { |
| 141 | + return { |
| 142 | + mode: 'texture', |
| 143 | + texture: renderer.getBackgroundTexture(), |
| 144 | + textureName: 'BackgroundTexture', |
| 145 | + pipelineHash: 'backgroundTexture', |
| 146 | + }; |
| 147 | + } |
| 148 | + |
| 149 | + if ( |
| 150 | + renderer.getUseEnvironmentTextureAsBackground?.() && |
| 151 | + renderer.getEnvironmentTexture?.().getImageLoaded?.() |
| 152 | + ) { |
| 153 | + return { |
| 154 | + mode: 'environment', |
| 155 | + texture: renderer.getEnvironmentTexture(), |
| 156 | + textureName: 'EnvironmentTexture', |
| 157 | + pipelineHash: 'backgroundEnvironment', |
| 158 | + }; |
| 159 | + } |
| 160 | + |
| 161 | + const isGradientBackground = renderer.getGradientBackground?.(); |
| 162 | + const background = renderer.getBackgroundByReference?.() ?? [0, 0, 0, 1]; |
| 163 | + const background2 = renderer.getBackground2ByReference?.() ?? [0, 0, 0]; |
| 164 | + const background2rgba = [...background2, 1.0]; |
| 165 | + if (isGradientBackground && !areEquals(background, background2rgba)) { |
| 166 | + return { |
| 167 | + mode: 'gradient', |
| 168 | + texture: null, |
| 169 | + textureName: null, |
| 170 | + pipelineHash: 'backgroundGradient', |
| 171 | + }; |
| 172 | + } |
| 173 | + |
| 174 | + return { |
| 175 | + mode: 'solid', |
| 176 | + texture: null, |
| 177 | + textureName: null, |
| 178 | + pipelineHash: 'backgroundSolid', |
| 179 | + }; |
| 180 | + }; |
| 181 | + |
| 182 | + publicAPI.ensureQuad = (device) => { |
| 183 | + if (model.quad) { |
| 184 | + return; |
| 185 | + } |
| 186 | + |
| 187 | + model.quad = vtkWebGPUFullScreenQuad.newInstance(); |
| 188 | + model.quad.setDevice(device); |
| 189 | + |
| 190 | + model.UBO = vtkWebGPUUniformBuffer.newInstance({ label: 'mapperUBO' }); |
| 191 | + model.UBO.addEntry('FSQMatrix', 'mat4x4<f32>'); |
| 192 | + model.UBO.addEntry('BackgroundColor', 'vec4<f32>'); |
| 193 | + model.UBO.addEntry('BackgroundColor2', 'vec4<f32>'); |
| 194 | + model.quad.setUBO(model.UBO); |
| 195 | + }; |
| 196 | + |
| 197 | + publicAPI.getFragmentTemplate = (mode) => { |
| 198 | + switch (mode) { |
| 199 | + case 'gradient': |
| 200 | + return gradientFragTemplate; |
| 201 | + case 'texture': |
| 202 | + return textureFragTemplate; |
| 203 | + case 'environment': |
| 204 | + return environmentFragTemplate; |
| 205 | + case 'solid': |
| 206 | + default: |
| 207 | + return solidFragTemplate; |
| 208 | + } |
| 209 | + }; |
| 210 | + |
| 211 | + publicAPI.updateTexture = (device, texture, textureName) => { |
| 212 | + if (!texture || !textureName) { |
| 213 | + model.quad.setTextureViews([]); |
| 214 | + return; |
| 215 | + } |
| 216 | + |
| 217 | + const webgpuTexture = device |
| 218 | + .getTextureManager() |
| 219 | + .getTextureForVTKTexture(texture, textureName); |
| 220 | + if (!webgpuTexture.getReady()) { |
| 221 | + model.quad.setTextureViews([]); |
| 222 | + return; |
| 223 | + } |
| 224 | + |
| 225 | + const tview = webgpuTexture.createView(textureName); |
| 226 | + const interpolate = texture.getInterpolate?.() ? 'linear' : 'nearest'; |
| 227 | + let options = { |
| 228 | + minFilter: interpolate, |
| 229 | + magFilter: interpolate, |
| 230 | + }; |
| 231 | + if (textureName === 'EnvironmentTexture') { |
| 232 | + options = { |
| 233 | + addressModeU: 'repeat', |
| 234 | + addressModeV: 'clamp-to-edge', |
| 235 | + addressModeW: 'repeat', |
| 236 | + minFilter: interpolate, |
| 237 | + magFilter: interpolate, |
| 238 | + mipmapFilter: 'linear', |
| 239 | + }; |
| 240 | + } |
| 241 | + tview.addSampler(device, options); |
| 242 | + model.quad.setTextureViews([tview]); |
| 243 | + }; |
| 244 | + |
| 245 | + publicAPI.updateUBO = (device, rendererNode, renderer) => { |
| 246 | + const background = renderer.getBackgroundByReference?.() ?? [0, 0, 0, 1]; |
| 247 | + const background2 = renderer.getBackground2ByReference?.() ?? [0, 0, 0]; |
| 248 | + model.UBO.setArray('BackgroundColor', background); |
| 249 | + model.UBO.setArray('BackgroundColor2', [...background2, 1.0]); |
| 250 | + |
| 251 | + const keyMats = model.webgpuCamera.getKeyMatrices(rendererNode); |
| 252 | + mat4.transpose(_tNormalMat4, keyMats.normalMatrix); |
| 253 | + mat4.mul(_fsqMat4, keyMats.scvc, keyMats.pcsc); |
| 254 | + mat4.mul(_fsqMat4, _tNormalMat4, _fsqMat4); |
| 255 | + model.UBO.setArray('FSQMatrix', _fsqMat4); |
| 256 | + model.UBO.sendIfNeeded(device); |
| 257 | + }; |
| 258 | + |
| 259 | + publicAPI.render = (renderEncoder, rendererNode) => { |
| 260 | + const renderer = rendererNode.getRenderable(); |
| 261 | + const device = rendererNode.getParent().getDevice(); |
| 262 | + publicAPI.ensureQuad(device); |
| 263 | + |
| 264 | + model.webgpuCamera = rendererNode.getViewNodeFor( |
| 265 | + renderer.getActiveCamera(), |
| 266 | + model.webgpuCamera |
| 267 | + ); |
| 268 | + |
| 269 | + const { mode, texture, textureName, pipelineHash } = |
| 270 | + publicAPI.getMode(renderer); |
| 271 | + model.quad.setPipelineHash(pipelineHash); |
| 272 | + model.quad.setFragmentShaderTemplate(publicAPI.getFragmentTemplate(mode)); |
| 273 | + publicAPI.updateTexture(device, texture, textureName); |
| 274 | + publicAPI.updateUBO(device, rendererNode, renderer); |
| 275 | + model.quad.prepareAndDraw(renderEncoder); |
| 276 | + }; |
| 277 | +} |
| 278 | + |
| 279 | +const DEFAULT_VALUES = { |
| 280 | + quad: null, |
| 281 | + UBO: null, |
| 282 | + webgpuCamera: null, |
| 283 | +}; |
| 284 | + |
| 285 | +export function extend(publicAPI, model, initialValues = {}) { |
| 286 | + Object.assign(model, DEFAULT_VALUES, initialValues); |
| 287 | + macro.obj(publicAPI, model); |
| 288 | + vtkWebGPUBackground(publicAPI, model); |
| 289 | +} |
| 290 | + |
| 291 | +export const newInstance = macro.newInstance(extend, 'vtkWebGPUBackground'); |
| 292 | + |
| 293 | +export default { newInstance, extend }; |
0 commit comments