@kitware/vtk.js 25.2.4 → 25.5.0

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.
@@ -151,6 +151,10 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
151
151
  if (model.renderable.getVolumetricScatteringBlending() < 1.0) {
152
152
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::SurfaceShadowOn', "#define SurfaceShadowOn").result;
153
153
  }
154
+
155
+ if (model.renderable.getVolumetricScatteringBlending() === 0.0 && model.renderable.getLocalAmbientOcclusion() && actor.getProperty().getAmbient() > 0.0) {
156
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::localAmbientOcclusionOn', "#define localAmbientOcclusionOn").result;
157
+ }
154
158
  } // if using gradient opacity define that
155
159
 
156
160
 
@@ -210,6 +214,10 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
210
214
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::VolumeShadow::Dec', ["uniform float volumetricScatteringBlending;", "uniform float giReach;", "uniform float volumeShadowSamplingDistFactor;", "uniform float anisotropy;", "uniform float anisotropy2;"], false).result;
211
215
  }
212
216
 
217
+ if (model.renderable.getVolumetricScatteringBlending() === 0.0 && model.renderable.getLocalAmbientOcclusion() && actor.getProperty().getAmbient() > 0.0) {
218
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LAO::Dec', ["uniform int kernelRadius;", "uniform vec2 kernelSample[".concat(model.renderable.getLAOKernelRadius(), "];"), "uniform int kernelSize;"], false).result;
219
+ }
220
+
213
221
  shaders.Fragment = FSSource;
214
222
  };
215
223
 
@@ -617,6 +625,20 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
617
625
  program.setUniformf('anisotropy', model.renderable.getAnisotropy());
618
626
  program.setUniformf('anisotropy2', Math.pow(model.renderable.getAnisotropy(), 2.0));
619
627
  }
628
+
629
+ if (model.renderable.getVolumetricScatteringBlending() === 0.0 && model.renderable.getLocalAmbientOcclusion() && actor.getProperty().getAmbient() > 0.0) {
630
+ var ks = model.renderable.getLAOKernelSize();
631
+ program.setUniformi('kernelSize', ks);
632
+ var kernelSample = [];
633
+
634
+ for (var _i2 = 0; _i2 < ks; _i2++) {
635
+ kernelSample[_i2 * 2] = Math.random() * 0.5;
636
+ kernelSample[_i2 * 2 + 1] = Math.random() * 0.5;
637
+ }
638
+
639
+ program.setUniform2fv('kernelSample', kernelSample);
640
+ program.setUniformi('kernelRadius', model.renderable.getLAOKernelRadius());
641
+ }
620
642
  };
621
643
 
