struct KernelConstants { int gridType_; float stepsize_; float intensity_; int numTimesteps_; int numValuesPerNode_; int numOTNodes_; int numBoxesPerAxis_; float temporalTolerance_; float spatialTolerance_; int rootLevel_; int paddedBrickDim_; }; float3 CartesianToSpherical(float3 _cartesian); float Lerp(float _v0, float _v1, float _d); int LeftBST(int _bstNodeIndex, int _numValuesPerNode, int _numOTNodes, bool _bstRoot, __global __read_only int *_tsp); int RightBST(int _bstNodeIndex, int _numValuesPerNode, int _numOTNodes, bool _bstRoot, __global __read_only int *_tsp); int ChildNodeIndex(int _bstNodeIndex, int *_timespanStart, int *_timespanEnd, int _timestep, int _numValuesPerNode, int _numOTNodes, bool _bstRoot, __global __read_only int *_tsp); int BrickIndex(int _bstNodeIndex, int _numValuesPerNode, __global __read_only int *_tsp); bool IsBSTLeaf(int _bstNodeIndex, int _numValuesPerNode, bool _bstRoot, __global __read_only int *_tsp); bool IsOctreeLeaf(int _otNodeIndex, int _numValuesPerNode, __global __read_only int *_tsp); int OTChildIndex(int _otNodeIndex, int _numValuesPerNode, int _child, __global __read_only int *_tsp) ; float TemporalError(int _bstNodeIndex, int _numValuesPerNode, __global __read_only int *_tsp) ; float SpatialError(int _bstNodeIndex, int _numValuesPerNode, __global __read_only int *_tsp); int3 BoxCoords(float3 _globalCoords, int _boxesPerAxis) ; int3 AtlasBoxCoords(int _brickIndex, __global __read_only int *_brickList); float3 AtlasCoords(float3 _globalCoords, int _brickIndex, int _boxesPerAxis, int _paddedBrickDim, int _level, __global __read_only int *_brickList) ; void SampleAtlas(float4 *_color, float3 _coords, int _brickIndex, int _boxesPerAxis, int _paddedBrickDim, int _level, const sampler_t _atlasSampler, __read_only image3d_t _textureAtlas, __read_only image2d_t _transferFunction, const sampler_t _tfSampler, __global __read_only int *_brickList); bool TraverseBST(int _otNodeIndex, int *_brickIndex, __constant __read_only struct KernelConstants *_constants, __global __read_only int *_tsp, const int _timestep); int EnclosingChild(float3 _P, float _boxMid, float3 _offset) ; void UpdateOffset(float3 *_offset, float _boxDim, int _child) ; float4 TraverseOctree(float3 _rayO, float3 _rayD, float _maxDist, __read_only image3d_t _textureAtlas, __constant struct KernelConstants *_constants, __read_only image2d_t _transferFunction, __global __read_only int *_tsp, __global __read_only int *_brickList, const int _timestep); __kernel void RaycasterTSP(__read_only image2d_t _cubeFront, __read_only image2d_t _cubeBack, __write_only image2d_t _output, __read_only image3d_t _textureAtlas, __constant struct KernelConstants *_constants, __read_only image2d_t _transferFunction, //__global __read_only float *_transferFunction, __global __read_only int *_tsp, __global __read_only int *_brickList, const int _timestep); // Turn normalized [0..1] cartesian coordinates // to normalized spherical [0..1] coordinates float3 CartesianToSpherical(float3 _cartesian) { // Put cartesian in [-1..1] range first _cartesian = (float3)(-1.0) + 2.0f* _cartesian; float r = length(_cartesian); float theta, phi; if (r == 0.0) { theta = phi = 0.0; } else { theta = acospi(_cartesian.z/r); phi = (M_PI + atan2(_cartesian.y, _cartesian.x)) / (2.0*M_PI); } r = r / native_sqrt(3.0f); // Sampler ignores w component return (float3)(r, theta, phi); } // Linearly interpolate between two values. Distance // is assumed to be normalized. float Lerp(float _v0, float _v1, float _d) { return _v0*(1.0 - _d) + _v1*_d; } /* float4 TransferFunction(__global __read_only image2d_t _tf, const sampler_t _tfSampler, float _i) { // TODO remove hard-coded value and change to 1D texture int i0 = (int)floor(1023.0*_i); int i1 = (i0 < 1023) ? i0+1 : i0; float di = _i - floor(_i); float tfr0 = _tf[i0*4+0]; float tfr1 = _tf[i1*4+0]; float tfg0 = _tf[i0*4+1]; float tfg1 = _tf[i1*4+1]; float tfb0 = _tf[i0*4+2]; float tfb1 = _tf[i1*4+2]; float tfa0 = _tf[i0*4+3]; float tfa1 = _tf[i1*4+3]; float tfr = Lerp(tfr0, tfr1, di); float tfg = Lerp(tfg0, tfg1, di); float tfb = Lerp(tfb0, tfb1, di); float tfa = Lerp(tfa0, tfa1, di); return clamp((float4)(tfr, tfg, tfb, tfa), (float4)(0.0), (float4)(1.0)); //return (float4)(tfr, tfg, tfb, tfa); float2 sampleCoords = (float2)(1.0, _i); return read_imagef(_tf, _tfSampler, sampleCoords); } */ /* // Return index to the octree root (same index as BST root at that OT node) int OctreeRootNodeIndex() { return 0; } */ // Return index to left BST child (low timespan) int LeftBST(int _bstNodeIndex, int _numValuesPerNode, int _numOTNodes, bool _bstRoot, __global __read_only int *_tsp) { // If the BST node is a root, the child pointer is used for the OT. // The child index is next to the root. // If not root, look up in TSP structure. if (_bstRoot) { return _bstNodeIndex + _numOTNodes; } else { return _tsp[_bstNodeIndex*_numValuesPerNode + 1]; } } // Return index to right BST child (high timespan) int RightBST(int _bstNodeIndex, int _numValuesPerNode, int _numOTNodes, bool _bstRoot, __global __read_only int *_tsp) { if (_bstRoot) { return _bstNodeIndex + _numOTNodes*2; } else { return _tsp[_bstNodeIndex*_numValuesPerNode + 1] + _numOTNodes; } } // Return child node index given a BST node, a time span and a timestep // Updates timespan int ChildNodeIndex(int _bstNodeIndex, int *_timespanStart, int *_timespanEnd, int _timestep, int _numValuesPerNode, int _numOTNodes, bool _bstRoot, __global __read_only int *_tsp) { // Choose left or right child int middle = *_timespanStart + (*_timespanEnd - *_timespanStart)/2; if (_timestep <= middle) { // Left subtree *_timespanEnd = middle; return LeftBST(_bstNodeIndex, _numValuesPerNode, _numOTNodes, _bstRoot, _tsp); } else { // Right subtree *_timespanStart = middle+1; return RightBST(_bstNodeIndex, _numValuesPerNode, _numOTNodes, _bstRoot, _tsp); } } // Return the brick index that a BST node represents int BrickIndex(int _bstNodeIndex, int _numValuesPerNode, __global __read_only int *_tsp) { return _tsp[_bstNodeIndex*_numValuesPerNode + 0]; } // Checks if a BST node is a leaf ot not bool IsBSTLeaf(int _bstNodeIndex, int _numValuesPerNode, bool _bstRoot, __global __read_only int *_tsp) { if (_bstRoot) return false; return (_tsp[_bstNodeIndex*_numValuesPerNode + 1] == -1); } // Checks if an OT node is a leaf or not bool IsOctreeLeaf(int _otNodeIndex, int _numValuesPerNode, __global __read_only int *_tsp) { // CHILD_INDEX is at offset 1, and -1 represents leaf return (_tsp[_otNodeIndex*_numValuesPerNode + 1] == -1); } // Return OT child index given current node and child number (0-7) int OTChildIndex(int _otNodeIndex, int _numValuesPerNode, int _child, __global __read_only int *_tsp) { int firstChild = _tsp[_otNodeIndex*_numValuesPerNode + 1]; return firstChild + _child; } float TemporalError(int _bstNodeIndex, int _numValuesPerNode, __global __read_only int *_tsp) { return as_float(_tsp[_bstNodeIndex*_numValuesPerNode + 3]); } float SpatialError(int _bstNodeIndex, int _numValuesPerNode, __global __read_only int *_tsp) { return as_float(_tsp[_bstNodeIndex*_numValuesPerNode + 2]); } // Converts a global coordinate [0..1] to a box coordinate [0..boxesPerAxis] int3 BoxCoords(float3 _globalCoords, int _boxesPerAxis) { int3 boxCoords = convert_int3((_globalCoords * (float)_boxesPerAxis)); return clamp(boxCoords, (int3)(0, 0, 0), (int3)(_boxesPerAxis-1)); } // Fetch atlas box coordinates from brick list int3 AtlasBoxCoords(int _brickIndex, __global __read_only int *_brickList) { int x = _brickList[3*_brickIndex+0]; int y = _brickList[3*_brickIndex+1]; int z = _brickList[3*_brickIndex+2]; return (int3)(x, y, z); } // Convert a global coordinate to a local in-box coordinate, given // the number of boxes (of this size) per axis and the box coordinates float3 InBoxCoords(float3 _globalCoords, int3 _boxCoords, int _boxesPerAxis, int _paddedBrickDim) { // Calculate [0.0 1.0] box coordinates float3 inbox = (_globalCoords - convert_float3(_boxCoords)/(float)_boxesPerAxis) * (float)_boxesPerAxis; // Map to padding range float low = 1.0/(float)_paddedBrickDim; float high = (float)(_paddedBrickDim-1)/(float)_paddedBrickDim; return (float3)(low) + inbox * ((float3)(high)-(float3)(low)); } float3 AtlasCoords(float3 _globalCoords, int _brickIndex, int _boxesPerAxis, int _paddedBrickDim, int _level, __global __read_only int *_brickList) { // Use current octree level to calculate dividing factor for coordinates int divisor = (int)pow(2.0, _level); // Calculate box coordinates, taking current subdivision level into account int3 boxCoords = BoxCoords(_globalCoords, _boxesPerAxis/divisor); // Calculate local in-box coordinates for the point float3 inBoxCoords = InBoxCoords(_globalCoords, boxCoords, _boxesPerAxis/divisor, _paddedBrickDim*divisor); // Fetch atlas box coordinates int3 atlasBoxCoords = AtlasBoxCoords(_brickIndex, _brickList); // Transform coordinates to atlas coordinates return inBoxCoords/(float)_boxesPerAxis + convert_float3(atlasBoxCoords)/(float)_boxesPerAxis; } // Sample atlas void SampleAtlas(float4 *_color, float3 _coords, int _brickIndex, int _boxesPerAxis, int _paddedBrickDim, int _level, const sampler_t _atlasSampler, __read_only image3d_t _textureAtlas, __read_only image2d_t _transferFunction, const sampler_t _tfSampler, __global __read_only int *_brickList) { // Find the texture atlas coordinates for the point float3 atlasCoords = AtlasCoords(_coords, _brickIndex, _boxesPerAxis, _paddedBrickDim, _level, _brickList); int3 boxCoords = BoxCoords(_coords, _boxesPerAxis); float4 a4 = (float4)(atlasCoords.x, atlasCoords.y, atlasCoords.z, 1.0); // Sample the atlas float sample = read_imagef(_textureAtlas, _atlasSampler, a4).x; // Composition float4 tf = read_imagef(_transferFunction, _tfSampler, (float2)(sample, 0.0)); *_color += (1.0f - _color->w)*tf; } bool TraverseBST(int _otNodeIndex, int *_brickIndex, __constant __read_only struct KernelConstants *_constants, __global __read_only int *_tsp, const int _timestep) { // Start att the root of the current BST int bstNodeIndex = _otNodeIndex; bool bstRoot = true; int timespanStart = 0; int timespanEnd = _constants->numTimesteps_; while (true) { *_brickIndex = BrickIndex(bstNodeIndex, _constants->numValuesPerNode_, _tsp); // Check temporal error if (TemporalError(bstNodeIndex, _constants->numValuesPerNode_, _tsp) <= _constants->temporalTolerance_) { // If the OT node is a leaf, we cannot do any better spatially if (IsOctreeLeaf(_otNodeIndex, _constants->numValuesPerNode_, _tsp)) { return true; } else if (SpatialError(bstNodeIndex, _constants->numValuesPerNode_, _tsp) <= _constants->spatialTolerance_) { return true; } else if (IsBSTLeaf(bstNodeIndex, _constants->numValuesPerNode_, bstRoot, _tsp)) { return false; } else { // Keep traversing bstNodeIndex = ChildNodeIndex(bstNodeIndex, ×panStart, ×panEnd, _timestep, _constants->numValuesPerNode_, _constants->numOTNodes_, bstRoot, _tsp); } } else if (IsBSTLeaf(bstNodeIndex, _constants->numValuesPerNode_, bstRoot, _tsp)) { return false; } else { // Keep traversing bstNodeIndex = ChildNodeIndex(bstNodeIndex, ×panStart, ×panEnd, _timestep, _constants->numValuesPerNode_, _constants->numOTNodes_, bstRoot, _tsp); } bstRoot = false; } } // Given a point, a box mid value and an offset, return enclosing child int EnclosingChild(float3 _P, float _boxMid, float3 _offset) { if (_P.x < _boxMid+_offset.x) { if (_P.y < _boxMid+_offset.y) { if (_P.z < _boxMid+_offset.z) { return 0; } else { return 4; } } else { if (_P.z < _boxMid+_offset.z) { return 2; } else { return 6; } } } else { if (_P.y < _boxMid+_offset.y) { if (_P.z < _boxMid+_offset.z) { return 1; } else { return 5; } } else { if (_P.z < _boxMid+_offset.z) { return 3; } else { return 7; } } } } void UpdateOffset(float3 *_offset, float _boxDim, int _child) { if (_child == 0) { // do nothing } else if (_child == 1) { _offset->x += _boxDim; } else if (_child == 2) { _offset->y += _boxDim; } else if (_child == 3) { _offset->x += _boxDim; _offset->y += _boxDim; } else if (_child == 4) { _offset->z += _boxDim; } else if (_child == 5) { _offset->x += _boxDim; _offset->z += _boxDim; } else if (_child == 6) { _offset->y += _boxDim; _offset->z += _boxDim; } else if (_child == 7) { *_offset += (float3)(_boxDim); } } float4 TraverseOctree(float3 _rayO, float3 _rayD, float _maxDist, __read_only image3d_t _textureAtlas, __constant struct KernelConstants *_constants, __read_only image2d_t _transferFunction, __global __read_only int *_tsp, __global __read_only int *_brickList, const int _timestep) { float stepsize = _constants->stepsize_; // Sample point float3 cartesianP = _rayO; // Keep track of distance traveled along ray float traversed = 0; // Cumulative color for ray to return float4 color = (float4)(0.0); // Sampler for texture atlas const sampler_t atlasSampler = CLK_FILTER_LINEAR | CLK_NORMALIZED_COORDS_TRUE | CLK_ADDRESS_CLAMP_TO_EDGE; // Sampler for transfer function texture const sampler_t tfSampler = CLK_FILTER_LINEAR | CLK_NORMALIZED_COORDS_TRUE | CLK_ADDRESS_CLAMP_TO_EDGE; // Traverse until sample point is outside of volume while (traversed < _maxDist) { // Reset octree traversal variables float3 offset = (float3)(0.0); float boxDim = 1.0; bool foundBrick = false; int child; int level = _constants->rootLevel_; int otNodeIndex = 0; // Rely on finding a leaf for loop termination while (true) { // Traverse BST to get a brick index, and see if the found brick // is good enough int brickIndex; bool bstSuccess = TraverseBST(otNodeIndex, &brickIndex, _constants, _tsp, _timestep); // Convert to spherical if needed float3 sampleP; if (_constants->gridType_ == 0) { // cartesian sampleP = cartesianP; } else { // spherical ( == 1) sampleP = CartesianToSpherical(cartesianP); } if (bstSuccess || IsOctreeLeaf(otNodeIndex, _constants->numValuesPerNode_, _tsp)) { //float s = 0.008*SpatialError(brickIndex, 4, _tsp); //color += (float4)(s); // Sample the brick SampleAtlas(&color, sampleP, brickIndex, _constants->numBoxesPerAxis_, _constants->paddedBrickDim_, level, atlasSampler, _textureAtlas, _transferFunction, tfSampler, _brickList); break; } else { // Keep traversing the octree // Next box dimension boxDim /= 2.0f; // Current mid point float boxMid = boxDim; // Check which child encloses the sample point child = EnclosingChild(sampleP, boxMid, offset); // Update offset for next level UpdateOffset(&offset, boxDim, child); // Update index to new node otNodeIndex = OTChildIndex(otNodeIndex, _constants->numValuesPerNode_, child, _tsp); level--; } } // while traversing // Update sample point traversed += stepsize; cartesianP += stepsize * _rayD; } // while (traversed < maxDist) return color; } __kernel void RaycasterTSP(__read_only image2d_t _cubeFront, __read_only image2d_t _cubeBack, __write_only image2d_t _output, __read_only image3d_t _textureAtlas, __constant struct KernelConstants *_constants, __read_only image2d_t _transferFunction, //__global __read_only float *_transferFunction, __global __read_only int *_tsp, __global __read_only int *_brickList, const int _timestep) { // Kernel should be launched in 2D with one work item per pixel int2 intCoords = (int2)(get_global_id(0), get_global_id(1)); // Sampler for color cube reading const sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE; // Read from color cube textures float4 cubeFrontColor = read_imagef(_cubeFront, sampler, intCoords); float4 cubeBackColor = read_imagef(_cubeBack, sampler, intCoords); // Figure out ray direction and distance to traverse float3 direction = cubeBackColor.xyz - cubeFrontColor.xyz; float maxDist = length(direction); direction = normalize(direction); float4 color = TraverseOctree(cubeFrontColor.xyz, // ray origin direction, // direction maxDist, // distance to traverse _textureAtlas, // voxel data atlas _constants, // kernel constants _transferFunction, // transfer function _tsp, // TSP tree struct _brickList, _timestep); //color = 0.0001*color + cubeFrontColor; write_imagef(_output, intCoords, _constants->intensity_*color); return; }