UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

1 lines 64 kB
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\nconst float infinity = 3.402823466e38;\n\n// the output of this shader\n//VTK::Output::Dec\n\nin vec3 vertexVCVSOutput;\n\n// From Sources\\Rendering\\Core\\VolumeProperty\\Constants.js\n#define COMPOSITE_BLEND 0\n#define MAXIMUM_INTENSITY_BLEND 1\n#define MINIMUM_INTENSITY_BLEND 2\n#define AVERAGE_INTENSITY_BLEND 3\n#define ADDITIVE_INTENSITY_BLEND 4\n#define RADON_TRANSFORM_BLEND 5\n#define LABELMAP_EDGE_PROJECTION_BLEND 6\n\n#define vtkNumberOfLights //VTK::NumberOfLights\n#define vtkMaxLaoKernelSize //VTK::MaxLaoKernelSize\n#define vtkNumberOfComponents //VTK::NumberOfComponents\n#define vtkBlendMode //VTK::BlendMode\n#define vtkMaximumNumberOfSamples //VTK::MaximumNumberOfSamples\n\n//VTK::EnabledColorFunctions\n\n//VTK::EnabledLightings\n\n//VTK::EnabledMultiTexturePerVolume\n\n//VTK::EnabledGradientOpacity\n\n//VTK::EnabledIndependentComponents\n\n//VTK::vtkProportionalComponents\n\n//VTK::vtkForceNearestComponents\n\nuniform int twoSidedLighting;\n\n#if vtkMaxLaoKernelSize > 0\n vec2 kernelSample[vtkMaxLaoKernelSize];\n#endif\n\n// Textures\n#ifdef EnabledMultiTexturePerVolume\n #define vtkNumberOfVolumeTextures vtkNumberOfComponents\n#else\n #define vtkNumberOfVolumeTextures 1\n#endif\nuniform highp sampler3D volumeTexture[vtkNumberOfVolumeTextures];\nuniform sampler2D colorTexture;\nuniform sampler2D opacityTexture;\nuniform sampler2D jtexture;\nuniform sampler2D labelOutlineThicknessTexture;\n\nstruct Volume {\n // ---- Volume geometry settings ----\n\n vec3 originVC; // in VC\n vec3 spacing; // in VC per IC\n vec3 inverseSpacing; // 1/spacing\n ivec3 dimensions; // in IC\n vec3 inverseDimensions; // 1/vec3(dimensions)\n mat3 vecISToVCMatrix; // convert from IS to VC without translation\n mat3 vecVCToISMatrix; // convert from VC to IS without translation\n mat4 PCWCMatrix;\n mat4 worldToIndex;\n float diagonalLength; // in VC, this is: length(size)\n\n // ---- Texture settings ----\n\n // Texture shift and scale\n vec4 colorTextureScale;\n vec4 colorTextureShift;\n vec4 opacityTextureScale;\n vec4 opacityTextureShift;\n\n // The heights defined below are the locations for the up to four components\n // of the transfer functions. The transfer functions have a height of (2 *\n // numberOfComponents) pixels so the values are computed to hit the middle of\n // the two rows for that component\n vec4 transferFunctionsSampleHeight;\n\n // ---- Mode specific settings ----\n\n // Independent component default preset settings per component\n vec4 independentComponentMix;\n\n // Additive / average blending mode settings\n vec4 ipScalarRangeMin;\n vec4 ipScalarRangeMax;\n\n // ---- Rendering settings ----\n\n // Lighting\n float ambient;\n float diffuse;\n float specular;\n float specularPower;\n int computeNormalFromOpacity;\n\n // Gradient opacity\n vec4 gradientOpacityScale;\n vec4 gradientOpacityShift;\n vec4 gradientOpacityMin;\n vec4 gradientOpacityMax;\n\n // Volume shadow\n float volumetricScatteringBlending;\n float globalIlluminationReach;\n float anisotropy;\n float anisotropySquared;\n\n // LAO\n int kernelSize;\n int kernelRadius;\n\n // Label outline\n float outlineOpacity;\n};\nuniform Volume volume;\n\nstruct Light {\n vec3 color;\n vec3 positionVC;\n vec3 directionVC; // normalized\n vec3 halfAngleVC;\n vec3 attenuation;\n float exponent;\n float coneAngle;\n int isPositional;\n};\n#if vtkNumberOfLights > 0\n uniform Light lights[vtkNumberOfLights];\n#endif\n\nuniform float vpWidth;\nuniform float vpHeight;\nuniform float vpOffsetX;\nuniform float vpOffsetY;\n\n// Bitmasks for label outline\nconst int MAX_SEGMENT_INDEX = 256; // Define as per expected maximum\n#define MAX_SEGMENTS 256\n#define UINT_SIZE 32\n// We add UINT_SIZE - 1, as we want the ceil of the division instead of the\n// floor\n#define BITMASK_SIZE ((MAX_SEGMENTS + UINT_SIZE - 1) / UINT_SIZE)\nuint labelOutlineBitmasks[BITMASK_SIZE];\n\n// Set the corresponding bit in the bitmask\nvoid setLabelOutlineBit(int segmentIndex) {\n int arrayIndex = segmentIndex / UINT_SIZE;\n int bitIndex = segmentIndex % UINT_SIZE;\n labelOutlineBitmasks[arrayIndex] |= 1u << bitIndex;\n}\n\n// Check if a bit is set in the bitmask\nbool isLabelOutlineBitSet(int segmentIndex) {\n int arrayIndex = segmentIndex / UINT_SIZE;\n int bitIndex = segmentIndex % UINT_SIZE;\n return ((labelOutlineBitmasks[arrayIndex] & (1u << bitIndex)) != 0u);\n}\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//VTK::ClipPlane::Dec\n\n// A random number between 0 and 1 that only depends on the fragment\n// It uses the jtexture, so this random seed repeats by blocks of 32 fragments\n// in screen space\nfloat fragmentSeed;\n\n// sample texture is global\nuniform float sampleDistance;\nuniform float volumeShadowSampleDistance;\n\n// declaration for intermixed geometry\n//VTK::ZBuffer::Dec\n\n//=======================================================================\n// global and custom variables (a temporary section before photorealistics\n// rendering module is complete)\nvec3 rayDirVC;\n\n#define INV4PI 0.0796\n#define EPSILON 0.001\n#define PI 3.1415\n#define PI2 9.8696\n\nvec4 rawSampleTexture(vec3 pos) {\n #ifdef EnabledMultiTexturePerVolume\n vec4 rawSample;\n rawSample[0] = texture(volumeTexture[0], pos)[0];\n #if vtkNumberOfComponents > 1\n rawSample[1] = texture(volumeTexture[1], pos)[0];\n #endif\n #if vtkNumberOfComponents > 2\n rawSample[2] = texture(volumeTexture[2], pos)[0];\n #endif\n #if vtkNumberOfComponents > 3\n rawSample[3] = texture(volumeTexture[3], pos)[0];\n #endif\n return rawSample;\n #else\n return texture(volumeTexture[0], pos);\n #endif\n}\n\nvec4 rawFetchTexture(ivec3 pos) {\n #ifdef EnabledMultiTexturePerVolume\n vec4 rawSample;\n #if vtkNumberOfComponents > 0\n rawSample[0] = texelFetch(volumeTexture[0], pos, 0)[0];\n #endif\n #if vtkNumberOfComponents > 1\n rawSample[1] = texelFetch(volumeTexture[1], pos, 0)[0];\n #endif\n #if vtkNumberOfComponents > 2\n rawSample[2] = texelFetch(volumeTexture[2], pos, 0)[0];\n #endif\n #if vtkNumberOfComponents > 3\n rawSample[3] = texelFetch(volumeTexture[3], pos, 0)[0];\n #endif\n return rawSample;\n #else\n return texelFetch(volumeTexture[0], pos, 0);\n #endif\n}\n\nvec4 getTextureValue(vec3 pos) {\n vec4 tmp = rawSampleTexture(pos);\n\n // Force nearest\n #if defined(vtkComponent0ForceNearest) || \\\n defined(vtkComponent1ForceNearest) || \\\n defined(vtkComponent2ForceNearest) || \\\n defined(vtkComponent3ForceNearest)\n vec3 nearestPos = (floor(pos * vec3(volume.dimensions)) + 0.5) *\n volume.inverseDimensions;\n vec4 nearestValue = rawSampleTexture(nearestPos);\n #ifdef vtkComponent0ForceNearest\n tmp[0] = nearestValue[0];\n #endif\n #ifdef vtkComponent1ForceNearest\n tmp[1] = nearestValue[1];\n #endif\n #ifdef vtkComponent2ForceNearest\n tmp[2] = nearestValue[2];\n #endif\n #ifdef vtkComponent3ForceNearest\n tmp[3] = nearestValue[3];\n #endif\n #endif\n\n // Set alpha when using dependent components\n #ifndef EnabledIndependentComponents\n #if vtkNumberOfComponents == 1\n tmp.a = tmp.r;\n #endif\n #if vtkNumberOfComponents == 2\n tmp.a = tmp.g;\n #endif\n #if vtkNumberOfComponents == 3\n tmp.a = length(tmp.rgb);\n #endif\n #endif\n\n return tmp;\n}\n\n// `height` is usually `volume.transferFunctionsSampleHeight[component]`\n// when using independent component and `0.5` otherwise. Don't move the if\n// statement in these function, as the callers usually already knows if it is\n// using independent component or not\nfloat getOpacityFromTexture(float scalar, int component, float height) {\n float scaledScalar = scalar * volume.opacityTextureScale[component] +\n volume.opacityTextureShift[component];\n return texture2D(opacityTexture, vec2(scaledScalar, height)).r;\n}\nvec3 getColorFromTexture(float scalar, int component, float height) {\n float scaledScalar = scalar * volume.colorTextureScale[component] +\n volume.colorTextureShift[component];\n return texture2D(colorTexture, vec2(scaledScalar, height)).rgb;\n}\n\n//=======================================================================\n// transformation between VC and IS space\n\n// convert vector position from idx to vc\nvec3 posIStoVC(vec3 posIS) {\n return volume.vecISToVCMatrix * posIS + volume.originVC;\n}\n\n// convert vector position from vc to idx\nvec3 posVCtoIS(vec3 posVC) {\n return volume.vecVCToISMatrix * (posVC - volume.originVC);\n}\n\n// Rotate vector to view coordinate\nvec3 vecISToVC(vec3 dirIS) {\n return volume.vecISToVCMatrix * dirIS;\n}\n\n// Rotate vector to idx coordinate\nvec3 vecVCToIS(vec3 dirVC) {\n return volume.vecVCToISMatrix * dirVC;\n}\n\n//=======================================================================\n// Given a normal compute the gradient opacity factors\nfloat computeGradientOpacityFactor(float normalMag, int component) {\n float goscale = volume.gradientOpacityScale[component];\n float goshift = volume.gradientOpacityShift[component];\n float gomin = volume.gradientOpacityMin[component];\n float gomax = volume.gradientOpacityMax[component];\n return clamp(normalMag * goscale + goshift, gomin, gomax);\n}\n\n#ifdef vtkClippingPlanesOn\n bool isPointClipped(vec3 posVC) {\n for (int i = 0; i < clip_numPlanes; ++i) {\n if (dot(vec3(vClipPlaneOrigins[i] - posVC), vClipPlaneNormals[i]) > 0.0) {\n return true;\n }\n }\n return false;\n }\n#endif\n\n//=======================================================================\n// compute the normal and gradient magnitude for a position, uses forward\n// difference\n\n// The output normal is in VC\nvec4 computeDensityNormal(vec3 opacityUCoords[2], float opacityTextureHeight,\n float gradientOpacity, int component) {\n // Pass the scalars through the opacity functions\n vec4 opacityG;\n opacityG.x += getOpacityFromTexture(opacityUCoords[0].x, component,\n opacityTextureHeight);\n opacityG.y += getOpacityFromTexture(opacityUCoords[0].y, component,\n opacityTextureHeight);\n opacityG.z += getOpacityFromTexture(opacityUCoords[0].z, component,\n opacityTextureHeight);\n opacityG.x -= getOpacityFromTexture(opacityUCoords[1].x, component,\n opacityTextureHeight);\n opacityG.y -= getOpacityFromTexture(opacityUCoords[1].y, component,\n opacityTextureHeight);\n opacityG.z -= getOpacityFromTexture(opacityUCoords[1].z, component,\n opacityTextureHeight);\n\n // Divide by spacing and convert to VC\n opacityG.xyz *= gradientOpacity * volume.inverseSpacing;\n opacityG.w = length(opacityG.xyz);\n if (opacityG.w == 0.0) {\n return vec4(0.0);\n }\n\n // Normalize\n opacityG.xyz = normalize(vecISToVC(opacityG.xyz));\n\n return opacityG;\n}\n\n// The output normal is in VC\nvec4 computeNormalForDensity(vec3 posIS, out vec3 scalarInterp[2],\n const int opacityComponent) {\n vec3 offsetedPosIS;\n for (int axis = 0; axis < 3; ++axis) {\n // Positive direction\n offsetedPosIS = posIS;\n offsetedPosIS[axis] += volume.inverseDimensions[axis];\n scalarInterp[0][axis] =\n getTextureValue(offsetedPosIS)[opacityComponent];\n #ifdef vtkClippingPlanesOn\n if (isPointClipped(posIStoVC(offsetedPosIS))) {\n scalarInterp[0][axis] = 0.0;\n }\n #endif\n\n // Negative direction\n offsetedPosIS = posIS;\n offsetedPosIS[axis] -= volume.inverseDimensions[axis];\n scalarInterp[1][axis] =\n getTextureValue(offsetedPosIS)[opacityComponent];\n #ifdef vtkClippingPlanesOn\n if (isPointClipped(posIStoVC(offsetedPosIS))) {\n scalarInterp[1][axis] = 0.0;\n }\n #endif\n }\n\n vec4 result;\n result.xyz = (scalarInterp[0] - scalarInterp[1]) * volume.inverseSpacing;\n result.w = length(result.xyz);\n if (result.w == 0.0) {\n return vec4(0.0);\n }\n result.xyz = normalize(vecISToVC(result.xyz));\n return result;\n}\n\nvec4 fragCoordToPCPos(vec4 fragCoord) {\n return vec4((fragCoord.x / vpWidth - vpOffsetX - 0.5) * 2.0,\n (fragCoord.y / vpHeight - vpOffsetY - 0.5) * 2.0,\n (fragCoord.z - 0.5) * 2.0, 1.0);\n}\n\nvec4 pcPosToWorldCoord(vec4 pcPos) {\n return volume.PCWCMatrix * pcPos;\n}\n\nvec3 fragCoordToIndexSpace(vec4 fragCoord) {\n vec4 pcPos = fragCoordToPCPos(fragCoord);\n vec4 worldCoord = pcPosToWorldCoord(pcPos);\n vec4 vertex = (worldCoord / worldCoord.w);\n\n vec3 index = (volume.worldToIndex * vertex).xyz;\n\n // half voxel fix for labelmapOutline\n return (index + vec3(0.5)) * volume.inverseDimensions;\n}\n\nvec3 fragCoordToWorld(vec4 fragCoord) {\n vec4 pcPos = fragCoordToPCPos(fragCoord);\n vec4 worldCoord = pcPosToWorldCoord(pcPos);\n return worldCoord.xyz;\n}\n\n//=======================================================================\n// Compute the normals and gradient magnitudes for a position for independent\n// components The output normals are in VC\nmat4 computeMat4Normal(vec3 posIS, vec4 tValue) {\n vec3 xvec = vec3(volume.inverseDimensions.x, 0.0, 0.0);\n vec3 yvec = vec3(0.0, volume.inverseDimensions.y, 0.0);\n vec3 zvec = vec3(0.0, 0.0, volume.inverseDimensions.z);\n\n vec4 distX = getTextureValue(posIS + xvec) - getTextureValue(posIS - xvec);\n vec4 distY = getTextureValue(posIS + yvec) - getTextureValue(posIS - yvec);\n vec4 distZ = getTextureValue(posIS + zvec) - getTextureValue(posIS - zvec);\n\n // divide by spacing\n distX *= 0.5 * volume.inverseSpacing.x;\n distY *= 0.5 * volume.inverseSpacing.y;\n distZ *= 0.5 * volume.inverseSpacing.z;\n\n mat4 result;\n\n // optionally compute the 1st component\n #if vtkNumberOfComponents > 0 && !defined(vtkComponent0Proportional)\n {\n const int component = 0;\n vec3 normal = vec3(distX[component], distY[component], distZ[component]);\n float normalLength = length(normal);\n if (normalLength > 0.0) {\n normal = normalize(vecISToVC(normal));\n }\n result[component] = vec4(normal, normalLength);\n }\n #endif\n\n // optionally compute the 2nd component\n #if vtkNumberOfComponents > 1 && !defined(vtkComponent1Proportional)\n {\n const int component = 1;\n vec3 normal = vec3(distX[component], distY[component], distZ[component]);\n float normalLength = length(normal);\n if (normalLength > 0.0) {\n normal = normalize(vecISToVC(normal));\n }\n result[component] = vec4(normal, normalLength);\n }\n #endif\n\n // optionally compute the 3rd component\n #if vtkNumberOfComponents > 2 && !defined(vtkComponent2Proportional)\n {\n const int component = 2;\n vec3 normal = vec3(distX[component], distY[component], distZ[component]);\n float normalLength = length(normal);\n if (normalLength > 0.0) {\n normal = normalize(vecISToVC(normal));\n }\n result[component] = vec4(normal, normalLength);\n }\n #endif\n\n // optionally compute the 4th component\n #if vtkNumberOfComponents > 3 && !defined(vtkComponent3Proportional)\n {\n const int component = 3;\n vec3 normal = vec3(distX[component], distY[component], distZ[component]);\n float normalLength = length(normal);\n if (normalLength > 0.0) {\n normal = normalize(vecISToVC(normal));\n }\n result[component] = vec4(normal, normalLength);\n }\n #endif\n\n return result;\n}\n\n//=======================================================================\n// global shadow - secondary ray\n\n// henyey greenstein phase function\nfloat phaseFunction(float cos_angle) {\n // divide by 2.0 instead of 4pi to increase intensity\n float anisotropy = volume.anisotropy;\n if (abs(anisotropy) <= EPSILON) {\n // isotropic scatter returns 0.5 instead of 1/4pi to increase intensity\n return 0.5;\n }\n float anisotropy2 = volume.anisotropySquared;\n return ((1.0 - anisotropy2) /\n pow(1.0 + anisotropy2 - 2.0 * anisotropy * cos_angle, 1.5)) /\n 2.0;\n}\n\n// Compute the two intersection distances of the ray with the volume in VC\n// The entry point is `rayOriginVC + distanceMin * rayDirVC` and the exit point\n// is `rayOriginVC + distanceMax * rayDirVC` If distanceMin < distanceMax, the\n// volume is not intersected The ray origin is inside the box when distanceMin <\n// 0.0 < distanceMax\nvec2 rayIntersectVolumeDistances(vec3 rayOriginVC, vec3 rayDirVC) {\n // Compute origin and direction in IS\n vec3 rayOriginIS = posVCtoIS(rayOriginVC);\n vec3 rayDirIS = vecVCToIS(rayDirVC);\n // Don't check for infinity as the min/max combination afterward will always\n // find an intersection before infinity\n vec3 invDir = 1.0 / rayDirIS;\n\n // We have: bound = origin + t * dir\n // So: t = (1/dir) * (bound - origin)\n vec3 distancesTo0 = invDir * (vec3(0.0) - rayOriginIS);\n vec3 distancesTo1 = invDir * (vec3(1.0) - rayOriginIS);\n // Min and max distances to plane intersection per plane\n vec3 dMinPerAxis = min(distancesTo0, distancesTo1);\n vec3 dMaxPerAxis = max(distancesTo0, distancesTo1);\n // Overall first and last intersection\n float distanceMin = max(dMinPerAxis.x, max(dMinPerAxis.y, dMinPerAxis.z));\n float distanceMax = min(dMaxPerAxis.x, min(dMaxPerAxis.y, dMaxPerAxis.z));\n return vec2(distanceMin, distanceMax);\n}\n\n//=======================================================================\n// local ambient occlusion\n#if vtkMaxLaoKernelSize > 0\n\n // Return a random point on the unit sphere\n vec3 sampleDirectionUniform(int rayIndex) {\n // Each ray of each fragment should be different, two sources of randomness\n // are used. Only depends on ray index\n vec2 rayRandomness = kernelSample[rayIndex];\n // Only depends on fragment\n float fragmentRandomness = fragmentSeed;\n // Merge both source of randomness in a single uniform random variable using\n // the formula (x+y < 1 ? x+y : x+y-1). The simpler formula (x+y)/2 doesn't\n // result in a uniform distribution\n vec2 mergedRandom = rayRandomness + vec2(fragmentRandomness);\n mergedRandom -= vec2(greaterThanEqual(mergedRandom, vec2(1.0)));\n\n // Insipred by:\n // https://karthikkaranth.me/blog/generating-random-points-in-a-sphere/#better-choice-of-spherical-coordinates\n float u = mergedRandom[0];\n float v = mergedRandom[1];\n float theta = u * 2.0 * PI;\n float phi = acos(2.0 * v - 1.0);\n float sinTheta = sin(theta);\n float cosTheta = cos(theta);\n float sinPhi = sin(phi);\n float cosPhi = cos(phi);\n return vec3(sinPhi * cosTheta, sinPhi * sinTheta, cosPhi);\n }\n\n float computeLAO(vec3 posVC, vec4 normalVC, float originalOpacity) {\n // apply LAO only at selected locations, otherwise return full brightness\n if (normalVC.w <= 0.0 || originalOpacity <= 0.05) {\n return 1.0;\n }\n\n #ifdef EnabledGradientOpacity\n float gradientOpacityFactor = computeGradientOpacityFactor(normalVC.w, 0);\n #endif\n\n float visibilitySum = 0.0;\n float weightSum = 0.0;\n for (int i = 0; i < volume.kernelSize; i++) {\n // Only sample on an hemisphere around the normalVC.xyz axis, so\n // normalDotRay should be negative\n vec3 rayDirectionVC = sampleDirectionUniform(i);\n float normalDotRay = dot(normalVC.xyz, rayDirectionVC);\n if (normalDotRay > 0.0) {\n // Flip rayDirectionVC when it is in the wrong hemisphere\n rayDirectionVC = -rayDirectionVC;\n normalDotRay = -normalDotRay;\n }\n\n vec3 currPosIS = posVCtoIS(posVC);\n float visibility = 1.0;\n vec3 randomDirStepIS = vecVCToIS(rayDirectionVC * sampleDistance);\n for (int j = 0; j < volume.kernelRadius; j++) {\n currPosIS += randomDirStepIS;\n // If out of the volume, we are done\n if (any(lessThan(currPosIS, vec3(0.0))) ||\n any(greaterThan(currPosIS, vec3(1.0)))) {\n break;\n }\n float opacity = getOpacityFromTexture(getTextureValue(currPosIS).r, 0, 0.5);\n #ifdef EnabledGradientOpacity\n opacity *= gradientOpacityFactor;\n #endif\n visibility *= 1.0 - opacity;\n // If visibility is less than EPSILON, consider it to be 0\n if (visibility < EPSILON) {\n visibility = 0.0;\n break;\n }\n }\n float rayWeight = -normalDotRay;\n visibilitySum += visibility * rayWeight;\n weightSum += rayWeight;\n }\n\n // If no sample, LAO factor is one\n if (weightSum == 0.0) {\n return 1.0;\n }\n\n // LAO factor is the average visibility:\n // - visibility low => ambient low\n // - visibility high => ambient high\n float lao = visibilitySum / weightSum;\n\n // Reduce variance by clamping\n return clamp(lao, 0.3, 1.0);\n }\n#endif\n\n//=======================================================================\n// Volume shadows\n#if vtkNumberOfLights > 0\n\n // Non-memoised version\n float computeVolumeShadowWithoutCache(vec3 posVC, vec3 lightDirNormVC) {\n // modify sample distance with a random number between 1.5 and 3.0\n float rayStepLength =\n volumeShadowSampleDistance * mix(1.5, 3.0, fragmentSeed);\n\n // in case the first sample near surface has a very tiled light ray, we need\n // to offset start position\n vec3 initialPosVC = posVC + rayStepLength * lightDirNormVC;\n\n #ifdef vtkClippingPlanesOn\n float clippingPlanesMaxDistance = infinity;\n for (int i = 0; i < clip_numPlanes; ++i) {\n // Find distance of intersection with the plane\n // Points are clipped when:\n // dot(planeOrigin - (rayOrigin + distance * rayDirection), planeNormal) > 0\n // This is equivalent to:\n // dot(planeOrigin - rayOrigin, planeNormal) - distance * dot(rayDirection,\n // planeNormal) > 0.0\n // We precompute the dot products, so we clip ray points when:\n // dotOrigin - distance * dotDirection > 0.0\n float dotOrigin =\n dot(vClipPlaneOrigins[i] - initialPosVC, vClipPlaneNormals[i]);\n if (dotOrigin > 0.0) {\n // The initialPosVC is clipped by this plane\n return 1.0;\n }\n float dotDirection = dot(lightDirNormVC, vClipPlaneNormals[i]);\n if (dotDirection < 0.0) {\n // We only hit the plane if dotDirection is negative, as (distance is\n // positive)\n float intersectionDistance =\n dotOrigin / dotDirection; // negative divided by negative => positive\n clippingPlanesMaxDistance =\n min(clippingPlanesMaxDistance, intersectionDistance);\n }\n }\n #endif\n\n vec2 intersectionDistances =\n rayIntersectVolumeDistances(initialPosVC, lightDirNormVC);\n\n if (intersectionDistances[1] <= intersectionDistances[0] ||\n intersectionDistances[1] <= 0.0) {\n // Volume not hit or behind the ray\n return 1.0;\n }\n\n // When globalIlluminationReach is 0, no sample at all\n // When globalIlluminationReach is 1, the ray will go through the whole\n // volume\n float maxTravelDistance = mix(0.0, volume.diagonalLength,\n volume.globalIlluminationReach);\n float startDistance = max(intersectionDistances[0], 0.0);\n float endDistance = min(intersectionDistances[1], startDistance + maxTravelDistance);\n #ifdef vtkClippingPlanesOn\n endDistance = min(endDistance, clippingPlanesMaxDistance);\n #endif\n if (endDistance - startDistance < 0.0) {\n return 1.0;\n }\n\n // These two variables are used to compute posIS, without having to call\n // VCtoIS at each step\n vec3 initialPosIS = posVCtoIS(initialPosVC);\n // The light dir is scaled and rotated, but not translated, as it is a\n // vector (w = 0)\n vec3 scaledLightDirIS = vecVCToIS(lightDirNormVC);\n\n float shadow = 1.0;\n for (float currentDistance = startDistance; currentDistance <= endDistance;\n currentDistance += rayStepLength) {\n vec3 posIS = initialPosIS + currentDistance * scaledLightDirIS;\n vec4 scalar = getTextureValue(posIS);\n float opacity = getOpacityFromTexture(scalar.r, 0, 0.5);\n #if defined(EnabledGradientOpacity) && !defined(EnabledIndependentComponents)\n vec3 scalarInterp[2];\n vec4 normal = computeNormalForDensity(posIS, scalarInterp, 3);\n float opacityFactor = computeGradientOpacityFactor(normal.w, 0);\n opacity *= opacityFactor;\n #endif\n shadow *= 1.0 - opacity;\n\n // Early termination if shadow coeff is near 0.0\n if (shadow < EPSILON) {\n return 0.0;\n }\n }\n return shadow;\n }\n\n // Some cache for volume shadows\n struct {\n vec3 posVC;\n float shadow;\n } cachedShadows[vtkNumberOfLights];\n\n // Memoised version\n float computeVolumeShadow(vec3 posVC, vec3 lightDirNormVC, int lightIdx) {\n if (posVC == cachedShadows[lightIdx].posVC) {\n return cachedShadows[lightIdx].shadow;\n }\n float shadow = computeVolumeShadowWithoutCache(posVC, lightDirNormVC);\n cachedShadows[lightIdx].posVC = posVC;\n cachedShadows[lightIdx].shadow = shadow;\n return shadow;\n }\n\n#endif\n\n//=======================================================================\n// surface light contribution\n#if vtkNumberOfLights > 0\n vec3 applyLighting(vec3 tColor, vec4 normalVC) {\n vec3 diffuse = vec3(0.0, 0.0, 0.0);\n vec3 specular = vec3(0.0, 0.0, 0.0);\n for (int lightIdx = 0; lightIdx < vtkNumberOfLights; lightIdx++) {\n float df = dot(normalVC.xyz, lights[lightIdx].directionVC);\n if (df > 0.0) {\n diffuse += df * lights[lightIdx].color;\n float sf = dot(normalVC.xyz, -lights[lightIdx].halfAngleVC);\n if (sf > 0.0) {\n specular += pow(sf, volume.specularPower) * lights[lightIdx].color;\n }\n }\n }\n return tColor * (diffuse * volume.diffuse + volume.ambient) +\n specular * volume.specular;\n }\n\n vec3 applySurfaceShadowLighting(vec3 tColor, float alpha, vec3 posVC,\n vec4 normalVC) {\n // everything in VC\n vec3 diffuse = vec3(0.0);\n vec3 specular = vec3(0.0);\n for (int ligthIdx = 0; ligthIdx < vtkNumberOfLights; ligthIdx++) {\n vec3 vertLightDirection;\n float attenuation;\n if (lights[ligthIdx].isPositional == 1) {\n vertLightDirection = posVC - lights[ligthIdx].positionVC;\n float lightDistance = length(vertLightDirection);\n // Normalize with precomputed length\n vertLightDirection = vertLightDirection / lightDistance;\n // Base attenuation\n vec3 attenuationPolynom = lights[ligthIdx].attenuation;\n attenuation =\n 1.0 / (attenuationPolynom[0] +\n lightDistance * (attenuationPolynom[1] +\n lightDistance * attenuationPolynom[2]));\n // Cone attenuation\n float coneDot = dot(vertLightDirection, lights[ligthIdx].directionVC);\n // Per OpenGL standard cone angle is 90 or less for a spot light\n if (lights[ligthIdx].coneAngle <= 90.0) {\n if (coneDot >= cos(radians(lights[ligthIdx].coneAngle))) {\n // Inside the cone\n attenuation *= pow(coneDot, lights[ligthIdx].exponent);\n } else {\n // Outside the cone\n attenuation = 0.0;\n }\n }\n } else {\n vertLightDirection = lights[ligthIdx].directionVC;\n attenuation = 1.0;\n }\n\n float ndotL = dot(normalVC.xyz, vertLightDirection);\n if (ndotL < 0.0 && twoSidedLighting == 1) {\n ndotL = -ndotL;\n }\n if (ndotL > 0.0) {\n // Diffuse\n diffuse += ndotL * attenuation * lights[ligthIdx].color;\n // Specular\n float vdotR =\n dot(-rayDirVC, normalize(vertLightDirection - 2.0 * ndotL * normalVC.xyz));\n if (vdotR > 0.0) {\n specular += pow(vdotR, volume.specularPower) * attenuation *\n lights[ligthIdx].color;\n }\n }\n }\n #if vtkMaxLaoKernelSize > 0\n float laoFactor = computeLAO(posVC, normalVC, alpha);\n #else\n const float laoFactor = 1.0;\n #endif\n return tColor * (diffuse * volume.diffuse +\n volume.ambient * laoFactor) +\n specular * volume.specular;\n }\n\n vec3 applyVolumeShadowLighting(vec3 tColor, vec3 posVC) {\n // Here we have no effect of cones and no attenuation\n vec3 diffuse = vec3(0.0);\n for (int lightIdx = 0; lightIdx < vtkNumberOfLights; lightIdx++) {\n vec3 lightDirVC = lights[lightIdx].isPositional == 1\n ? normalize(lights[lightIdx].positionVC - posVC)\n : -lights[lightIdx].directionVC;\n float shadowCoeff = computeVolumeShadow(posVC, lightDirVC, lightIdx);\n float phaseAttenuation = phaseFunction(dot(rayDirVC, lightDirVC));\n diffuse += phaseAttenuation * shadowCoeff * lights[lightIdx].color;\n }\n return tColor * (diffuse * volume.diffuse + volume.ambient);\n }\n#endif\n\n// LAO of surface shadows and volume shadows only work with dependent components\nvec3 applyAllLightning(vec3 tColor, float alpha, vec3 posVC,\n vec4 surfaceNormalVC) {\n #if vtkNumberOfLights > 0\n // 0 <= volCoeff < EPSILON => only surface shadows\n // EPSILON <= volCoeff < 1 - EPSILON => mix of surface and volume shadows\n // 1 - EPSILON <= volCoeff => only volume shadows\n float volCoeff = volume.volumetricScatteringBlending *\n (1.0 - alpha / 2.0) *\n (1.0 - atan(surfaceNormalVC.w) * INV4PI);\n\n // Compute surface lighting if needed\n vec3 surfaceShadedColor = tColor;\n #ifdef EnableSurfaceLighting\n if (volCoeff < 1.0 - EPSILON) {\n surfaceShadedColor =\n applySurfaceShadowLighting(tColor, alpha, posVC, surfaceNormalVC);\n }\n #endif\n\n // Compute volume lighting if needed\n vec3 volumeShadedColor = tColor;\n #ifdef EnableVolumeLighting\n if (volCoeff >= EPSILON) {\n volumeShadedColor = applyVolumeShadowLighting(tColor, posVC);\n }\n #endif\n\n // Return the right mix\n if (volCoeff < EPSILON) {\n // Surface shadows\n return surfaceShadedColor;\n }\n if (volCoeff >= 1.0 - EPSILON) {\n // Volume shadows\n return volumeShadedColor;\n }\n // Mix of surface and volume shadows\n return mix(surfaceShadedColor, volumeShadedColor, volCoeff);\n #endif\n return tColor;\n}\n\nvec4 getColorForLabelOutline() {\n vec3 centerPosIS =\n fragCoordToIndexSpace(gl_FragCoord); // pos in texture space\n vec4 centerValue = getTextureValue(centerPosIS);\n bool pixelOnBorder = false;\n vec4 tColor = vec4(getColorFromTexture(centerValue.r, 0, 0.5),\n getOpacityFromTexture(centerValue.r, 0, 0.5));\n\n int segmentIndex = int(centerValue.r * 255.0);\n\n // Use texture sampling for outlineThickness\n float textureCoordinate = float(segmentIndex - 1) / 1024.0;\n float textureValue =\n texture2D(labelOutlineThicknessTexture, vec2(textureCoordinate, 0.5)).r;\n int actualThickness = int(textureValue * 255.0);\n\n // If it is the background (segment index 0), we should quickly bail out.\n // Previously, this was determined by tColor.a, which was incorrect as it\n // prevented the outline from appearing when the fill is 0.\n if (segmentIndex == 0) {\n return vec4(0, 0, 0, 0);\n }\n\n // Only perform outline check on fragments rendering voxels that aren't\n // invisible. Saves a bunch of needless checks on the background.\n // TODO define epsilon when building shader?\n for (int i = -actualThickness; i <= actualThickness; i++) {\n for (int j = -actualThickness; j <= actualThickness; j++) {\n if (i == 0 || j == 0) {\n continue;\n }\n\n vec4 neighborPixelCoord =\n vec4(gl_FragCoord.x + float(i), 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 = volume.outlineOpacity;\n }\n\n return tColor;\n}\n\nvec4 getColorForAdditivePreset(vec4 tValue, vec3 posVC, vec3 posIS) {\n // compute normals\n mat4 normalMat = computeMat4Normal(posIS, tValue);\n vec4 normalLights[2];\n normalLights[0] = normalMat[0];\n normalLights[1] = normalMat[1];\n #if vtkNumberOfLights > 0\n if (volume.computeNormalFromOpacity == 1) {\n for (int component = 0; component < 2; ++component) {\n vec3 scalarInterp[2];\n float height = volume.transferFunctionsSampleHeight[component];\n computeNormalForDensity(posIS, scalarInterp, component);\n normalLights[component] =\n computeDensityNormal(scalarInterp, height, 1.0, component);\n }\n }\n #endif\n\n // compute opacities\n float opacities[2];\n opacities[0] = getOpacityFromTexture(\n tValue[0], 0, volume.transferFunctionsSampleHeight[0]);\n opacities[1] = getOpacityFromTexture(\n tValue[1], 1, volume.transferFunctionsSampleHeight[1]);\n #ifdef EnabledGradientOpacity\n for (int component = 0; component < 2; ++component) {\n opacities[component] *=\n computeGradientOpacityFactor(normalMat[component].a, component);\n }\n #endif\n float opacitySum = opacities[0] + opacities[1];\n if (opacitySum <= 0.0) {\n return vec4(0.0);\n }\n\n // mix the colors and opacities\n vec3 colors[2];\n for (int component = 0; component < 2; ++component) {\n float sampleHeight = volume.transferFunctionsSampleHeight[component];\n vec3 color = getColorFromTexture(tValue[component], component, sampleHeight);\n color = applyAllLightning(color, opacities[component], posVC,\n normalLights[component]);\n colors[component] = color;\n }\n vec3 mixedColor =\n (opacities[0] * colors[0] + opacities[1] * colors[1]) / opacitySum;\n return vec4(mixedColor, min(1.0, opacitySum));\n}\n\nvec4 getColorForColorizePreset(vec4 tValue, vec3 posVC, vec3 posIS) {\n // compute normals\n mat4 normalMat = computeMat4Normal(posIS, tValue);\n vec4 normalLight = normalMat[0];\n #if vtkNumberOfLights > 0\n if (volume.computeNormalFromOpacity == 1) {\n vec3 scalarInterp[2];\n float height = volume.transferFunctionsSampleHeight[0];\n computeNormalForDensity(posIS, scalarInterp, 0);\n normalLight = computeDensityNormal(scalarInterp, height, 1.0, 0);\n }\n #endif\n\n // compute opacities\n float opacity = getOpacityFromTexture(\n tValue[0], 0, volume.transferFunctionsSampleHeight[0]);\n #ifdef EnabledGradientOpacity\n opacity *= computeGradientOpacityFactor(normalMat[0].a, 0);\n #endif\n\n // colorizing component\n vec3 colorizingColor = getColorFromTexture(\n tValue[0], 1, volume.transferFunctionsSampleHeight[1]);\n float colorizingOpacity = getOpacityFromTexture(\n tValue[1], 1, volume.transferFunctionsSampleHeight[1]);\n\n // mix the colors and opacities\n vec3 color =\n getColorFromTexture(tValue[0], 0,\n volume.transferFunctionsSampleHeight[0]) *\n mix(vec3(1.0), colorizingColor, colorizingOpacity);\n color = applyAllLightning(color, opacity, posVC, normalLight);\n return vec4(color, opacity);\n}\n\nvec4 getColorForDefaultIndependentPreset(vec4 tValue, vec3 posIS) {\n\n // compute the normal vectors as needed\n #if defined(EnabledGradientOpacity) || vtkNumberOfLights > 0\n mat4 normalMat = computeMat4Normal(posIS, tValue);\n #endif\n\n // process color and opacity for each component\n // initial value of alpha is determined by wether the first component is\n // proportional or not\n #if defined(vtkComponent0Proportional)\n // when it is proportional, it starts at 1 (neutral for multiplications)\n float alpha = 1.0;\n #else\n // when it is not proportional, it starts at 0 (neutral for additions)\n float alpha = 0.0;\n #endif\n\n vec3 mixedColor = vec3(0.0);\n #if vtkNumberOfComponents > 0\n {\n const int component = 0;\n vec3 color = getColorFromTexture(\n tValue[component], component,\n volume.transferFunctionsSampleHeight[component]);\n float opacity = getOpacityFromTexture(\n tValue[component], component,\n volume.transferFunctionsSampleHeight[component]);\n #if !defined(vtkComponent0Proportional)\n float alphaContribution = volume.independentComponentMix[component] * opacity;\n #ifdef EnabledGradientOpacity\n alphaContribution *= computeGradientOpacityFactor(normalMat[component].a, component);\n #endif\n alpha += alphaContribution;\n #if vtkNumberOfLights > 0\n color = applyLighting(color, normalMat[component]);\n #endif\n #else\n color *= opacity;\n alpha *= mix(opacity, 1.0,\n (1.0 - volume.independentComponentMix[component]));\n #endif\n mixedColor += volume.independentComponentMix[component] * color;\n }\n #endif\n #if vtkNumberOfComponents > 1\n {\n const int component = 1;\n vec3 color = getColorFromTexture(\n tValue[component], component,\n volume.transferFunctionsSampleHeight[component]);\n float opacity = getOpacityFromTexture(\n tValue[component], component,\n volume.transferFunctionsSampleHeight[component]);\n #if !defined(vtkComponent1Proportional)\n float alphaContribution = volume.independentComponentMix[component] * opacity;\n #ifdef EnabledGradientOpacity\n alphaContribution *= computeGradientOpacityFactor(normalMat[component].a, component);\n #endif\n alpha += alphaContribution;\n #if vtkNumberOfLights > 0\n color = applyLighting(color, normalMat[component]);\n #endif\n #else\n color *= opacity;\n alpha *= mix(opacity, 1.0,\n (1.0 - volume.independentComponentMix[component]));\n #endif\n mixedColor += volume.independentComponentMix[component] * color;\n }\n #endif\n #if vtkNumberOfComponents > 2\n {\n const int component = 2;\n vec3 color = getColorFromTexture(\n tValue[component], component,\n volume.transferFunctionsSampleHeight[component]);\n float opacity = getOpacityFromTexture(\n tValue[component], component,\n volume.transferFunctionsSampleHeight[component]);\n #if !defined(vtkComponent2Proportional)\n float alphaContribution = volume.independentComponentMix[component] * opacity;\n #ifdef EnabledGradientOpacity\n alphaContribution *= computeGradientOpacityFactor(normalMat[component].a, component);\n #endif\n alpha += alphaContribution;\n #if vtkNumberOfLights > 0\n color = applyLighting(color, normalMat[component]);\n #endif\n #else\n color *= opacity;\n alpha *= mix(opacity, 1.0,\n (1.0 - volume.independentComponentMix[component]));\n #endif\n mixedColor += volume.independentComponentMix[component] * color;\n }\n #endif\n #if vtkNumberOfComponents > 3\n {\n const int component = 3;\n vec3 color = getColorFromTexture(\n tValue[component], component,\n volume.transferFunctionsSampleHeight[component]);\n float opacity = getOpacityFromTexture(\n tValue[component], component,\n volume.transferFunctionsSampleHeight[component]);\n #if !defined(vtkComponent3Proportional)\n float alphaContribution = volume.independentComponentMix[component] * opacity;\n #ifdef EnabledGradientOpacity\n alphaContribution *= computeGradientOpacityFactor(normalMat[component].a, component);\n #endif\n alpha += alphaContribution;\n #if vtkNumberOfLights > 0\n color = applyLighting(color, normalMat[component]);\n #endif\n #else\n color *= opacity;\n alpha *= mix(opacity, 1.0,\n (1.0 - volume.independentComponentMix[component]));\n #endif\n mixedColor += volume.independentComponentMix[component] * color;\n }\n #endif\n\n return vec4(mixedColor, alpha);\n}\n\nvec4 getColorForDependentComponents(vec4 tValue, vec3 posVC, vec3 posIS) {\n #if defined(EnabledGradientOpacity) || vtkNumberOfLights > 0\n // use component 3 of the opacity texture as getTextureValue() sets alpha to\n // the opacity value\n vec3 scalarInterp[2];\n vec4 normal0 = computeNormalForDensity(posIS, scalarInterp, 3);\n float gradientOpacity = computeGradientOpacityFactor(normal0.a, 0);\n #endif\n\n // get color and opacity\n #if vtkNumberOfComponents == 1\n vec3 tColor = getColorFromTexture(tValue.r, 0, 0.5);\n float alpha = getOpacityFromTexture(tValue.r, 0, 0.5);\n #endif\n #if vtkNumberOfComponents == 2\n vec3 tColor = vec3(tValue.r * volume.colorTextureScale[0] +\n volume.colorTextureShift[0]);\n float alpha = getOpacityFromTexture(tValue.a, 1, 0.5);\n #endif\n #if vtkNumberOfComponents == 3\n vec3 tColor = tValue.rgb * volume.colorTextureScale.rgb +\n volume.colorTextureShift.rgb;\n float alpha = getOpacityFromTexture(tValue.a, 0, 0.5);\n #endif\n #if vtkNumberOfComponents == 4\n vec3 tColor = tValue.rgb * volume.colorTextureScale.rgb +\n volume.colorTextureShift.rgb;\n float alpha = getOpacityFromTexture(tValue.a, 3, 0.5);\n #endif\n\n // Apply gradient opacity\n #if defined(EnabledGradientOpacity)\n alpha *= gradientOpacity;\n #endif\n\n #if vtkNumberOfComponents == 1\n if (alpha < EPSILON) {\n return vec4(0.0);\n }\n #endif\n\n // lighting\n #if vtkNumberOfLights > 0\n vec4 normalLight;\n if (volume.computeNormalFromOpacity == 1) {\n if (normal0[3] != 0.0) {\n normalLight =\n computeDensityNormal(scalarInterp, 0.5, gradientOpacity, 0);\n if (normalLight[3] == 0.0) {\n normalLight = normal0;\n }\n }\n } else {\n normalLight = normal0;\n }\n tColor = applyAllLightning(tColor, alpha, posVC, normalLight);\n #endif\n\n return vec4(tColor, alpha);\n}\n\nvec4 getColorForValue(vec4 tValue, vec3 posVC, vec3 posIS) {\n #ifdef EnableColorForValueFunctionId0\n return getColorForDependentComponents(tValue, posVC, posIS);\n #endif\n\n #ifdef EnableColorForValueFunctionId1\n return getColorForAdditivePreset(tValue, posVC, posIS);\n #endif\n\n #ifdef EnableColorForValueFunctionId2\n return getColorForColorizePreset(tValue, posVC, posIS);\n #endif\n\n #ifdef EnableColorForValueFunctionId3\n /*\n * Mix the color information from all the independent components to get a\n * single rgba output. See other shader functions like\n * `getColorForAdditivePreset` to learn how to create a custom color mix.\n * The custom color mix should return a value, but if it doesn't, it will\n * fallback on the default shading\n */\n //VTK::CustomColorMix\n #endif\n\n #if defined(EnableColorForValueFunctionId4) || defined(EnableColorForValueFunctionId3)\n return getColorForDefaultIndependentPreset(tValue, posIS);\n #endif\n\n #ifdef EnableColorForValueFunctionId5\n return getColorForLabelOutline();\n #endif\n}\n\nbool valueWithinScalarRange(vec4 val) {\n #if vtkNumberOfComponents > 1 && !defined(EnabledIndependentComponents)\n return false;\n #endif\n vec4 rangeMin = volume.ipScalarRangeMin;\n vec4 rangeMax = volume.ipScalarRangeMax;\n for (int component = 0; component < vtkNumberOfComponents; ++component) {\n if (val[component] < rangeMin[component] ||\n rangeMax[component] < val[component]) {\n return false;\n }\n }\n return true;\n}\n\n#if vtkBlendMode == LABELMAP_EDGE_PROJECTION_BLEND\n bool checkOnEdgeForNeighbor(int xFragmentOffset, int yFragmentOffset,\n int segmentIndex, vec3 stepIS) {\n vec3 volumeDimensions = vec3(volume.dimensions);\n vec4 neighborPixelCoord = vec4(gl_FragCoord.x + float(xFragmentOffset),\n gl_FragCoord.y + float(yFragmentOffset),\n gl_FragCoord.z, gl_FragCoord.w);\n vec3 originalNeighborPosIS = fragCoordToIndexSpace(neighborPixelCoord);\n\n vec3 neighborPosIS = originalNeighborPosIS;\n for (int k = 0; k < vtkMaximumNumberOfSamples / 2; ++k) {\n ivec3 texCoord = ivec3(neighborPosIS * volumeDimensions);\n vec4 texValue = rawFetchTexture(texCoord);\n if (int(texValue.g) == segmentIndex) {\n // not on edge\n return false;\n }\n neighborPosIS += stepIS;\n }\n\n neighborPosIS = originalNeighborPosIS;\n for (int k = 0; k < vtkMaximumNumberOfSamples / 2; ++k) {\n ivec3 texCoord = ivec3(neighborPosIS * volumeDimensions);\n vec4 texValue = rawFetchTexture(texCoord);\n if (int(texValue.g) == segmentIndex) {\n // not on edge\n return false;\n }\n neighborPosIS -= stepIS;\n }\n\n // onedge\n float sampleHeight = volume.transferFunctionsSampleHeight[1];\n vec3 tColorSegment =\n getColorFromTexture(float(segmentIndex), 1, sampleHeight);\n float pwfValueSegment =\n getOpacityFromTexture(float(segmentIndex), 1, sampleHeight);\n gl_FragData[0] = vec4(tColorSegment, pwfValueSegment);\n return true;\n }\n#endif\n\nvec4 getColorAtPos(vec3 posVC) {\n vec3 posIS = posVCtoIS(posVC);\n vec4 texValue = getTextureValue(posIS);\n return getColorForValue(texValue, posVC, posIS);\n}\n\n//=======================================================================\n// Apply the specified blend mode operation along the ray's path.\n//\nvoid applyBlend(vec3 rayOriginVC, vec3 rayDirVC, float minDistance,\n float maxDistance) {\n // start slightly inside and apply some jitter\n vec3 stepVC = rayDirVC * sampleDistance;\n float raySteps = (maxDistance - minDistance) / sampleDistance;\n\n // Avoid 0.0 jitter\n float jitter = 0.01 + 0.99 * fragmentSeed;\n\n #if vtkBlendMode == COMPOSITE_BLEND\n // now map through opacity and color\n vec3 firstPosVC = rayOriginVC + minDistance * rayDirVC;\n vec4 firstColor = getColorAtPos(firstPosVC);\n\n // handle very thin volumes\n if (raySteps <= 1.0) {\n firstColor.a = 1.0 - pow(1.0 - firstColor.a, raySteps);\n gl_FragData[0] = firstColor;\n return;\n }\n\n // first color only counts for `jitter` factor of the step\n firstColor.a = 1.0 - pow(1.0 - firstColor.a, jitter);\n vec4 color = vec4(firstColor.rgb * firstColor.a, firstColor.a);\n vec3 posVC = firstPosVC + jitter * stepVC;\n float stepsTraveled = jitter;\n\n for (int i = 0; i < vtkMaximumNumberOfSamples; ++i) {\n // If we have reached the last step, break\n if (stepsTraveled + 1.0 >= raySteps) {\n break;\n }\n vec4 tColor = getColorAtPos(posVC);\n\n color = color + vec4(tColor.rgb * tColor.a, tColor.a) * (1.0 - color.a);\n stepsTraveled++;\n posVC += stepVC;\n if (color.a > 0.99) {\n color.a = 1.0;\n break;\n }\n }\n\n if (color.a < 0.99 && (raySteps - stepsTraveled) > 0.0) {\n vec3 endPosVC = rayOriginVC + maxDistance * rayDirVC;\n vec4 tColor = getColorAtPos(endPosVC);\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\n #if vtkBlendMode == MAXIMUM_INTENSITY_BLEND || \\\n vtkBlendMode == 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 == MAXIMUM_INTENSITY_BLEND\n #define OP max\n #else\n #define OP min\n #endif\n\n vec3 posVC