622
644
  publicAPI.setPropertyShaderParameters = function (cellBO, ren, actor) {
@@ -638,19 +660,19 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
638
660
  // for performance in the fragment shader
639
661
 
640
662
 
641
- for (var _i2 = 0; _i2 < numComp; _i2++) {
642
- var target = iComps ? _i2 : 0;
643
- var sscale = volInfo.scale[_i2];
663
+ for (var _i3 = 0; _i3 < numComp; _i3++) {
664
+ var target = iComps ? _i3 : 0;
665
+ var sscale = volInfo.scale[_i3];
644
666
  var ofun = vprop.getScalarOpacity(target);
645
667
  var oRange = ofun.getRange();
646
668
  var oscale = sscale / (oRange[1] - oRange[0]);
647
- var oshift = (volInfo.offset[_i2] - oRange[0]) / (oRange[1] - oRange[0]);
648
- program.setUniformf("oshift".concat(_i2), oshift);
649
- program.setUniformf("oscale".concat(_i2), oscale);
669
+ var oshift = (volInfo.offset[_i3] - oRange[0]) / (oRange[1] - oRange[0]);
670
+ program.setUniformf("oshift".concat(_i3), oshift);
671
+ program.setUniformf("oscale".concat(_i3), oscale);
650
672
  var cfun = vprop.getRGBTransferFunction(target);
651
673
  var cRange = cfun.getRange();
652
- program.setUniformf("cshift".concat(_i2), (volInfo.offset[_i2] - cRange[0]) / (cRange[1] - cRange[0]));
653
- program.setUniformf("cscale".concat(_i2), sscale / (cRange[1] - cRange[0]));
674
+ program.setUniformf("cshift".concat(_i3), (volInfo.offset[_i3] - cRange[0]) / (cRange[1] - cRange[0]));
675
+ program.setUniformf("cscale".concat(_i3), sscale / (cRange[1] - cRange[0]));
654
676
  }
655
677
 
656
678
  if (model.gopacity) {
@@ -1009,9 +1031,9 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1009
1031
  var oRange = ofun.getRange();
1010
1032
  ofun.getTable(oRange[0], oRange[1], oWidth, tmpTable, 1); // adjust for sample distance etc
1011
1033
 
1012
- for (var _i3 = 0; _i3 < oWidth; ++_i3) {
1013
- ofTable[c * oWidth * 2 + _i3] = 1.0 - Math.pow(1.0 - tmpTable[_i3], opacityFactor);
1014
- ofTable[c * oWidth * 2 + _i3 + oWidth] = ofTable[c * oWidth * 2 + _i3];
1034
+ for (var _i4 = 0; _i4 < oWidth; ++_i4) {
1035
+ ofTable[c * oWidth * 2 + _i4] = 1.0 - Math.pow(1.0 - tmpTable[_i4], opacityFactor);
1036
+ ofTable[c * oWidth * 2 + _i4 + oWidth] = ofTable[c * oWidth * 2 + _i4];
1015
1037
  }
1016
1038
  }
1017
1039
 
@@ -1027,8 +1049,8 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1027
1049
  } else {
1028
1050
  var _oTable = new Uint8Array(oSize);
1029
1051
 
1030
- for (var _i4 = 0; _i4 < oSize; ++_i4) {
1031
- _oTable[_i4] = 255.0 * ofTable[_i4];
1052
+ for (var _i5 = 0; _i5 < oSize; ++_i5) {
1053
+ _oTable[_i5] = 255.0 * ofTable[_i5];
1032
1054
  }
1033
1055
 
1034
1056
  model.opacityTexture.create2DFromRaw(oWidth, 2 * numIComps, 1, VtkDataTypes.UNSIGNED_CHAR, _oTable);
@@ -1052,9 +1074,9 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1052
1074
  var cRange = cfun.getRange();
1053
1075
  cfun.getTable(cRange[0], cRange[1], cWidth, _tmpTable, 1);
1054
1076
 
1055
- for (var _i5 = 0; _i5 < cWidth * 3; ++_i5) {
1056
- cTable[_c * cWidth * 6 + _i5] = 255.0 * _tmpTable[_i5];
1057
- cTable[_c * cWidth * 6 + _i5 + cWidth * 3] = 255.0 * _tmpTable[_i5];
1077
+ for (var _i6 = 0; _i6 < cWidth * 3; ++_i6) {
1078
+ cTable[_c * cWidth * 6 + _i6] = 255.0 * _tmpTable[_i6];
1079
+ cTable[_c * cWidth * 6 + _i6 + cWidth * 3] = 255.0 * _tmpTable[_i6];
1058
1080
  }
1059
1081
  }
1060
1082
 
@@ -1081,10 +1103,10 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1081
1103
  // build the CABO
1082
1104
  var ptsArray = new Float32Array(12);
1083
1105
 
1084
- for (var _i6 = 0; _i6 < 4; _i6++) {
1085
- ptsArray[_i6 * 3] = _i6 % 2 * 2 - 1.0;
1086
- ptsArray[_i6 * 3 + 1] = _i6 > 1 ? 1.0 : -1.0;
1087
- ptsArray[_i6 * 3 + 2] = -1.0;
1106
+ for (var _i7 = 0; _i7 < 4; _i7++) {
1107
+ ptsArray[_i7 * 3] = _i7 % 2 * 2 - 1.0;
1108
+ ptsArray[_i7 * 3 + 1] = _i7 > 1 ? 1.0 : -1.0;
1109
+ ptsArray[_i7 * 3 + 2] = -1.0;
1088
1110
  }
1089
1111
 
1090
1112
  var cellArray = new Uint16Array(8);
@@ -1,3 +1,3 @@
1
- var vtkVolumeFS = "//VTK::System::Dec\n\n/*=========================================================================\n\n Program: Visualization Toolkit\n Module: vtkVolumeFS.glsl\n\n Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen\n All rights reserved.\n See Copyright.txt or http://www.kitware.com/Copyright.htm for details.\n\n This software is distributed WITHOUT ANY WARRANTY; without even\n the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR\n PURPOSE. See the above copyright notice for more information.\n\n=========================================================================*/\n// Template for the volume mappers fragment shader\n\n// the output of this shader\n//VTK::Output::Dec\n\nvarying vec3 vertexVCVSOutput;\n\n// first declare the settings from the mapper\n// that impact the code paths in here\n\n// always set vtkNumComponents 1,2,3,4\n//VTK::NumComponents\n\n// possibly define vtkTrilinearOn\n//VTK::TrilinearOn\n\n// possibly define vtkIndependentComponents\n//VTK::IndependentComponentsOn\n\n// possibly define any \"proportional\" components\n//VTK::vtkProportionalComponents\n\n// Define the blend mode to use\n#define vtkBlendMode //VTK::BlendMode\n\n// Possibly define vtkImageLabelOutlineOn\n//VTK::ImageLabelOutlineOn\n\n#ifdef vtkImageLabelOutlineOn\nuniform int outlineThickness;\nuniform float vpWidth;\nuniform float vpHeight;\nuniform float vpOffsetX;\nuniform float vpOffsetY;\nuniform mat4 PCWCMatrix;\nuniform mat4 vWCtoIDX;\n#endif\n\n// define vtkLightComplexity\n//VTK::LightComplexity\n#if vtkLightComplexity > 0\nuniform float vSpecularPower;\nuniform float vAmbient;\nuniform float vDiffuse;\nuniform float vSpecular;\n//VTK::Light::Dec\n#endif\n\n//VTK::VolumeShadowOn\n//VTK::SurfaceShadowOn\n//VTK::VolumeShadow::Dec\n\n// define vtkComputeNormalFromOpacity\n//VTK::vtkComputeNormalFromOpacity\n\n// possibly define vtkGradientOpacityOn\n//VTK::GradientOpacityOn\n#ifdef vtkGradientOpacityOn\nuniform float goscale0;\nuniform float goshift0;\nuniform float gomin0;\nuniform float gomax0;\n#if defined(vtkIndependentComponentsOn) && (vtkNumComponents > 1)\nuniform float goscale1;\nuniform float goshift1;\nuniform float gomin1;\nuniform float gomax1;\n#if vtkNumComponents >= 3\nuniform float goscale2;\nuniform float goshift2;\nuniform float gomin2;\nuniform float gomax2;\n#endif\n#if vtkNumComponents >= 4\nuniform float goscale3;\nuniform float goshift3;\nuniform float gomin3;\nuniform float gomax3;\n#endif\n#endif\n#endif\n\n// if you want to see the raw tiled\n// data in webgl1 uncomment the following line\n// #define debugtile\n\n// camera values\nuniform float camThick;\nuniform float camNear;\nuniform float camFar;\nuniform int cameraParallel;\n\n// values describing the volume geometry\nuniform vec3 vOriginVC;\nuniform vec3 vSpacing;\nuniform ivec3 volumeDimensions; // 3d texture dimensions\nuniform vec3 vPlaneNormal0;\nuniform float vPlaneDistance0;\nuniform vec3 vPlaneNormal1;\nuniform float vPlaneDistance1;\nuniform vec3 vPlaneNormal2;\nuniform float vPlaneDistance2;\nuniform vec3 vPlaneNormal3;\nuniform float vPlaneDistance3;\nuniform vec3 vPlaneNormal4;\nuniform float vPlaneDistance4;\nuniform vec3 vPlaneNormal5;\nuniform float vPlaneDistance5;\n\n//VTK::ClipPlane::Dec\n\n// opacity and color textures\nuniform sampler2D otexture;\nuniform float oshift0;\nuniform float oscale0;\nuniform sampler2D ctexture;\nuniform float cshift0;\nuniform float cscale0;\n\n// jitter texture\nuniform sampler2D jtexture;\n\n// some 3D texture values\nuniform float sampleDistance;\nuniform vec3 vVCToIJK;\n\n// the heights defined below are the locations\n// for the up to four components of the tfuns\n// the tfuns have a height of 2XnumComps pixels so the\n// values are computed to hit the middle of the two rows\n// for that component\n#ifdef vtkIndependentComponentsOn\n#if vtkNumComponents == 2\nuniform float mix0;\nuniform float mix1;\n#define height0 0.25\n#define height1 0.75\n#endif\n#if vtkNumComponents == 3\nuniform float mix0;\nuniform float mix1;\nuniform float mix2;\n#define height0 0.17\n#define height1 0.5\n#define height2 0.83\n#endif\n#if vtkNumComponents == 4\nuniform float mix0;\nuniform float mix1;\nuniform float mix2;\nuniform float mix3;\n#define height0 0.125\n#define height1 0.375\n#define height2 0.625\n#define height3 0.875\n#endif\n#endif\n\n#if vtkNumComponents >= 2\nuniform float oshift1;\nuniform float oscale1;\nuniform float cshift1;\nuniform float cscale1;\n#endif\n#if vtkNumComponents >= 3\nuniform float oshift2;\nuniform float oscale2;\nuniform float cshift2;\nuniform float cscale2;\n#endif\n#if vtkNumComponents >= 4\nuniform float oshift3;\nuniform float oscale3;\nuniform float cshift3;\nuniform float cscale3;\n#endif\n\nuniform vec4 ipScalarRangeMin;\nuniform vec4 ipScalarRangeMax;\n\n// declaration for intermixed geometry\n//VTK::ZBuffer::Dec\n\n//=======================================================================\n// global and custom variables (a temporary section before photorealistics rendering module is complete)\nvec3 rayDirVC;\nfloat sampleDistanceISVS;\n\n#define SQRT3 1.7321\n#define INV4PI 0.0796\n#define EPSILON 0.001\n\n//=======================================================================\n// Webgl2 specific version of functions\n#if __VERSION__ == 300\n\nuniform highp sampler3D texture1;\n\nvec4 getTextureValue(vec3 pos)\n{\n vec4 tmp = texture(texture1, pos);\n#if vtkNumComponents == 1\n tmp.a = tmp.r;\n#endif\n#if vtkNumComponents == 2\n tmp.a = tmp.g;\n#endif\n#if vtkNumComponents == 3\n tmp.a = length(tmp.rgb);\n#endif\n return tmp;\n}\n\n//=======================================================================\n// WebGL1 specific version of functions\n#else\n\nuniform sampler2D texture1;\n\nuniform float texWidth;\nuniform float texHeight;\nuniform int xreps;\nuniform int xstride;\nuniform int ystride;\n\n// if computing trilinear values from multiple z slices\n#ifdef vtkTrilinearOn\nvec4 getTextureValue(vec3 ijk)\n{\n float zoff = 1.0/float(volumeDimensions.z);\n vec4 val1 = getOneTextureValue(ijk);\n vec4 val2 = getOneTextureValue(vec3(ijk.xy, ijk.z + zoff));\n\n float indexZ = float(volumeDimensions)*ijk.z;\n float zmix = indexZ - floor(indexZ);\n\n return mix(val1, val2, zmix);\n}\n\nvec4 getOneTextureValue(vec3 ijk)\n#else // nearest or fast linear\nvec4 getTextureValue(vec3 ijk)\n#endif\n{\n vec3 tdims = vec3(volumeDimensions);\n\n#ifdef debugtile\n vec2 tpos = vec2(ijk.x, ijk.y);\n vec4 tmp = texture2D(texture1, tpos);\n tmp.a = 1.0;\n\n#else\n int z = int(ijk.z * tdims.z);\n int yz = z / xreps;\n int xz = z - yz*xreps;\n\n int tileWidth = volumeDimensions.x/xstride;\n int tileHeight = volumeDimensions.y/ystride;\n\n xz *= tileWidth;\n yz *= tileHeight;\n\n float ni = float(xz) + (ijk.x*float(tileWidth));\n float nj = float(yz) + (ijk.y*float(tileHeight));\n\n vec2 tpos = vec2(ni/texWidth, nj/texHeight);\n\n vec4 tmp = texture2D(texture1, tpos);\n\n#if vtkNumComponents == 1\n tmp.a = tmp.r;\n#endif\n#if vtkNumComponents == 2\n tmp.g = tmp.a;\n#endif\n#if vtkNumComponents == 3\n tmp.a = length(tmp.rgb);\n#endif\n#endif\n\n return tmp;\n}\n\n// End of Webgl1 specific code\n//=======================================================================\n#endif\n\n//=======================================================================\n// transformation between VC and IS space\n\n// convert vector position from idx to vc\n#if vtkLightComplexity > 0\nvec3 IStoVC(vec3 posIS){\n vec3 posVC = posIS / vVCToIJK;\n return posVC.x * vPlaneNormal0 + \n posVC.y * vPlaneNormal2 + \n posVC.z * vPlaneNormal4 + \n vOriginVC;\n}\n\n// convert vector position from vc to idx\nvec3 VCtoIS(vec3 posVC){\n posVC = posVC - vOriginVC;\n posVC = vec3(\n dot(posVC, vPlaneNormal0),\n dot(posVC, vPlaneNormal2),\n dot(posVC, vPlaneNormal4)); \n return posVC * vVCToIJK;\n}\n#endif\n\n//Rotate vector to view coordinate\n#if (vtkLightComplexity > 0) || (defined vtkGradientOpacityOn)\nvoid rotateToViewCoord(inout vec3 dirIS){\n dirIS.xyz =\n dirIS.x * vPlaneNormal0 +\n dirIS.y * vPlaneNormal2 +\n dirIS.z * vPlaneNormal4;\n}\n\n//Rotate vector to idx coordinate\nvec3 rotateToIDX(vec3 dirVC){\n vec3 dirIS;\n dirIS.xyz = vec3(\n dot(dirVC, vPlaneNormal0),\n dot(dirVC, vPlaneNormal2),\n dot(dirVC, vPlaneNormal4)); \n return dirIS;\n}\n#endif\n\n//=======================================================================\n// Given a normal compute the gradient opacity factors\nfloat computeGradientOpacityFactor(\n float normalMag, float goscale, float goshift, float gomin, float gomax)\n{\n#if defined(vtkGradientOpacityOn)\n return clamp(normalMag * goscale + goshift, gomin, gomax);\n#else\n return 1.0;\n#endif\n}\n\n//=======================================================================\n// compute the normal and gradient magnitude for a position, uses forward difference\n#if (vtkLightComplexity > 0) || (defined vtkGradientOpacityOn)\n #ifdef vtkComputeNormalFromOpacity\n #ifdef vtkGradientOpacityOn\n vec4 computeNormalForDensity(vec3 pos, float scalar, vec3 tstep, out mat3 scalarInterp, out vec3 secondaryGradientMag)\n {\n vec4 result;\n scalarInterp[0][0] = getTextureValue(pos + vec3(tstep.x, 0.0, 0.0)).a;\n scalarInterp[0][1] = getTextureValue(pos + vec3(0.0, tstep.y, 0.0)).a;\n scalarInterp[0][2] = getTextureValue(pos + vec3(0.0, 0.0, tstep.z)).a;\n // look up scalar values for computing secondary gradient\n scalarInterp[1][0] = getTextureValue(pos + vec3(2.0*tstep.x, 0.0, 0.0)).a;\n scalarInterp[1][1] = getTextureValue(pos + vec3(0.0, 2.0*tstep.y, 0.0)).a;\n scalarInterp[1][2] = getTextureValue(pos + vec3(0.0, 0.0, 2.0*tstep.z)).a;\n scalarInterp[2][0] = getTextureValue(pos + vec3(tstep.x, tstep.y, 0.0)).a;\n scalarInterp[2][1] = getTextureValue(pos + vec3(tstep.x, 0.0, tstep.z)).a;\n scalarInterp[2][2] = getTextureValue(pos + vec3(0.0, tstep.y, tstep.z)).a;\n result.x = scalarInterp[0][0] - scalar;\n result.y = scalarInterp[0][1] - scalar;\n result.z = scalarInterp[0][2] - scalar;\n // divide by spacing\n result.xyz /= vSpacing;\n result.w = length(result.xyz);\n rotateToViewCoord(result.xyz);\n secondaryGradientMag.x = length(vec3(scalarInterp[1][0] - scalarInterp[0][0],\n scalarInterp[2][0] - scalarInterp[0][0],\n scalarInterp[2][1] - scalarInterp[0][0]) / vSpacing);\n secondaryGradientMag.y = length(vec3(scalarInterp[2][0] - scalarInterp[0][1],\n scalarInterp[1][1] - scalarInterp[0][1],\n scalarInterp[2][2] - scalarInterp[0][1]) / vSpacing);\n secondaryGradientMag.z = length(vec3(scalarInterp[2][1] - scalarInterp[0][2],\n scalarInterp[2][2] - scalarInterp[0][2],\n scalarInterp[1][2] - scalarInterp[0][2]) / vSpacing);\n if (length(result.xyz) > 0.0) {\n return vec4(normalize(result.xyz),result.w);\n } else {\n return vec4(0.0);\n }\n }\n\n vec4 computeDensityNormal(float scalar, float gradientMag, mat3 scalarInterp, vec3 secondaryGradientMag)\n {\n vec4 opacityG;\n vec3 opacityInterp = vec3(0.0);\n float opacity = texture2D(otexture, vec2(scalar * oscale0 + oshift0, 0.5)).r;\n if (gradientMag >= 0.0){\n opacity *= computeGradientOpacityFactor(gradientMag, goscale0, goshift0, gomin0, gomax0);\n }\n opacityInterp.x = texture2D(otexture, vec2(scalarInterp[0][0] * oscale0 + oshift0, 0.5)).r; \n if (secondaryGradientMag.x >= 0.0){\n opacityInterp.x *= computeGradientOpacityFactor(secondaryGradientMag.x, goscale0, goshift0, gomin0, gomax0);\n }\n \n opacityInterp.y = texture2D(otexture, vec2(scalarInterp[0][1] * oscale0 + oshift0, 0.5)).r;\n if (secondaryGradientMag.y >= 0.0){\n opacityInterp.y *= computeGradientOpacityFactor(secondaryGradientMag.y, goscale0, goshift0, gomin0, gomax0);\n }\n\n opacityInterp.z = texture2D(otexture, vec2(scalarInterp[0][2] * oscale0 + oshift0, 0.5)).r;\n if (secondaryGradientMag.z >= 0.0){\n opacityInterp.z *= computeGradientOpacityFactor(secondaryGradientMag.z, goscale0, goshift0, gomin0, gomax0);\n }\n\n opacityG.xyz = opacityInterp - vec3(opacity,opacity,opacity);\n // divide by spacing\n opacityG.xyz /= vSpacing;\n opacityG.w = length(opacityG.xyz);\n rotateToViewCoord(opacityG.xyz);\n if (length(opacityG.xyz) > 0.0) { \n return vec4(normalize(opacityG.xyz),opacityG.w);\n } else {\n return vec4(0.0);\n }\n } \n\n #else\n //if gradient opacity not on but using density gradient\n vec4 computeDensityNormal(float scalar, vec3 scalarInterp) \n { \n vec4 opacityG; \n float opacity = texture2D(otexture, vec2(scalar * oscale0 + oshift0, 0.5)).r; \n opacityG.x = texture2D(otexture, vec2(scalarInterp.x * oscale0 + oshift0, 0.5)).r - opacity; \n opacityG.y = texture2D(otexture, vec2(scalarInterp.y * oscale0 + oshift0, 0.5)).r - opacity; \n opacityG.z = texture2D(otexture, vec2(scalarInterp.z * oscale0 + oshift0, 0.5)).r - opacity; \n // divide by spacing \n opacityG.xyz /= vSpacing; \n opacityG.w = length(opacityG.xyz); \n // rotate to View Coords \n rotateToViewCoord(opacityG.xyz);\n if (length(opacityG.xyz) > 0.0) { \n return vec4(normalize(opacityG.xyz),opacityG.w); \n } else { \n return vec4(0.0); \n } \n } \n vec4 computeNormalForDensity(vec3 pos, float scalar, vec3 tstep, out vec3 scalarInterp) \n { \n vec4 result; \n scalarInterp.x = getTextureValue(pos + vec3(tstep.x, 0.0, 0.0)).a; \n scalarInterp.y = getTextureValue(pos + vec3(0.0, tstep.y, 0.0)).a; \n scalarInterp.z = getTextureValue(pos + vec3(0.0, 0.0, tstep.z)).a; \n result.x = scalarInterp.x - scalar; \n result.y = scalarInterp.y - scalar; \n result.z = scalarInterp.z - scalar; \n // divide by spacing\n result.xyz /= vSpacing;\n result.w = length(result.xyz); \n // rotate to View Coords \n rotateToViewCoord(result.xyz); \n if (length(result.xyz) > 0.0) { \n return vec4(normalize(result.xyz),result.w); \n } else { \n return vec4(0.0); \n } \n } \n #endif\n #endif\n // compute scalar density\n vec4 computeNormal(vec3 pos, float scalar, vec3 tstep) \n { \n vec4 result; \n result.x = getTextureValue(pos + vec3(tstep.x, 0.0, 0.0)).a - scalar; \n result.y = getTextureValue(pos + vec3(0.0, tstep.y, 0.0)).a - scalar; \n result.z = getTextureValue(pos + vec3(0.0, 0.0, tstep.z)).a - scalar; \n // divide by spacing \n result.xyz /= vSpacing; \n result.w = length(result.xyz); \n // rotate to View Coords \n rotateToViewCoord(result.xyz);\n return vec4(normalize(result.xyz),result.w); \n } \n#endif\n\n#ifdef vtkImageLabelOutlineOn\nvec3 fragCoordToIndexSpace(vec4 fragCoord) {\n vec4 pcPos = vec4(\n (fragCoord.x / vpWidth - vpOffsetX - 0.5) * 2.0,\n (fragCoord.y / vpHeight - vpOffsetY - 0.5) * 2.0,\n (fragCoord.z - 0.5) * 2.0,\n 1.0);\n\n vec4 worldCoord = PCWCMatrix * pcPos;\n vec4 vertex = (worldCoord/worldCoord.w);\n\n vec3 index = (vWCtoIDX * vertex).xyz;\n\n // half voxel fix for labelmapOutline \n return (index + vec3(0.5)) / vec3(volumeDimensions);\n}\n#endif\n\n//=======================================================================\n// compute the normals and gradient magnitudes for a position\n// for independent components\nmat4 computeMat4Normal(vec3 pos, vec4 tValue, vec3 tstep)\n{\n mat4 result;\n vec4 distX = getTextureValue(pos + vec3(tstep.x, 0.0, 0.0)) - tValue;\n vec4 distY = getTextureValue(pos + vec3(0.0, tstep.y, 0.0)) - tValue;\n vec4 distZ = getTextureValue(pos + vec3(0.0, 0.0, tstep.z)) - tValue;\n\n // divide by spacing\n distX /= vSpacing.x;\n distY /= vSpacing.y;\n distZ /= vSpacing.z;\n\n mat3 rot;\n rot[0] = vPlaneNormal0;\n rot[1] = vPlaneNormal2;\n rot[2] = vPlaneNormal4;\n\n#if !defined(vtkComponent0Proportional)\n result[0].xyz = vec3(distX.r, distY.r, distZ.r);\n result[0].a = length(result[0].xyz);\n result[0].xyz *= rot;\n if (result[0].w > 0.0)\n {\n result[0].xyz /= result[0].w;\n }\n#endif\n\n// optionally compute the 2nd component\n#if vtkNumComponents >= 2 && !defined(vtkComponent1Proportional)\n result[1].xyz = vec3(distX.g, distY.g, distZ.g);\n result[1].a = length(result[1].xyz);\n result[1].xyz *= rot;\n if (result[1].w > 0.0)\n {\n result[1].xyz /= result[1].w;\n }\n#endif\n\n// optionally compute the 3rd component\n#if vtkNumComponents >= 3 && !defined(vtkComponent2Proportional)\n result[2].xyz = vec3(distX.b, distY.b, distZ.b);\n result[2].a = length(result[2].xyz);\n result[2].xyz *= rot;\n if (result[2].w > 0.0)\n {\n result[2].xyz /= result[2].w;\n }\n#endif\n\n// optionally compute the 4th component\n#if vtkNumComponents >= 4 && !defined(vtkComponent3Proportional)\n result[3].xyz = vec3(distX.a, distY.a, distZ.a);\n result[3].a = length(result[3].xyz);\n result[3].xyz *= rot;\n if (result[3].w > 0.0)\n {\n result[3].xyz /= result[3].w;\n }\n#endif\n\n return result;\n}\n\n//=======================================================================\n// global shadow - secondary ray\n#ifdef VolumeShadowOn\n\n// henyey greenstein phase function\nfloat phase_function(float cos_angle)\n{\n // divide by 2.0 instead of 4pi to increase intensity\n return ((1.0-anisotropy2)/pow(1.0+anisotropy2-2.0*anisotropy*cos_angle, 1.5))/2.0;\n}\n\nfloat random()\n{ \n float rand = fract(sin(dot(gl_FragCoord.xy,vec2(12.9898,78.233)))*43758.5453123);\n float jitter=texture2D(jtexture,gl_FragCoord.xy/32.).r;\n uint pcg_state = floatBitsToUint(jitter);\n uint state = pcg_state;\n pcg_state = pcg_state * uint(747796405) + uint(2891336453);\n uint word = ((state >> ((state >> uint(28)) + uint(4))) ^ state) * uint(277803737);\n return (float((((word >> uint(22)) ^ word) >> 1 ))/float(2147483647) + rand)/2.0;\n}\n\n// Computes the intersection between a ray and a box\nstruct Hit\n{\n float tmin;\n float tmax;\n};\n\nstruct Ray\n{\n vec3 origin;\n vec3 dir;\n vec3 invDir;\n};\n\nbool BBoxIntersect(vec3 boundMin, vec3 boundMax, const Ray r, out Hit hit)\n{\n vec3 tbot = r.invDir * (boundMin - r.origin);\n vec3 ttop = r.invDir * (boundMax - r.origin);\n vec3 tmin = min(ttop, tbot);\n vec3 tmax = max(ttop, tbot);\n vec2 t = max(tmin.xx, tmin.yz);\n float t0 = max(t.x, t.y);\n t = min(tmax.xx, tmax.yz);\n float t1 = min(t.x, t.y);\n hit.tmin = t0;\n hit.tmax = t1;\n return t1 > max(t0,0.0);\n}\n\n// As BBoxIntersect requires the inverse of the ray coords,\n// this function is used to avoid numerical issues\nvoid safe_0_vector(inout Ray ray)\n{\n if(abs(ray.dir.x) < EPSILON) ray.dir.x = sign(ray.dir.x) * EPSILON;\n if(abs(ray.dir.y) < EPSILON) ray.dir.y = sign(ray.dir.y) * EPSILON;\n if(abs(ray.dir.z) < EPSILON) ray.dir.z = sign(ray.dir.z) * EPSILON;\n}\n\nfloat volume_shadow(vec3 posIS, vec3 lightDirNormIS)\n{\n float shadow = 1.0;\n float opacity = 0.0;\n\n // modify sample distance with a random number between 0.8 and 1.0\n float sampleDistanceISVS_jitter = sampleDistanceISVS * mix(0.8, 1.0, random());\n float opacityPrev = texture2D(otexture, vec2(getTextureValue(posIS).r * oscale0 + oshift0, 0.5)).r;\n \n // in case the first sample near surface has a very tiled light ray, we need to offset start position \n posIS += sampleDistanceISVS_jitter * lightDirNormIS; \n\n // compute the start and end points for the ray\n Ray ray;\n Hit hit; \n ray.origin = posIS;\n ray.dir = lightDirNormIS;\n safe_0_vector(ray);\n ray.invDir = 1.0/ray.dir;\n \n if(!BBoxIntersect(vec3(0.0),vec3(1.0), ray, hit))\n {\n return 1.0;\n }\n vec4 scalar = vec4(0.0);\n float maxdist = hit.tmax;\n if(maxdist < EPSILON) {\n return 1.0;\n }\n\n // interpolate shadow ray length between: 1 unit of sample distance in IS to SQRT3, based on globalIlluminationReach\n float maxgi = mix(sampleDistanceISVS_jitter,SQRT3,giReach);\n maxdist = min(maxdist,maxgi);\n\n // support gradient opacity\n #ifdef vtkGradientOpacityOn\n vec4 normal;\n #endif\n\n vec3 current_step = sampleDistanceISVS_jitter * lightDirNormIS;\n float maxSteps = ceil(maxdist/sampleDistanceISVS_jitter);\n float opacityDelta = 0.0;\n\n for (float i = 0.0; i < maxSteps; i++)\n {\n scalar = getTextureValue(posIS);\n opacity = texture2D(otexture, vec2(scalar.r * oscale0 + oshift0, 0.5)).r;\n #ifdef vtkGradientOpacityOn \n normal = computeNormal(posIS, scalar.a, vec3(1.0/vec3(volumeDimensions))); \n opacity *= computeGradientOpacityFactor(normal.w, goscale0, goshift0, gomin0, gomax0);\n #endif \n shadow *= 1.0 - opacity;\n\n // optimization: early termination\n if (shadow < EPSILON){\n return 0.0;\n }\n\n // optimization: increase/decrease sample distance based on changed in opacity value\n opacityDelta = opacityPrev - opacity;\n opacityPrev = opacity;\n if (opacityDelta > 0.0){\n current_step *= 0.9;\n } else if (opacityDelta < 0.0){\n current_step *= 1.1;\n }\n posIS += current_step;\n }\n\n return shadow; \n}\n\nvec3 applyShadowRay(vec3 tColor, vec3 posIS, vec3 viewDirectionVC)\n{\n vec3 vertLight = vec3(0.0);\n vec3 secondary_contrib = vec3(0.0);\n // here we assume only positional light, no effect of cones\n for (int i = 0; i < lightNum; i++)\n {\n #if(vtkLightComplexity==3)\n if (lightPositional[i] == 1){\n vertLight = lightPositionVC[i] - IStoVC(posIS);\n }else{\n vertLight = - lightDirectionVC[i];\n }\n #else\n vertLight = - lightDirectionVC[i];\n #endif\n // here we assume achromatic light, only intensity\n float dDotL = dot(viewDirectionVC, normalize(vertLight));\n // isotropic scatter returns 0.5 instead of 1/4pi to increase intensity\n float phase_attenuation = 0.5;\n if (abs(anisotropy) > EPSILON){\n phase_attenuation = phase_function(dDotL);\n }\n float vol_shadow = volume_shadow(posIS, normalize(rotateToIDX(vertLight)));\n secondary_contrib += tColor * vDiffuse * lightColor[i] * vol_shadow * phase_attenuation; \n secondary_contrib += tColor * vAmbient;\n } \n return secondary_contrib;\n}\n#endif\n\n//=======================================================================\n// surface light contribution\n#if vtkLightComplexity > 0\n void applyLighting(inout vec3 tColor, vec4 normal)\n {\n vec3 diffuse = vec3(0.0, 0.0, 0.0);\n vec3 specular = vec3(0.0, 0.0, 0.0);\n float df, sf = 0.0;\n for (int i = 0; i < lightNum; i++){\n df = abs(dot(normal.rgb, -lightDirectionVC[i]));\n diffuse += df * lightColor[i];\n sf = pow( abs(dot(lightHalfAngleVC[i],normal.rgb)), vSpecularPower);\n specular += sf * lightColor[i];\n }\n tColor.rgb = tColor.rgb*(diffuse*vDiffuse + vAmbient) + specular*vSpecular;\n }\n #ifdef SurfaceShadowOn\n #if vtkLightComplexity < 3\n vec3 applyLightingDirectional(inout vec3 tColor, vec4 normal)\n {\n // everything in VC\n vec3 diffuse = vec3(0.0);\n vec3 specular = vec3(0.0);\n vec3 vertLightDirection;\n for (int i = 0; i < lightNum; i++){\n float ndotL,vdotR;\n vertLightDirection = lightDirectionVC[i];\n ndotL = dot(normal.xyz, vertLightDirection);\n if (ndotL < 0.0 && twoSidedLighting)\n {\n ndotL = -ndotL;\n }\n if (ndotL > 0.0)\n {\n diffuse += ndotL * lightColor[i];\n //specular\n vdotR = dot(-rayDirVC, normalize(2.0 * ndotL * -normal.xyz + vertLightDirection));\n if (vdotR > 0.0)\n {\n specular += pow(vdotR, vSpecularPower) * lightColor[i];\n }\n }\n } \n return tColor.rgb*(diffuse*vDiffuse + vAmbient) + specular*vSpecular;\n }\n #else\n vec3 applyLightingPositional(inout vec3 tColor, vec4 normal, vec3 posVC)\n {\n // everything in VC\n vec3 diffuse = vec3(0.0);\n vec3 specular = vec3(0.0);\n vec3 vertLightDirection;\n for (int i = 0; i < lightNum; i++){\n float distance,attenuation,ndotL,vdotR;\n vec3 lightDir;\n if (lightPositional[i] == 1){\n lightDir = lightDirectionVC[i];\n vertLightDirection = posVC - lightPositionVC[i]; \n distance = length(vertLightDirection);\n vertLightDirection = normalize(vertLightDirection);\n attenuation = 1.0 / (lightAttenuation[i].x\n + lightAttenuation[i].y * distance\n + lightAttenuation[i].z * distance * distance);\n // per OpenGL standard cone angle is 90 or less for a spot light\n if (lightConeAngle[i] <= 90.0){\n float coneDot = dot(vertLightDirection, lightDir);\n if (coneDot >= cos(radians(lightConeAngle[i]))){ // if inside cone\n attenuation = attenuation * pow(coneDot, lightExponent[i]);\n }\n else {\n attenuation = 0.0;\n }\n }\n ndotL = dot(normal.xyz, vertLightDirection);\n if (ndotL < 0.0 && twoSidedLighting)\n {\n ndotL = -ndotL;\n }\n if (ndotL > 0.0)\n {\n diffuse += ndotL * attenuation * lightColor[i];\n //specular\n vdotR = dot(-rayDirVC, normalize(2.0 * ndotL * -normal.xyz + vertLightDirection));\n if (vdotR > 0.0)\n {\n specular += pow(vdotR, vSpecularPower) * attenuation * lightColor[i];\n }\n }\n } else {\n vertLightDirection = lightDirectionVC[i];\n ndotL = dot(normal.xyz, vertLightDirection);\n if (ndotL < 0.0 && twoSidedLighting)\n {\n ndotL = -ndotL;\n }\n if (ndotL > 0.0)\n {\n diffuse += ndotL * lightColor[i];\n //specular\n vdotR = dot(-rayDirVC, normalize(2.0 * ndotL * -normal.xyz + vertLightDirection));\n if (vdotR > 0.0)\n {\n specular += pow(vdotR, vSpecularPower) * lightColor[i];\n }\n }\n }\n }\n return tColor.rgb * (diffuse * vDiffuse + vAmbient) + specular*vSpecular;\n }\n #endif \n #endif\n#endif\n\n//=======================================================================\n// Given a texture value compute the color and opacity\n//\nvec4 getColorForValue(vec4 tValue, vec3 posIS, vec3 tstep)\n{\n#ifdef vtkImageLabelOutlineOn\n vec3 centerPosIS = fragCoordToIndexSpace(gl_FragCoord); // pos in texture space\n vec4 centerValue = getTextureValue(centerPosIS);\n bool pixelOnBorder = false;\n vec4 tColor = texture2D(ctexture, vec2(centerValue.r * cscale0 + cshift0, 0.5));\n\n // Get alpha of segment from opacity function.\n tColor.a = texture2D(otexture, vec2(centerValue.r * oscale0 + oshift0, 0.5)).r;\n\n // Only perform outline check on fragments rendering voxels that aren't invisible.\n // Saves a bunch of needless checks on the background.\n // TODO define epsilon when building shader?\n if (float(tColor.a) > 0.01) {\n for (int i = -outlineThickness; i <= outlineThickness; i++) {\n for (int j = -outlineThickness; j <= outlineThickness; j++) {\n if (i == 0 || j == 0) {\n continue;\n }\n\n vec4 neighborPixelCoord = vec4(gl_FragCoord.x + float(i),\n gl_FragCoord.y + float(j),\n gl_FragCoord.z, gl_FragCoord.w);\n\n vec3 neighborPosIS = fragCoordToIndexSpace(neighborPixelCoord);\n vec4 value = getTextureValue(neighborPosIS);\n\n // If any of my neighbours are not the same value as I\n // am, this means I am on the border of the segment.\n // We can break the loops\n if (any(notEqual(value, centerValue))) {\n pixelOnBorder = true;\n break;\n }\n }\n\n if (pixelOnBorder == true) {\n break;\n }\n }\n\n // If I am on the border, I am displayed at full opacity\n if (pixelOnBorder == true) {\n tColor.a = 1.0;\n }\n }\n\n#else\n // compute the normal and gradient magnitude if needed\n // We compute it as a vec4 if possible otherwise a mat4\n //\n vec4 goFactor = vec4(1.0,1.0,1.0,1.0);\n\n // compute the normal vectors as needed\n #if (vtkLightComplexity > 0) || defined(vtkGradientOpacityOn)\n #if defined(vtkIndependentComponentsOn) && (vtkNumComponents > 1)\n mat4 normalMat = computeMat4Normal(posIS, tValue, tstep);\n #if !defined(vtkComponent0Proportional)\n vec4 normal0 = normalMat[0];\n #endif\n #if !defined(vtkComponent1Proportional)\n vec4 normal1 = normalMat[1];\n #endif\n #if vtkNumComponents > 2\n #if !defined(vtkComponent2Proportional)\n vec4 normal2 = normalMat[2];\n #endif\n #if vtkNumComponents > 3\n #if !defined(vtkComponent3Proportional)\n vec4 normal3 = normalMat[3];\n #endif\n #endif\n #endif\n #else\n vec4 normalLight;\n #ifdef vtkComputeNormalFromOpacity\n #ifdef vtkGradientOpacityOn\n mat3 scalarInterp; \n vec3 secondaryGradientMag; \n vec4 normal0 = computeNormalForDensity(posIS, tValue.a, tstep, scalarInterp, secondaryGradientMag); \n normalLight = computeDensityNormal(tValue.a, normal0.w, scalarInterp,secondaryGradientMag); \n if (length(normalLight) == 0.0){ \n normalLight = normal0; \n } \n #else\n vec3 scalarInterp; \n vec4 normal0 = computeNormalForDensity(posIS, tValue.a, tstep, scalarInterp); \n if (length(normal0)>0.0){ \n normalLight = computeDensityNormal(tValue.a,scalarInterp); \n if (length(normalLight)==0.0){ \n normalLight = normal0; \n } \n } \n #endif\n #else \n vec4 normal0 = computeNormal(posIS, tValue.a, tstep); \n normalLight = normal0; \n #endif\n #endif\n #endif\n\n // compute gradient opacity factors as needed\n #if defined(vtkGradientOpacityOn)\n #if !defined(vtkComponent0Proportional)\n goFactor.x =\n computeGradientOpacityFactor(normal0.a, goscale0, goshift0, gomin0, gomax0);\n #endif\n #if defined(vtkIndependentComponentsOn) && (vtkNumComponents > 1)\n #if !defined(vtkComponent1Proportional)\n goFactor.y =\n computeGradientOpacityFactor(normal1.a, goscale1, goshift1, gomin1, gomax1);\n #endif\n #if vtkNumComponents > 2\n #if !defined(vtkComponent2Proportional)\n goFactor.z =\n computeGradientOpacityFactor(normal2.a, goscale2, goshift2, gomin2, gomax2);\n #endif\n #if vtkNumComponents > 3\n #if !defined(vtkComponent3Proportional)\n goFactor.w =\n computeGradientOpacityFactor(normal3.a, goscale3, goshift3, gomin3, gomax3);\n #endif\n #endif\n #endif\n #endif\n #endif\n\n // single component is always independent\n #if vtkNumComponents == 1\n vec4 tColor = texture2D(ctexture, vec2(tValue.r * cscale0 + cshift0, 0.5));\n tColor.a = goFactor.x*texture2D(otexture, vec2(tValue.r * oscale0 + oshift0, 0.5)).r;\n if (tColor.a < EPSILON){\n return vec4(0.0);\n } \n #endif\n\n #if defined(vtkIndependentComponentsOn) && vtkNumComponents >= 2\n vec4 tColor = mix0*texture2D(ctexture, vec2(tValue.r * cscale0 + cshift0, height0));\n #if !defined(vtkComponent0Proportional)\n tColor.a = goFactor.x*mix0*texture2D(otexture, vec2(tValue.r * oscale0 + oshift0, height0)).r;\n #else\n float pwfValue = texture2D(otexture, vec2(tValue.r * oscale0 + oshift0, height0)).r;\n tColor *= pwfValue;\n tColor.a *= mix(pwfValue, 1.0, (1.0 - mix0));\n #endif\n\n vec3 tColor1 = mix1*texture2D(ctexture, vec2(tValue.g * cscale1 + cshift1, height1)).rgb;\n #if !defined(vtkComponent1Proportional)\n tColor.a += goFactor.y*mix1*texture2D(otexture, vec2(tValue.g * oscale1 + oshift1, height1)).r;\n #else\n float pwfValue = texture2D(otexture, vec2(tValue.g * oscale1 + oshift1, height1)).r;\n tColor1 *= pwfValue;\n tColor.a *= mix(pwfValue, 1.0, (1.0 - mix1));\n #endif\n\n #if vtkNumComponents >= 3\n vec3 tColor2 = mix2*texture2D(ctexture, vec2(tValue.b * cscale2 + cshift2, height2)).rgb;\n #if !defined(vtkComponent2Proportional)\n tColor.a += goFactor.z*mix2*texture2D(otexture, vec2(tValue.b * oscale2 + oshift2, height2)).r;\n #else\n float pwfValue = texture2D(otexture, vec2(tValue.b * oscale2 + oshift2, height2)).r;\n tColor2 *= pwfValue;\n tColor.a *= mix(pwfValue, 1.0, (1.0 - mix2));\n #endif\n\n #if vtkNumComponents >= 4\n vec3 tColor3 = mix3*texture2D(ctexture, vec2(tValue.a * cscale3 + cshift3, height3)).rgb;\n #if !defined(vtkComponent3Proportional)\n tColor.a += goFactor.w*mix3*texture2D(otexture, vec2(tValue.a * oscale3 + oshift3, height3)).r;\n #else\n float pwfValue = texture2D(otexture, vec2(tValue.a * oscale3 + oshift3, height3)).r;\n tColor3 *= pwfValue;\n tColor.a *= mix(pwfValue, 1.0, (1.0 - mix3));\n #endif\n #endif\n #endif\n #else // then not independent\n\n #if vtkNumComponents == 2\n float lum = tValue.r * cscale0 + cshift0;\n float alpha = goFactor.x*texture2D(otexture, vec2(tValue.a * oscale1 + oshift1, 0.5)).r;\n vec4 tColor = vec4(lum, lum, lum, alpha);\n #endif\n #if vtkNumComponents == 3\n vec4 tColor;\n tColor.r = tValue.r * cscale0 + cshift0;\n tColor.g = tValue.g * cscale1 + cshift1;\n tColor.b = tValue.b * cscale2 + cshift2;\n tColor.a = goFactor.x*texture2D(otexture, vec2(tValue.a * oscale0 + oshift0, 0.5)).r;\n #endif\n #if vtkNumComponents == 4\n vec4 tColor;\n tColor.r = tValue.r * cscale0 + cshift0;\n tColor.g = tValue.g * cscale1 + cshift1;\n tColor.b = tValue.b * cscale2 + cshift2;\n tColor.a = goFactor.x*texture2D(otexture, vec2(tValue.a * oscale3 + oshift3, 0.5)).r;\n #endif\n #endif // dependent\n\n // apply lighting if requested as appropriate\n #if vtkLightComplexity > 0\n #if !defined(vtkComponent0Proportional) \n #if vtkNumComponents == 1\n \n #ifdef SurfaceShadowOn\n #if vtkLightComplexity < 3\n vec3 tColorS = applyLightingDirectional(tColor.rgb, normalLight);\n #else\n vec3 tColorS = applyLightingPositional(tColor.rgb, normalLight, IStoVC(posIS));\n #endif\n #endif\n\n #ifdef VolumeShadowOn\n vec3 tColorVS = applyShadowRay(tColor.rgb, posIS, rayDirVC);\n #ifdef SurfaceShadowOn\n float vol_coef = volumetricScatteringBlending * (1.0 - tColor.a / 2.0) * (1.0 - atan(normalLight.w) * INV4PI);\n tColor.rgb = (1.0-vol_coef) * tColorS + vol_coef * tColorVS;\n #else\n tColor.rgb = tColorVS;\n #endif\n #else\n tColor.rgb = tColorS;\n #endif\n \n #else\n applyLighting(tColor.rgb, normal0);\n #endif\n #endif\n\n #if defined(vtkIndependentComponentsOn) && vtkNumComponents >= 2\n #if !defined(vtkComponent1Proportional)\n applyLighting(tColor1, normal1);\n #endif\n #if vtkNumComponents >= 3\n #if !defined(vtkComponent2Proportional)\n applyLighting(tColor2, normal2);\n #endif\n #if vtkNumComponents >= 4\n #if !defined(vtkComponent3Proportional)\n applyLighting(tColor3, normal3);\n #endif\n #endif\n #endif\n #endif\n #endif\n\n// perform final independent blend as needed\n#if defined(vtkIndependentComponentsOn) && vtkNumComponents >= 2\n tColor.rgb += tColor1;\n#if vtkNumComponents >= 3\n tColor.rgb += tColor2;\n#if vtkNumComponents >= 4\n tColor.rgb += tColor3;\n#endif\n#endif\n#endif\n\n#endif\n\n\n\n\n\n\n\nreturn tColor;\n}\n\nbool valueWithinScalarRange(vec4 val, vec4 min, vec4 max) {\n bool withinRange = false;\n #if vtkNumComponents == 1\n if (val.r >= min.r && val.r <= max.r) {\n withinRange = true;\n }\n #endif\n #if defined(vtkIndependentComponentsOn) && vtkNumComponents == 2\n if (val.r >= min.r && val.r <= max.r &&\n val.g >= min.g && val.g <= max.g) {\n withinRange = true;\n }\n #endif\n #if defined(vtkIndependentComponentsOn) && vtkNumComponents >= 3\n if (all(greaterThanEqual(val, ipScalarRangeMin)) &&\n all(lessThanEqual(val, ipScalarRangeMax))) {\n withinRange = true;\n }\n #endif\n return withinRange;\n}\n\n//=======================================================================\n// Apply the specified blend mode operation along the ray's path.\n//\nvoid applyBlend(vec3 posIS, vec3 endIS, float sampleDistanceIS, vec3 tdims)\n{\n vec3 tstep = 1.0/tdims;\n\n // start slightly inside and apply some jitter\n vec3 delta = endIS - posIS;\n vec3 stepIS = normalize(delta)*sampleDistanceIS;\n float raySteps = length(delta)/sampleDistanceIS;\n\n // avoid 0.0 jitter\n float jitter = 0.01 + 0.99*texture2D(jtexture, gl_FragCoord.xy/32.0).r;\n float stepsTraveled = jitter;\n\n // local vars for the loop\n vec4 color = vec4(0.0, 0.0, 0.0, 0.0);\n vec4 tValue;\n vec4 tColor;\n\n // if we have less than one step then pick the middle point\n // as our value\n // if (raySteps <= 1.0)\n // {\n // posIS = (posIS + endIS)*0.5;\n // }\n\n // Perform initial step at the volume boundary\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n #if vtkBlendMode == 0 // COMPOSITE_BLEND\n // now map through opacity and color\n tColor = getColorForValue(tValue, posIS, tstep);\n\n // handle very thin volumes\n if (raySteps <= 1.0)\n {\n tColor.a = 1.0 - pow(1.0 - tColor.a, raySteps);\n gl_FragData[0] = tColor;\n return;\n }\n\n tColor.a = 1.0 - pow(1.0 - tColor.a, jitter);\n color = vec4(tColor.rgb*tColor.a, tColor.a);\n posIS += (jitter*stepIS);\n\n for (int i = 0; i < //VTK::MaximumSamplesValue ; ++i)\n {\n if (stepsTraveled + 1.0 >= raySteps) { break; }\n\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n // now map through opacity and color\n tColor = getColorForValue(tValue, posIS, tstep);\n\n float mix = (1.0 - color.a);\n\n // this line should not be needed but nvidia seems to not handle\n // the break correctly on windows/chrome 58 angle\n //mix = mix * sign(max(raySteps - stepsTraveled - 1.0, 0.0));\n\n color = color + vec4(tColor.rgb*tColor.a, tColor.a)*mix;\n stepsTraveled++;\n posIS += stepIS;\n if (color.a > 0.99) { color.a = 1.0; break; }\n }\n\n if (color.a < 0.99 && (raySteps - stepsTraveled) > 0.0)\n {\n posIS = endIS;\n\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n // now map through opacity and color\n tColor = getColorForValue(tValue, posIS, tstep);\n tColor.a = 1.0 - pow(1.0 - tColor.a, raySteps - stepsTraveled);\n\n float mix = (1.0 - color.a);\n color = color + vec4(tColor.rgb*tColor.a, tColor.a)*mix;\n }\n\n gl_FragData[0] = vec4(color.rgb/color.a, color.a);\n #endif\n #if vtkBlendMode == 1 || vtkBlendMode == 2\n // MAXIMUM_INTENSITY_BLEND || MINIMUM_INTENSITY_BLEND\n // Find maximum/minimum intensity along the ray.\n\n // Define the operation we will use (min or max)\n #if vtkBlendMode == 1\n #define OP max\n #else\n #define OP min\n #endif\n\n // If the clipping range is shorter than the sample distance\n // we can skip the sampling loop along the ray.\n if (raySteps <= 1.0)\n {\n gl_FragData[0] = getColorForValue(tValue, posIS, tstep);\n return;\n }\n\n vec4 value = tValue;\n posIS += (jitter*stepIS);\n\n // Sample along the ray until MaximumSamplesValue,\n // ending slightly inside the total distance\n for (int i = 0; i < //VTK::MaximumSamplesValue ; ++i)\n {\n // If we have reached the last step, break\n if (stepsTraveled + 1.0 >= raySteps) { break; }\n\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n // Update the maximum value if necessary\n value = OP(tValue, value);\n\n // Otherwise, continue along the ray\n stepsTraveled++;\n posIS += stepIS;\n }\n\n // Perform the last step along the ray using the\n // residual distance\n posIS = endIS;\n tValue = getTextureValue(posIS);\n value = OP(tValue, value);\n\n // Now map through opacity and color\n gl_FragData[0] = getColorForValue(value, posIS, tstep);\n #endif\n #if vtkBlendMode == 3 || vtkBlendMode == 4 //AVERAGE_INTENSITY_BLEND || ADDITIVE_BLEND\n vec4 sum = vec4(0.);\n\n if (valueWithinScalarRange(tValue, ipScalarRangeMin, ipScalarRangeMax)) {\n sum += tValue;\n }\n\n if (raySteps <= 1.0) {\n gl_FragData[0] = getColorForValue(sum, posIS, tstep);\n return;\n }\n\n posIS += (jitter*stepIS);\n\n // Sample along the ray until MaximumSamplesValue,\n // ending slightly inside the total distance\n for (int i = 0; i < //VTK::MaximumSamplesValue ; ++i)\n {\n // If we have reached the last step, break\n if (stepsTraveled + 1.0 >= raySteps) { break; }\n\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n // One can control the scalar range by setting the AverageIPScalarRange to disregard scalar values, not in the range of interest, from the average computation.\n // Notes:\n // - We are comparing all values in the texture to see if any of them\n // are outside of the scalar range. In the future we might want to allow\n // scalar ranges for each component.\n if (valueWithinScalarRange(tValue, ipScalarRangeMin, ipScalarRangeMax)) {\n // Sum the values across each step in the path\n sum += tValue;\n }\n stepsTraveled++;\n posIS += stepIS;\n }\n\n // Perform the last step along the ray using the\n // residual distance\n posIS = endIS;\n\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n // One can control the scalar range by setting the IPScalarRange to disregard scalar values, not in the range of interest, from the average computation\n if (valueWithinScalarRange(tValue, ipScalarRangeMin, ipScalarRangeMax)) {\n sum += tValue;\n\n stepsTraveled++;\n }\n\n #if vtkBlendMode == 3 // Average\n sum /= vec4(stepsTraveled, stepsTraveled, stepsTraveled, 1.0);\n #endif\n\n gl_FragData[0] = getColorForValue(sum, posIS, tstep);\n #endif\n}\n\n//=======================================================================\n// Compute a new start and end point for a given ray based\n// on the provided bounded clipping plane (aka a rectangle)\nvoid getRayPointIntersectionBounds(\n vec3 rayPos, vec3 rayDir,\n vec3 planeDir, float planeDist,\n inout vec2 tbounds, vec3 vPlaneX, vec3 vPlaneY,\n float vSize1, float vSize2)\n{\n float result = dot(rayDir, planeDir);\n if (abs(result) < 1e-6)\n {\n return;\n }\n result = -1.0 * (dot(rayPos, planeDir) + planeDist) / result;\n vec3 xposVC = rayPos + rayDir*result;\n vec3 vxpos = xposVC - vOriginVC;\n vec2 vpos = vec2(\n dot(vxpos, vPlaneX),\n dot(vxpos, vPlaneY));\n\n // on some apple nvidia systems this does not work\n // if (vpos.x < 0.0 || vpos.x > vSize1 ||\n // vpos.y < 0.0 || vpos.y > vSize2)\n // even just\n // if (vpos.x < 0.0 || vpos.y < 0.0)\n // fails\n // so instead we compute a value that represents in and out\n //and then compute the return using this value\n float xcheck = max(0.0, vpos.x * (vpos.x - vSize1)); // 0 means in bounds\n float check = sign(max(xcheck, vpos.y * (vpos.y - vSize2))); // 0 means in bounds, 1 = out\n\n tbounds = mix(\n vec2(min(tbounds.x, result), max(tbounds.y, result)), // in value\n tbounds, // out value\n check); // 0 in 1 out\n}\n\n//=======================================================================\n// given a\n// - ray direction (rayDir)\n// - starting point (vertexVCVSOutput)\n// - bounding planes of the volume\n// - optionally depth buffer values\n// - far clipping plane\n// compute the start/end distances of the ray we need to cast\nvec2 computeRayDistances(vec3 rayDir, vec3 tdims)\n{\n vec2 dists = vec2(100.0*camFar, -1.0);\n\n vec3 vSize = vSpacing*tdims;\n\n // all this is in View Coordinates\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal0, vPlaneDistance0, dists, vPlaneNormal2, vPlaneNormal4,\n vSize.y, vSize.z);\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal1, vPlaneDistance1, dists, vPlaneNormal2, vPlaneNormal4,\n vSize.y, vSize.z);\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal2, vPlaneDistance2, dists, vPlaneNormal0, vPlaneNormal4,\n vSize.x, vSize.z);\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal3, vPlaneDistance3, dists, vPlaneNormal0, vPlaneNormal4,\n vSize.x, vSize.z);\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal4, vPlaneDistance4, dists, vPlaneNormal0, vPlaneNormal2,\n vSize.x, vSize.y);\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal5, vPlaneDistance5, dists, vPlaneNormal0, vPlaneNormal2,\n vSize.x, vSize.y);\n\n //VTK::ClipPlane::Impl\n\n // do not go behind front clipping plane\n dists.x = max(0.0,dists.x);\n\n // do not go PAST far clipping plane\n float farDist = -camThick/rayDir.z;\n dists.y = min(farDist,dists.y);\n\n // Do not go past the zbuffer value if set\n // This is used for intermixing opaque geometry\n //VTK::ZBuffer::Impl\n\n return dists;\n}\n\n//=======================================================================\n// Compute the index space starting position (pos) and end\n// position\n//\nvoid computeIndexSpaceValues(out vec3 pos, out vec3 endPos, out float sampleDistanceIS, vec3 rayDir, vec2 dists)\n{\n // compute starting and ending values in volume space\n pos = vertexVCVSOutput + dists.x*rayDir;\n pos = pos - vOriginVC;\n // convert to volume basis and origin\n pos = vec3(\n dot(pos, vPlaneNormal0),\n dot(pos, vPlaneNormal2),\n dot(pos, vPlaneNormal4));\n\n endPos = vertexVCVSOutput + dists.y*rayDir;\n endPos = endPos - vOriginVC;\n endPos = vec3(\n dot(endPos, vPlaneNormal0),\n dot(endPos, vPlaneNormal2),\n dot(endPos, vPlaneNormal4));\n\n float delta = length(endPos - pos);\n\n pos *= vVCToIJK;\n endPos *= vVCToIJK;\n\n float delta2 = length(endPos - pos);\n sampleDistanceIS = sampleDistance*delta2/delta;\n #ifdef VolumeShadowOn\n sampleDistanceISVS = sampleDistanceIS * volumeShadowSamplingDistFactor;\n #endif\n}\n\nvoid main()\n{\n\n if (cameraParallel == 1)\n {\n // Camera is parallel, so the rayDir is just the direction of the camera.\n rayDirVC = vec3(0.0, 0.0, -1.0);\n } else {\n // camera is at 0,0,0 so rayDir for perspective is just the vc coord\n rayDirVC = normalize(vertexVCVSOutput);\n }\n\n vec3 tdims = vec3(volumeDimensions);\n\n // compute the start and end points for the ray\n vec2 rayStartEndDistancesVC = computeRayDistances(rayDirVC, tdims);\n\n // do we need to composite? aka does the ray have any length\n // If not, bail out early\n if (rayStartEndDistancesVC.y <= rayStartEndDistancesVC.x)\n {\n discard;\n }\n\n // IS = Index Space\n vec3 posIS;\n vec3 endIS;\n float sampleDistanceIS;\n computeIndexSpaceValues(posIS, endIS, sampleDistanceIS, rayDirVC, rayStartEndDistancesVC);\n\n // Perform the blending operation along the ray\n applyBlend(posIS, endIS, sampleDistanceIS, tdims);\n}\n";
1
+ var vtkVolumeFS = "//VTK::System::Dec\n\n/*=========================================================================\n\n Program: Visualization Toolkit\n Module: vtkVolumeFS.glsl\n\n Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen\n All rights reserved.\n See Copyright.txt or http://www.kitware.com/Copyright.htm for details.\n\n This software is distributed WITHOUT ANY WARRANTY; without even\n the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR\n PURPOSE. See the above copyright notice for more information.\n\n=========================================================================*/\n// Template for the volume mappers fragment shader\n\n// the output of this shader\n//VTK::Output::Dec\n\nvarying vec3 vertexVCVSOutput;\n\n// first declare the settings from the mapper\n// that impact the code paths in here\n\n// always set vtkNumComponents 1,2,3,4\n//VTK::NumComponents\n\n// possibly define vtkTrilinearOn\n//VTK::TrilinearOn\n\n// possibly define vtkIndependentComponents\n//VTK::IndependentComponentsOn\n\n// possibly define any \"proportional\" components\n//VTK::vtkProportionalComponents\n\n// Define the blend mode to use\n#define vtkBlendMode //VTK::BlendMode\n\n// Possibly define vtkImageLabelOutlineOn\n//VTK::ImageLabelOutlineOn\n\n#ifdef vtkImageLabelOutlineOn\nuniform int outlineThickness;\nuniform float vpWidth;\nuniform float vpHeight;\nuniform float vpOffsetX;\nuniform float vpOffsetY;\nuniform mat4 PCWCMatrix;\nuniform mat4 vWCtoIDX;\n#endif\n\n// define vtkLightComplexity\n//VTK::LightComplexity\n#if vtkLightComplexity > 0\nuniform float vSpecularPower;\nuniform float vAmbient;\nuniform float vDiffuse;\nuniform float vSpecular;\n//VTK::Light::Dec\n#endif\n\n//VTK::VolumeShadowOn\n//VTK::SurfaceShadowOn\n//VTK::localAmbientOcclusionOn\n//VTK::LAO::Dec\n//VTK::VolumeShadow::Dec\n\n// define vtkComputeNormalFromOpacity\n//VTK::vtkComputeNormalFromOpacity\n\n// possibly define vtkGradientOpacityOn\n//VTK::GradientOpacityOn\n#ifdef vtkGradientOpacityOn\nuniform float goscale0;\nuniform float goshift0;\nuniform float gomin0;\nuniform float gomax0;\n#if defined(vtkIndependentComponentsOn) && (vtkNumComponents > 1)\nuniform float goscale1;\nuniform float goshift1;\nuniform float gomin1;\nuniform float gomax1;\n#if vtkNumComponents >= 3\nuniform float goscale2;\nuniform float goshift2;\nuniform float gomin2;\nuniform float gomax2;\n#endif\n#if vtkNumComponents >= 4\nuniform float goscale3;\nuniform float goshift3;\nuniform float gomin3;\nuniform float gomax3;\n#endif\n#endif\n#endif\n\n// if you want to see the raw tiled\n// data in webgl1 uncomment the following line\n// #define debugtile\n\n// camera values\nuniform float camThick;\nuniform float camNear;\nuniform float camFar;\nuniform int cameraParallel;\n\n// values describing the volume geometry\nuniform vec3 vOriginVC;\nuniform vec3 vSpacing;\nuniform ivec3 volumeDimensions; // 3d texture dimensions\nuniform vec3 vPlaneNormal0;\nuniform float vPlaneDistance0;\nuniform vec3 vPlaneNormal1;\nuniform float vPlaneDistance1;\nuniform vec3 vPlaneNormal2;\nuniform float vPlaneDistance2;\nuniform vec3 vPlaneNormal3;\nuniform float vPlaneDistance3;\nuniform vec3 vPlaneNormal4;\nuniform float vPlaneDistance4;\nuniform vec3 vPlaneNormal5;\nuniform float vPlaneDistance5;\n\n//VTK::ClipPlane::Dec\n\n// opacity and color textures\nuniform sampler2D otexture;\nuniform float oshift0;\nuniform float oscale0;\nuniform sampler2D ctexture;\nuniform float cshift0;\nuniform float cscale0;\n\n// jitter texture\nuniform sampler2D jtexture;\n\n// some 3D texture values\nuniform float sampleDistance;\nuniform vec3 vVCToIJK;\n\n// the heights defined below are the locations\n// for the up to four components of the tfuns\n// the tfuns have a height of 2XnumComps pixels so the\n// values are computed to hit the middle of the two rows\n// for that component\n#ifdef vtkIndependentComponentsOn\n#if vtkNumComponents == 2\nuniform float mix0;\nuniform float mix1;\n#define height0 0.25\n#define height1 0.75\n#endif\n#if vtkNumComponents == 3\nuniform float mix0;\nuniform float mix1;\nuniform float mix2;\n#define height0 0.17\n#define height1 0.5\n#define height2 0.83\n#endif\n#if vtkNumComponents == 4\nuniform float mix0;\nuniform float mix1;\nuniform float mix2;\nuniform float mix3;\n#define height0 0.125\n#define height1 0.375\n#define height2 0.625\n#define height3 0.875\n#endif\n#endif\n\n#if vtkNumComponents >= 2\nuniform float oshift1;\nuniform float oscale1;\nuniform float cshift1;\nuniform float cscale1;\n#endif\n#if vtkNumComponents >= 3\nuniform float oshift2;\nuniform float oscale2;\nuniform float cshift2;\nuniform float cscale2;\n#endif\n#if vtkNumComponents >= 4\nuniform float oshift3;\nuniform float oscale3;\nuniform float cshift3;\nuniform float cscale3;\n#endif\n\nuniform vec4 ipScalarRangeMin;\nuniform vec4 ipScalarRangeMax;\n\n// declaration for intermixed geometry\n//VTK::ZBuffer::Dec\n\n//=======================================================================\n// global and custom variables (a temporary section before photorealistics rendering module is complete)\nvec3 rayDirVC;\nfloat sampleDistanceISVS;\nfloat sampleDistanceIS;\n\n#define SQRT3 1.7321\n#define INV4PI 0.0796\n#define EPSILON 0.001\n#define PI 3.1415\n#define PI2 9.8696\n\n//=======================================================================\n// Webgl2 specific version of functions\n#if __VERSION__ == 300\n\nuniform highp sampler3D texture1;\n\nvec4 getTextureValue(vec3 pos)\n{\n vec4 tmp = texture(texture1, pos);\n#if vtkNumComponents == 1\n tmp.a = tmp.r;\n#endif\n#if vtkNumComponents == 2\n tmp.a = tmp.g;\n#endif\n#if vtkNumComponents == 3\n tmp.a = length(tmp.rgb);\n#endif\n return tmp;\n}\n\n//=======================================================================\n// WebGL1 specific version of functions\n#else\n\nuniform sampler2D texture1;\n\nuniform float texWidth;\nuniform float texHeight;\nuniform int xreps;\nuniform int xstride;\nuniform int ystride;\n\n// if computing trilinear values from multiple z slices\n#ifdef vtkTrilinearOn\nvec4 getTextureValue(vec3 ijk)\n{\n float zoff = 1.0/float(volumeDimensions.z);\n vec4 val1 = getOneTextureValue(ijk);\n vec4 val2 = getOneTextureValue(vec3(ijk.xy, ijk.z + zoff));\n\n float indexZ = float(volumeDimensions)*ijk.z;\n float zmix = indexZ - floor(indexZ);\n\n return mix(val1, val2, zmix);\n}\n\nvec4 getOneTextureValue(vec3 ijk)\n#else // nearest or fast linear\nvec4 getTextureValue(vec3 ijk)\n#endif\n{\n vec3 tdims = vec3(volumeDimensions);\n\n#ifdef debugtile\n vec2 tpos = vec2(ijk.x, ijk.y);\n vec4 tmp = texture2D(texture1, tpos);\n tmp.a = 1.0;\n\n#else\n int z = int(ijk.z * tdims.z);\n int yz = z / xreps;\n int xz = z - yz*xreps;\n\n int tileWidth = volumeDimensions.x/xstride;\n int tileHeight = volumeDimensions.y/ystride;\n\n xz *= tileWidth;\n yz *= tileHeight;\n\n float ni = float(xz) + (ijk.x*float(tileWidth));\n float nj = float(yz) + (ijk.y*float(tileHeight));\n\n vec2 tpos = vec2(ni/texWidth, nj/texHeight);\n\n vec4 tmp = texture2D(texture1, tpos);\n\n#if vtkNumComponents == 1\n tmp.a = tmp.r;\n#endif\n#if vtkNumComponents == 2\n tmp.g = tmp.a;\n#endif\n#if vtkNumComponents == 3\n tmp.a = length(tmp.rgb);\n#endif\n#endif\n\n return tmp;\n}\n\n// End of Webgl1 specific code\n//=======================================================================\n#endif\n\n//=======================================================================\n// transformation between VC and IS space\n\n// convert vector position from idx to vc\n#if vtkLightComplexity > 0\nvec3 IStoVC(vec3 posIS){\n vec3 posVC = posIS / vVCToIJK;\n return posVC.x * vPlaneNormal0 + \n posVC.y * vPlaneNormal2 + \n posVC.z * vPlaneNormal4 + \n vOriginVC;\n}\n\n// convert vector position from vc to idx\nvec3 VCtoIS(vec3 posVC){\n posVC = posVC - vOriginVC;\n posVC = vec3(\n dot(posVC, vPlaneNormal0),\n dot(posVC, vPlaneNormal2),\n dot(posVC, vPlaneNormal4)); \n return posVC * vVCToIJK;\n}\n#endif\n\n//Rotate vector to view coordinate\n#if (vtkLightComplexity > 0) || (defined vtkGradientOpacityOn)\nvoid rotateToViewCoord(inout vec3 dirIS){\n dirIS.xyz =\n dirIS.x * vPlaneNormal0 +\n dirIS.y * vPlaneNormal2 +\n dirIS.z * vPlaneNormal4;\n}\n\n//Rotate vector to idx coordinate\nvec3 rotateToIDX(vec3 dirVC){\n vec3 dirIS;\n dirIS.xyz = vec3(\n dot(dirVC, vPlaneNormal0),\n dot(dirVC, vPlaneNormal2),\n dot(dirVC, vPlaneNormal4)); \n return dirIS;\n}\n#endif\n\n//=======================================================================\n// Given a normal compute the gradient opacity factors\nfloat computeGradientOpacityFactor(\n float normalMag, float goscale, float goshift, float gomin, float gomax)\n{\n#if defined(vtkGradientOpacityOn)\n return clamp(normalMag * goscale + goshift, gomin, gomax);\n#else\n return 1.0;\n#endif\n}\n\n//=======================================================================\n// compute the normal and gradient magnitude for a position, uses forward difference\n#if (vtkLightComplexity > 0) || (defined vtkGradientOpacityOn)\n #ifdef vtkComputeNormalFromOpacity\n #ifdef vtkGradientOpacityOn\n vec4 computeNormalForDensity(vec3 pos, float scalar, vec3 tstep, out mat3 scalarInterp, out vec3 secondaryGradientMag)\n {\n vec4 result;\n scalarInterp[0][0] = getTextureValue(pos + vec3(tstep.x, 0.0, 0.0)).a;\n scalarInterp[0][1] = getTextureValue(pos + vec3(0.0, tstep.y, 0.0)).a;\n scalarInterp[0][2] = getTextureValue(pos + vec3(0.0, 0.0, tstep.z)).a;\n // look up scalar values for computing secondary gradient\n scalarInterp[1][0] = getTextureValue(pos + vec3(2.0*tstep.x, 0.0, 0.0)).a;\n scalarInterp[1][1] = getTextureValue(pos + vec3(0.0, 2.0*tstep.y, 0.0)).a;\n scalarInterp[1][2] = getTextureValue(pos + vec3(0.0, 0.0, 2.0*tstep.z)).a;\n scalarInterp[2][0] = getTextureValue(pos + vec3(tstep.x, tstep.y, 0.0)).a;\n scalarInterp[2][1] = getTextureValue(pos + vec3(tstep.x, 0.0, tstep.z)).a;\n scalarInterp[2][2] = getTextureValue(pos + vec3(0.0, tstep.y, tstep.z)).a;\n result.x = scalarInterp[0][0] - scalar;\n result.y = scalarInterp[0][1] - scalar;\n result.z = scalarInterp[0][2] - scalar;\n // divide by spacing\n result.xyz /= vSpacing;\n result.w = length(result.xyz);\n rotateToViewCoord(result.xyz);\n secondaryGradientMag.x = length(vec3(scalarInterp[1][0] - scalarInterp[0][0],\n scalarInterp[2][0] - scalarInterp[0][0],\n scalarInterp[2][1] - scalarInterp[0][0]) / vSpacing);\n secondaryGradientMag.y = length(vec3(scalarInterp[2][0] - scalarInterp[0][1],\n scalarInterp[1][1] - scalarInterp[0][1],\n scalarInterp[2][2] - scalarInterp[0][1]) / vSpacing);\n secondaryGradientMag.z = length(vec3(scalarInterp[2][1] - scalarInterp[0][2],\n scalarInterp[2][2] - scalarInterp[0][2],\n scalarInterp[1][2] - scalarInterp[0][2]) / vSpacing);\n if (length(result.xyz) > 0.0) {\n return vec4(normalize(result.xyz),result.w);\n } else {\n return vec4(0.0);\n }\n }\n\n vec4 computeDensityNormal(float scalar, float gradientMag, mat3 scalarInterp, vec3 secondaryGradientMag)\n {\n vec4 opacityG;\n vec3 opacityInterp = vec3(0.0);\n float opacity = texture2D(otexture, vec2(scalar * oscale0 + oshift0, 0.5)).r;\n if (gradientMag >= 0.0){\n opacity *= computeGradientOpacityFactor(gradientMag, goscale0, goshift0, gomin0, gomax0);\n }\n opacityInterp.x = texture2D(otexture, vec2(scalarInterp[0][0] * oscale0 + oshift0, 0.5)).r; \n if (secondaryGradientMag.x >= 0.0){\n opacityInterp.x *= computeGradientOpacityFactor(secondaryGradientMag.x, goscale0, goshift0, gomin0, gomax0);\n }\n \n opacityInterp.y = texture2D(otexture, vec2(scalarInterp[0][1] * oscale0 + oshift0, 0.5)).r;\n if (secondaryGradientMag.y >= 0.0){\n opacityInterp.y *= computeGradientOpacityFactor(secondaryGradientMag.y, goscale0, goshift0, gomin0, gomax0);\n }\n\n opacityInterp.z = texture2D(otexture, vec2(scalarInterp[0][2] * oscale0 + oshift0, 0.5)).r;\n if (secondaryGradientMag.z >= 0.0){\n opacityInterp.z *= computeGradientOpacityFactor(secondaryGradientMag.z, goscale0, goshift0, gomin0, gomax0);\n }\n\n opacityG.xyz = opacityInterp - vec3(opacity,opacity,opacity);\n // divide by spacing\n opacityG.xyz /= vSpacing;\n opacityG.w = length(opacityG.xyz);\n rotateToViewCoord(opacityG.xyz);\n if (length(opacityG.xyz) > 0.0) { \n return vec4(normalize(opacityG.xyz),opacityG.w);\n } else {\n return vec4(0.0);\n }\n } \n\n #else\n //if gradient opacity not on but using density gradient\n vec4 computeDensityNormal(float scalar, vec3 scalarInterp) \n { \n vec4 opacityG; \n float opacity = texture2D(otexture, vec2(scalar * oscale0 + oshift0, 0.5)).r; \n opacityG.x = texture2D(otexture, vec2(scalarInterp.x * oscale0 + oshift0, 0.5)).r - opacity; \n opacityG.y = texture2D(otexture, vec2(scalarInterp.y * oscale0 + oshift0, 0.5)).r - opacity; \n opacityG.z = texture2D(otexture, vec2(scalarInterp.z * oscale0 + oshift0, 0.5)).r - opacity; \n // divide by spacing \n opacityG.xyz /= vSpacing; \n opacityG.w = length(opacityG.xyz); \n // rotate to View Coords \n rotateToViewCoord(opacityG.xyz);\n if (length(opacityG.xyz) > 0.0) { \n return vec4(normalize(opacityG.xyz),opacityG.w); \n } else { \n return vec4(0.0); \n } \n } \n vec4 computeNormalForDensity(vec3 pos, float scalar, vec3 tstep, out vec3 scalarInterp) \n { \n vec4 result; \n scalarInterp.x = getTextureValue(pos + vec3(tstep.x, 0.0, 0.0)).a; \n scalarInterp.y = getTextureValue(pos + vec3(0.0, tstep.y, 0.0)).a; \n scalarInterp.z = getTextureValue(pos + vec3(0.0, 0.0, tstep.z)).a; \n result.x = scalarInterp.x - scalar; \n result.y = scalarInterp.y - scalar; \n result.z = scalarInterp.z - scalar; \n // divide by spacing\n result.xyz /= vSpacing;\n result.w = length(result.xyz); \n // rotate to View Coords \n rotateToViewCoord(result.xyz); \n if (length(result.xyz) > 0.0) { \n return vec4(normalize(result.xyz),result.w); \n } else { \n return vec4(0.0); \n } \n } \n #endif\n #endif\n // compute scalar density\n vec4 computeNormal(vec3 pos, float scalar, vec3 tstep) \n { \n vec4 result; \n result.x = getTextureValue(pos + vec3(tstep.x, 0.0, 0.0)).a - scalar; \n result.y = getTextureValue(pos + vec3(0.0, tstep.y, 0.0)).a - scalar; \n result.z = getTextureValue(pos + vec3(0.0, 0.0, tstep.z)).a - scalar; \n // divide by spacing \n result.xyz /= vSpacing; \n result.w = length(result.xyz);\n if (result.w > 0.0){\n // rotate to View Coords \n rotateToViewCoord(result.xyz);\n return vec4(normalize(result.xyz),result.w); \n } else {\n return vec4(0.0);\n }\n } \n#endif\n\n#ifdef vtkImageLabelOutlineOn\nvec3 fragCoordToIndexSpace(vec4 fragCoord) {\n vec4 pcPos = vec4(\n (fragCoord.x / vpWidth - vpOffsetX - 0.5) * 2.0,\n (fragCoord.y / vpHeight - vpOffsetY - 0.5) * 2.0,\n (fragCoord.z - 0.5) * 2.0,\n 1.0);\n\n vec4 worldCoord = PCWCMatrix * pcPos;\n vec4 vertex = (worldCoord/worldCoord.w);\n\n vec3 index = (vWCtoIDX * vertex).xyz;\n\n // half voxel fix for labelmapOutline \n return (index + vec3(0.5)) / vec3(volumeDimensions);\n}\n#endif\n\n//=======================================================================\n// compute the normals and gradient magnitudes for a position\n// for independent components\nmat4 computeMat4Normal(vec3 pos, vec4 tValue, vec3 tstep)\n{\n mat4 result;\n vec4 distX = getTextureValue(pos + vec3(tstep.x, 0.0, 0.0)) - tValue;\n vec4 distY = getTextureValue(pos + vec3(0.0, tstep.y, 0.0)) - tValue;\n vec4 distZ = getTextureValue(pos + vec3(0.0, 0.0, tstep.z)) - tValue;\n\n // divide by spacing\n distX /= vSpacing.x;\n distY /= vSpacing.y;\n distZ /= vSpacing.z;\n\n mat3 rot;\n rot[0] = vPlaneNormal0;\n rot[1] = vPlaneNormal2;\n rot[2] = vPlaneNormal4;\n\n#if !defined(vtkComponent0Proportional)\n result[0].xyz = vec3(distX.r, distY.r, distZ.r);\n result[0].a = length(result[0].xyz);\n result[0].xyz *= rot;\n if (result[0].w > 0.0)\n {\n result[0].xyz /= result[0].w;\n }\n#endif\n\n// optionally compute the 2nd component\n#if vtkNumComponents >= 2 && !defined(vtkComponent1Proportional)\n result[1].xyz = vec3(distX.g, distY.g, distZ.g);\n result[1].a = length(result[1].xyz);\n result[1].xyz *= rot;\n if (result[1].w > 0.0)\n {\n result[1].xyz /= result[1].w;\n }\n#endif\n\n// optionally compute the 3rd component\n#if vtkNumComponents >= 3 && !defined(vtkComponent2Proportional)\n result[2].xyz = vec3(distX.b, distY.b, distZ.b);\n result[2].a = length(result[2].xyz);\n result[2].xyz *= rot;\n if (result[2].w > 0.0)\n {\n result[2].xyz /= result[2].w;\n }\n#endif\n\n// optionally compute the 4th component\n#if vtkNumComponents >= 4 && !defined(vtkComponent3Proportional)\n result[3].xyz = vec3(distX.a, distY.a, distZ.a);\n result[3].a = length(result[3].xyz);\n result[3].xyz *= rot;\n if (result[3].w > 0.0)\n {\n result[3].xyz /= result[3].w;\n }\n#endif\n\n return result;\n}\n\n//=======================================================================\n// global shadow - secondary ray\n#if defined(VolumeShadowOn) || defined(localAmbientOcclusionOn)\nfloat random()\n{ \n float rand = fract(sin(dot(gl_FragCoord.xy,vec2(12.9898,78.233)))*43758.5453123);\n float jitter=texture2D(jtexture,gl_FragCoord.xy/32.).r;\n uint pcg_state = floatBitsToUint(jitter);\n uint state = pcg_state;\n pcg_state = pcg_state * uint(747796405) + uint(2891336453);\n uint word = ((state >> ((state >> uint(28)) + uint(4))) ^ state) * uint(277803737);\n return (float((((word >> uint(22)) ^ word) >> 1 ))/float(2147483647) + rand)/2.0;\n}\n#endif\n\n#ifdef VolumeShadowOn\n// henyey greenstein phase function\nfloat phase_function(float cos_angle)\n{\n // divide by 2.0 instead of 4pi to increase intensity\n return ((1.0-anisotropy2)/pow(1.0+anisotropy2-2.0*anisotropy*cos_angle, 1.5))/2.0;\n}\n\n// Computes the intersection between a ray and a box\nstruct Hit\n{\n float tmin;\n float tmax;\n};\n\nstruct Ray\n{\n vec3 origin;\n vec3 dir;\n vec3 invDir;\n};\n\nbool BBoxIntersect(vec3 boundMin, vec3 boundMax, const Ray r, out Hit hit)\n{\n vec3 tbot = r.invDir * (boundMin - r.origin);\n vec3 ttop = r.invDir * (boundMax - r.origin);\n vec3 tmin = min(ttop, tbot);\n vec3 tmax = max(ttop, tbot);\n vec2 t = max(tmin.xx, tmin.yz);\n float t0 = max(t.x, t.y);\n t = min(tmax.xx, tmax.yz);\n float t1 = min(t.x, t.y);\n hit.tmin = t0;\n hit.tmax = t1;\n return t1 > max(t0,0.0);\n}\n\n// As BBoxIntersect requires the inverse of the ray coords,\n// this function is used to avoid numerical issues\nvoid safe_0_vector(inout Ray ray)\n{\n if(abs(ray.dir.x) < EPSILON) ray.dir.x = sign(ray.dir.x) * EPSILON;\n if(abs(ray.dir.y) < EPSILON) ray.dir.y = sign(ray.dir.y) * EPSILON;\n if(abs(ray.dir.z) < EPSILON) ray.dir.z = sign(ray.dir.z) * EPSILON;\n}\n\nfloat volume_shadow(vec3 posIS, vec3 lightDirNormIS)\n{\n float shadow = 1.0;\n float opacity = 0.0;\n\n // modify sample distance with a random number between 0.8 and 1.0\n float sampleDistanceISVS_jitter = sampleDistanceISVS * mix(0.8, 1.0, random());\n float opacityPrev = texture2D(otexture, vec2(getTextureValue(posIS).r * oscale0 + oshift0, 0.5)).r;\n \n // in case the first sample near surface has a very tiled light ray, we need to offset start position \n posIS += sampleDistanceISVS_jitter * lightDirNormIS; \n\n // compute the start and end points for the ray\n Ray ray;\n Hit hit; \n ray.origin = posIS;\n ray.dir = lightDirNormIS;\n safe_0_vector(ray);\n ray.invDir = 1.0/ray.dir;\n \n if(!BBoxIntersect(vec3(0.0),vec3(1.0), ray, hit))\n {\n return 1.0;\n }\n vec4 scalar = vec4(0.0);\n float maxdist = hit.tmax;\n if(maxdist < EPSILON) {\n return 1.0;\n }\n\n // interpolate shadow ray length between: 1 unit of sample distance in IS to SQRT3, based on globalIlluminationReach\n float maxgi = mix(sampleDistanceISVS_jitter,SQRT3,giReach);\n maxdist = min(maxdist,maxgi);\n\n // support gradient opacity\n #ifdef vtkGradientOpacityOn\n vec4 normal;\n #endif\n\n vec3 current_step = sampleDistanceISVS_jitter * lightDirNormIS;\n float maxSteps = ceil(maxdist/sampleDistanceISVS_jitter);\n float opacityDelta = 0.0;\n\n for (float i = 0.0; i < maxSteps; i++)\n {\n scalar = getTextureValue(posIS);\n opacity = texture2D(otexture, vec2(scalar.r * oscale0 + oshift0, 0.5)).r;\n #ifdef vtkGradientOpacityOn \n normal = computeNormal(posIS, scalar.a, vec3(1.0/vec3(volumeDimensions))); \n opacity *= computeGradientOpacityFactor(normal.w, goscale0, goshift0, gomin0, gomax0);\n #endif \n shadow *= 1.0 - opacity;\n\n // optimization: early termination\n if (shadow < EPSILON){\n return 0.0;\n }\n\n // optimization: increase/decrease sample distance based on changed in opacity value\n opacityDelta = opacityPrev - opacity;\n opacityPrev = opacity;\n if (opacityDelta > 0.0){\n current_step *= 0.9;\n } else if (opacityDelta < 0.0){\n current_step *= 1.1;\n }\n posIS += current_step;\n }\n\n return shadow; \n}\n\nvec3 applyShadowRay(vec3 tColor, vec3 posIS, vec3 viewDirectionVC)\n{\n vec3 vertLight = vec3(0.0);\n vec3 secondary_contrib = vec3(0.0);\n // here we assume only positional light, no effect of cones\n for (int i = 0; i < lightNum; i++)\n {\n #if(vtkLightComplexity==3)\n if (lightPositional[i] == 1){\n vertLight = lightPositionVC[i] - IStoVC(posIS);\n }else{\n vertLight = - lightDirectionVC[i];\n }\n #else\n vertLight = - lightDirectionVC[i];\n #endif\n // here we assume achromatic light, only intensity\n float dDotL = dot(viewDirectionVC, normalize(vertLight));\n // isotropic scatter returns 0.5 instead of 1/4pi to increase intensity\n float phase_attenuation = 0.5;\n if (abs(anisotropy) > EPSILON){\n phase_attenuation = phase_function(dDotL);\n }\n float vol_shadow = volume_shadow(posIS, normalize(rotateToIDX(vertLight)));\n secondary_contrib += tColor * vDiffuse * lightColor[i] * vol_shadow * phase_attenuation; \n secondary_contrib += tColor * vAmbient;\n } \n return secondary_contrib;\n}\n#endif\n\n//=======================================================================\n// local ambient occlusion\n#ifdef localAmbientOcclusionOn\nvec3 sample_direction_uniform(int i)\n{\n float rand = random() * 0.5;\n float theta = PI2 * (kernelSample[i][0] + rand);\n float phi = acos(2.0 * (kernelSample[i][1] + rand) -1.0) / 2.5;\n return normalize(vec3(cos(theta)*sin(phi), sin(theta)*sin(phi), cos(phi)));\n}\n\n// return a matrix that transform startDir into z axis; startDir should be normalized\nmat3 zBaseRotationalMatrix(vec3 startDir){\n vec3 axis = cross(startDir, vec3(0.0,0.0,1.0));\n float cosA = startDir.z;\n float k = 1.0 / (1.0 + cosA);\n mat3 matrix = mat3((axis.x * axis.x * k) + cosA, (axis.y * axis.x * k) - axis.z, (axis.z * axis.x * k) + axis.y,\n (axis.x * axis.y * k) + axis.z, (axis.y * axis.y * k) + cosA, (axis.z * axis.y * k) - axis.x,\n (axis.x * axis.z * k) - axis.y, (axis.y * axis.z * k) + axis.x, (axis.z * axis.z * k) + cosA);\n return matrix;\n}\n\nfloat computeLAO(vec3 posIS, float opacity, vec3 lightDir, vec4 normal){\n // apply LAO only at selected locations, otherwise return full brightness\n if (normal.w > 0.0 && opacity > 0.05){\n float total_transmittance = 0.0;\n mat3 inverseRotateBasis = inverse(zBaseRotationalMatrix(normalize(-normal.xyz)));\n vec3 currPos, randomDirStep;\n float weight, transmittance, opacity;\n for (int i = 0; i < kernelSize; i++)\n {\n randomDirStep = inverseRotateBasis * sample_direction_uniform(i) * sampleDistanceIS;\n weight = 1.0 - dot(normalize(lightDir), normalize(randomDirStep));\n currPos = posIS;\n transmittance = 1.0;\n for (int j = 0; j < kernelRadius ; j++){\n currPos += randomDirStep;\n // check if it's at clipping plane, if so return full brightness\n if (all(greaterThan(currPos, vec3(EPSILON))) && all(lessThan(currPos,vec3(1.0-EPSILON)))){\n opacity = texture2D(otexture, vec2(getTextureValue(currPos).r * oscale0 + oshift0, 0.5)).r;\n #ifdef vtkGradientOpacityOn\n opacity *= computeGradientOpacityFactor(normal.w, goscale0, goshift0, gomin0, gomax0);\n #endif\n transmittance *= 1.0 - opacity;\n }\n else{\n break;\n }\n }\n total_transmittance += transmittance / float(kernelRadius) * weight;\n\n // early termination if fully translucent\n if (total_transmittance > 1.0 - EPSILON){\n return 1.0;\n }\n }\n // average transmittance and reduce variance\n return clamp(total_transmittance / float(kernelSize), 0.3, 1.0); \n } else {\n return 1.0;\n }\n}\n#endif\n\n//=======================================================================\n// surface light contribution\n#if vtkLightComplexity > 0\n void applyLighting(inout vec3 tColor, vec4 normal)\n {\n vec3 diffuse = vec3(0.0, 0.0, 0.0);\n vec3 specular = vec3(0.0, 0.0, 0.0);\n float df, sf = 0.0;\n for (int i = 0; i < lightNum; i++){\n df = abs(dot(normal.rgb, -lightDirectionVC[i]));\n diffuse += df * lightColor[i];\n sf = pow( abs(dot(lightHalfAngleVC[i],normal.rgb)), vSpecularPower);\n specular += sf * lightColor[i];\n }\n tColor.rgb = tColor.rgb*(diffuse*vDiffuse + vAmbient) + specular*vSpecular;\n }\n #ifdef SurfaceShadowOn\n #if vtkLightComplexity < 3\n vec3 applyLightingDirectional(vec3 posIS, vec4 tColor, vec4 normal)\n {\n // everything in VC\n vec3 diffuse = vec3(0.0);\n vec3 specular = vec3(0.0);\n #ifdef localAmbientOcclusionOn\n vec3 ambient = vec3(0.0);\n #endif \n vec3 vertLightDirection;\n for (int i = 0; i < lightNum; i++){\n float ndotL,vdotR;\n vertLightDirection = lightDirectionVC[i];\n ndotL = dot(normal.xyz, vertLightDirection);\n if (ndotL < 0.0 && twoSidedLighting)\n {\n ndotL = -ndotL;\n }\n if (ndotL > 0.0)\n {\n diffuse += ndotL * lightColor[i];\n //specular\n vdotR = dot(-rayDirVC, normalize(2.0 * ndotL * -normal.xyz + vertLightDirection));\n if (vdotR > 0.0)\n {\n specular += pow(vdotR, vSpecularPower) * lightColor[i];\n }\n }\n #ifdef localAmbientOcclusionOn\n ambient += computeLAO(posIS, tColor.a, vertLightDirection, normal);\n #endif\n } \n #ifdef localAmbientOcclusionOn\n return tColor.rgb * (diffuse * vDiffuse + vAmbient * ambient) + specular*vSpecular;\n #else \n return tColor.rgb * (diffuse * vDiffuse + vAmbient) + specular*vSpecular;\n #endif \n }\n #else\n vec3 applyLightingPositional(vec3 posIS, vec4 tColor, vec4 normal, vec3 posVC)\n {\n // everything in VC\n vec3 diffuse = vec3(0.0);\n vec3 specular = vec3(0.0);\n #ifdef localAmbientOcclusionOn\n vec3 ambient = vec3(0.0);\n #endif \n vec3 vertLightDirection;\n for (int i = 0; i < lightNum; i++){\n float distance,attenuation,ndotL,vdotR;\n vec3 lightDir;\n if (lightPositional[i] == 1){\n lightDir = lightDirectionVC[i];\n vertLightDirection = posVC - lightPositionVC[i]; \n distance = length(vertLightDirection);\n vertLightDirection = normalize(vertLightDirection);\n attenuation = 1.0 / (lightAttenuation[i].x\n + lightAttenuation[i].y * distance\n + lightAttenuation[i].z * distance * distance);\n // per OpenGL standard cone angle is 90 or less for a spot light\n if (lightConeAngle[i] <= 90.0){\n float coneDot = dot(vertLightDirection, lightDir);\n if (coneDot >= cos(radians(lightConeAngle[i]))){ // if inside cone\n attenuation = attenuation * pow(coneDot, lightExponent[i]);\n }\n else {\n attenuation = 0.0;\n }\n }\n ndotL = dot(normal.xyz, vertLightDirection);\n if (ndotL < 0.0 && twoSidedLighting)\n {\n ndotL = -ndotL;\n }\n if (ndotL > 0.0)\n {\n diffuse += ndotL * attenuation * lightColor[i];\n //specular\n vdotR = dot(-rayDirVC, normalize(2.0 * ndotL * -normal.xyz + vertLightDirection));\n if (vdotR > 0.0)\n {\n specular += pow(vdotR, vSpecularPower) * attenuation * lightColor[i];\n }\n }\n #ifdef localAmbientOcclusionOn\n ambient += computeLAO(posIS, tColor.a, vertLightDirection, normal);\n #endif \n } else {\n vertLightDirection = lightDirectionVC[i];\n ndotL = dot(normal.xyz, vertLightDirection);\n if (ndotL < 0.0 && twoSidedLighting)\n {\n ndotL = -ndotL;\n }\n if (ndotL > 0.0)\n {\n diffuse += ndotL * lightColor[i];\n //specular\n vdotR = dot(-rayDirVC, normalize(2.0 * ndotL * -normal.xyz + vertLightDirection));\n if (vdotR > 0.0)\n {\n specular += pow(vdotR, vSpecularPower) * lightColor[i];\n }\n }\n #ifdef localAmbientOcclusionOn\n ambient += computeLAO(posIS, tColor.a, vertLightDirection, normal);\n #endif \n }\n }\n #ifdef localAmbientOcclusionOn\n return tColor.rgb * (diffuse * vDiffuse + vAmbient * ambient) + specular*vSpecular;\n #else \n return tColor.rgb * (diffuse * vDiffuse + vAmbient) + specular*vSpecular;\n #endif\n }\n #endif \n #endif\n#endif\n\n//=======================================================================\n// Given a texture value compute the color and opacity\n//\nvec4 getColorForValue(vec4 tValue, vec3 posIS, vec3 tstep)\n{\n#ifdef vtkImageLabelOutlineOn\n vec3 centerPosIS = fragCoordToIndexSpace(gl_FragCoord); // pos in texture space\n vec4 centerValue = getTextureValue(centerPosIS);\n bool pixelOnBorder = false;\n vec4 tColor = texture2D(ctexture, vec2(centerValue.r * cscale0 + cshift0, 0.5));\n\n // Get alpha of segment from opacity function.\n tColor.a = texture2D(otexture, vec2(centerValue.r * oscale0 + oshift0, 0.5)).r;\n\n // Only perform outline check on fragments rendering voxels that aren't invisible.\n // Saves a bunch of needless checks on the background.\n // TODO define epsilon when building shader?\n if (float(tColor.a) > 0.01) {\n for (int i = -outlineThickness; i <= outlineThickness; i++) {\n for (int j = -outlineThickness; j <= outlineThickness; j++) {\n if (i == 0 || j == 0) {\n continue;\n }\n\n vec4 neighborPixelCoord = vec4(gl_FragCoord.x + float(i),\n gl_FragCoord.y + float(j),\n gl_FragCoord.z, gl_FragCoord.w);\n\n vec3 neighborPosIS = fragCoordToIndexSpace(neighborPixelCoord);\n vec4 value = getTextureValue(neighborPosIS);\n\n // If any of my neighbours are not the same value as I\n // am, this means I am on the border of the segment.\n // We can break the loops\n if (any(notEqual(value, centerValue))) {\n pixelOnBorder = true;\n break;\n }\n }\n\n if (pixelOnBorder == true) {\n break;\n }\n }\n\n // If I am on the border, I am displayed at full opacity\n if (pixelOnBorder == true) {\n tColor.a = 1.0;\n }\n }\n\n#else\n // compute the normal and gradient magnitude if needed\n // We compute it as a vec4 if possible otherwise a mat4\n //\n vec4 goFactor = vec4(1.0,1.0,1.0,1.0);\n\n // compute the normal vectors as needed\n #if (vtkLightComplexity > 0) || defined(vtkGradientOpacityOn)\n #if defined(vtkIndependentComponentsOn) && (vtkNumComponents > 1)\n mat4 normalMat = computeMat4Normal(posIS, tValue, tstep);\n #if !defined(vtkComponent0Proportional)\n vec4 normal0 = normalMat[0];\n #endif\n #if !defined(vtkComponent1Proportional)\n vec4 normal1 = normalMat[1];\n #endif\n #if vtkNumComponents > 2\n #if !defined(vtkComponent2Proportional)\n vec4 normal2 = normalMat[2];\n #endif\n #if vtkNumComponents > 3\n #if !defined(vtkComponent3Proportional)\n vec4 normal3 = normalMat[3];\n #endif\n #endif\n #endif\n #else\n vec4 normalLight;\n #ifdef vtkComputeNormalFromOpacity\n #ifdef vtkGradientOpacityOn\n mat3 scalarInterp; \n vec3 secondaryGradientMag; \n vec4 normal0 = computeNormalForDensity(posIS, tValue.a, tstep, scalarInterp, secondaryGradientMag); \n normalLight = computeDensityNormal(tValue.a, normal0.w, scalarInterp,secondaryGradientMag); \n if (length(normalLight) == 0.0){ \n normalLight = normal0; \n } \n #else\n vec3 scalarInterp; \n vec4 normal0 = computeNormalForDensity(posIS, tValue.a, tstep, scalarInterp); \n if (length(normal0)>0.0){ \n normalLight = computeDensityNormal(tValue.a,scalarInterp); \n if (length(normalLight)==0.0){ \n normalLight = normal0; \n } \n } \n #endif\n #else \n vec4 normal0 = computeNormal(posIS, tValue.a, tstep); \n normalLight = normal0; \n #endif\n #endif\n #endif\n\n // compute gradient opacity factors as needed\n #if defined(vtkGradientOpacityOn)\n #if !defined(vtkComponent0Proportional)\n goFactor.x =\n computeGradientOpacityFactor(normal0.a, goscale0, goshift0, gomin0, gomax0);\n #endif\n #if defined(vtkIndependentComponentsOn) && (vtkNumComponents > 1)\n #if !defined(vtkComponent1Proportional)\n goFactor.y =\n computeGradientOpacityFactor(normal1.a, goscale1, goshift1, gomin1, gomax1);\n #endif\n #if vtkNumComponents > 2\n #if !defined(vtkComponent2Proportional)\n goFactor.z =\n computeGradientOpacityFactor(normal2.a, goscale2, goshift2, gomin2, gomax2);\n #endif\n #if vtkNumComponents > 3\n #if !defined(vtkComponent3Proportional)\n goFactor.w =\n computeGradientOpacityFactor(normal3.a, goscale3, goshift3, gomin3, gomax3);\n #endif\n #endif\n #endif\n #endif\n #endif\n\n // single component is always independent\n #if vtkNumComponents == 1\n vec4 tColor = texture2D(ctexture, vec2(tValue.r * cscale0 + cshift0, 0.5));\n tColor.a = goFactor.x*texture2D(otexture, vec2(tValue.r * oscale0 + oshift0, 0.5)).r;\n if (tColor.a < EPSILON){\n return vec4(0.0);\n } \n #endif\n\n #if defined(vtkIndependentComponentsOn) && vtkNumComponents >= 2\n vec4 tColor = mix0*texture2D(ctexture, vec2(tValue.r * cscale0 + cshift0, height0));\n #if !defined(vtkComponent0Proportional)\n tColor.a = goFactor.x*mix0*texture2D(otexture, vec2(tValue.r * oscale0 + oshift0, height0)).r;\n #else\n float pwfValue = texture2D(otexture, vec2(tValue.r * oscale0 + oshift0, height0)).r;\n tColor *= pwfValue;\n tColor.a *= mix(pwfValue, 1.0, (1.0 - mix0));\n #endif\n\n vec3 tColor1 = mix1*texture2D(ctexture, vec2(tValue.g * cscale1 + cshift1, height1)).rgb;\n #if !defined(vtkComponent1Proportional)\n tColor.a += goFactor.y*mix1*texture2D(otexture, vec2(tValue.g * oscale1 + oshift1, height1)).r;\n #else\n float pwfValue = texture2D(otexture, vec2(tValue.g * oscale1 + oshift1, height1)).r;\n tColor1 *= pwfValue;\n tColor.a *= mix(pwfValue, 1.0, (1.0 - mix1));\n #endif\n\n #if vtkNumComponents >= 3\n vec3 tColor2 = mix2*texture2D(ctexture, vec2(tValue.b * cscale2 + cshift2, height2)).rgb;\n #if !defined(vtkComponent2Proportional)\n tColor.a += goFactor.z*mix2*texture2D(otexture, vec2(tValue.b * oscale2 + oshift2, height2)).r;\n #else\n float pwfValue = texture2D(otexture, vec2(tValue.b * oscale2 + oshift2, height2)).r;\n tColor2 *= pwfValue;\n tColor.a *= mix(pwfValue, 1.0, (1.0 - mix2));\n #endif\n\n #if vtkNumComponents >= 4\n vec3 tColor3 = mix3*texture2D(ctexture, vec2(tValue.a * cscale3 + cshift3, height3)).rgb;\n #if !defined(vtkComponent3Proportional)\n tColor.a += goFactor.w*mix3*texture2D(otexture, vec2(tValue.a * oscale3 + oshift3, height3)).r;\n #else\n float pwfValue = texture2D(otexture, vec2(tValue.a * oscale3 + oshift3, height3)).r;\n tColor3 *= pwfValue;\n tColor.a *= mix(pwfValue, 1.0, (1.0 - mix3));\n #endif\n #endif\n #endif\n #else // then not independent\n\n #if vtkNumComponents == 2\n float lum = tValue.r * cscale0 + cshift0;\n float alpha = goFactor.x*texture2D(otexture, vec2(tValue.a * oscale1 + oshift1, 0.5)).r;\n vec4 tColor = vec4(lum, lum, lum, alpha);\n #endif\n #if vtkNumComponents == 3\n vec4 tColor;\n tColor.r = tValue.r * cscale0 + cshift0;\n tColor.g = tValue.g * cscale1 + cshift1;\n tColor.b = tValue.b * cscale2 + cshift2;\n tColor.a = goFactor.x*texture2D(otexture, vec2(tValue.a * oscale0 + oshift0, 0.5)).r;\n #endif\n #if vtkNumComponents == 4\n vec4 tColor;\n tColor.r = tValue.r * cscale0 + cshift0;\n tColor.g = tValue.g * cscale1 + cshift1;\n tColor.b = tValue.b * cscale2 + cshift2;\n tColor.a = goFactor.x*texture2D(otexture, vec2(tValue.a * oscale3 + oshift3, 0.5)).r;\n #endif\n #endif // dependent\n\n // apply lighting if requested as appropriate\n #if vtkLightComplexity > 0\n #if !defined(vtkComponent0Proportional) \n #if vtkNumComponents == 1\n \n #ifdef SurfaceShadowOn\n #if vtkLightComplexity < 3\n vec3 tColorS = applyLightingDirectional(posIS, tColor, normalLight);\n #else\n vec3 tColorS = applyLightingPositional(posIS, tColor, normalLight, IStoVC(posIS));\n #endif\n #endif\n\n #ifdef VolumeShadowOn\n vec3 tColorVS = applyShadowRay(tColor.rgb, posIS, rayDirVC);\n #ifdef SurfaceShadowOn\n float vol_coef = volumetricScatteringBlending * (1.0 - tColor.a / 2.0) * (1.0 - atan(normalLight.w) * INV4PI);\n tColor.rgb = (1.0-vol_coef) * tColorS + vol_coef * tColorVS;\n #else\n tColor.rgb = tColorVS;\n #endif\n #else\n tColor.rgb = tColorS;\n #endif\n \n #else\n applyLighting(tColor.rgb, normal0);\n #endif\n #endif\n\n #if defined(vtkIndependentComponentsOn) && vtkNumComponents >= 2\n #if !defined(vtkComponent1Proportional)\n applyLighting(tColor1, normal1);\n #endif\n #if vtkNumComponents >= 3\n #if !defined(vtkComponent2Proportional)\n applyLighting(tColor2, normal2);\n #endif\n #if vtkNumComponents >= 4\n #if !defined(vtkComponent3Proportional)\n applyLighting(tColor3, normal3);\n #endif\n #endif\n #endif\n #endif\n #endif\n\n// perform final independent blend as needed\n#if defined(vtkIndependentComponentsOn) && vtkNumComponents >= 2\n tColor.rgb += tColor1;\n#if vtkNumComponents >= 3\n tColor.rgb += tColor2;\n#if vtkNumComponents >= 4\n tColor.rgb += tColor3;\n#endif\n#endif\n#endif\n\n#endif\n\n\n\n\n\n\n\nreturn tColor;\n}\n\nbool valueWithinScalarRange(vec4 val, vec4 min, vec4 max) {\n bool withinRange = false;\n #if vtkNumComponents == 1\n if (val.r >= min.r && val.r <= max.r) {\n withinRange = true;\n }\n #endif\n #if defined(vtkIndependentComponentsOn) && vtkNumComponents == 2\n if (val.r >= min.r && val.r <= max.r &&\n val.g >= min.g && val.g <= max.g) {\n withinRange = true;\n }\n #endif\n #if defined(vtkIndependentComponentsOn) && vtkNumComponents >= 3\n if (all(greaterThanEqual(val, ipScalarRangeMin)) &&\n all(lessThanEqual(val, ipScalarRangeMax))) {\n withinRange = true;\n }\n #endif\n return withinRange;\n}\n\n//=======================================================================\n// Apply the specified blend mode operation along the ray's path.\n//\nvoid applyBlend(vec3 posIS, vec3 endIS, vec3 tdims)\n{\n vec3 tstep = 1.0/tdims;\n\n // start slightly inside and apply some jitter\n vec3 delta = endIS - posIS;\n vec3 stepIS = normalize(delta)*sampleDistanceIS;\n float raySteps = length(delta)/sampleDistanceIS;\n\n // avoid 0.0 jitter\n float jitter = 0.01 + 0.99*texture2D(jtexture, gl_FragCoord.xy/32.0).r;\n float stepsTraveled = jitter;\n\n // local vars for the loop\n vec4 color = vec4(0.0, 0.0, 0.0, 0.0);\n vec4 tValue;\n vec4 tColor;\n\n // if we have less than one step then pick the middle point\n // as our value\n // if (raySteps <= 1.0)\n // {\n // posIS = (posIS + endIS)*0.5;\n // }\n\n // Perform initial step at the volume boundary\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n #if vtkBlendMode == 0 // COMPOSITE_BLEND\n // now map through opacity and color\n tColor = getColorForValue(tValue, posIS, tstep);\n\n // handle very thin volumes\n if (raySteps <= 1.0)\n {\n tColor.a = 1.0 - pow(1.0 - tColor.a, raySteps);\n gl_FragData[0] = tColor;\n return;\n }\n\n tColor.a = 1.0 - pow(1.0 - tColor.a, jitter);\n color = vec4(tColor.rgb*tColor.a, tColor.a);\n posIS += (jitter*stepIS);\n\n for (int i = 0; i < //VTK::MaximumSamplesValue ; ++i)\n {\n if (stepsTraveled + 1.0 >= raySteps) { break; }\n\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n // now map through opacity and color\n tColor = getColorForValue(tValue, posIS, tstep);\n\n float mix = (1.0 - color.a);\n\n // this line should not be needed but nvidia seems to not handle\n // the break correctly on windows/chrome 58 angle\n //mix = mix * sign(max(raySteps - stepsTraveled - 1.0, 0.0));\n\n color = color + vec4(tColor.rgb*tColor.a, tColor.a)*mix;\n stepsTraveled++;\n posIS += stepIS;\n if (color.a > 0.99) { color.a = 1.0; break; }\n }\n\n if (color.a < 0.99 && (raySteps - stepsTraveled) > 0.0)\n {\n posIS = endIS;\n\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n // now map through opacity and color\n tColor = getColorForValue(tValue, posIS, tstep);\n tColor.a = 1.0 - pow(1.0 - tColor.a, raySteps - stepsTraveled);\n\n float mix = (1.0 - color.a);\n color = color + vec4(tColor.rgb*tColor.a, tColor.a)*mix;\n }\n\n gl_FragData[0] = vec4(color.rgb/color.a, color.a);\n #endif\n #if vtkBlendMode == 1 || vtkBlendMode == 2\n // MAXIMUM_INTENSITY_BLEND || MINIMUM_INTENSITY_BLEND\n // Find maximum/minimum intensity along the ray.\n\n // Define the operation we will use (min or max)\n #if vtkBlendMode == 1\n #define OP max\n #else\n #define OP min\n #endif\n\n // If the clipping range is shorter than the sample distance\n // we can skip the sampling loop along the ray.\n if (raySteps <= 1.0)\n {\n gl_FragData[0] = getColorForValue(tValue, posIS, tstep);\n return;\n }\n\n vec4 value = tValue;\n posIS += (jitter*stepIS);\n\n // Sample along the ray until MaximumSamplesValue,\n // ending slightly inside the total distance\n for (int i = 0; i < //VTK::MaximumSamplesValue ; ++i)\n {\n // If we have reached the last step, break\n if (stepsTraveled + 1.0 >= raySteps) { break; }\n\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n // Update the maximum value if necessary\n value = OP(tValue, value);\n\n // Otherwise, continue along the ray\n stepsTraveled++;\n posIS += stepIS;\n }\n\n // Perform the last step along the ray using the\n // residual distance\n posIS = endIS;\n tValue = getTextureValue(posIS);\n value = OP(tValue, value);\n\n // Now map through opacity and color\n gl_FragData[0] = getColorForValue(value, posIS, tstep);\n #endif\n #if vtkBlendMode == 3 || vtkBlendMode == 4 //AVERAGE_INTENSITY_BLEND || ADDITIVE_BLEND\n vec4 sum = vec4(0.);\n\n if (valueWithinScalarRange(tValue, ipScalarRangeMin, ipScalarRangeMax)) {\n sum += tValue;\n }\n\n if (raySteps <= 1.0) {\n gl_FragData[0] = getColorForValue(sum, posIS, tstep);\n return;\n }\n\n posIS += (jitter*stepIS);\n\n // Sample along the ray until MaximumSamplesValue,\n // ending slightly inside the total distance\n for (int i = 0; i < //VTK::MaximumSamplesValue ; ++i)\n {\n // If we have reached the last step, break\n if (stepsTraveled + 1.0 >= raySteps) { break; }\n\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n // One can control the scalar range by setting the AverageIPScalarRange to disregard scalar values, not in the range of interest, from the average computation.\n // Notes:\n // - We are comparing all values in the texture to see if any of them\n // are outside of the scalar range. In the future we might want to allow\n // scalar ranges for each component.\n if (valueWithinScalarRange(tValue, ipScalarRangeMin, ipScalarRangeMax)) {\n // Sum the values across each step in the path\n sum += tValue;\n }\n stepsTraveled++;\n posIS += stepIS;\n }\n\n // Perform the last step along the ray using the\n // residual distance\n posIS = endIS;\n\n // compute the scalar\n tValue = getTextureValue(posIS);\n\n // One can control the scalar range by setting the IPScalarRange to disregard scalar values, not in the range of interest, from the average computation\n if (valueWithinScalarRange(tValue, ipScalarRangeMin, ipScalarRangeMax)) {\n sum += tValue;\n\n stepsTraveled++;\n }\n\n #if vtkBlendMode == 3 // Average\n sum /= vec4(stepsTraveled, stepsTraveled, stepsTraveled, 1.0);\n #endif\n\n gl_FragData[0] = getColorForValue(sum, posIS, tstep);\n #endif\n}\n\n//=======================================================================\n// Compute a new start and end point for a given ray based\n// on the provided bounded clipping plane (aka a rectangle)\nvoid getRayPointIntersectionBounds(\n vec3 rayPos, vec3 rayDir,\n vec3 planeDir, float planeDist,\n inout vec2 tbounds, vec3 vPlaneX, vec3 vPlaneY,\n float vSize1, float vSize2)\n{\n float result = dot(rayDir, planeDir);\n if (abs(result) < 1e-6)\n {\n return;\n }\n result = -1.0 * (dot(rayPos, planeDir) + planeDist) / result;\n vec3 xposVC = rayPos + rayDir*result;\n vec3 vxpos = xposVC - vOriginVC;\n vec2 vpos = vec2(\n dot(vxpos, vPlaneX),\n dot(vxpos, vPlaneY));\n\n // on some apple nvidia systems this does not work\n // if (vpos.x < 0.0 || vpos.x > vSize1 ||\n // vpos.y < 0.0 || vpos.y > vSize2)\n // even just\n // if (vpos.x < 0.0 || vpos.y < 0.0)\n // fails\n // so instead we compute a value that represents in and out\n //and then compute the return using this value\n float xcheck = max(0.0, vpos.x * (vpos.x - vSize1)); // 0 means in bounds\n float check = sign(max(xcheck, vpos.y * (vpos.y - vSize2))); // 0 means in bounds, 1 = out\n\n tbounds = mix(\n vec2(min(tbounds.x, result), max(tbounds.y, result)), // in value\n tbounds, // out value\n check); // 0 in 1 out\n}\n\n//=======================================================================\n// given a\n// - ray direction (rayDir)\n// - starting point (vertexVCVSOutput)\n// - bounding planes of the volume\n// - optionally depth buffer values\n// - far clipping plane\n// compute the start/end distances of the ray we need to cast\nvec2 computeRayDistances(vec3 rayDir, vec3 tdims)\n{\n vec2 dists = vec2(100.0*camFar, -1.0);\n\n vec3 vSize = vSpacing*tdims;\n\n // all this is in View Coordinates\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal0, vPlaneDistance0, dists, vPlaneNormal2, vPlaneNormal4,\n vSize.y, vSize.z);\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal1, vPlaneDistance1, dists, vPlaneNormal2, vPlaneNormal4,\n vSize.y, vSize.z);\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal2, vPlaneDistance2, dists, vPlaneNormal0, vPlaneNormal4,\n vSize.x, vSize.z);\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal3, vPlaneDistance3, dists, vPlaneNormal0, vPlaneNormal4,\n vSize.x, vSize.z);\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal4, vPlaneDistance4, dists, vPlaneNormal0, vPlaneNormal2,\n vSize.x, vSize.y);\n getRayPointIntersectionBounds(vertexVCVSOutput, rayDir,\n vPlaneNormal5, vPlaneDistance5, dists, vPlaneNormal0, vPlaneNormal2,\n vSize.x, vSize.y);\n\n //VTK::ClipPlane::Impl\n\n // do not go behind front clipping plane\n dists.x = max(0.0,dists.x);\n\n // do not go PAST far clipping plane\n float farDist = -camThick/rayDir.z;\n dists.y = min(farDist,dists.y);\n\n // Do not go past the zbuffer value if set\n // This is used for intermixing opaque geometry\n //VTK::ZBuffer::Impl\n\n return dists;\n}\n\n//=======================================================================\n// Compute the index space starting position (pos) and end\n// position\n//\nvoid computeIndexSpaceValues(out vec3 pos, out vec3 endPos, vec3 rayDir, vec2 dists)\n{\n // compute starting and ending values in volume space\n pos = vertexVCVSOutput + dists.x*rayDir;\n pos = pos - vOriginVC;\n // convert to volume basis and origin\n pos = vec3(\n dot(pos, vPlaneNormal0),\n dot(pos, vPlaneNormal2),\n dot(pos, vPlaneNormal4));\n\n endPos = vertexVCVSOutput + dists.y*rayDir;\n endPos = endPos - vOriginVC;\n endPos = vec3(\n dot(endPos, vPlaneNormal0),\n dot(endPos, vPlaneNormal2),\n dot(endPos, vPlaneNormal4));\n\n float delta = length(endPos - pos);\n\n pos *= vVCToIJK;\n endPos *= vVCToIJK;\n\n float delta2 = length(endPos - pos);\n sampleDistanceIS = sampleDistance*delta2/delta;\n #ifdef VolumeShadowOn\n sampleDistanceISVS = sampleDistanceIS * volumeShadowSamplingDistFactor;\n #endif\n}\n\nvoid main()\n{\n\n if (cameraParallel == 1)\n {\n // Camera is parallel, so the rayDir is just the direction of the camera.\n rayDirVC = vec3(0.0, 0.0, -1.0);\n } else {\n // camera is at 0,0,0 so rayDir for perspective is just the vc coord\n rayDirVC = normalize(vertexVCVSOutput);\n }\n\n vec3 tdims = vec3(volumeDimensions);\n\n // compute the start and end points for the ray\n vec2 rayStartEndDistancesVC = computeRayDistances(rayDirVC, tdims);\n\n // do we need to composite? aka does the ray have any length\n // If not, bail out early\n if (rayStartEndDistancesVC.y <= rayStartEndDistancesVC.x)\n {\n discard;\n }\n\n // IS = Index Space\n vec3 posIS;\n vec3 endIS;\n computeIndexSpaceValues(posIS, endIS, rayDirVC, rayStartEndDistancesVC);\n\n // Perform the blending operation along the ray\n applyBlend(posIS, endIS, tdims);\n}\n";
2
2
 
3
3
  export { vtkVolumeFS as v };
@@ -18,7 +18,7 @@ var ScalarMode = vtkMapper.ScalarMode;
18
18
  var CoordinateSystem = vtkProp.CoordinateSystem;
19
19
  var DisplayLocation = vtkProperty2D.DisplayLocation;
20
20
  var vtkWebGPUPolyDataVS = "\n//VTK::Renderer::Dec\n\n//VTK::Color::Dec\n\n//VTK::Normal::Dec\n\n//VTK::TCoord::Dec\n\n//VTK::Select::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::IOStructs::Dec\n\n@vertex\nfn main(\n//VTK::IOStructs::Input\n)\n//VTK::IOStructs::Output\n{\n var output : vertexOutput;\n\n var vertex: vec4<f32> = vertexBC;\n\n //VTK::Color::Impl\n\n //VTK::Normal::Impl\n\n //VTK::TCoord::Impl\n\n //VTK::Select::Impl\n\n //VTK::Position::Impl\n\n return output;\n}\n";
21
- var vtkWebGPUPolyDataFS = "\nstruct PBRData {\n diffuse: vec3<f32>,\n specular: vec3<f32>,\n}\n\n// Dot product with the max already in it\nfn mdot(a: vec3<f32>, b: vec3<f32>) -> f32 {\n return max(0.0, dot(a, b));\n}\n\n// Lambertian diffuse model\nfn lambertDiffuse(base: vec3<f32>, N: vec3<f32>, L: vec3<f32>) -> vec3<f32> {\n var pi: f32 = 3.14159265359; \n var NdotL: f32 = mdot(N, L);\n NdotL = pow(NdotL, 1.5);\n return (base/pi)*NdotL;\n}\n\n// Yasuhiro Fujii improvement on the Oren-Nayar model\n// https://mimosa-pudica.net/improved-oren-nayar.html\n// p is surface color, o is roughness\nfn fujiiOrenNayar(p: vec3<f32>, o: f32, N: vec3<f32>, L: vec3<f32>, V: vec3<f32>) -> vec3<f32> {\n var invpi: f32 = 0.31830988618; // 1/pi\n\n var o2 = o*o;\n var NdotL: f32 = mdot(N, L);\n NdotL = pow(NdotL, 1.5); // Less physically accurate, but hides the \"seams\" between lights better\n\n var NdotV: f32 = mdot(N, V);\n var LdotV: f32 = mdot(L, V);\n\n var s: f32 = LdotV - NdotL*NdotV;\n var t: f32 = mix(1, max(NdotL, NdotV), step(0, s)); // Mix with step is the equivalent of an if statement\n var A: vec3<f32> = 0.5*(o2 / (o2 + 0.33)) + 0.17*p*(o2 / (o2 + 0.13));\n A = invpi*(1 - A);\n var B: f32 = 0.45*(o2 / (o2 + 0.09));\n B = invpi*B;\n\n return p*NdotL*(A + B*(s/t));\n}\n\n// Fresnel portion of BRDF (IOR only, simplified)\nfn schlickFresnelIOR(V: vec3<f32>, N: vec3<f32>, ior: f32, k: f32) -> f32 {\n var NdotV: f32 = mdot(V, N);\n // var R0: f32 = pow((ior - 1.0) / (ior + 1.0), 2); // 1.0 is about the ior of air, and it is assumed that light will be traveling through air\n var F0: f32 = (pow((ior - 1.0), 2) + k*k) / (pow((ior + 1.0), 2) + k*k); // This takes into account the roughness, whic the other one does not\n return F0 + (1 - F0) * pow((1-NdotV), 5); \n}\n\n// Fresnel portion of BRDF (Color ior, better)\nfn schlickFresnelRGB(V: vec3<f32>, N: vec3<f32>, F0: vec3<f32>) -> vec3<f32> {\n var NdotV: f32 = mdot(V, N);\n return F0 + (1 - F0) * pow((1-NdotV), 5); \n}\n\n// Normal portion of BRDF\n// https://learnopengl.com/PBR/Theory\n// Trowbridge-Reitz GGX functions: normal, halfway, roughness^2\nfn trGGX(N: vec3<f32>, H: vec3<f32>, a: f32) -> f32 {\n var pi: f32 = 3.14159265359; \n\n var a2: f32 = a*a;\n var NdotH = mdot(N, H);\n var NdotH2 = NdotH*NdotH;\n \n var denom: f32 = NdotH2 * (a2 - 1.0) + 1.0;\n\n return a2 / max((pi*denom*denom), 0.000001);\n}\n\n// A VERY bad approximation of anisotropy. Real anisotropic calculations require tangent and bitangent\nfn anisotrophicTrGGX(N: vec3<f32>, H: vec3<f32>, O: vec3<f32>, s: f32, a: f32) -> f32 {\n var Op: vec3<f32> = (rendererUBO.WCVCNormals * vec4<f32>(normalize(O) * s, 0.)).xyz;\n\n var ggx1: f32 = trGGX(N + Op*s, H, a);\n var ggx2: f32 = trGGX(N - Op*s, H, a);\n return (0.5 * ggx1 + 0.5 * ggx2);\n}\n\n// Geometry portion of BRDF\nfn schlickGGX(N: vec3<f32>, X: vec3<f32>, k: f32) -> f32 {\n var NdotX = mdot(N, X);\n return NdotX / max(0.000001, (NdotX*(1-k) + k));\n}\n\nfn smithSurfaceRoughness(N: vec3<f32>, V: vec3<f32>, L: vec3<f32>, k: f32) -> f32 {\n var ggx1: f32 = max(0.01, schlickGGX(N, V, k)); // Prevents void zones at the cost of some accuracy\n var ggx2: f32 = schlickGGX(N, L, k);\n return ggx1*ggx2;\n}\n\n// BRDF Combination\nfn cookTorrance(D: f32, F: f32, G: f32, N: vec3<f32>, V: vec3<f32>, L: vec3<f32>) -> f32 {\n var num: f32 = D*F*G;\n var denom: f32 = 4*mdot(V, N)*mdot(L, N);\n\n return num / max(denom, 0.000001);\n}\n\n// Different lighting calculations for different light sources\nfn calcDirectionalLight(N: vec3<f32>, V: vec3<f32>, ior: f32, roughness: f32, metallic: f32, direction: vec3<f32>, color: vec3<f32>, base: vec3<f32>) -> PBRData { \n var L: vec3<f32> = normalize(direction); // Light Vector\n var H: vec3<f32> = normalize(L + V); // Halfway Vector\n\n var alpha = roughness*roughness;\n var k: f32 = alpha*alpha / 2;\n\n var D: f32 = trGGX(N, H, alpha); // Distribution\n // var F: f32 = schlickFresnelIOR(V, N, ior, k); // Fresnel\n var G: f32 = smithSurfaceRoughness(N, V, L, k); // Geometry\n\n var brdf: f32 = cookTorrance(D, 1, G, N, V, L); // Fresnel term is replaced with 1 because it is added later\n var incoming: vec3<f32> = color;\n var angle: f32 = mdot(L, N);\n angle = pow(angle, 1.5);\n\n var specular: vec3<f32> = brdf*incoming*angle;\n // 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\n // control property for the diffuse vs specular roughness\n var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V); \n // Stores the specular and diffuse separately to allow for finer post processing\n // Could also be done (propably more properly) with a struct\n var out = PBRData(diffuse, specular);\n \n return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas)\n}\n\n// TODO: find some way to reduce the number of arguments going in here\nfn 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 {\n var L: vec3<f32> = normalize(position - fragPos); // Light Vector\n var H: vec3<f32> = normalize(L + V); // Halfway Vector\n var dist = distance(position, fragPos);\n\n var alpha = roughness*roughness;\n var k: f32 = alpha*alpha / 2; // could also be pow(alpha + 1.0, 2) / 8\n\n var D: f32 = trGGX(N, H, alpha); // Distribution\n // var F: f32 = schlickFresnelIOR(V, N, ior, k); // Fresnel\n var G: f32 = smithSurfaceRoughness(N, V, L, k); // Geometry\n\n var brdf: f32 = cookTorrance(D, 1, G, N, V, L); \n var incoming: vec3<f32> = color * (1. / (dist*dist));\n var angle: f32 = mdot(L, N);\n angle = pow(angle, 1.5); // Smoothing factor makes it less accurate, but reduces ugly \"seams\" bewteen light sources\n\n var specular: vec3<f32> = brdf*incoming*angle;\n var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V);\n\n // Stores the specular and diffuse separately to allow for finer post processing\n // Could also be done (propably more properly) with a struct\n var out = PBRData(diffuse, specular);\n \n return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas)\n}\n\n// For a reason unknown to me, spheres dont seem to behave propperly with head-on spot lights\nfn 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 {\n var L: vec3<f32> = normalize(position - fragPos);\n var H: vec3<f32> = normalize(L + V); // Halfway Vector\n var dist = distance(position, fragPos);\n\n var alpha = roughness*roughness;\n var k: f32 = alpha*alpha / 2; // could also be pow(alpha + 1.0, 2) / 8\n\n var D: f32 = trGGX(N, H, alpha); // Distribution\n // var F: f32 = schlickFresnelIOR(V, N, ior, k); // Fresnel\n var G: f32 = smithSurfaceRoughness(N, V, L, k); // Geometry\n\n var brdf: f32 = cookTorrance(D, 1, G, N, V, L); \n \n // Cones.x is the inner phi and cones.y is the outer phi\n var theta: f32 = mdot(normalize(direction), L);\n var epsilon: f32 = cones.x - cones.y;\n var intensity: f32 = (theta - cones.y) / epsilon;\n intensity = clamp(intensity, 0.0, 1.0);\n intensity /= dist*dist;\n\n var incoming: vec3<f32> = color * intensity;\n\n var angle: f32 = mdot(L, N);\n angle = pow(angle, 1.5); // Smoothing factor makes it less accurate, but reduces ugly \"seams\" bewteen light sources\n\n var specular: vec3<f32> = brdf*incoming*angle;\n var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V);\n\n // Stores the specular and diffuse separately to allow for finer post processing\n // Could also be done (propably more properly) with a struct\n var out = PBRData(diffuse, specular);\n \n return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas)\n}\n\n// Environment mapping stuff\n// Takes in a vector and converts it to an equivalent coordinate in a rectilinear texture. Should be replaced with cubemaps at some point\nfn vecToRectCoord(dir: vec3<f32>) -> vec2<f32> {\n var tau: f32 = 6.28318530718;\n var out: vec2<f32> = vec2<f32>(0.);\n\n out.x = atan2(dir.z, dir.x) / tau;\n out.x += 0.5;\n\n out.y = (dir.y * .5) + .5;\n\n return out;\n}\n\n//VTK::Renderer::Dec\n\n//VTK::Color::Dec\n\n//VTK::TCoord::Dec\n\n// optional surface normal declaration\n//VTK::Normal::Dec\n\n//VTK::Select::Dec\n\n//VTK::RenderEncoder::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::IOStructs::Dec\n\n@fragment\nfn main(\n//VTK::IOStructs::Input\n)\n//VTK::IOStructs::Output\n{\n var output : fragmentOutput;\n\n // Temporary ambient, diffuse, and opacity\n var ambientColor: vec4<f32> = mapperUBO.AmbientColor;\n var diffuseColor: vec4<f32> = mapperUBO.DiffuseColor;\n var opacity: f32 = mapperUBO.Opacity;\n\n // This should be declared somewhere else\n var _diffuseMap: vec4<f32> = vec4<f32>(1);\n var _roughnessMap: vec4<f32> = vec4<f32>(1);\n var _metallicMap: vec4<f32> = vec4<f32>(1);\n var _normalMap: vec4<f32> = vec4<f32>(0, 0, 1, 0); // normal map was setting off the normal vector detection in fragment\n var _ambientOcclusionMap: vec4<f32> = vec4<f32>(0);\n var _emissionMap: vec4<f32> = vec4<f32>(0);\n\n //VTK::Color::Impl\n\n //VTK::TCoord::Impl\n\n //VTK::Normal::Impl\n\n var computedColor: vec4<f32> = vec4<f32>(diffuseColor.rgb, 1.);\n\n //VTK::Light::Impl\n\n //VTK::Select::Impl\n\n if (computedColor.a == 0.0) { discard; };\n\n //VTK::Position::Impl\n\n //VTK::RenderEncoder::Impl\n\n return output;\n}\n";
21
+ var vtkWebGPUPolyDataFS = "\nstruct PBRData {\n diffuse: vec3<f32>,\n specular: vec3<f32>,\n}\n\n// Dot product with the max already in it\nfn mdot(a: vec3<f32>, b: vec3<f32>) -> f32 {\n return max(0.0, dot(a, b));\n}\n// Dot product with a max in it that does not allow for negative values\n// Physically based rendering is accurate as long as normals are accurate,\n// however this is pretty often not the case. In order to prevent negative\n// values from ruining light calculations and creating zones of zero light,\n// this remapping is used, which smoothly clamps the dot product between\n// zero and one while still maintaining a good amount of accuracy.\nfn cdot(a: vec3<f32>, b: vec3<f32>) -> f32 {\n var d: f32 = max(0.0, dot(a, b));\n d = pow((d + 1) / 2.0, 2.6);\n return d;\n}\n\n// Lambertian diffuse model\nfn lambertDiffuse(base: vec3<f32>, N: vec3<f32>, L: vec3<f32>) -> vec3<f32> {\n var pi: f32 = 3.14159265359; \n var NdotL: f32 = mdot(N, L);\n NdotL = pow(NdotL, 1.5);\n return (base/pi)*NdotL;\n}\n\n// Yasuhiro Fujii improvement on the Oren-Nayar model\n// https://mimosa-pudica.net/improved-oren-nayar.html\n// p is surface color, o is roughness\nfn fujiiOrenNayar(p: vec3<f32>, o: f32, N: vec3<f32>, L: vec3<f32>, V: vec3<f32>) -> vec3<f32> {\n var invpi: f32 = 0.31830988618; // 1/pi\n\n var o2 = o*o;\n var NdotL: f32 = mdot(N, L);\n NdotL = pow(NdotL, 1.5); // Less physically accurate, but hides the \"seams\" between lights better\n\n var NdotV: f32 = mdot(N, V);\n var LdotV: f32 = mdot(L, V);\n\n var s: f32 = LdotV - NdotL*NdotV;\n var t: f32 = mix(1, max(NdotL, NdotV), step(0, s)); // Mix with step is the equivalent of an if statement\n var A: vec3<f32> = 0.5*(o2 / (o2 + 0.33)) + 0.17*p*(o2 / (o2 + 0.13));\n A = invpi*(1 - A);\n var B: f32 = 0.45*(o2 / (o2 + 0.09));\n B = invpi*B;\n\n return p*NdotL*(A + B*(s/t));\n}\n\n// Fresnel portion of BRDF (IOR only, simplified)\nfn schlickFresnelIOR(V: vec3<f32>, N: vec3<f32>, ior: f32, k: f32) -> f32 {\n var NdotV: f32 = mdot(V, N);\n var F0: f32 = (pow((ior - 1.0), 2) + k*k) / (pow((ior + 1.0), 2) + k*k); // This takes into account the roughness, which the other one does not\n return F0 + (1 - F0) * pow((1-NdotV), 5); \n}\n\n// Fresnel portion of BRDF (Color ior, better)\nfn schlickFresnelRGB(V: vec3<f32>, N: vec3<f32>, F0: vec3<f32>) -> vec3<f32> {\n var NdotV: f32 = mdot(V, N);\n return F0 + (1 - F0) * pow((1-NdotV), 5); \n}\n\n// Normal portion of BRDF\n// https://learnopengl.com/PBR/Theory\n// Trowbridge-Reitz GGX functions: normal, halfway, roughness^2\nfn trGGX(N: vec3<f32>, H: vec3<f32>, a: f32) -> f32 {\n var pi: f32 = 3.14159265359; \n\n var a2: f32 = a*a;\n var NdotH = mdot(N, H);\n var NdotH2 = NdotH*NdotH;\n \n var denom: f32 = NdotH2 * (a2 - 1.0) + 1.0;\n\n return a2 / max((pi*denom*denom), 0.000001);\n}\n\n// A VERY bad approximation of anisotropy. Real anisotropic calculations require tangent and bitangent\nfn anisotrophicTrGGX(N: vec3<f32>, H: vec3<f32>, O: vec3<f32>, s: f32, a: f32) -> f32 {\n var Op: vec3<f32> = (rendererUBO.WCVCNormals * vec4<f32>(normalize(O) * s, 0.)).xyz;\n\n var ggx1: f32 = trGGX(N + Op*s, H, a);\n var ggx2: f32 = trGGX(N - Op*s, H, a);\n return (0.5 * ggx1 + 0.5 * ggx2);\n}\n\n// Geometry portion of BRDF\nfn schlickGGX(N: vec3<f32>, X: vec3<f32>, k: f32) -> f32 {\n var NdotX = cdot(N, X);\n return NdotX / max(0.000001, (NdotX*(1-k) + k));\n}\n\nfn smithSurfaceRoughness(N: vec3<f32>, V: vec3<f32>, L: vec3<f32>, k: f32) -> f32 {\n var ggx1: f32 = min(1, schlickGGX(N, V, k));\n var ggx2: f32 = min(1, schlickGGX(N, L, k));\n return ggx1*ggx2;\n}\n\n// BRDF Combination\nfn cookTorrance(D: f32, F: f32, G: f32, N: vec3<f32>, V: vec3<f32>, L: vec3<f32>) -> f32 {\n var num: f32 = D*F*G;\n var denom: f32 = 4*cdot(V, N)*cdot(L, N);\n\n return num / max(denom, 0.000001);\n}\n\n// Different lighting calculations for different light sources\nfn calcDirectionalLight(N: vec3<f32>, V: vec3<f32>, ior: f32, roughness: f32, metallic: f32, direction: vec3<f32>, color: vec3<f32>, base: vec3<f32>) -> PBRData { \n var L: vec3<f32> = normalize(direction); // Light Vector\n var H: vec3<f32> = normalize(L + V); // Halfway Vector\n\n var alpha = roughness*roughness;\n var k: f32 = alpha*alpha / 2;\n\n var D: f32 = trGGX(N, H, alpha); // Distribution\n // var F: f32 = schlickFresnelIOR(V, N, ior, k); // Fresnel\n var G: f32 = smithSurfaceRoughness(N, V, L, k); // Geometry\n\n var brdf: f32 = cookTorrance(D, 1, G, N, V, L); // Fresnel term is replaced with 1 because it is added later\n var incoming: vec3<f32> = color;\n var angle: f32 = mdot(L, N);\n angle = pow(angle, 1.5);\n\n var specular: vec3<f32> = brdf*incoming*angle;\n // 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\n // control property for the diffuse vs specular roughness\n var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V); \n // Stores the specular and diffuse separately to allow for finer post processing\n var out = PBRData(diffuse, specular);\n \n return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas)\n}\n\n// TODO: find some way to reduce the number of arguments going in here\nfn 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 {\n var L: vec3<f32> = normalize(position - fragPos); // Light Vector\n var H: vec3<f32> = normalize(L + V); // Halfway Vector\n var dist = distance(position, fragPos);\n\n var alpha = roughness*roughness;\n var k: f32 = alpha*alpha / 2; // could also be pow(alpha + 1.0, 2) / 8\n\n var D: f32 = trGGX(N, H, alpha); // Distribution\n // var F: f32 = schlickFresnelIOR(V, N, ior, k); // Fresnel\n var G: f32 = smithSurfaceRoughness(N, V, L, k); // Geometry\n\n var brdf: f32 = cookTorrance(D, 1, G, N, V, L); \n var incoming: vec3<f32> = color * (1. / (dist*dist));\n var angle: f32 = mdot(L, N);\n angle = pow(angle, 1.5); // Smoothing factor makes it less accurate, but reduces ugly \"seams\" bewteen light sources\n\n var specular: vec3<f32> = brdf*incoming*angle;\n var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V);\n\n // Stores the specular and diffuse separately to allow for finer post processing\n // Could also be done (propably more properly) with a struct\n var out = PBRData(diffuse, specular);\n \n return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas)\n}\n\n// For a reason unknown to me, spheres dont seem to behave propperly with head-on spot lights\nfn 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 {\n var L: vec3<f32> = normalize(position - fragPos);\n var H: vec3<f32> = normalize(L + V); // Halfway Vector\n var dist = distance(position, fragPos);\n\n var alpha = roughness*roughness;\n var k: f32 = alpha*alpha / 2; // could also be pow(alpha + 1.0, 2) / 8\n\n var D: f32 = trGGX(N, H, alpha); // Distribution\n // var F: f32 = schlickFresnelIOR(V, N, ior, k); // Fresnel\n var G: f32 = smithSurfaceRoughness(N, V, L, k); // Geometry\n\n var brdf: f32 = cookTorrance(D, 1, G, N, V, L); \n \n // Cones.x is the inner phi and cones.y is the outer phi\n var theta: f32 = mdot(normalize(direction), L);\n var epsilon: f32 = cones.x - cones.y;\n var intensity: f32 = (theta - cones.y) / epsilon;\n intensity = clamp(intensity, 0.0, 1.0);\n intensity /= dist*dist;\n\n var incoming: vec3<f32> = color * intensity;\n\n var angle: f32 = mdot(L, N);\n angle = pow(angle, 1.5); // Smoothing factor makes it less accurate, but reduces ugly \"seams\" bewteen light sources\n\n var specular: vec3<f32> = brdf*incoming*angle;\n var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V);\n\n // Stores the specular and diffuse separately to allow for finer post processing\n // Could also be done (propably more properly) with a struct\n var out = PBRData(diffuse, specular);\n \n return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas)\n}\n\n// Environment mapping stuff\n// Takes in a vector and converts it to an equivalent coordinate in a rectilinear texture. Should be replaced with cubemaps at some point\nfn vecToRectCoord(dir: vec3<f32>) -> vec2<f32> {\n var tau: f32 = 6.28318530718;\n var pi: f32 = 3.14159265359;\n var out: vec2<f32> = vec2<f32>(0.0);\n\n out.x = atan2(dir.z, dir.x) / tau;\n out.x += 0.5;\n\n var phix: f32 = length(vec2(dir.x, dir.z));\n out.y = atan2(dir.y, phix) / pi + 0.5;\n\n return out;\n}\n\n//VTK::Renderer::Dec\n\n//VTK::Color::Dec\n\n//VTK::TCoord::Dec\n\n// optional surface normal declaration\n//VTK::Normal::Dec\n\n//VTK::Select::Dec\n\n//VTK::RenderEncoder::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::IOStructs::Dec\n\n@fragment\nfn main(\n//VTK::IOStructs::Input\n)\n//VTK::IOStructs::Output\n{\n var output : fragmentOutput;\n\n // Temporary ambient, diffuse, and opacity\n var ambientColor: vec4<f32> = mapperUBO.AmbientColor;\n var diffuseColor: vec4<f32> = mapperUBO.DiffuseColor;\n var opacity: f32 = mapperUBO.Opacity;\n\n // This should be declared somewhere else\n var _diffuseMap: vec4<f32> = vec4<f32>(1);\n var _roughnessMap: vec4<f32> = vec4<f32>(1);\n var _metallicMap: vec4<f32> = vec4<f32>(1);\n var _normalMap: vec4<f32> = vec4<f32>(0, 0, 1, 0); // normal map was setting off the normal vector detection in fragment\n var _ambientOcclusionMap: vec4<f32> = vec4<f32>(1);\n var _emissionMap: vec4<f32> = vec4<f32>(0);\n\n //VTK::Color::Impl\n\n //VTK::TCoord::Impl\n\n //VTK::Normal::Impl\n\n var computedColor: vec4<f32> = vec4<f32>(diffuseColor.rgb, 1.0);\n\n //VTK::Light::Impl\n\n //VTK::Select::Impl\n\n if (computedColor.a == 0.0) { discard; };\n\n //VTK::Position::Impl\n\n //VTK::RenderEncoder::Impl\n\n return output;\n}\n";
22
22
 
23
23
  function isEdges(hash) {
24
24
  // edge pipelines have "edge" in them
@@ -209,7 +209,7 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
209
209
  code = fDesc.getCode();
210
210
 
211
211
  if (actor.getProperty().getNormalTexture()) {
212
- 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,', ' );', ' normal = TCVCMatrix * (_normalMap.xyz * 2 - 1);', ' normal = mix(input.normalVC, normal, mapperUBO.NormalStrength);', ' normal = normalize(normal);']).result;
212
+ 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;
213
213
  } else {
214
214
  code = vtkWebGPUShaderCache.substitute(code, '//VTK::Normal::Impl', [' var normal: vec3<f32> = input.normalVC;', ' if (!input.frontFacing) { normal = -normal; }', ' normal = normalize(normal);']).result;
215
215
  }
@@ -225,18 +225,30 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
225
225
  if (hash.includes('sel')) return;
226
226
  var vDesc = pipeline.getShaderDescription('vertex');
227
227
  if (!vDesc.hasOutput('vertexVC')) vDesc.addOutput('vec4<f32>', 'vertexVC');
228
+ var renderer = model.WebGPURenderer.getRenderable();
228
229
  var fDesc = pipeline.getShaderDescription('fragment');
229
230
  var code = fDesc.getCode(); // Code that runs if the fragment shader includes normals
230
231
 
231
232
  if (code.includes('var normal:') && model.useRendererMatrix && !isEdges(hash) && !model.is2D && !hash.includes('sel')) {
232
- code = vtkWebGPUShaderCache.substitute(code, '//VTK::Light::Impl', [// Constants
233
+ var _renderer$getEnvironm;
234
+
235
+ var lightingCode = [// Constants
233
236
  ' var pi: f32 = 3.14159265359;', // Vectors needed for light calculations
234
237
  ' 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', // Values needed for light calculations
235
238
  ' var baseColor: vec3<f32> = _diffuseMap.rgb * diffuseColor.rgb;', ' var roughness: f32 = max(0.000001, mapperUBO.Roughness * _roughnessMap.r);', // Need to have a different way of sampling greyscale values aside from .r
236
239
  ' var metallic: f32 = mapperUBO.Metallic * _metallicMap.r;', ' var alpha: f32 = roughness*roughness;', ' var ior: f32 = mapperUBO.BaseIOR;', ' var k: f32 = alpha*alpha / 2;', // Split diffuse and specular components
237
240
  ' var diffuse: vec3<f32> = vec3<f32>(0.);', ' var specular: vec3<f32> = vec3<f32>(0.);', ' var emission: vec3<f32> = _emissionMap.rgb * mapperUBO.Emission;', // Summing diffuse and specular components of directional lights
238
241
  ' {', ' 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++; }', ' }', ' }', // Final variables for combining specular and diffuse
239
- ' var fresnel: f32 = schlickFresnelIOR(V, normal, ior, k); // Fresnel', ' fresnel = min(1, 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), 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);']).result;
242
+ ' var fresnel: f32 = schlickFresnelIOR(V, normal, ior, k); // Fresnel', ' fresnel = min(1, 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), 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);'];
243
+
244
+ if ((_renderer$getEnvironm = renderer.getEnvironmentTexture()) !== null && _renderer$getEnvironm !== void 0 && _renderer$getEnvironm.getImageLoaded()) {
245
+ 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);', // Level multiplier should be set by UBO
246
+ ' var level = roughness * rendererUBO.MaxEnvironmentMipLevel;', ' var specularIBL = textureSampleLevel(EnvironmentTexture, EnvironmentTextureSampler, specularCoords, level);', // Manual mip smoothing since not all formats support smooth level sampling
247
+ ' 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;', // Multipy by baseColor may be changed
248
+ ' computedColor += vec4<f32>(diffuseIBLContribution*kD, 0);');
249
+ }
250
+
251
+ code = vtkWebGPUShaderCache.substitute(code, '//VTK::Light::Impl', lightingCode).result;
240
252
  fDesc.setCode(code); // If theres no normals, just set the specular color to be flat
241
253
  } else {
242
254
  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;
@@ -287,8 +299,7 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
287
299
  var numComp = vtkWebGPUTypes.getNumberOfComponentsFromBufferFormat(tcoords.getArrayInformation()[0].format);
288
300
  var code = vDesc.getCode();
289
301
  vDesc.addOutput("vec".concat(numComp, "<f32>"), 'tcoordVS');
290
- code = vtkWebGPUShaderCache.substitute(code, '//VTK::TCoord::Impl', [' output.tcoordVS = tcoord;' // Ensure that UV coordinates are always between 0-1
291
- ]).result;
302
+ code = vtkWebGPUShaderCache.substitute(code, '//VTK::TCoord::Impl', [' output.tcoordVS = tcoord;']).result;
292
303
  vDesc.setCode(code);
293
304
  var fDesc = pipeline.getShaderDescription('fragment');
294
305
  code = fDesc.getCode();
@@ -568,7 +579,7 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
568
579
  };
569
580
 
570
581
  publicAPI.updateTextures = function () {
571
- var _model$renderable$get2, _model$renderable2, _actor$getProperty$ge14, _actor$getProperty8, _actor$getProperty$ge15, _actor$getProperty9, _actor$getProperty$ge16, _actor$getProperty10, _actor$getProperty$ge17, _actor$getProperty11, _actor$getProperty$ge18, _actor$getProperty12, _actor$getProperty$ge19, _actor$getProperty13, _renderer$getBackgrou;
582
+ var _model$renderable$get2, _model$renderable2, _actor$getProperty$ge14, _actor$getProperty8, _actor$getProperty$ge15, _actor$getProperty9, _actor$getProperty$ge16, _actor$getProperty10, _actor$getProperty$ge17, _actor$getProperty11, _actor$getProperty$ge18, _actor$getProperty12, _actor$getProperty$ge19, _actor$getProperty13, _renderer$getEnvironm2;
572
583
 
573
584
  // we keep track of new and used textures so
574
585
  // that we can clean up any unused textures so we don't hold onto them
@@ -634,8 +645,8 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
634
645
  textures.push(_pair7);
635
646
  }
636
647
 
637
- if ((_renderer$getBackgrou = renderer.getBackgroundTexture) !== null && _renderer$getBackgrou !== void 0 && _renderer$getBackgrou.call(renderer)) {
638
- var _pair8 = ['Background', renderer.getBackgroundTexture()];
648
+ if ((_renderer$getEnvironm2 = renderer.getEnvironmentTexture) !== null && _renderer$getEnvironm2 !== void 0 && _renderer$getEnvironm2.call(renderer)) {
649
+ var _pair8 = ['Environment', renderer.getEnvironmentTexture()];
639
650
  textures.push(_pair8);
640
651
  }
641
652
 
@@ -671,10 +682,29 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
671
682
  model.textures.push(newTex);
672
683
  model.textureViews.push(tview);
673
684
  var interpolate = srcTexture.getInterpolate() ? 'linear' : 'nearest';
674
- tview.addSampler(model.device, {
675
- minFilter: interpolate,
676
- magFilter: interpolate
677
- });
685
+ var addressMode = null;
686
+ if (!addressMode && srcTexture.getEdgeClamp() && srcTexture.getRepeat()) addressMode = 'mirror-repeat';
687
+ if (!addressMode && srcTexture.getEdgeClamp()) addressMode = 'clamp-to-edge';
688
+ if (!addressMode && srcTexture.getRepeat()) addressMode = 'repeat';
689
+
690
+ if (textureName !== 'Environment') {
691
+ tview.addSampler(model.device, {
692
+ addressModeU: addressMode,
693
+ addressModeV: addressMode,
694
+ addressModeW: addressMode,
695
+ minFilter: interpolate,
696
+ magFilter: interpolate
697
+ });
698
+ } else {
699
+ tview.addSampler(model.device, {
700
+ addressModeU: 'repeat',
701
+ addressModeV: 'clamp-to-edge',
702
+ addressModeW: 'repeat',
703
+ minFilter: interpolate,
704
+ magFilter: interpolate,
705
+ mipmapFilter: 'linear'
706
+ });
707
+ }
678
708
  }
679
709
  }
680
710
  } // remove unused textures
@@ -12,8 +12,9 @@ function vtkWebGPUFullScreenQuad(publicAPI, model) {
12
12
  publicAPI.replaceShaderPosition = function (hash, pipeline, vertexInput) {
13
13
  var vDesc = pipeline.getShaderDescription('vertex');
14
14
  vDesc.addBuiltinOutput('vec4<f32>', '@builtin(position) Position');
15
+ vDesc.addOutput('vec4<f32>', 'vertexVC');
15
16
  var code = vDesc.getCode();
16
- code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', ['output.tcoordVS = vec2<f32>(vertexBC.x * 0.5 + 0.5, 1.0 - vertexBC.y * 0.5 - 0.5);', 'output.Position = vec4<f32>(vertexBC, 1.0);']).result;
17
+ code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', ['output.tcoordVS = vec2<f32>(vertexBC.x * 0.5 + 0.5, 1.0 - vertexBC.y * 0.5 - 0.5);', 'output.Position = vec4<f32>(vertexBC, 1.0);', 'output.vertexVC = vec4<f32>(vertexBC, 1);']).result;
17
18
  vDesc.setCode(code);
18
19
  };
19
20